
To add an OAuth MCP server to Claude Code, run claude mcp add --transport http <name> <url>, then open /mcp in a session (or run claude mcp login <name> in your shell) and sign in in the browser. Claude Code finds the server's authorization server, registers itself as a client, stores the token and refreshes it. Every tool call then runs as you, with the scopes you approved.
That flow covers one developer using their own accounts. The sign-in itself needs an interactive session with a browser, so it cannot run inside claude -p, CI or an Agent SDK app, and it does not help when one setup has to act for many different users. Those cases need a credential minted outside Claude Code for each person, which is the problem a managed MCP integration layer solves. For the IT side of a Claude Code rollout (central control, revocation and audit for a whole engineering team), see Claude Code MCP governance for IT teams.
Remote servers use the HTTP transport, which the Claude Code MCP docs recommend for cloud-hosted servers. Sentry's server is the example Anthropic uses for a server that needs sign-in:
Before you sign in, claude mcp list shows the server as ! Needs authentication. Claude Code also prints a startup notice that counts the servers waiting for sign-in. Select the server in /mcp, finish the browser sign-in, and the status changes to ✔ Connected. The /mcp panel then shows how many tools the server exposes, and flags a server that advertises tools but exposes none.
Pick the scope when you add the server, because it decides who sees it:
OAuth sign-ins are stored per endpoint. If the same name points to different URLs in two scopes, Claude Code warns about the conflict, and signing in to one does not sign you in to the other.
claude mcp login runs the same OAuth flow without opening a session. When no local browser is available, such as over SSH or on Linux without a display, it prints the authorization URL instead. Open the URL on your own machine, sign in, and paste the full redirect URL from the address bar back into the prompt. The paste step needs an interactive terminal, so connect with ssh -t.
Claude Code follows the MCP authorization spec, and most steps have a setting you can override:
On the v2 client runtime (built on MCP TypeScript SDK 2.0, the default in recent versions), Claude Code adds two checks. It fails a sign-in whose authorization response names an unexpected issuer, and it sends token requests only to HTTPS endpoints or to localhost, 127.0.0.1 and ::1. Both protect against a malicious or misconfigured authorization server, and the fix for either belongs on the server side.
Removing a server with claude mcp remove also deletes the OAuth tokens and client registration Claude Code stored for it. For a server with a pre-registered app, the client secret is kept in the macOS Keychain or a credentials file, outside your MCP config.
Some authorization servers support neither Dynamic Client Registration nor a Client ID Metadata Document. Claude Code then fails with “Incompatible auth server: does not support dynamic client registration”. Register an OAuth app in the server's developer portal with the redirect URI http://localhost:8080/callback (any free port works), then pass its credentials when you add the server:
--client-secret prompts for the secret with masked input. Set MCP_CLIENT_SECRET instead to skip the prompt in a script. You can set the secret only when you add the server, so to change it, remove the server and add it again at the same --scope. For a public client with no secret, pass only --client-id. claude mcp get <name> shows whether OAuth credentials are configured.
When an authorization server advertises more scopes than you want to grant, pin the list in the server's config. oauth.scopes is one space-separated string, the same format as the OAuth scope parameter, and it takes precedence over discovered scopes and authServerMetadataUrl:
Pinning has one side effect to plan for. If a tool later needs a scope outside the list, the server answers 403 insufficient_scope, the call fails with a needs additional permissions message that names the scope, and the server shows as needing authentication. Claude Code requests your pinned scopes on the next sign-in, so signing in again without editing the list returns a token with the same gap. Add the scope to the list first, then authenticate again from /mcp.
Servers that use API keys or other tokens take a header instead of OAuth: --header on claude mcp add, or headers in JSON. For credentials that expire, headersHelper runs a command that prints a JSON object of headers. Claude Code merges the output into the connection headers, and dynamic headers override static headers with the same name.
Start with claude mcp get <name>. For a failed server it prints an Issue: line with the HTTP status and the server's error text, with credentials redacted. Then match the message:
Two errors come from the v2 runtime's checks: “Issuer mismatch in authorization response” and “Refusing to send credentials to non-https token endpoint”. Report the first to the server operator. For the second, serve the token endpoint over HTTPS. Starting Claude Code with MCP_SDK_GENERATION=v1 skips both checks for every server in that process, so treat it as a short-term workaround. The Claude Code error reference lists every message, and MCP server errors covers failures that start on the server side.
The /mcp flow assumes one person at a terminal with a browser, signing in to their own accounts. Three situations do not fit that:
A managed MCP gateway handles this by holding each user's app tokens and issuing a short-lived credential per run. Per-user OAuth for MCP compares the designs. In Scalekit AgentKit, the pieces are:
Claude Code's headersHelper fits this model. It mints a fresh session token on each connection, and when a token expires mid-session, the next tool call gets a 401, Claude Code runs the helper again and retries. One server can combine tools from several connections, drawn from Scalekit's 500+ connectors.
Create one server for the agent role and list only the tools it needs. This example uses a Slack connection named slack in the Scalekit dashboard. It prints the server ID and the shared URL:
Before the first run, confirm the user's connected account for each connection is ACTIVE, and send them the authorization link if it is not. MCP_USER_ID is your app's stable ID for the person; Scalekit's docs recommend an ID that is hard to guess over an email address.
Save the helper as mcp_headers.py. It prints the header JSON that Claude Code expects. Add access_level="READ_ONLY" to create_session_token if the session should see only read-only tools.
Register it at user scope, so the helper keeps the environment it reads its settings from and runs without a folder trust prompt. Paste the server URL that the first script printed, and give absolute paths for the Python interpreter that has the Scalekit SDK and for the script. Start Claude Code from a shell that exports SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET, MCP_CONFIG_ID and MCP_USER_ID:
claude mcp get slack-team runs the helper and health-checks the server. A working setup shows ✔ Connected. If the helper prints a token the server rejects, the status is ✘ Failed to connect with the headersHelper message from the table above. The server authenticates every request with a session token, so register it with the helper rather than as a bare URL.
This version keeps Scalekit client credentials on the developer's machine, which suits one developer or a pilot. Anyone holding those credentials can mint a token for any identifier. For a team, move the create_session_token call into a small internal endpoint that checks the engineer's SSO session and mints a token for that engineer only. The helper then becomes a short request to that endpoint, and client credentials stay on your backend. A product on the Agent SDK does the same from its backend and passes the token in the server's headers for each run, as described in Mint session tokens.
The example server includes slack_send_message. Claude Code names MCP tools mcp__<server>__<tool>, so an ask rule in settings.json makes Claude Code prompt before every send, even when an allow rule also matches. Claude Code evaluates deny rules first, then ask, then allow:
A read-only session token goes further: tools that are not marked read-only drop out of the tool list and are refused if called. Use it for sessions that only need to search and read. The permissions docs cover managed settings for rules your users cannot override.
Run claude mcp add --transport http with a name and the server URL, then open /mcp in a Claude Code session and sign in through the browser. You can also run claude mcp login with the server name from your shell. claude mcp list shows Connected once the sign-in works.
Claude Code stores the tokens securely, outside your MCP config, and refreshes them automatically. A client secret for a pre-registered app goes in the macOS Keychain or a credentials file. To clear the tokens, use Clear authentication in /mcp or run claude mcp logout; claude mcp remove also deletes the tokens and the client registration.
Check for a pinned oauth.scopes list that lacks a scope the server requests, because each new token then has the same gap. Add the scope, then authenticate again. Also check for the same server name defined at two scopes with different URLs, since sign-ins are stored per endpoint.
Headless runs have no /mcp panel, so they cannot run the browser flow. On your own machine, sign in first with claude mcp login in an interactive terminal. In CI, give the server a credential through headers or a headersHelper script.
It runs a command that prints a JSON object of request headers. Claude Code runs it on every connection and again after a 401 or 403, then retries the call once. When it supplies the Authorization header, Claude Code does not fall back to OAuth.
Use a gateway that stores each user's app tokens and issues a per-user credential. With Scalekit, every user shares one Virtual MCP server URL, and each run sends a session token minted for that user, valid for one hour by default.
Details checked October 2026.