Claude MCP server connection failed — SSE and stdio transport errors (2026) — Fix Guide (2026) | AI Error Hub
Home Providers Claude MCP connection failed
Claude MCP · Server Connection Severity: Medium HTTP n/a

Claude MCP server — connection failed, tools not available

MCP is the open standard for attaching tools and data sources to Claude. When a server fails to connect, Claude appears "less capable" — the tools you configured are simply invisible.

By Sana K. · Cloud AI Reliability Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: Claude connects to MCP servers via two transports: stdio (local child processes) and SSE / streamable HTTP (remote or containerized). Fix connection failures by (a) verifying the transport-appropriate config in the client (Claude Desktop, Claude Code, or API), (b) reading the server logs — most failures are inside the server process, not the transport, and (c) matching MCP protocol versions between client and server.

Real error messages you'll see

These are the exact strings returned by the Claude API service and its SDKs when this error occurs. Copy-paste-searching any of them should land on this page.

Claude Desktop — MCP server failed to start
[MCP] Server "my-tools" failed to start
Error: spawn ENOENT
(the command in mcpServers.my-tools.command is not on PATH)
SSE — 503 on GET /sse
HTTP/1.1 503 Service Unavailable
x-mcp-error: session_expired
(client tried to reconnect to an expired session)
Protocol version mismatch
Server sent: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05"}}
Client expected: "2025-06-18"
(client aborts; tools not registered)

Reference

MCP transports — quick reference

TransportUse caseConfiguration
stdioLocal scripts, dev toolsCommand + args in client config
sseRemote / containerizedURL to SSE endpoint
streamable-httpNewer HTTP transportURL to single HTTP endpoint

Claude Desktop / Claude Code MCP config shape

Both Claude Desktop and Claude Code look for MCP server config in a JSON file — location varies by client but the shape is the same.

FieldTypePurpose
commandstringExecutable for stdio transport
argsstring[]Arguments passed to the command
envobjectEnvironment variables for the child process
urlstringSSE/HTTP endpoint (for remote transports)
typestdio | sse | streamable-httpOptional — client infers from other fields

Root causes, ranked by frequency

Based on developer reports across Claude API forums, GitHub issues, and Anthropic community during 2025–2026.

  • 24%
    Command not on PATH. node, uvx, python, or your custom binary is not in the PATH of the client process. Common on macOS when using Homebrew paths not inherited by Claude Desktop.
  • 18%
    Missing environment variables. Server needs API keys or config that lives in ~/.zshrc — not passed through to the child.
  • 14%
    Manifest / capability mismatch. Server does not implement the tools it advertises; client sees a tool but call fails.
  • 10%
    Wrong transport type. Config points to command when the server is HTTP-only, or vice versa.
  • 8%
    SSE session expired. Long-running Claude Desktop session lost its SSE stream; server dropped state; reconnect returns 503.
  • 8%
    Protocol version mismatch. Client and server implement incompatible MCP spec versions.
  • 7%
    Server process crashes on startup. Silent failure with no logs surfacing to the client; visible only in stderr of the child process.
  • 11%
    Corporate firewall blocking SSE. Corporate networks with TLS interception break persistent SSE streams; connection resets every ~30 seconds.

Fixes — copy-paste solutions

Fix #1

Configure the client with an absolute command path

Do not rely on PATH — GUI apps have unpredictable environments.

In your Claude Desktop or Claude Code MCP config, use the absolute path to the command binary (/opt/homebrew/bin/node, not node). Set explicit env vars for anything the server needs.

claude_desktop_config.json
// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "my-postgres": {
      "command": "/opt/homebrew/bin/uvx",   // ABSOLUTE path — critical
      "args": ["mcp-server-postgres", "--connection-string", "postgres://..."],
      "env": {
        "PGSSLMODE": "require",
        "PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
      }
    },

    "custom-tools": {
      "command": "/Users/me/.venv/bin/python",   // venv Python
      "args": ["/Users/me/tools/my_server.py"],
      "env": {
        "MY_API_KEY": "sk-...",
        "PYTHONUNBUFFERED": "1"                 // helps with log flushing
      }
    },

    "remote-service": {
      "url": "https://mcp.example.com/sse",
      "type": "sse"
    }
  }
}
check_path.sh
# Diagnose why the command is not found

# 1) What is on PATH inside Claude Desktop?
#    Add this to your MCP config temporarily:
#      {"command": "/bin/sh", "args": ["-c", "echo PATH=$PATH >&2; exit 1"]}
#    Then check Claude Desktop's MCP log:
#      ~/Library/Logs/Claude/mcp*.log

# 2) Find the real path of the missing command
which uvx
# /opt/homebrew/bin/uvx  <- use this exact path

# 3) Test the server manually before wiring Claude
/opt/homebrew/bin/uvx mcp-server-postgres --help
Claude Desktop on macOS launches without your login shell environment. Even if your terminal has uvx on PATH, Claude does not. Always use absolute paths in production configs.
Fix #2

