Creuto is now an OpenAI Select Partner Read More

Software Architecture & Technical

MCP server authentication: shared key or OAuth?

MCP server authentication, decided per tool: Claude's shared header key can't identify a user and ChatGPT won't send one. When per-user OAuth is required.

MCP server authentication: shared key or OAuth?

MCP server authentication is a per-tool decision, not a per-server one. A shared API key only holds up for tools that read data every caller may see. Claude's static header credential is a beta, organization-wide key that its own docs say not to use to identify a user, and ChatGPT's Apps SDK cannot present custom API keys at all. Any tool that reads one person's records or acts in their name needs per-user OAuth.

That matters if your customers are asking to use your product from Claude and ChatGPT and you are about to put an MCP server in front of your REST API. By the end of this post you will know which of your tools can ship behind the org key you already issue, which need OAuth before either client will connect them properly, and what the current MCP spec revision, 2026-07-28, changed about client registration that most guides written before it still get wrong.

ClientShared API keyPer-user OAuth
Claude (claude.ai, Desktop, mobile, Claude Code, Cowork)static_headers: beta, limited set of organizations, entered once by an org OwnerDCR, CIMD, Anthropic-held client credentials, or credentials entered at connection time
ChatGPT (Apps SDK)Not supported: cannot present custom API keysOAuth 2.1 with PKCE S256 and protected resource metadata; CIMD preferred, DCR still supported
Both clientsNo machine-to-machine fallbackNo client_credentials grant: no token is issued without a user

Neither Claude nor ChatGPT treats a shared API key as a user

Claude lists six authentication types for remote MCP servers in its connector authentication docs: oauth_dcr, oauth_cimd, oauth_anthropic_creds, custom_connection, static_headers and none. The static option is the one that looks like your existing API key. An organization Owner enters an API key or bearer token once, and Claude sends it in a request header on every call from anyone in that organization.

The same page is blunt about the limits. static_headers is in beta for a limited set of organizations, and Owners whose organization lacks access do not even see the request headers section. It tells you to treat the value as the organization's credential, not a person's: "every member's requests carry the same key, so don't use it to identify which user is calling. If your tools need to act as the individual user, use OAuth instead."

ChatGPT goes further. OpenAI's Apps SDK authentication page says ChatGPT cannot present custom API keys or customer-provided mTLS certificates, and does not support machine-to-machine grants such as client credentials, service accounts or JWT bearer assertions. If you need to know that a request really came from ChatGPT, it offers two transport-level options: verify the OpenAI-managed client certificate it presents over mTLS, or allowlist ChatGPT's published egress IP ranges. Neither tells you which user is calling.

The ChatGPT API key claim, corrected

A DEV Community piece published on 9 November 2026 on this exact decision says ChatGPT plugins declare API key authentication in mcp.json under extensions["com.openai"].auth. That is true of the manifest: OpenAI's plugin packaging docs accept auth.type: "api_key" with a bearer, basic or custom_header scheme. The article also notes the catch, and it is the part that decides your plan. OpenAI's submission docs say the portal's connection form supports only no authentication, OAuth and mixed access, has no API key connection flow or bearer token input, and an API key declaration alone will not complete MCP setup. Read together with the Apps SDK statement above, a public ChatGPT listing should be planned on OAuth.

What the 2026-07-28 spec requires for MCP server authentication

Authorization is optional in MCP. The authorization specification says HTTP-based transports SHOULD conform to it when they support authorization, and stdio implementations SHOULD NOT, retrieving credentials from the environment instead. If your server runs locally over stdio, the questions are different; our post on the local port you open during an MCP setup covers that side.

For a remote server that does authorize, the spec is built on the OAuth 2.1 draft and adds specific requirements:

  1. MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC 9728), and clients MUST use it to discover the authorization server.
  2. Clients MUST send the RFC 8707 resource parameter, the canonical URI of your MCP server, in both authorization and token requests.
  3. Servers MUST validate that each access token was issued specifically for them as the audience, and "MUST NOT accept or transit any other tokens."
  4. Tokens go in the Authorization: Bearer header on every request and MUST NOT appear in the query string.
  5. Clients MUST record the expected issuer and validate the iss parameter in the authorization response per RFC 9207 before sending the code to any token endpoint.

