Claude Remote MCP OAuth: Build Discovery, PKCE, Scopes, and Token Refresh
A production implementation contract for remote MCP OAuth in Claude, including discovery, client registration, audience validation, refresh rotation, and failure tests.
A remote MCP server is not ready for Claude when the login screen appears. It is ready when discovery is deterministic, every token is bound to the right resource and user, scopes match the requested tools, refresh survives rotation, and failure tests prove that the server rejects the wrong token.
This guide turns the current Claude connector behavior and MCP authorization requirements into an implementation contract. The original contribution is an acceptance model: eight interfaces, three client-registration choices, and a test matrix that a platform team can run before a connector reaches users.
Evidence status before implementation
Keep three evidence classes separate.
- Measured first-party search evidence: unavailable. ClaudeExperts has no configured Search Console or GA4 property, so this topic is a low-data experiment rather than a claim of measured demand.
- Current external evidence: Anthropic documents Claude's supported connector authentication modes, PKCE behavior, metadata discovery, scope selection, callback differences, and token refresh behavior. The MCP specification and OAuth RFCs define protected-resource metadata, resource indicators, token audience binding, and public-client protections.
- Implementation inference: the architecture, decision table, operational ownership, and acceptance tests below are a proposed production pattern derived from those sources. They are not requirements published by Anthropic.
Specialist review: Dhruv Khatri is the named technical reviewer. Review is requested and pending. Treat the proposed defaults below as review-ready controls, not as a record of completed expert approval.
The eight-interface authorization contract
Most failed OAuth integrations are not one bug. They are a mismatch between interfaces owned by different teams.
| Interface | Producer | Consumer | Acceptance condition |
|---|---|---|---|
| Initial protected request | Claude client | MCP server | Missing or invalid bearer token returns 401, not a tool-shaped success response |
| Authentication challenge | MCP server | Claude client | WWW-Authenticate points to the protected-resource metadata document |
| Protected-resource metadata | MCP server | Claude client | resource exactly matches the configured MCP URL and lists the intended authorization server |
| Authorization-server discovery | Identity platform | Claude client | Discovery exposes supported endpoints, PKCE S256, scopes, and a usable registration path |
| Client registration | DCR, CIMD, or pre-registered credentials | Claude client and identity platform | Redirect URIs and public or confidential client behavior match the Claude surface |
| Authorization request | Claude client | Identity platform | state, PKCE challenge, resource, and least-privilege scopes survive the round trip |
| Token validation | MCP server | Presented access token | Signature, issuer, audience/resource, expiry, and scope are verified before any tool executes |
| Refresh and revocation | Claude client and identity platform | Token endpoint and MCP server | Rotation, expiry, revocation, and reconnect behavior work without reusing an invalid token |
Assign an owner to every row. If the MCP team owns only the last HTTP handler while identity, networking, and directory onboarding are unowned, the connector is not production-ready.
Choose the registration model deliberately
Claude supports more than one way to establish an OAuth client. Use the operating model, not implementation convenience, to choose.
| Model | Use when | Main tradeoff | Production check |
|---|---|---|---|
| Dynamic Client Registration (DCR) | The authorization server can create public clients safely and connection volume is modest | Simple onboarding, but fresh connections can create many registered clients | Rate-limit and lifecycle-test registration records |
| Client ID Metadata Document (CIMD) | The authorization server can resolve a self-hosted client metadata URL and you want stable public-client metadata | Avoids a registration call, but discovery support must be exact | Verify client_id_metadata_document_supported and public-client token authentication |
| Anthropic-held credentials | A directory connector needs one stable OAuth application and the integration follows Anthropic's review path | Operational coordination is required when credentials or authorization-server ownership change | Test the hosted Claude surfaces and document credential rotation ownership |
| Customer-supplied credentials | An enterprise custom connector needs an OAuth application scoped to that organization | More customer setup, but cleaner tenant control | Validate redirect registration and secret handling per tenant |
Do not use a query-string API key as a shortcut. Anthropic's connector documentation says user-pasted bearer tokens and credentials in connector URLs are not supported. A server-to-server client_credentials grant is also not a substitute for a consent-gated user connection.
Implement the discovery path first
Start with the unauthenticated request because it controls everything that follows.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"
The protected-resource document should identify the exact MCP resource and its authorization server:
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": ["https://identity.example.com"],
"scopes_supported": ["records.read", "records.write"]
}
Exactness matters. If a user enters https://mcp.example.com/mcp, do not advertise only the origin as the resource. Anthropic documents path-aware discovery and recommends an explicit resource_metadata pointer in the 401 challenge. Relying on client probing adds round trips and breaks when a hosting platform cannot serve the expected well-known path.
The authorization server must then publish RFC 8414 or OpenID Connect discovery metadata. For Claude interoperability, verify the registration mechanism and advertise S256 PKCE support. Return discovery documents over HTTPS and test them from outside the corporate network so a WAF or conditional-access policy does not create a false local success.
Bind every token to this MCP resource
An access token that proves a user signed in is not sufficient. The MCP server must also prove that the token was issued for this resource.
At the start of every authenticated request, validate:
- the token signature against the trusted issuer's current keys;
- the issuer against an explicit allowlist;
- the audience or resource identifier against the exact MCP server URL;
- expiry and not-before claims with a small documented clock-skew allowance;
- required scopes for the requested tool;
- the subject and tenant expected for this connection;
- revocation or session state where the identity architecture requires it.
Then build an authorization context that tools receive explicitly. Do not let a tool recover identity from a global variable or a connection-wide cache.
type McpAuthContext = {
subject: string;
tenantId: string;
scopes: Set<string>;
tokenId?: string;
};
function requireScope(ctx: McpAuthContext, scope: string) {
if (!ctx.scopes.has(scope)) throw new Error("insufficient_scope");
}
The token from Claude is for the MCP resource. If the MCP server calls Salesforce, Google Drive, or another upstream API, use a separate upstream credential or delegated token issued for that API. Token passthrough creates an audience-confusion problem and is explicitly rejected by the MCP security model.
Make scopes match tools, not teams
Broad role names such as employee or admin are weak tool boundaries. Prefer scopes tied to stable actions:
records.readfor search and retrieval;records.writefor reversible edits;records.exportfor bulk data movement;billing.refundfor a narrow irreversible action.
Return only tools that the current authorization context may see, and recheck scope inside the handler. The first control reduces accidental selection; the second is the enforcement boundary.
Use the scope parameter in the WWW-Authenticate challenge when a request needs step-up access. Do not request every possible scope during initial connection. A useful policy is read scopes at connection time, write scopes when a write-capable tool is first requested, and a fresh consent event for high-impact scopes.
Design refresh as a state transition
Anthropic documents both proactive refresh shortly before expiry and reactive refresh after a 401. Your token endpoint therefore needs predictable behavior under overlap, retries, and rotation.
For public clients, rotate refresh tokens or sender-constrain them. When rotating, return the replacement refresh token in the same successful response that invalidates the old token. Store only a protected representation, link the token family to the user and client, and detect reuse of an invalidated family member.
Model refresh with these states:
| State | Server response | Operator meaning |
|---|---|---|
| Access token still valid | Resource request succeeds | No refresh needed |
| Access token expired, refresh valid | Token endpoint returns new access token and rotated refresh token | Normal renewal |
| Refresh token already used or revoked | invalid_grant | Stop retrying and require reconnection |
| Client metadata no longer trusted | registration or token request rejected | Investigate client lifecycle or identity configuration |
| User lost required permission | refreshed token omits scope or resource returns insufficient scope | Access change propagated correctly |
Accept form-encoded bodies at the token endpoint. A JSON-only parser that returns 415 can make the entire flow look like a Claude client problem.
Run this acceptance test matrix
Do not stop at one successful login. Run positive, negative, expiry, and tenant-isolation cases.
| Test | Input or fault | Expected result |
|---|---|---|
| Discovery happy path | No token on exact MCP URL | 401, valid challenge, metadata and authorization-server discovery resolve |
| Wrong resource | Validly signed token for another API | MCP request rejected before tool listing or execution |
| Wrong issuer | Token from an untrusted tenant or issuer | Rejected even if signature format is valid |
| Missing scope | Read token calls a write tool | Tool hidden or call rejected with insufficient scope |
| PKCE downgrade | Authorization request without S256 | Authorization server rejects the request |
| Redirect mismatch | Unregistered callback | Authorization server rejects the request without redirection |
| Refresh rotation | Old refresh token reused after successful rotation | invalid_grant; token family alert recorded if policy requires it |
| Revoked user | User disabled between access-token expiry and refresh | Refresh fails or returns a token without access |
| Cross-tenant replay | Tenant A token sent to tenant B resource context | Rejected; no data or tool result returned |
| Upstream boundary | MCP token presented to downstream API path | Design prevents passthrough; downstream credential is separate |
| WAF reachability | Discovery from Anthropic network path | Metadata and token endpoints remain reachable under intended policy |
| Audit integrity | Successful and denied calls | Log captures subject, tenant, tool, decision, and correlation ID without storing bearer tokens |
Worked example: a CRM connector
Assume the MCP endpoint is https://mcp.example.com/crm. A sales operator connects with crm.contacts.read. Claude can list and search contacts, but the update_contact tool is absent. When the operator asks to correct a title, the server challenges for crm.contacts.write. After consent, the refreshed tool list includes the update operation.
The handler validates the token audience as the exact /crm resource, checks tenant membership, applies the write, and logs the subject and record identifier. It does not forward the MCP token to the CRM. A separate service credential, constrained to that tenant, performs the upstream API call.
This example is intentionally narrow. The design makes a mistaken write observable and bounded, and it lets removing crm.contacts.write take effect at the next refresh rather than waiting for a human to delete an integration.
Ship the contract, not only the code
Before review, attach four artifacts to the implementation:
- the exact protected-resource and authorization-server metadata documents;
- a registration-model decision with an owner for credential and redirect changes;
- the scope-to-tool matrix and upstream credential boundary;
- automated results for the acceptance matrix, including denied cases.
This turns OAuth from a login feature into a testable authorization system. If your team needs a review of the MCP resource boundary, identity model, token validation, or interoperability tests, the MCP and Tool Integration engagement is designed around that implementation work. Start with MCP versus ad-hoc Claude tools for the protocol decision and Claude tool schema design for the tool contract.
Sources
Related notes
Production systems
Claude Agent SDK Session Recovery: Resume, Fork, Compact, and Checkpoint Safely
Learn when to continue, resume, fork, compact, and checkpoint Claude Agent SDK sessions so long-running agents can recover safely.
Production systems
Why most Claude pilots never reach production
Most Claude pilots stall on integration, evaluation and cost, not prompting. Here is what actually blocks a production launch, and how to unblock it.
Production systems
MCP versus ad-hoc Claude tools: pick the contract, not the acronym
MCP is a protocol. The production question is whether Claude has a narrow, permissioned contract with your systems of record.