Connect an MCP Server with Shared OAuth¶
Purpose¶
Connect an upstream MCP server using one shared company authorization. Every allowed Mission Control caller uses that upstream account; Mission Control still checks the caller's Team, Team allowance, key policy and budgets. The current release supports pre-registered clients with explicit authorization_code or client_credentials grants, and dynamic client registration (RFC 7591) for authorization_code servers that issue clients on demand. A Connector that should act as each calling person instead uses the per-user modes described in Connector administration; delegated token exchange is not included in the current release.
Prerequisites¶
organization.settings.manageat organization scope (organization.ownerororganization.admin).- An HTTPS MCP Streamable HTTP server supporting revision
2026-07-28, protected-resource metadata and OAuth/OIDC authorization-server metadata. Browser consent requires PKCE S256. - A pre-registered client ID, or
registration: dynamicfor anauthorization_codeserver whose authorization server publishes aregistration_endpoint. Dynamic registration happens during the first Connect, registers a public PKCE client with the exact Console callback, is bound to the issuer and the Connector's credential generation, is stored encrypted, and is repeated only after a generation change (changed credential-affecting configuration or Disconnect). It cannot be combined withclientId,clientSecretorclient_credentials. - 1–64 explicit least-privilege scopes. Empty scopes fail startup preparation; discovery does not supply or expand the configured scope set. Confidential pre-registered clients also need a gateway environment secret. Client credentials requires a secret and uses the SDK's client-secret Basic authentication.
- Back up control state, then run
pnpm --filter @mission-control/database db:migratefrom the repository'splatform/directory with the gateway's configuredDATABASE_URL. The connector authorization tables are part of the database schema; the command applies any migration the release adds without resetting existing state. - Configure gateway-only
MCP_CREDENTIAL_ENCRYPTION_KEYSandMCP_CREDENTIAL_ACTIVE_KEY_ID. Set gatewayAPP_URLto the same public Console URL used by the Console. See deployment setup. - Register the exact callback
APP_URLplus/api/connectors/oauth/callback, for examplehttps://console.example.test/api/connectors/oauth/callback. Keep/internalroutes private. Proxy/access logs must omit or redact callback queries; the application never copies them into sign-inreturnTo.
Procedure¶
- Add the Connector to organization YAML. Substitute your registered URLs, client ID, scopes and secret reference; these values are fictional:
connectors:
- id: crm-oauth
name: CRM with OAuth
transport: streamable-http
url: https://crm.example.test/mcp
timeoutMs: 15000
enabled: true
auth:
type: oauth
grant: authorization_code
registration: preregistered
issuer: https://identity.example.test
clientId: mission-control-crm
clientSecret: env/CRM_OAUTH_CLIENT_SECRET
scopes: [crm.read]
allowedAuthOrigins: [https://identity.example.test]
headers:
X-Tenant-ID:
value: example-team
tools: []
For a server that issues clients on demand, omit clientId and clientSecret and declare registration: dynamic:
connectors:
- id: notes-oauth
name: Notes with dynamic registration
transport: streamable-http
url: https://notes.example.test/mcp
timeoutMs: 15000
enabled: true
auth:
type: oauth
grant: authorization_code
registration: dynamic
issuer: https://notes.example.test
scopes: [notes.read]
allowedAuthOrigins: [https://notes.example.test]
tools: []
- Public authorization-code clients omit
clientSecret; PKCE remains required. Unattended services usegrant: client_credentialsand retainclientSecret. Grant selection never changes after an error. Supplemental headers use the existing literal/reference rules; OAuth ownsAuthorization. - Run preflight and restart gateway and Console with the reviewed files/environment. Startup checks client/header references and encryption availability. Authorization-code configuration loads before consent; invocation still requires a connected grant and eligible tools.
- Open Connectors, select the server, and choose Connect in the OAuth 2.0 card. Complete provider consent in the same Console session. Client credentials obtains a machine token without browser consent; it can also acquire a token on the first authorized refresh/invocation.
- Confirm Connected, Grant, Client, Granted scopes, and Expires. Client shows whether the client was pre-registered or registered dynamically. Connected by identifies the Mission Control administrator, not an inferred upstream account. Expiry is displayed in UTC.
- Select Refresh connection to update health and schemas together. Declare approved tools and hashes in YAML, restart with the reviewed files, then enable and bind them in the Team using Connector administration.
- Use Reconnect to replace authorization or Disconnect to invalidate the local grant. New preparation cannot use the disconnected generation. An already-prepared invocation may finish; restarting unchanged configuration does not restore a disconnected grant.
Expected result¶
Tool calls prepare upstream credentials after local authorization and argument validation, before admission and business dispatch. Authorization-code tokens and PKCE verifiers are encrypted in the PostgreSQL database, and so is a dynamically registered client identifier with any secret the authorization server issued; a generation change clears the registration so the next Connect registers again. Client-credentials access tokens use bounded gateway memory and are reacquired after restart. Tokens/client secrets do not enter the Console, configuration responses, traces or managed-runtime envelopes.
Refresh happens before expiration and is coordinated across concurrent requests. The connector timeout covers credential waiting and MCP work. Authentication network work is limited to four operations per organization and 32 per process, with at most 256 queued operations. A cancelled waiter does not cancel another caller's shared refresh. The gateway never automatically replays a business call after 401/403 or an uncertain result.
The synthetic example separates OAuth status from network health and schema matching. Its administrator identifier and expiry are deterministic fixture labels.
Verification¶
- Invoke an enabled tool with a current Mission Control key using the MCP guide; the caller does not supply the upstream token.
- Check OAuth, health and discovery separately. Reconnect does not automatically replace schemas; Refresh connection is the explicit verification action.
- After Disconnect, a new call fails in-band on HTTP 200 with JSON-RPC
-32603, messageConnector authentication could not be completed, anddata.code: connector_auth_required. It does not start a Mission Control sign-in challenge. - A gateway restart preserves encrypted grants and ten-minute pending flows. Startup loads the normal invocation configuration automatically; compatible durable flows remain available.
Control routes¶
The Console uses the existing signed internal-service protocol, acting for the current administrator. These are private control routes, not public bearer-key APIs:
| Method and route | Result |
|---|---|
POST /internal/connectors/{serverId}/oauth/start |
Authorization URL/expiry, or null URL for a machine grant. |
GET /internal/connectors/{serverId}/oauth/status |
Safe grant status, administrator, scopes and expiry. |
POST /internal/connectors/{serverId}/oauth/complete |
Signed code/state/optional issuer body; 204 after a verified completion. |
POST /internal/connectors/{serverId}/oauth/disconnect |
204 after local invalidation and bounded best-effort revocation. |
Unknown/foreign Connectors remain unavailable, missing authentication returns 401, missing permission returns 403, malformed callbacks return 400, and safe authorization/availability failures return 409/503. Responses never contain tokens, client secrets or verifiers. The browser entry point is GET /api/connectors/oauth/callback on the Console. Provider extension parameters such as session_state are ignored and never forwarded or stored. Provider error responses and duplicate or malformed code, state, or iss values remain rejected; the redirect removes callback query parameters.
Failure recovery¶
| Condition | Recovery |
|---|---|
| Not connected or Needs authorization | Connect/Reconnect. Missing refresh tokens, rejected grants or uncertain rotating-refresh outcomes can require consent again. |
| Temporarily unavailable | Check gateway/authorization-server availability, endpoint policy and encryption keys. Failure before a token exchange does not destroy a usable refresh grant. |
| Insufficient scopes | Review least privilege, change configured scopes if appropriate, restart and Reconnect. Business calls never automatically expand a shared grant. |
| Expired session or rejected callback | Sign in and restart Connect. State is single-use, expires after ten minutes and is bound to person, organization, server and generation. Permission loss, issuer mismatch or replay rejects completion. |
| Issuer/endpoint rejected | Verify issuer and allowedAuthOrigins. Metadata cannot add origins. HTTPS private endpoints also need MCP_AUTH_ALLOWED_PRIVATE_ORIGINS and deployment egress controls; loopback/link-local addresses remain blocked. |
| Missing token expiry | Set tokenTtlSeconds from 1–300 only when the provider contract supports that conservative local reuse bound. Missing expiry without this override, or invalid/nonpositive expires_in, rejects the response. |
| Registration refused | The authorization server rejected the registration request, for example for an unregistered callback origin. Nothing is stored and the card shows Temporarily unavailable; correct APP_URL or the provider's allowed redirect URIs, then Connect again. |
| Client-secret/header rotation | Change environment and restart. Changed material rotates the internal generation even with unchanged YAML and invalidates incompatible grants/flows. |
| Local discovery CLI with OAuth | Use Console Connect/Refresh connection. connector:discover supports static credentials and does not read the gateway grant store. |
Both grant types surface runtime outages as Temporarily unavailable. Discovery, endpoint/resource policy, unsupported client authentication, invalid local client material, and rejected/cancelled network admission before submission preserve a usable refresh grant. Retry Refresh connection after recovery; a successful refresh or fresh Reconnect consent restores Connected. A delayed failure from the previous grant cannot overwrite the new grant’s status.
Disconnect clears local access before best-effort provider revocation. A failed revocation does not restore access; damaged token ciphertext does not block local Disconnect when the binding itself remains readable. Lost encryption keys must be restored from protected backup; credential access fails closed without them.