Skip to main content

Documentation 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.

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 against 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 the oauth_clients row:
  • client_secret_postclient_id and client_secret in the request body (with secret redacted in auth-sdk logs)
  • private_key_jwt — signed assertion (with secret redacted in auth-sdk logs)
There is no auto-registration endpoint. Clients are created via the admin panel (Phase 6) or via a versioned Alembic migration.

Shared SDK, Not Per-Component Auth

The auth-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

ComponentLocationRole
mcp-auth-serviceapps/mcp-auth-serviceEmits, validates, and revokes tokens. Manages clients, scopes, and policies. Exposes /oauth/token, JWKS, and introspection endpoints.
auth-sdkpackages/auth-sdkShared TS/Python client for token validation, refresh, and decision reporting. Used by all MCPs, CLI wrappers, runtime, webhooks, and the admin panel.
harness_auth_dbDedicated PostgreSQL clusterStores all OAuth state and the tool_registry. Independent of harness_db.
tool_registrypackages/tool-registry on harness_auth_dbDeclares 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:
1

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.
POST /oauth/token HTTP/1.1
Host: mcp-auth-service
Authorization: Basic <base64(client_id:client_secret)>
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=sql%3Aread
2

mcp-auth-service issues the token

The service validates the client_id, client_secret, requested scopes, and that oauth_clients.state = 'active'. It emits an access token and records the issuance.
{
  "access_token": "<signed-jwt>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "sql:read"
}
An audit_auth_event row is written with type = "token.issued".
3

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.
POST /mcp/readonly-sql HTTP/1.1
Host: mcp-readonly-sql
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": "<uuid>",
  "method": "tools/call",
  "params": {
    "name": "execute_query",
    "arguments": {
      "sql": "SELECT * FROM vw_expedientes_resumen LIMIT 50",
      "limit": 50
    }
  }
}
4

MCP validates via auth-sdk

The MCP server calls auth-sdk to validate the token. The SDK performs the following checks in order:
  1. Verify JWT signature and expiration against JWKS (or local key cache).
  2. Confirm the token’s scope covers the operation being requested (e.g., sql:read for execute_query).
  3. Confirm tool_registry.state = 'active' and oauth_clients.state = 'active'.
  4. Evaluate the tool’s risk_level. If the operation requires human confirmation (e.g., DESTRUCTIVE or EXTERNAL_SIDE_EFFECT), respond 202 with an approval_id and hold execution.
  5. Record audit_auth_event with type = "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 Unauthorized responses must include the WWW-Authenticate header with the resource’s protection metadata and, when applicable, error="insufficient_scope" or error="invalid_token".
  • Expired or revoked tokens always produce 401 with error="invalid_token".
  • Timeouts and retry behavior for each tool are configured in its tool_registry row, not in the MCP server itself.

Risk Levels

Every tool row in tool_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_levelDescriptionDefault policy
READ_ONLYRead with no side effects.Allowed with valid scope. No human confirmation required.
READ_SENSITIVERead of sensitive data (PII, health records, financials).Allowed with valid scope. Reinforced audit required.
WRITEInternal write with reversible effect.Requires explicit write scope and risk_level configured on the tool row.
DESTRUCTIVEDelete, drop, truncate, or revoke operations.Requires destructive scope and human approval (Phase 9).
ADMINISTRATIVEConfiguration changes, secret rotation, client management.Requires admin scope and resolved administrator role.
EXTERNAL_SIDE_EFFECTObservable 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.
ScopeMeaningSeed tools that require it
sql:readSQL read over allowlisted views.mcp-readonly-sql
sql:list_viewsEnumerate the view catalog.mcp-readonly-sql
sql:describe_viewDescribe a view’s schema.mcp-readonly-sql
identity:readResolve user/actor context.mcp-identity-connector
documents:readRead document metadata.mcp-document-registry, cli-pdf-tools, cli-ocr-tools
expedientes:readRead municipal case-file data.mcp-expedientes
reportes:runExecute a report query.mcp-reportes
writeReversible write operations.cli-office-converter (and future Phase 11+ tools)
destructiveDestructive operations.(Phase 11+ tools)
externalExternal side effects (notifications, outbound webhooks).mcp-notificaciones (and Phase 13 tools)
adminAdministrative operations on mcp-auth-service.mcp-auth-service client management

Client and Scope Lifecycle

OAuth clients move through three states:
StateMeaning
activeCan obtain new tokens. All existing tokens remain valid.
suspendedCannot obtain new tokens. Existing tokens are invalidated at their next use or refresh attempt.
revokedPermanently disabled. All associated tokens are immediately invalidated.
Only a client in state = 'active' may receive a new token from mcp-auth-service. OAuth scopes move through two states:
StateMeaning
activeMay be assigned to clients and used in token requests.
deprecatedNot assignable to new clients. Existing assignments remain valid through the migration window.
Tools (in tool_registry) move through three states — covered in detail in the Tool Registry document.

Anti-Patterns

Auth by MCP — prohibitedAny MCP server, CLI wrapper, or HTTP adapter that implements its own OAuth token issuance or validation logic, instead of delegating to auth-sdk, must be rejected at code review. This includes issuing tokens from within an MCP endpoint, hardcoding secrets in MCP configuration, or checking credentials without going through /packages/auth-sdk.
Public auto-registration — prohibitedThere is no POST /register or equivalent open registration endpoint. Clients are created by an administrator through the admin panel or via a versioned Alembic migration. Any code that accepts a client_id and client_secret from an unauthenticated request and creates a new client record is unconditionally prohibited.
Tokens in query strings — prohibitedAccess tokens must only travel in the Authorization: Bearer header. A token appearing as a URL query parameter (e.g., ?access_token=... or ?token=...) violates this rule regardless of TLS encryption. Logging, proxies, and access log infrastructure regularly record query strings; tokens in query strings create an unavoidable audit and secret-exposure risk.
Mixing harness_db and harness_auth_db — prohibitedThese are two physically separate PostgreSQL instances with separate Compose services, separate volumes, and separate credentials. Writing auth data to harness_db or business/audit data to harness_auth_db violates the isolation principle that applies from the first commit. The two Alembic migration directories (infra/postgres/migrations and infra/postgres/auth-migrations) must never be merged or cross-applied.

Build docs developers (and LLMs) love