How to Debug an MCP Server
Most MCP failures look the same from the chat side — the model says it cannot find the tool, or the server status flips red. Underneath, the actual cause is one of three layers: the process did not start, the protocol handshake failed, or the handler is throwing. Knowing which layer to inspect cuts debug time from hours to minutes.
Goal
Diagnose any MCP failure to its actual root cause
Time
~10 minutes per debug session
Tools
Client logs + MCP Inspector + jq
The debugging playbook
Read the client logs first
Every client surfaces MCP logs. Claude Desktop: Settings → Developer → MCP Logs. Claude Code: `claude mcp list` or /mcp in a session. Cursor: the MCP panel in Settings. VS Code: Output panel → MCP. Read the actual error before guessing — 80% of MCP failures are obvious from the log line.
Run the server outside any client
Take the exact command from your config and run it manually in a terminal. If it fails there, the issue is the server (missing binary, bad path, runtime error). If it succeeds there but fails in the client, the issue is the wrapper — wrong path, wrong env, wrong scope.
# Example: run a Filesystem MCP exactly as Claude Desktop would npx -y @modelcontextprotocol/server-filesystem /Users/you/code # It should print nothing to stdout (stdout is the protocol stream) # and either run silently or print errors to stderr.
Use the MCP Inspector to confirm tools register
The Inspector is the canonical tool for debugging MCPs. It connects to your server, lists every tool, and lets you call them with arbitrary inputs. If the Inspector cannot see your tools, no client will — fix the manifest before debugging the wrapper config.
npx @modelcontextprotocol/inspector \ npx -y @modelcontextprotocol/server-filesystem /Users/you/code # Open the printed URL. # Click "List Tools" — every tool the server exposes should appear. # Click any tool, fill in inputs, "Call" — the response echoes back.
Validate the JSON config separately
A stray comma or missing brace in claude_desktop_config.json or .vscode/mcp.json silently breaks every server. Paste the file into any JSON validator (or `jq . < file.json` from the shell). Many parse errors are not surfaced as warnings — the client just ignores the file.
# Quick check from the shell jq . ~/Library/Application\ Support/Claude/claude_desktop_config.json # If jq exits non-zero, fix the JSON before going further.
Isolate stdio vs protocol vs handler
Three failure layers, in order. (1) stdio: the process did not start at all — error appears in client logs. (2) protocol: the server started but the handshake failed — Inspector shows no tools. (3) handler: tools are listed but calls return errors — Inspector shows the actual error from your handler. Identify the layer first, then fix.
Check env vars are reaching the process
For credentialled servers, the most common silent failure is an env var that did not propagate. In Claude Code, ${VAR_NAME} expands from the shell that launched `claude`. In Claude Desktop, env values must be hardcoded or set via the OS-level environment. Add a temporary log line to your server that prints `process.env.YOUR_VAR ? "set" : "missing"` and re-run.
Never write to stdout from inside an MCP server
The Inspector cheat-sheet
The MCP Inspector is the highest-leverage debugging tool in the ecosystem. Run it against your server before debugging any client wrapper.
# Inspect a stdio server (most common case) npx @modelcontextprotocol/inspector \ npx -y @modelcontextprotocol/server-filesystem ~/code # Inspect a remote (HTTP / SSE) server npx @modelcontextprotocol/inspector \ --transport sse https://my-mcp.example.com/sse # Pass env vars to the inspected server npx @modelcontextprotocol/inspector \ -e GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx \ -- npx @github/github-mcp-server
Specific failure modes
"connected" but tool count is zero
The server started but registered no tools. Check the order of `server.tool()` and `server.connect()` calls — connecting before declaring tools is a common bug. Inspector will show the same zero count, confirming the issue is the manifest.
Server hangs on launch
Almost always something writing to stdout. MCP reserves stdout for the protocol stream — any `console.log()`, `print()`, or framework boot message corrupts it. Move every log to stderr (`console.error`, `print(..., file=sys.stderr)`).
Tool calls return "method not found"
Tool name in the call does not match what the server registered. Check the exact name in the Inspector's tool list — case matters, hyphens vs underscores matter. Some clients normalize names; the protocol does not.
Random disconnects under load
Your handler is throwing async errors that crash the event loop. Wrap the handler body in try/catch, log the error to stderr, and return an error result instead of throwing. The protocol expects a structured response, not an unhandled exception.
FAQ
Why does my MCP work in the Inspector but not in Claude Desktop?
Almost always the wrapper config — the path or command in claude_desktop_config.json does not match what you ran in the Inspector. Copy the exact Inspector command into the JSON entry and try again. Pay close attention to absolute vs relative paths.
Where do I write logs in an MCP server?
Standard error (stderr). MCP uses stdout for the protocol; anything you write there corrupts the stream. `console.error()` in Node, `print(..., file=sys.stderr)` in Python, structured loggers configured to stderr in any other language. Never `console.log()`.
How do I see network requests an MCP makes to external APIs?
Easiest: run the server through a logging proxy (mitmproxy, Charles) when debugging. Slightly more involved: instrument the HTTP client inside the server to log requests to stderr. Both surface the upstream call without needing to modify the MCP runtime.
Is there a way to step through an MCP server in a debugger?
Yes. For Node, launch the server with --inspect-brk and attach VS Code's built-in debugger. For Python, run with `python -m debugpy`. The Inspector or your client launches the process; if the process is started in debug mode, you get full breakpoints inside handlers.
Next steps
Once a server is reliably working, the next concern is making sure agents cannot use it to do something they should not. Lock down the surface before scaling up.