Your server also SHOULD include a scope parameter in the WWW-Authenticate header of its 401, so clients request only what the operation needs.

What is CIMD, and is dynamic client registration deprecated?

Yes. In the 2026-07-28 client registration section, Client ID Metadata Documents (CIMD) are the SHOULD, and Dynamic Client Registration (DCR) is a MAY carrying a deprecation warning: new implementations should use CIMD, and DCR remains for authorization servers that do not support it. With CIMD the client's client_id is an HTTPS URL pointing to a JSON document with at least client_id, client_name and redirect_uris. Clients that support every option SHOULD try pre-registered credentials first, then CIMD if your authorization server advertises client_id_metadata_document_supported, then DCR, then ask the user.

Deprecated in the spec does not mean unused in practice. Claude still falls back to DCR unless your authorization server metadata advertises both "client_id_metadata_document_supported": true and "none" in token_endpoint_auth_methods_supported. Its docs also warn that DCR makes Claude register a new client on every fresh connection, which can leave very large numbers of registered clients on your authorization server. Advertising CIMD properly is the cheaper path for both clients.

Decide per tool: whose data does the call touch?

Both clients already let you set authentication per tool, which is why the decision belongs at that level. ChatGPT's securitySchemes marks a tool noauth (callable anonymously), oauth2, or both, and the Apps SDK docs recommend per-tool declarations even if the entire server uses the same policy. Claude's docs point servers whose tools partly work without an account to lazy authentication, which lets people use those tools straight away and sign in only when Claude reaches one that needs their account.

What the tool touchesExampleAuth model
Public data, same for everyoneProduct catalogue, docs search, status pageNo auth, or a shared key where you need rate limiting
Org data every member may readShared dashboards, read-only org reportsShared key can work in Claude's beta; ChatGPT still needs OAuth
One user's recordsMy tickets, my inbox, my payrollPer-user OAuth
Writes in a person's nameCreate invoice, approve leave, send messagePer-user OAuth, with narrow scopes

The strongest case for the shared key deserves a fair hearing. Your API already issues one key per customer org, your permission model may already be org-level, and OAuth means running or integrating an authorization server, metadata endpoints, PKCE, refresh rotation and consent screens. If every member of a customer org genuinely has identical rights to everything the MCP server exposes, a shared key loses nothing, and building OAuth first would delay a launch for no security gain.

That case breaks at the first tool where two members of the same org should see different rows. With one key, your API cannot tell the finance lead from the intern, so either your tools return what the most privileged person could see or they enforce nothing. Your audit log records the org, not the person. And in ChatGPT the shared key path is not available for a public listing at all. This is the wrong choice if any tool reads personal data, writes on someone's behalf, or needs an audit trail that names a human.

Org connectors can share one identity even with OAuth

OAuth does not guarantee per-user identity if the connector is configured at org level. GitHub issue #46207 on the claude-code repository, opened on 10 April 2026, reports that org-level MCP connectors use "a single shared OAuth token for all users in the org", that "whoever re-authenticates last sets the token for everyone", and that this rules out row-level or user-level data access control. A bot closed it as not planned on 13 April 2026, saying it did not appear to be about Claude Code; no maintainer replied. A related report, issue #44980, describes custom connectors in Claude Teams exposing shared credentials to all team members and was closed as not planned on 15 June 2026.

These are user reports, not confirmed behavior, and we have not reproduced them. Anthropic's own guidance points the same way regardless: its connector docs tell you to scope what each user can reach with your service's own per-user permissions rather than with a per-tenant OAuth app. Build your MCP server so that the token's subject, not the connector it arrived through, decides what a call can return.

Putting an MCP server over an existing API without token passthrough

