For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /reference/services/api/services/mcp/README.md.

Experimental MCP service

This branch provides a native Go Streamable HTTP gateway and an API-owned OAuth broker adapted from architecture-reset. The gateway uses generated OpenAPI types and calls the current Go API. The broker uses oidc-provider and openid-client for OAuth and AAI federation.

The contract guide defines tool parity, inputs, scopes, command recovery and the private assertion protocol.

Process boundaries

MCP client -> OAuth broker -> AAI
                  |
                  +-> private API identity endpoint -> Party/AUP owners

MCP client -> Go MCP gateway -> Go API -> existing context owners

The gateway retains no session or business state. The broker persists OAuth grants, sessions, clients, immutable Party bindings and single-use upstream login state in its own PostgreSQL database. Its signing keys, cookie keys and database must persist across restarts and be shared by broker replicas. Runtime code checks its restricted database role and performs no migration.

OAuth accounts include the exact AAI identity, canonical Party, captured generation and operator enrolment in their immutable identity. Refresh rechecks the API and fails after a change. Every protected API invocation independently checks Party eligibility, generation and AUP. Browser logout revokes browser sessions; OAuth revocation uses the broker's advertised revocation endpoint. Already issued OAuth access tokens last up to ten minutes, with immediate owner generation and operator checks at the API. Revoke the refresh token to prevent future token issuance, then discard the client's access token. The broker's stateless JWT access tokens cannot be individually revoked through the revocation endpoint; they retain their signed expiry and owner checks.

Enrolment

The optional MCP laboratory profile provides coordinated HTTPS routes, a dedicated OAuth database, persistent keys and a local AAI client registered with the exact /interaction/callback URI. See client setup for its explicit OAuth enrolments. Existing deployments keep MCP disabled until they select this development-only profile. The identity must already be registered in Pharos with current AUP.

The enrolled AAI callback remains /interaction/callback. It performs an expiry-aware, non-consuming state lookup and redirects within the broker origin to that interaction's callback path. The browser then supplies its original scoped signed cookie. Cookie, path and upstream state must agree before the single-use code exchange, which uses the original enrolled redirect URI. Concurrent interactions retain separate cookie paths; callback relays use no-store and no-referrer.

The query-free consent page uses same-origin referrer policy so a native browser form supplies the exact broker Origin. Consent continues to refuse missing, opaque or foreign origins and non-form content types. The policy sends no referrer to another origin; sensitive callback responses retain no-referrer. Its form CSP permits the broker and the current client's provider-validated callback origin so approval and denial can reach a separate native listener. Other origins remain blocked; callback URLs never supply raw CSP text.

Keep the API identity endpoint private. Preserve the advertised public Host when proxying the gateway and broker. Do not expose the internal API assertion audience as an alternative OAuth resource.

All secret settings support direct values or their _FILE variants; use one form per setting. Every process currently requires APP_ENV=development or isolated APP_ENV=local. HTTPS is required. Isolated HTTP fixtures additionally set MCP_ALLOW_HTTP=true; the API uses its existing authentication HTTP gate.

The existing API authentication enrolment remains required. Add:

API settingValue
API_MCP_ENABLEDtrue; absent/false leaves delegation disabled.
API_MCP_RESOURCE_URLExact public gateway URL ending in /mcp.
API_MCP_BROKER_ISSUERExact public broker origin with trailing /.
API_MCP_GATEWAY_JWKSPublic Ed25519 gateway verification keys.
API_MCP_BROKER_JWKSPublic broker verification keys.
API_MCP_OPERATOR_PARTIESOptional JSON array of canonical operator Party IDs; default empty.

Gateway configuration:

SettingValue
MCP_RESOURCE_URLPublic gateway URL ending in /mcp.
MCP_BROKER_ISSUERExact broker issuer.
MCP_BROKER_JWKS_URLEnrolled HTTPS broker JWKS endpoint, normally /jwks.
MCP_BROKER_JWKS_CONNECT_PORTOptional exact TCP port for the fixed HTTPS JWKS connection. Keeps the enrolled URL, HTTP Host and TLS name; this explicit route connects directly. The laboratory uses 8443. Absent leaves normal HTTPS routing unchanged.
MCP_AAI_ISSUERExact AAI issuer also enrolled in the API.
MCP_API_URLFixed private HTTPS API origin.
MCP_ASSERTION_KEY, MCP_ASSERTION_KEY_IDDedicated PKCS8 Ed25519 private key and JWK key ID.
MCP_ORIGINSOptional comma-separated browser origins; other Origin headers are rejected. Native clients omit Origin.
LISTEN_ADDRDefault :8082; TLS terminates at the enrolled proxy.

