Authorization in Harness AI flows through a single, centralized service. Every tool — MCP server, CLI wrapper, HTTP adapter, or webhook handler — validates its Bearer token againstDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/joseluis-dev/harness-ai/llms.txt
Use this file to discover all available pages before exploring further.
mcp-auth-service using a shared auth-sdk. There is no per-component OAuth, no public client self-registration, and no token in a URL query string. The authorization plane lives in its own PostgreSQL database (harness_auth_db), isolated from the business database (harness_db) from the first commit of the project.
Core Principles
Single Authority
mcp-auth-service is the only token issuer and validator in the harness. No MCP server, CLI wrapper, HTTP integration, or external channel implements its own OAuth flow. When any component needs to verify a token, it calls the auth-sdk; the auth-sdk validates the signature against the JWKS endpoint or a local cache of public keys.
Dedicated Auth Database
harness_auth_db is a PostgreSQL instance that is always independent of harness_db. The two databases run on separate Compose services, with separate volumes, separate credentials, and separate Alembic version tables. The tables in harness_auth_db are:
oauth_clients, oauth_scopes, oauth_tokens, oauth_policies, audit_auth_events, tools (the tool_registry), tool_operations, permissions, agent_permissions, issued_tokens, revoked_tokens, tool_execution_logs, security_events.
Confidential Clients by Default
Every technical client acts on behalf of itself using the OAuth 2.1 Client Credentials grant. Credentials are sent via HTTP Basic (the preferred method for confidential clients under OAuth 2.1). Two additional credential profiles are supported when explicitly declared on theoauth_clients row:
client_secret_post—client_idandclient_secretin the request body (with secret redacted inauth-sdklogs)private_key_jwt— signed assertion (with secret redacted inauth-sdklogs)
Shared SDK, Not Per-Component Auth
Theauth-sdk (/packages/auth-sdk, available in TypeScript and Python) is the only approved path for token validation across every service. MCPs, CLI wrappers, the runtime, webhooks, and the admin panel all use it. Logic is not duplicated.
Authorization Components
| Component | Location | Role |
|---|---|---|
mcp-auth-service | apps/mcp-auth-service | Emits, validates, and revokes tokens. Manages clients, scopes, and policies. Exposes /oauth/token, JWKS, and introspection endpoints. |
auth-sdk | packages/auth-sdk | Shared TS/Python client for token validation, refresh, and decision reporting. Used by all MCPs, CLI wrappers, runtime, webhooks, and the admin panel. |
harness_auth_db | Dedicated PostgreSQL cluster | Stores all OAuth state and the tool_registry. Independent of harness_db. |
tool_registry | packages/tool-registry on harness_auth_db | Declares which scopes each tool requires and what risk_level applies. Consumed by mcp-auth-service during authorization evaluation. |
Authorization Flow
The following four-step sequence covers the complete lifecycle from token issuance to tool invocation:Client requests a token
The client (MCP server, CLI wrapper, or HTTP adapter) authenticates with its
client_id and client_secret via HTTP Basic and requests a scoped token.mcp-auth-service issues the token
The service validates the An
client_id, client_secret, requested scopes, and that oauth_clients.state = 'active'. It emits an access token and records the issuance.audit_auth_event row is written with type = "token.issued".Client invokes a tool
The client calls the tool endpoint over HTTP, attaching the Bearer token in the
Authorization header. The token is never sent in a query parameter.MCP validates via auth-sdk
The MCP server calls
auth-sdk to validate the token. The SDK performs the following checks in order:- Verify JWT signature and expiration against JWKS (or local key cache).
- Confirm the token’s scope covers the operation being requested (e.g.,
sql:readforexecute_query). - Confirm
tool_registry.state = 'active'andoauth_clients.state = 'active'. - Evaluate the tool’s
risk_level. If the operation requires human confirmation (e.g.,DESTRUCTIVEorEXTERNAL_SIDE_EFFECT), respond202with anapproval_idand hold execution. - Record
audit_auth_eventwithtype = "token.used", actor, tool, scope, and decision.
HTTP Rules
These rules are unconditional and apply to every HTTP interaction in the harness:- Every request to an HTTP tool must include
Authorization: Bearer <access_token>. - Tokens in URL query strings are unconditionally prohibited.
401 Unauthorizedresponses must include theWWW-Authenticateheader with the resource’s protection metadata and, when applicable,error="insufficient_scope"orerror="invalid_token".- Expired or revoked tokens always produce
401witherror="invalid_token". - Timeouts and retry behavior for each tool are configured in its
tool_registryrow, not in the MCP server itself.
Risk Levels
Every tool row intool_registry declares a risk_level. Authorization evaluation always starts from this level. If a single operation combines multiple risk categories (e.g., reading sensitive data and writing), the effective level is the highest applicable, and all lower-level controls are inherited.
risk_level | Description | Default policy |
|---|---|---|
READ_ONLY | Read with no side effects. | Allowed with valid scope. No human confirmation required. |
READ_SENSITIVE | Read of sensitive data (PII, health records, financials). | Allowed with valid scope. Reinforced audit required. |
WRITE | Internal write with reversible effect. | Requires explicit write scope and risk_level configured on the tool row. |
DESTRUCTIVE | Delete, drop, truncate, or revoke operations. | Requires destructive scope and human approval (Phase 9). |
ADMINISTRATIVE | Configuration changes, secret rotation, client management. | Requires admin scope and resolved administrator role. |
EXTERNAL_SIDE_EFFECT | Observable effect outside the harness — email, outbound webhooks, external APIs with side effects. | Requires external scope and human approval by default. |
Scope Catalog
The initial scope catalog maps one-to-one with the nine tool seeds registered at Phase 0.b.| Scope | Meaning | Seed tools that require it |
|---|---|---|
sql:read | SQL read over allowlisted views. | mcp-readonly-sql |
sql:list_views | Enumerate the view catalog. | mcp-readonly-sql |
sql:describe_view | Describe a view’s schema. | mcp-readonly-sql |
identity:read | Resolve user/actor context. | mcp-identity-connector |
documents:read | Read document metadata. | mcp-document-registry, cli-pdf-tools, cli-ocr-tools |
expedientes:read | Read municipal case-file data. | mcp-expedientes |
reportes:run | Execute a report query. | mcp-reportes |
write | Reversible write operations. | cli-office-converter (and future Phase 11+ tools) |
destructive | Destructive operations. | (Phase 11+ tools) |
external | External side effects (notifications, outbound webhooks). | mcp-notificaciones (and Phase 13 tools) |
admin | Administrative operations on mcp-auth-service. | mcp-auth-service client management |
Client and Scope Lifecycle
OAuth clients move through three states:| State | Meaning |
|---|---|
active | Can obtain new tokens. All existing tokens remain valid. |
suspended | Cannot obtain new tokens. Existing tokens are invalidated at their next use or refresh attempt. |
revoked | Permanently disabled. All associated tokens are immediately invalidated. |
state = 'active' may receive a new token from mcp-auth-service.
OAuth scopes move through two states:
| State | Meaning |
|---|---|
active | May be assigned to clients and used in token requests. |
deprecated | Not assignable to new clients. Existing assignments remain valid through the migration window. |
tool_registry) move through three states — covered in detail in the Tool Registry document.