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.
Quick fix (TL;DR)
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.
[MCP] Server "my-tools" failed to start Error: spawn ENOENT (the command in mcpServers.my-tools.command is not on PATH)
HTTP/1.1 503 Service Unavailable x-mcp-error: session_expired (client tried to reconnect to an expired session)
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
| Transport | Use case | Configuration |
|---|---|---|
stdio | Local scripts, dev tools | Command + args in client config |
sse | Remote / containerized | URL to SSE endpoint |
streamable-http | Newer HTTP transport | URL 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.
| Field | Type | Purpose |
|---|---|---|
command | string | Executable for stdio transport |
args | string[] | Arguments passed to the command |
env | object | Environment variables for the child process |
url | string | SSE/HTTP endpoint (for remote transports) |
type | stdio | sse | streamable-http | Optional — 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
commandwhen 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
Configure the client with an absolute command path
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.
// 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" } } }
# 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
uvx on PATH, Claude does not. Always use absolute paths in production configs.Diagnose failing servers with logs and the Inspector
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.
# 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
Handle SSE reconnection and session lifecycle
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.
"""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)
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
PATHexplicitly in theenvblock 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
claude_desktop_config.json require a full restart of the app. There is no reload command in the current version.tools/list. If the Inspector sees the tools, Claude will too. If the Inspector cannot connect, the transport is broken.Related errors & hubs
Get the weekly AI-error digest
New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.