Broker configuration:

SettingValue
MCP_RESOURCE_URL, MCP_BROKER_ISSUER, MCP_AAI_ISSUERThe same exact resource and issuer values.
MCP_API_URLPrivate API origin with trailing /.
MCP_AAI_CLIENT_ID, MCP_AAI_CLIENT_SECRETDedicated upstream OIDC registration.
MCP_BROKER_SIGNING_JWKSPersistent private RS256 JWK set; first key signs login assertions.
MCP_BROKER_COOKIE_KEYSJSON array of at least two distinct persistent keys, each at least 32 characters.
MCP_BROKER_DATABASE_URLDedicated restricted runtime PostgreSQL connection.
MCP_BROKER_CLIENTSJSON array of enrolled public OAuth clients with exact redirect URIs, code/refresh grants and PKCE.
MCP_BROKER_DCROptional true enables rate-limited dynamic registration; default disabled.
MCP_BROKER_TRUST_PROXYOptional true only behind a proxy that replaces forwarding headers.
HOST, PORTDefault 0.0.0.0:8083.

Broker and gateway signing keys must differ. Enrol new public keys before rotating signers; retain retiring verification keys for the longest outstanding token lifetime. API browser-attestation and session credentials retain their separate authority.

Database and commands

An administrator creates a dedicated broker database and separate migration and runtime roles. Revoke public database access, grant only CONNECT to runtime, and run the migration separately:

pnpm install --frozen-lockfile
pnpm --filter @pharos/mcp-broker build
pnpm --filter @pharos/mcp-broker migrate

The migration command requires MCP_BROKER_MIGRATION_DATABASE_URL, which must never enter the running broker container. Afterwards grant runtime USAGE on mcp_oauth, SELECT/INSERT/UPDATE/DELETE on artifacts, SELECT/INSERT on accounts, and SELECT/INSERT/DELETE on upstream_login_states. Runtime must have no database or schema CREATE privilege, superuser, role creation, database creation or RLS bypass rights. Deny it access to other application databases.

Start the configured broker and gateway in separate terminals:

pnpm --filter @pharos/mcp-broker start
PHAROS_GO_PROJECT=services/api python3 scripts/go.py run ./services/mcp

The gateway's /healthz is a liveness endpoint. OAuth discovery is published at /.well-known/oauth-protected-resource/mcp; unauthorised MCP requests include its URL in WWW-Authenticate. The broker publishes ordinary OAuth/OIDC metadata and /health/live, /health/ready (including its database permissions). MCP clients connect to the public /mcp URL and follow OAuth discovery.

Build images from the repository root:

docker build -f services/api/Dockerfile --build-arg APP=mcp -t pharos-mcp:experimental .
docker build -f services/api/services/mcp-broker/Dockerfile -t pharos-mcp-broker:experimental .

Verification

pnpm verify includes generation drift, architecture boundaries, Go race tests, broker TypeScript compilation and OAuth/AAI fixtures. The SDK fixture performs initialisation, scoped discovery and generated HTTP calls. Write fixtures cover exact command identity, invalid input, redirects, bounded replies and uncertain outcomes. AAI fixtures validate signatures, issuer, audience, nonce, PKCE and exact stable identity across ID token and UserInfo.

Run these infrastructure lanes on the configured dev box or build runner, in a separate checkout:

python3 scripts/mcp_broker_integration.py
pnpm --filter @pharos/mcp-broker build
node scripts/mcp_broker_browser.mjs
pnpm test:party:postgres
pnpm test:party:broker
pnpm test:party:sessions
pnpm test:authentication:e2e

The broker lane runs the OAuth journey against real PostgreSQL and tests immutable generations and concurrent single-use consumption with a restricted role. The browser lane needs Chromium (pnpm exec playwright install chromium) and runs real scoped-cookie redirects, approve/deny form submissions to a separate native callback server, unrelated-origin CSP rejection and PKCE code exchange against the broker with disposable AAI and Party fixtures. The authentication CI workflow runs this browser lane. Party lanes exercise the owner query and existing authority boundaries. These are disposable fixtures. External AAI, desktop MCP client consent screens, production proxy behaviour and the deployed business journeys remain separate qualification.

Page source

Source snapshot ยท Revision f928834c8.