Troubleshooting¶
Common issues and how to resolve them. For the full list of known workarounds in Claude Code, Claude Desktop, mcp-remote, and the MCP SDKs, see WORKAROUNDS.md.
Connection issues¶
Browser never opens for OAuth login¶
Problem: You ran mcp-stdio --oauth but no browser window appeared.
Solution: 1. Check stderr — mcp-stdio always prints the authorization URL there, whether or not the browser opened automatically. 2. Copy and paste that URL into your browser manually. 3. Complete the login flow; the authorization code is captured on the loopback callback.
401 Unauthorized — "Worked yesterday, broken today"¶
Problem: The connection worked fine, but today you get a 401 error.
Cause: The server may have rotated its keys or revoked the grant.
Solution:
mcp-stdio handles this on its own when the refresh token is what stopped working (#471). The request that hit the 401 comes back with -32002 ("re-authorizing … then retry"), and a browser window opens for a new sign-in; with --oauth-device, the device code is printed in the mcp-stdio log. Once you finish signing in, retry the request, and it goes through with the new token. No restart is needed.
If no sign-in starts, check the following:
- --no-oauth-reauth is not set.
- The refresh did not fail only transiently (a 5xx or network error keeps the token and does not open a browser).
- Another mcp-stdio process for the same server may already be signing in. This process then waits for it and uses its token (see the log).
- No failed attempt happened within the last 60 s.
To start over completely, delete the cached token and run your client again:
rm ~/.config/mcp-stdio/tokens.json
Repeated "Connection expired" prompts despite a valid refresh token¶
Problem: Your MCP client keeps reporting the connection as expired, even though mcp-stdio holds a working refresh token.
Cause: By default mcp-stdio refreshes the access token proactively, shortly before it expires (--oauth-refresh-leeway, default 60 s) — but this only helps if your MCP client actually reuses the connection through mcp-stdio.
Solution:
1. Make sure you are not passing --no-proactive-refresh (proactive refresh is on by default).
2. If the server signals expiry with an HTTP 200 response and a tool-result error instead of a transport 401 (e.g. some enterprise MCP gateways), mcp-stdio may not detect it reactively — proactive refresh is the primary defense here; if you still see stale sessions, please file an issue with the gateway's behavior (see shigechika/mcp-stdio#242 for the original report of this class of gateway).
3. If your server does not send expires_in at all, set the leeway explicitly:
mcp-stdio --oauth --oauth-refresh-leeway 300 https://your-server.example.com/mcp
Some MCP clients have their own history of not re-invoking their auth header callback when a token expires mid-session (see Claude Code #53267 and #65036) — this is exactly the class of problem mcp-stdio's proactive/reactive refresh is designed to route around, since mcp-stdio (not your MCP client) owns the token lifecycle end to end.
AADSTS9010010 on Microsoft Entra ID¶
Problem: Authorization fails with AADSTS9010010 ("audience validation failed") when connecting through Microsoft Entra ID with api:// scopes.
Cause: By default mcp-stdio derives the RFC 8707 resource parameter from the server URL. Entra ID validates that value as the token audience and rejects it when it does not match what the app registration expects.
Solution: pick the remedy that matches your Entra ID app registration.
-
Entra expects a specific App ID URI (most common) — send it verbatim so the audience matches:
The value is used on every OAuth request (authorization, token exchange, refresh, and device flow) and persisted in the token store.mcp-stdio --oauth --oauth-resource api://<app-id> https://your-server.example.com/mcp -
Entra rejects the
resourceparameter outright — omit it entirely:mcp-stdio --oauth --no-resource-indicator https://your-server.example.com/mcp
The two flags are mutually exclusive: --oauth-resource sends a specific value, --no-resource-indicator sends none.
Tool and resource issues¶
Tools beyond the first page are invisible¶
Problem: You have dozens of tools defined, but your client only sees the first ~20.
Cause: Claude Code sends only the first tools/list request and silently discards nextCursor, so tools on page 2+ never reach the client.
Solution:
Nothing to configure — mcp-stdio already follows nextCursor transparently across tools/list, resources/list, resources/templates/list, and prompts/list on the Streamable HTTP transport, merging every page into a single response before it reaches your client. This does not apply to --transport sse (legacy transport, not auto-paginated); keep your tool count under one page there.
See Claude Code #39586.
tools/list fails with "Invalid session ID" right after a successful connect¶
Problem: The connection shows as established and initialize succeeds, but a subsequent tools/list or tool call is rejected by the server as an invalid or missing session.
Cause: Some MCP clients (including some Claude Code versions) do not echo the Mcp-Session-Id header returned by initialize on later requests, so session-aware servers (e.g. mcp-grafana, mcp-go) reject them.
Solution:
Nothing to configure — mcp-stdio captures Mcp-Session-Id from the initialize response itself and injects it on every subsequent request, regardless of what your MCP client does. If you still see this, the session may have genuinely expired server-side; restart your client to force a fresh initialize.
See Claude Code #70386.
Tools disappear after a brief network drop or reconnect¶
Problem: Tools were listed fine, but after a short disconnect / reconnect they stop working or vanish from the list.
Cause: The MCP session was lost (e.g., the backend server restarted, or the connection was interrupted); some Claude Code versions have also had regressions in Streamable HTTP reconnection handling.
Solution:
1. mcp-stdio automatically re-initializes the session on a 404 "session not found" response from the server.
2. If tools remain unavailable, restart your MCP client (Claude Desktop / Claude Code) to force a fresh session.
See Claude Code #34498, #38631.
Authentication issues¶
Static bearer token not being sent¶
Problem: You set --bearer-token or MCP_BEARER_TOKEN, but the server reports it as missing or invalid.
Cause: mcp-stdio attaches the token to the Authorization header on every outbound request; a rejection is more likely one of: an incorrect or expired token, or the server expecting the credential somewhere other than the Authorization header (e.g. a query parameter or custom header).
Solution:
1. Verify your token is correct and not expired.
2. Check the server's authentication docs to confirm it expects an Authorization: Bearer header — if it expects a different header, pass it explicitly with -H 'Header-Name: value'.
3. Run mcp-stdio in the foreground and check stderr; connection and retry diagnostics are printed there.
OAuth scope not being honored¶
Problem: You request a scope with --oauth-scope, but the server still complains about insufficient scope.
Cause: The authorization server may have rejected your requested scope and granted a smaller one.
Solution:
1. Run mcp-stdio in the foreground and watch stderr: when the authorization server returns a scope different from the one requested, mcp-stdio logs authorization server granted scope: … (per RFC 6749 §5.1, a token response only includes scope when it differs from the request). If you see no such line, the server most likely granted what you asked for — but a non-conformant server may omit scope even on a downgrade, so confirm against its own logs or admin console before ruling scope out.
2. If the server downgraded your scope, check the authorization server's policy or contact the server operator.
3. Some servers support step-up authorization: if a tool call needs a broader scope, the server returns 403 insufficient_scope with the required scopes listed, and mcp-stdio automatically re-authorizes for the union of the granted and required scopes (RFC 9470), then retries the call. Note that some MCP clients do not automatically retry a tool call after mcp-stdio completes a step-up in the background (Claude Code #44652) — if the call still reports an error, try it again once.
claude.ai connector to mcp-stdio serve fails with unknown or missing client_id¶
Problem: A claude.ai custom connector pointing at a mcp-stdio serve --enable-oauth endpoint never finishes signing in, and serve's stderr shows GET /authorize … 400.
Cause: claude.ai identifies itself with a Client ID Metadata Document URL (https://claude.ai/oauth/mcp-oauth-client-metadata) instead of registering through Dynamic Client Registration — always in its "No sign-in" mode, and whenever the authorization server advertises support (serve never does). serve accepts such a client_id only when the URL is allowlisted.
Solution:
1. Add --allow-client-id-url https://claude.ai/oauth/mcp-oauth-client-metadata to the serve command (#463). No --allow-redirect-uri is needed for it: the document lists claude.ai's callback.
2. If /authorize then answers client metadata document unavailable or invalid, serve's stderr says why (cannot fetch client metadata document … or invalid client metadata document …). A fetch failure usually means the host cannot reach claude.ai over HTTPS, or claude.ai answered with a bot challenge (HTTP 403, seen from some datacenter IP ranges: anthropics/claude-ai-mcp#650); a copy validated earlier keeps working for up to 24 h after it expires.
Transport issues¶
Connection timeout or slow responses¶
Problem: Requests hang or time out frequently.
Cause: Network latency, proxy misconfiguration, or the server is slow.
Solution: 1. Increase the read timeout:
mcp-stdio --timeout-read 300 https://your-server.example.com/mcp
HTTPS_PROXY=http://proxy.example.com:8080 mcp-stdio --oauth https://your-server.example.com/mcp
NO_PROXY is respected too — this is a case where using mcp-stdio's own HTTP client sidesteps a bug in some other MCP clients' built-in transports; see Claude Code #34804.)
3. Test connectivity manually:
curl -v https://your-server.example.com/mcp
Connection reset by peer or SSL: CERTIFICATE_VERIFY_FAILED¶
Problem: mcp-stdio cannot establish a connection to the server.
Cause: Either the server is not reachable, the TLS certificate is invalid, or a proxy is intercepting the connection.
Solution: 1. Verify the server URL and check connectivity:
curl -v https://your-server.example.com/mcp
SSL_CERT_FILE env var pointing to your CA bundle.
- On Windows: Import the cert into the Windows Certificate Store.
SSE stream drops mid-request¶
Problem: You are using a legacy SSE server (--transport sse), and a tool call was in flight when the connection dropped.
Cause: The SSE GET stream disconnected while one or more requests were still awaiting their reply.
Solution: Nothing to configure — mcp-stdio reconnects automatically and synthesizes a JSON-RPC error ("SSE stream disconnected before response arrived; please retry") for every request that was in flight when the stream dropped, instead of leaving your client hanging forever. Just retry the call once you see the error. This mirrors the failure mode reported in Claude Code #60061; if possible, prefer upgrading the server to Streamable HTTP (the current MCP spec), which does not have this failure mode at all.
The server rejects requests with 400 and error code -32020¶
What it means: the server thinks the request contradicts itself — the headers say one thing and the body says another — so it refuses rather than guessing.
If you are connecting to a remote server with mcp-stdio, you did not
cause this: mcp-stdio builds those headers itself and keeps them
consistent — including the Mcp-Param-* headers a tool asks for with
x-mcp-header. On a -32020 for a tool call it re-lists the tools once
and retries, so a server whose tool list just changed recovers on its own.
If you passed --mcp-param-headers off, that switches the Mcp-Param-*
headers and the retry off, so a server that requires them answers
-32020 directly — remove the flag first. A server that requires them
even on an older-protocol session (GitHub's hosted MCP server, for
example: missing Mcp-Param-owner header) needs
--mcp-param-headers always. Otherwise, if the error still
reaches you, something between you and the server is rewriting the
headers — usually a proxy or an API gateway. Try connecting without it to
confirm.
If you are running mcp-stdio serve and a client gets this, that
client is sending inconsistent requests; the error message names which
header disagreed.
Two similar-looking errors mean different things:
| Code | Meaning | What to do |
|---|---|---|
-32020 |
headers and body disagree | look for something rewriting headers in between |
-32602 |
the request is missing information the newer spec requires | usually an incomplete client |
-32022 |
the protocol version is not supported | the error lists the versions that are — pick one of those |
Headless / SSH environment¶
Cannot open browser for OAuth login in SSH or CI¶
Problem: You are on an SSH box or in a CI environment with no display, and --oauth cannot open a browser.
Solution: Use the Device Authorization Grant (RFC 8628) instead:
mcp-stdio --oauth-device https://your-server.example.com/mcp
This prints a user code (e.g., ABCD-1234). Open it in your browser on any device and confirm. After that, mcp-stdio completes the OAuth flow on the SSH box without needing a local display. See Headless login for the full walkthrough, including how long you have to confirm and what happens if you don't.
Still stuck?¶
- Run mcp-stdio with
--checkto validate connectivity before adding it to your client config:mcp-stdio --check --oauth https://your-server.example.com/mcp - Check stderr for detailed error messages.
- Search GitHub Issues for your error.
- If all else fails, file an issue with the full command and error output.