Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix E: Troubleshooting Guide

Find your symptom in the table, then jump to its section.

Fast Triage Table

SymptomCheck firstUsual causesEscalate to
Server will not startRuntime version, dependency install, configured commandBroken local environment, bad entrypoint, stdout pollutionBuild or runtime owner
Client cannot connectCommand path or URL, Origin/Host, proxyWrong config, rejected origin, proxy stripping headersClient integration or platform owner
Tools are missing or look wrongserver/discover response, _meta.protocolVersion, tools/listVersion mismatch, undeclared client capability, malformed schemaServer owner
Auth failsWWW-Authenticate, resource metadata, token audienceExpired token, bad discovery, claim mapping bugAuth owner
Requests hang or time outReadiness, dependency health, retry loop, proxy timeoutsSlow dependency, buffering proxy, model loopService owner
One tenant sees another tenant’s dataIdentity binding, cache keying, task routingScope bug, shared cache issue, weak downstream checksSecurity and platform owner
A change broke working clientsCatalog diff since the last releaseRenamed tool, changed parameter meaningServer owner

Start With the Smallest Reproduction

Test the domain logic directly, then the MCP surface in the Inspector, then one real client, and only then cross-client variance. Narrow until one layer is obviously responsible.

Server Will Not Start

Check the runtime version, dependencies in the environment the client launches, an absolute command path, required environment variables, and stdout: on stdio one print() breaks the connection.

What you’ll see (the SDK client, when the server printed a banner):

Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='starting notes server', input_type=str]

Confirm it: send one request and print every stdout line that isn’t JSON-RPC. Any output means stdout is polluted.

echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' \
  | uv run server.py 2>/dev/null | grep -v '^{"jsonrpc"'

Client Cannot Connect

Check that the server stays healthy outside the client, that the URL is canonical, and that the proxy passes the MCP headers through unchanged.

What you’ll see: Invalid Origin header with HTTP 403 (a browser-mediated client whose origin isn’t allowed), Invalid Host header with 421 (a proxy forwarding the public Host to a loopback-bound server; set allowed_hosts, Chapter 7), or -32020 mcp-method header does not match the request body's method (a proxy rewrote one and not the other).

Confirm it:

npx @modelcontextprotocol/inspector --server-url https://notes.example.com/mcp

Tools Are Missing or Look Wrong

In 2026-07-28 there’s no handshake: each request states its version and capabilities in _meta (Appendix A). Check what server/discover advertises, whether the client surfaces the primitive at all, and whether descriptions are specific enough to be chosen. An interface too weak to use well is still a defect.

What you’ll see: -32022 Unsupported protocol version with data.supported (the client speaks a revision the server doesn’t), -32021 with data.requiredCapabilities (a tool needs, say, elicitation the client didn’t declare), or Unknown tool: … as an isError result (the client cached an old catalog).

Confirm it:

curl -s https://notes.example.com/mcp -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' -H 'Mcp-Method: server/discover' \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

Auth Fails

A 401 means authentication is missing or invalid; a 403 means the principal is known but not entitled. Don’t treat all auth failures as “bad token”: many are really identity-mapping bugs.

What you’ll see: a 401 whose WWW-Authenticate has no resource_metadata (discovery can’t start), or a resource in the metadata that differs from the URL the client used (a 401 loop). The correct bytes are in Appendix B.

Confirm it:

curl -si -X POST https://mcp.example.com/mcp | grep -i www-authenticate
curl -s https://mcp.example.com/.well-known/oauth-protected-resource/mcp

Requests Hang or Time Out

Check dependencies, tool count, response size, retries, and whether a proxy buffers text/event-stream or times out first. Slowness is often an interface problem disguised as an infrastructure problem.

What you’ll see: the same args_hash repeating for one principal in the tool-call log (Chapter 23), or streamed progress arriving all at once at the end.

Confirm it: curl -N the endpoint with a slow tool and watch whether events arrive incrementally.

One Tenant Sees Another Tenant’s Data

High severity until proven otherwise. Check tenant resolution, cache keys, background tasks and downstream checks; if you can’t answer quickly, contain first (Chapter 21).

What you’ll see: a tool-call event whose tenant differs from the token’s tenant claim.

Confirm it: query the event log for tenant != token_tenant over the incident window.

A Change Broke Working Clients

Look for renamed tools, changed parameter meaning, tightened approvals, a moved URL, and schema fields prompts relied on. “Small cleanup” changes cause most of these.

What you’ll see: Unknown tool: search after a rename, or a sudden drop in one tool’s selection share by catalog_version.

Confirm it: diff the tools/list output of the last good release against the current one.

Evidence to Capture Before Escalation

  • exact client, client version and environment
  • timestamp and trace ID
  • tool or resource involved
  • auth state or error code
  • whether the problem is reproducible
  • the last known-good comparison point