An MCP server over an existing API holds two credentials: the token the client presents to the MCP server, and whatever the MCP server uses to call your API. The MCP security best practices explicitly forbid collapsing the two. Token passthrough, where the server accepts a client's token without checking it was issued to the MCP server and forwards it downstream, breaks audience validation, hides which client is calling, and lets a stolen token use your server as an exfiltration proxy. The mitigation is one line: MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server.

If your MCP server acts as an OAuth proxy to a third-party API using one static client ID, the confused deputy rules apply. An attacker can combine that static client ID, dynamic registration and a consent cookie on the upstream authorization server to receive an authorization code without the user's approval. Proxies MUST keep a per-user registry of approved client_id values and check it before forwarding, match redirect_uri by exact string, and generate a single-use state stored only after consent, with a short expiry such as 10 minutes.

The same document adds a rule for servers that carry state between calls: they MUST NOT treat possession of a state handle as authentication, and SHOULD bind each handle server-side to the user ID taken from the verified token. If you followed our post on stateless MCP without sticky sessions and now pass cart or workflow IDs as tool arguments, that binding is what stops one user guessing another's handle.

Claude adds operational requirements on top of the spec. It needs a 401 to start sign-in and ignores WWW-Authenticate on a 200. It uses only the first entry in your authorization_servers list. It waits 10 seconds for discovery, registration and token endpoints and 30 seconds for refresh requests before treating the flow as failed. ChatGPT requires your authorization server to publish code_challenge_methods_supported with S256 and tells you to verify each token's signature, issuer, audience, expiry and scopes.

Scoping MCP server authentication over your existing API

Before writing code, list every tool you plan to expose and mark it with the row of the table it falls into. If every tool lands in the first two rows, ship with the org key you already have for Claude, accept that ChatGPT will need OAuth, and keep the key out of URLs and logs. If even one tool reads a person's records or writes in their name, design per-user OAuth now: protected resource metadata, CIMD support on your authorization server, audience checks, and a mapping from token subject to the permissions your API already enforces.

That mapping is usually where the real work sits, because it exposes whether your API's permission model is per-user at all. We build REST APIs in Node.js with JWT and role-based access control, and our API development and integrations team can take your existing endpoints, sort each proposed MCP tool into shared-key or per-user OAuth, and scope the authorization server and permission changes before you commit to a launch date.

Frequently asked questions

An MCP server does not strictly need OAuth, because authorization is optional in the MCP specification and stdio servers should read credentials from the environment. A remote MCP server that exposes one user's records or acts in a user's name does need per-user OAuth, since neither Claude nor ChatGPT can identify an individual through a shared key.

An MCP server can accept an API key from Claude through the static_headers connection type, which is in beta for a limited set of organizations and sends one org-wide key for every member. ChatGPT's Apps SDK cannot present custom API keys, so a public ChatGPT listing should plan on OAuth.

CIMD, or Client ID Metadata Documents, is the client registration method the MCP specification recommends from the 2026-07-28 revision. The client's client_id is an HTTPS URL pointing to a JSON document listing at least its client_id, client_name and redirect_uris, which the authorization server fetches and validates.

Dynamic Client Registration is deprecated in the 2026-07-28 MCP specification and kept only for backwards compatibility with authorization servers that lack CIMD support. Claude still falls back to DCR unless the authorization server advertises client_id_metadata_document_supported and the none token endpoint auth method.

Claude connectors authenticate to a remote MCP server through OAuth with DCR, CIMD, Anthropic-held client credentials or customer-entered credentials, through a beta static request header, or with no authentication. Claude requires a 401 pointing to protected resource metadata and uses only the first listed authorization server.

Token passthrough is forbidden in MCP because a server that forwards client tokens it never validated breaks audience checks, hides which client is calling, and can be used as a proxy to exfiltrate data with a stolen token. MCP servers must reject any token not explicitly issued for them.

Written by

Akash Mohapatra

Akash Mohapatra

Co Founder & Director

10 Nov 2026

·

10 min read

Share

LET'S CONNECT

Connect with Creuto!

Ready to take the first step towards unlocking opportunities, realizing goals, and embracing innovation? We're here and eager to connect.