MCP OAuth for SaaS connectors: Client ID Metadata Documents, the DCR fallback and API keys
How a SaaS MCP server and its authorization server meet the MCP spec: protected resource metadata, Client ID Metadata Documents, a DCR fallback and API keys.
- Published
Under the 2026-07-28 MCP specification, your MCP server is an OAuth resource server. It publishes protected resource metadata that names your authorization server, and it accepts only tokens issued for it. Clients can identify themselves with Client ID Metadata Documents: an HTTPS URL that your authorization server fetches, which the spec says clients and authorization servers should support. Dynamic Client Registration (DCR) is deprecated but still works, so keep it as a fallback for clients that don't support metadata documents yet. API keys sit outside the spec's OAuth flow, and Claude accepts an organization-wide key only in a limited beta.
This guide walks through each piece from the server side, with the documents your servers publish. Our tests run every example below through the MCP TypeScript SDK's OAuth discovery and validation functions.
The flow in one paragraph
An AI client calls your MCP server without a token. Your server answers 401 and points to its protected resource metadata. The client reads that document, finds your authorization server, and reads the authorization server's metadata to learn how to register, which code challenge methods it supports and where to send the user. The user signs in and approves on your authorization server, the client exchanges the code for a token bound to your MCP server, and from then on it sends Authorization: Bearer <token> on every request.
1. Answer 401 with the metadata location
Your MCP server must implement OAuth 2.0 Protected Resource Metadata (RFC 9728). Either name the document in the WWW-Authenticate header of a 401, or serve it at a well-known URI. The header is the clearer signal, and the spec says to include a scope parameter with the scopes the request needs:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.acme.test/.well-known/oauth-protected-resource/mcp", scope="projects:read"Clients that find no header try /.well-known/oauth-protected-resource with your endpoint's path inserted first (/.well-known/oauth-protected-resource/mcp for an endpoint at /mcp), then at the root. Serve the document at the path you name.
2. Publish protected resource metadata
The document must list at least one authorization server in authorization_servers. Keep scopes_supported to the minimal set for basic use: the spec's security guidance warns against publishing every scope you have, and against wildcard scopes.
{
"resource": "https://mcp.acme.test/mcp",
"authorization_servers": ["https://auth.acme.test"],
"scopes_supported": ["projects:read"],
"bearer_methods_supported": ["header"]
}resource is your server's canonical URI. Clients send it as the resource parameter (RFC 8707) in both the authorization and the token request, so your authorization server can bind the token to your MCP server.
3. Publish authorization server metadata
Clients look for RFC 8414 metadata at /.well-known/oauth-authorization-server, then OpenID Connect discovery at /.well-known/openid-configuration. They reject a document whose issuer differs from the URL they built it from. Four fields decide whether an MCP client can proceed:
code_challenge_methods_supported: if it is missing, the client must refuse to continue. ListS256, because clients must use PKCE.client_id_metadata_document_supported: set it totrueto accept Client ID Metadata Documents.registration_endpoint: present only if you keep the DCR fallback.authorization_response_iss_parameter_supported: set it totrueonce your authorization responses includeiss(RFC 9207). The spec says authorization servers should, and expects to make it a must in a future revision.
{
"issuer": "https://auth.acme.test",
"authorization_endpoint": "https://auth.acme.test/authorize",
"token_endpoint": "https://auth.acme.test/token",
"registration_endpoint": "https://auth.acme.test/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "private_key_jwt"],
"client_id_metadata_document_supported": true,
"authorization_response_iss_parameter_supported": true,
"scopes_supported": ["projects:read", "projects:write"]
}4. Accept Client ID Metadata Documents
With a Client ID Metadata Document, the client's client_id is an HTTPS URL with a path, and the URL serves a JSON document about the client. Your authorization server fetches it when it sees a URL-shaped client_id. This fits MCP, where your server and the AI client usually have no prior relationship. The 2025-11-25 revision made it the recommended registration mechanism, and 2026-07-28 says clients and authorization servers should support it. A document your authorization server might fetch:
{
"client_id": "https://chat.agent-client.test/oauth/client-metadata.json",
"client_name": "Example Agent",
"client_uri": "https://chat.agent-client.test",
"redirect_uris": ["https://chat.agent-client.test/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}What your authorization server must do with it:
- Check that the document's
client_idmatches the URL it was fetched from, exactly. - Validate the redirect URI in each authorization request against the document's
redirect_uris. - Reject a document that isn't valid JSON or lacks
client_id,client_nameorredirect_uris. - Cache it according to its HTTP cache headers.
- Treat the fetch as a request to an untrusted URL. A malicious client can point it at private addresses, so block private and link-local ranges or fetch through an egress proxy (the spec's server-side request forgery guidance).
- Show the redirect URI's hostname on the consent screen, and warn when the only redirect URIs are
localhost: a metadata document can't prove which local process is listening.
5. Keep a DCR fallback
The 2026-07-28 revision deprecates Dynamic Client Registration (RFC 7591) and keeps it for backward compatibility. A client that supports both uses a pre-registered client ID first, then a metadata document if your authorization server advertises support, then DCR through your registration_endpoint. Clients built before metadata documents existed register with DCR, so keeping the endpoint lets them connect.
Two rules apply to DCR now. Clients must send an application_type (native for desktop, CLI and localhost apps, web for hosted ones), so be ready to accept both. And if your MCP server signs users in to a third-party API with one static client ID while letting clients register dynamically, it must ask each registered client for the user's consent before forwarding to the third party: the spec's confused deputy rule.
6. Validate every token, and never pass it through
Your MCP server must check that each access token was issued for it, for example through the token's audience claim, and answer an invalid or expired token with 401. It must not forward the client's token to your own APIs or to anyone else's. If the MCP server calls an upstream API, it gets its own token for that call.
For permissions, start small. Ask for broader scopes only when a tool needs them: answer 403 with error="insufficient_scope" and the scopes the operation needs, all in one challenge, and the client re-authorizes with the union of its old and new scopes.
Where API keys fit
The spec's authorization flow is OAuth. Servers that run locally over stdio take credentials from the environment instead. For a remote connector in Claude's Connectors Directory, the options are OAuth 2.0 with each user signing in, a static API key or bearer token that an organization Owner enters once (in beta, for a limited set of organizations), or no authentication. Every requirement on the Claude Connectors Directory page has its source and the date we checked it.
ChatGPT adds a rule of its own: to restrict a plugin to one workspace domain, your OAuth server needs a UserInfo endpoint that returns email_verified, and must offer the openid and email scopes. Per-user OAuth is the option that works across directories; keys are a narrow exception.
Check it before you submit
The examples on this page are checked against the MCP TypeScript SDK's discovery functions: the 401 header parses to the metadata URL and scope, both metadata documents parse, and the client ID URL passes the SDK's URL check. Run your own server's responses through an MCP client the same way before you submit. In a Connector Launch Sprint we build this setup, with metadata documents and a DCR fallback, and test it with the MCP Inspector before submission.
Tested with: 2026-07-28, MCP TypeScript SDK (@modelcontextprotocol/client) 2.3.0, on .
Sources
- Model Context Protocol 2026-07-28: Authorization (accessed )
- Model Context Protocol 2026-07-28: Authorization Server Discovery (accessed )
- Model Context Protocol 2026-07-28: Client Registration (accessed )
- Model Context Protocol 2026-07-28: Authorization Security Considerations (accessed )
- Model Context Protocol 2026-07-28: Security Best Practices (accessed )
- Model Context Protocol 2025-11-25: Key changes (accessed )
- RFC 9728: OAuth 2.0 Protected Resource Metadata (accessed )
- IETF draft: OAuth Client ID Metadata Document (accessed )