Skip to content
Wiki

Most MCP connection failures come from stdout noise, bad launch config, version mismatch or HTTP header rules

DrFritzi · Reviewed · Updated 28 Sept 2026 · Markdown

Answer

Over stdio, most failures come from stray output on stdout or a server the client cannot launch (wrong command, relative paths, missing environment variables). Over Streamable HTTP, they come from HTTP rules: a missing Accept header (406), a GET the server does not serve (405), an expired session ID (404), an unsupported protocol version (400), or CORS in browser clients. Start by running the server alone in the MCP Inspector. Status codes and messages below follow MCP specification 2026-07-28 and 2025-11-25, and the TypeScript SDK where noted.

Details

stdio: stdout pollution

  • Symptom: The host logs SyntaxError: Unexpected token ... is not valid JSON and the server never shows up.
  • Cause: A non-protocol line reached stdout, which the spec forbids. A stray console.log, print or library banner is enough.
  • Fix: Log to stderr. The quoted token is usually the first character of the stray line.

stdio: wrong command, path or environment

  • Symptom: The server works in your terminal but not in the client.
  • Cause: The client may start the server with an undefined working directory (for example / on macOS). It also passes on only a limited, platform-dependent set of environment variables.
  • Fix: Use absolute paths in command and args, and pass variables through the config's env key:
{
  "mcpServers": {
    "notes": {
      "command": "/usr/local/bin/node",
      "args": ["/Users/me/notes-server/build/index.js"],
      "env": { "NOTES_DIR": "/Users/me/notes" }
    }
  }
}

Check the client's MCP logs (Claude Desktop on macOS: ~/Library/Logs/Claude/mcp*.log).

Protocol version mismatch

  • Symptom: A 2026-07-28 server returns error -32022 (UnsupportedProtocolVersionError) with a supported list. Over HTTP this comes with 400.
  • Cause: Client and server share no protocol revision.
  • Fix: Retry with a version from supported, or call server/discover to see what the server offers. In SDK v2 clients, an ERA_NEGOTIATION_FAILED error in pin mode means the pinned version is not offered. Switch to versionNegotiation: { mode: 'auto' } to fall back to initialize.

HTTP 406 Not Acceptable

  • Symptom: A POST with curl returns 406 and Not Acceptable: Client must accept both application/json and text/event-stream. That message comes from the TypeScript SDK v1 transport.
  • Cause: The spec requires the client's Accept header to list both types.
  • Fix: Send -H 'Accept: application/json, text/event-stream'.

HTTP 405 on GET

  • Symptom: A GET to /mcp returns 405 Method Not Allowed.
  • Cause: In 2025-11-25 a server MUST answer 405 if it offers no GET SSE stream. In 2026-07-28 GET is removed and servers SHOULD answer GET and DELETE with 405.
  • Fix: Nothing is broken. Use POST (in 2026-07-28, subscriptions/listen for long-lived notifications).

HTTP 404 with a session ID (2025-era servers)

  • Symptom: Requests that worked before now return 404. The SDK v1 body is Session not found (-32001).
  • Cause: The server ended or lost the session, for example after a restart.
  • Fix: The spec says the client MUST send a new initialize without a session ID. On the server side, stateless mode (sessionIdGenerator: undefined in SDK v1) avoids this failure entirely.

CORS in browser clients

  • Symptom: It works with curl but fails in a browser. Or the client cannot read Mcp-Session-Id, so every later call gets 400 Bad Request: Mcp-Session-Id header is required.
  • Cause: JavaScript in a browser can only read response headers that Access-Control-Expose-Headers allows. Any request header outside the CORS safelist triggers a preflight, and the server must allow it in Access-Control-Allow-Headers.
  • Fix: For 2025-era servers, the SDK README shows exposedHeaders: ['Mcp-Session-Id'] and allowedHeaders: ['Content-Type', 'mcp-session-id']. For 2026-07-28, also allow MCP-Protocol-Version, Mcp-Method and Mcp-Name, which clients MUST send. Keep Origin validation on. The README's origin: '*' is a placeholder: set explicit origins in production.

See also

Sources