Authenticating a Remote MCP Server with API Keys
Published July 9, 2026
Authenticating a remote MCP server for Claude Code and Cursor is an X-API-Key header on every request. Browser sign-in for Claude.ai, Claude Desktop, and Claude mobile is off while MCP_OAUTH_ENABLED is false. Do not send someone to those apps to sign in. The working path is the header.
The account has to be on the MCP plan. A key from Free, Basic, Core, Plus, or Custom is refused. That rule is the product, not a default of the protocol. The protocol itself is published at modelcontextprotocol.io.
API keys stay out of the tool arguments
If search_foods took an api_key argument, the model would see the key, repeat it, and sometimes send it to the wrong tool. The key is a transport header: X-API-Key.
The HTTP layer reads the header once and stores it in a context variable for that request. The tool calls resolve_mcp_caller(), which reads the context. The model never receives the key as a parameter. The same context carries X-API-Usage-Type. On this plan, commercial is rejected before a rate-limit token is spent. The MCP plan is personal use. Commercial use belongs on a REST plan. That split is on the MCP page.
A missing or unknown key is an authentication error. A key for another plan is a plan error: the key is real, the subscription is not this product. Those are different messages on purpose. "Invalid key" sends someone to rotate a credential. "Not on an MCP plan" sends them to MCP pricing.
The lookup that loads the plan does not select the account email. Logs for a failed lookup record the failure type, not the key and not the address.
What is off today
MCP_OAUTH_ENABLED defaults to false. While it is false, a request with no X-API-Key does not start a Claude app sign-in. Claude Code and Cursor are the clients to configure. The commands are in the connect article and on the MCP docs.
What the OAuth code does when the flag is on
This is a description of code that is not the public connect path. It is not a set of steps for Claude.ai, Claude Desktop, or Claude mobile.
When the flag is on, a request to /mcp with neither X-API-Key nor Authorization: Bearer is HTTP 401. The WWW-Authenticate header carries resource_metadata pointing at the protected-resource document. A 200 with an error in the JSON-RPC body does not start discovery. A request that already has X-API-Key stays on the API-key path. The flag would add a challenge. It would not remove the header.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="calorie-api", resource_metadata="https://<api-origin>/.well-known/oauth-protected-resource"
The metadata URL has to be absolute, and the resource inside the document has to be the MCP URL the client typed, path included. authorization_servers has one entry: the API origin.
The authorization-server document advertises code_challenge_methods_supported: ["S256"] and token_endpoint_auth_methods_supported including none. grant_types_supported is authorization_code and refresh_token. client_credentials is not in the list. A machine token with no user consent is the wrong shape for a personal nutrition account. The token endpoint returns unsupported_grant_type if a client asks for it. The token endpoint accepts application/x-www-form-urlencoded only. A JSON body is 415.
Authorization codes require PKCE S256. There is no plain method. The code is stored as a hash, expires in 10 minutes, and a second redeem is invalid_grant. Redirect targets that are private or not HTTPS are rejected, with the loopback exception for localhost and 127.0.0.1. Refresh tokens are opaque, stored as hashes, and rotated. Presenting a rotated token revokes the family.
Scopes in that design are nutrition:read, nutrition:vision, and offline_access. An API key does not carry scopes. The plan gate still decides whether photos are included. Photo calls through a bearer token, when that path is on, need nutrition:vision.
PUBLIC_API_ORIGIN is the origin inside the metadata: no path, HTTPS in production. It has to be the origin in the client URL. Dynamic registration is rate limited and stores a hash of the client address, not the raw address. Client metadata URLs are fetched over HTTPS only, with redirects disabled.
None of that is a reason to open Claude.ai and sign in today. The flag is off.
Claude Code and Cursor
Claude Code:
claude mcp add --transport http calorie-api https://<api-origin>/mcp --header "X-API-Key: YOUR_KEY"
Cursor uses the same URL and the same header in mcp.json. Replace YOUR_KEY before you run the command. The command is the contract the server accepts. It is not a transcript of a desktop session.
Personal use is $29 a month or $228 a year, with a 7-day trial that requires a card. REST search, foods, calc, and vision return 403 on this plan.
Related
Frequently Asked Questions
How do I authenticate a remote MCP server from Claude Code?
Send X-API-Key on every request. The key belongs to the MCP plan and is shown once in the dashboard. Browser sign-in for Claude.ai, Claude Desktop, and Claude mobile is off while MCP_OAUTH_ENABLED is false.
Does OAuth replace API keys today?
No. The public path is the API key. OAuth code exists for a flag that is off. Claude Code and Cursor keep sending X-API-Key.
Can a client use client_credentials?
No. The token endpoint rejects client_credentials. The MCP plan is a personal account, not a machine token with no consent.
Which plan can call the tools?
Only the MCP plan. A key on Free, Basic, Core, Plus, or Custom is refused. REST search, foods, calc, and vision return 403 on an MCP key.