Diagnose failing servers with logs and the Inspector

Most MCP issues are inside the server — you need its logs to see them.

Read the client-side MCP log to confirm the transport works. Read the server-side stderr to see what the server is actually doing. Use the MCP Inspector to call tools directly, isolating the server from the Claude client.

debug_mcp.sh
# 1) Client-side logs (Claude Desktop)
tail -f ~/Library/Logs/Claude/mcp.log
tail -f ~/Library/Logs/Claude/mcp-server-my-postgres.log   # per-server log

# On Linux: ~/.config/Claude/logs/
# On Windows: %APPDATA%\Claude\logs\

# 2) Test the server directly with the MCP Inspector
# The Inspector is a browser UI that speaks MCP; use it to confirm the server works
npx @modelcontextprotocol/inspector /opt/homebrew/bin/uvx mcp-server-postgres

# Opens a UI at http://localhost:5173 where you can:
#   - see the tool list the server advertises
#   - call each tool with test inputs
#   - watch the JSON-RPC traffic

# 3) For SSE servers, curl the endpoint to confirm it is reachable
curl -N -H "Accept: text/event-stream" https://mcp.example.com/sse

# 4) For stdio servers, send a manual initialise message
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"debug","version":"1.0"}}}' \
  | /opt/homebrew/bin/uvx mcp-server-postgres
The Inspector is faster than iterating in Claude Desktop because it does not require a full restart per config change. Confirm the server works standalone, then wire Claude.
Fix #3

Handle SSE reconnection and session lifecycle

For remote MCP servers, plan for the connection to drop.

SSE streams get killed by proxies, corporate firewalls, laptop sleep, and network changes. Server-side, use session tokens with generous TTL; client-side, implement reconnect with exponential backoff.

mcp_server_lifecycle.py
"""Minimal MCP-over-SSE server with session lifecycle awareness.

Uses fastapi + mcp SDK. Verify current SDK API in Anthropic MCP docs.
"""
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from mcp.server.models import InitializationOptions, NotificationOptions
from fastapi import FastAPI, Request
from starlette.responses import Response
import mcp.types as types

app = FastAPI()
mcp_server = Server("my-service")

@mcp_server.list_tools()
async def list_tools():
    return [types.Tool(
        name="ping", description="Health check",
        inputSchema={"type": "object", "properties": {}, "required": []},
    )]

@mcp_server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "ping":
        return [types.TextContent(type="text", text="pong")]
    raise ValueError(f"Unknown tool: {name}")

# SSE transport
transport = SseServerTransport("/messages")

@app.get("/sse")
async def handle_sse(request: Request):
    async with transport.connect_sse(request.scope, request.receive, request._send) as streams:
        await mcp_server.run(
            streams[0], streams[1],
            InitializationOptions(
                server_name="my-service", server_version="1.0.0",
                capabilities=mcp_server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )
    # Return an empty response — SSE stream is done
    return Response(status_code=200)

@app.post("/messages/{session_id}")
async def handle_messages(session_id: str, request: Request):
    await transport.handle_post_message(session_id, request.scope,
                                       request.receive, request._send)
    return Response(status_code=200)

# Keep-alive: send a heartbeat every 15 seconds to prevent proxy timeouts
# (Add this to your mcp_server.run loop via a background task)
For corporate networks with strict egress, prefer streamable-http over sse — it uses single HTTP requests rather than long-lived connections and survives most proxy configurations.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Always use absolute paths in command — never rely on the client's PATH.
  • Set PATH explicitly in the env block for servers that spawn subprocesses.
  • Test every server standalone with the MCP Inspector before wiring the Claude client.
  • Log server stderr to a file — Claude does not always surface it.
  • For SSE servers, implement heartbeat / keep-alive to survive corporate proxies (15-30s interval).
  • Version-pin your MCP SDK on both sides; protocol updates occasionally require synchronised bumps.
  • For production remote servers, use streamable-http — more reliable across networks than SSE.

Frequently asked questions

stdio launches the MCP server as a child process and communicates via stdin/stdout — best for local tools. SSE (or streamable-http) speaks HTTP to a remote server — best for containerized or shared services. Both use the same JSON-RPC protocol on top.
Yes — via the MCP connector on the API (see the api-side MCP docs). Any Anthropic API request can attach MCP server URLs; Claude routes tool calls to the servers automatically. The plumbing is different from Claude Desktop but the protocol is the same.
Claude Desktop reads the MCP config only at startup. Changes to claude_desktop_config.json require a full restart of the app. There is no reload command in the current version.
For SSE/HTTP servers, yes — the server can accept many concurrent client sessions. For stdio, no — each client spawns its own child process. Shared state must live outside the process (database, cache) for stdio servers.
Use the MCP Inspector — it shows the tool list the server advertises via tools/list. If the Inspector sees the tools, Claude will too. If the Inspector cannot connect, the transport is broken.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.