How to Debug an MCP Server (2026 Guide)
An MCP server usually fails silently: client and server trade JSON-RPC messages neither one shows you by default, so a broken tool call and a working one look identical from the chat window. This guide covers why that invisibility makes debugging hard, the toolbox for getting it back — the official MCP Inspector, server-side logging, and mcpsnoop, a transparent proxy that watches your real client-server session — a live walkthrough with real commands, and the failure signatures (bad schemas, unregistered tools, 401s, hangs) that finally become visible once you can see the wire.

On this page · 16 sections▾
TL;DR: get visibility, then debug
Debugging an MCP server means making the client-server JSON-RPC traffic visible, because none of it shows up in a chat window by default. Three tools do that, each at a different point in the connection:
- The official MCP Inspector — a browser UI that connects to your server as its own client, for testing tools, prompts, and resources before you wire up a real one.
- Server-side logging — stderr for stdio servers, log notifications for everything else; free, but only useful if you added it before the bug happened.
- A transparent proxy, like mcpsnoop — wraps your real server command and mirrors every frame from your actual client session, live, to a terminal UI you can filter, replay, and export.
The fastest way to see it working, against a published test server, with nothing to build:
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest
claude mcp add everything -- mcpsnoop -- npx -y @modelcontextprotocol/server-everythingRun mcpsnoop with no arguments in a second terminal, start a new client session, and ask it to use the tools — the walkthrough below covers exactly what you’ll see. One framing worth keeping in mind throughout: mcpsnoop is a small, single-maintainer project created in late June 2026 — a useful tool for this specific job, not the standard. Browse the servers this applies to at mcp.directory/servers.
Why MCP debugging is hard
An MCP client and server are two separate processes, not two functions on the same call stack. They agree to talk JSON-RPC — a request/response message format — over a transport: stdio (a local subprocess reachable over its own stdin/stdout) or Streamable HTTP (JSON-RPC riding an HTTP POST plus an SSE stream, for remote servers). Neither transport shows you its contents by default.
That invisibility collapses a wide range of real problems into one identical symptom: the agent didn’t call the tool you expected, called it with the wrong arguments, or called it and silently swallowed an error. From the chat window, a broken tool call and a working one that returned nothing interesting look the same — nothing to read, nothing to compare, nothing to attach to a bug report.
The official MCP debugging guide frames the fix directly: stop guessing from the chat window and get visibility at the protocol layer. If you haven’t worked with the protocol itself yet, our MCP explainer covers the JSON-RPC wire format and transports this guide assumes. Everything below is one of three ways to get that visibility back.
The toolbox: Inspector, logs, and a transparent proxy
The official debugging guide names three levels of tooling: an interactive inspector, server-side logging, and your client’s own developer tools. A newer class of tool, led by mcpsnoop, adds a fourth vantage point — sitting inside the real connection instead of beside it. Here is what each one actually shows you.
MCP Inspector — test before you integrate
MCP Inspector is the protocol maintainers’ own tool: run npx @modelcontextprotocol/inspector <command> and a browser UI opens with tabs for resources, prompts, tools, and a live notification pane. You pick the transport, invoke anything by hand, and watch what comes back.
Its limit is built into how it connects: Inspector is its own client. It negotiates its own capabilities and drives its own session — it never sees what Cursor, Claude Code, or Claude Desktop actually sent your server, because that is a different conversation entirely. A breakpoint only fires once a request arrives, so Inspector cannot show you a call the model never made, or made with arguments your real client happened to produce.
Takeaway: reach for Inspector first, every time — the official docs call it your first stop, and it catches most bugs before a real client is even in the loop. It cannot catch the bugs that exist only between your production client and your server.
Server-side logging — the tool you already have
For stdio servers, anything written to stderr is captured by the host application automatically — no extra wiring. Streamable HTTP servers don’t get that for free: stderr isn’t captured by the client, so you need log message notifications, your own log aggregation, or plain HTTP tooling (curl, a browser’s Network panel) to see requests, session headers, and SSE streams. MCP defines eight RFC 5424 severity levels, from debug to emergency, and clients can raise or lower the minimum level at runtime.
Takeaway: logging is free and always-on once you’ve added it, but it only covers what you thought to log. It cannot retroactively show you a call you didn’t instrument.
Transparent proxies — mcpsnoop and friends
mcpsnoop takes a different approach: instead of connecting as a client or waiting for you to log something, it becomes the process your real client spawns. Wrap your server’s existing launch command with it, and it forwards every byte verbatim while mirroring a copy of each JSON-RPC frame to a live terminal UI. Because it sits in the actual pipe, it sees exactly what your real client and real server say to each other — whatever the server is written in.
| MCP Inspector | Server logging | mcpsnoop | |
|---|---|---|---|
| Sees your real client’s traffic | No — its own client | Only what you logged | Yes — sits in the real connection |
| Needs server code changes | No | Yes | No |
| Works after the fact | No — live session only | Yes, if you kept the logs | Yes — sessions persist to disk |
| Flags slow or hung calls | No | Only if you built it | Yes — live timer, SLOW flag |
| Best for | Pre-integration testing | Cheap, always-on visibility | A bug that only reproduces live |
Takeaway: reach for a transparent proxy exactly when Inspector’s simulated session cannot reproduce what you’re seeing in production — and treat it as one option in this lane, not the only one; see the community signal section for a fair alternative a Hacker News commenter raised.
Mental model: frame, shim, hub, session
Four named pieces make the rest of this guide click:
- Frame — one complete JSON-RPC message: a request, a response, or a notification. Everything a debugging tool shows you is built from frames.
- Shim — the small process your client actually launches when you wrap a server command. It forwards bytes untouched and mirrors a copy elsewhere.
- Hub / TUI — the terminal viewer that receives mirrored frames and renders them live. It runs as its own process and finds the shim through a local socket.
- Session — one client connection’s worth of captured frames, persisted to a log file so you can review it after the connection closes.
The structural difference between Inspector and a transparent proxy is where each one sits relative to the real conversation:
Inspector — a separate conversation
MCP Inspector ────▶ Your Server
(its own session, its own capability negotiation)
Real Client ────▶ Your Server
(Cursor, Claude Code, Claude Desktop — a DIFFERENT session)
mcpsnoop — inside the real conversation
Real Client ──▶ mcpsnoop shim ──▶ Your Server
(unchanged) (spawned as the (unchanged)
server command)
│
│ mirrors every frame
▼
mcpsnoop hub / TUI
(live view · filter · replay · export)Takeaway: Inspector and your real client each hold a separate conversation with the server. A transparent proxy is the only one of the two that watches the same conversation your production client is actually having.
Walkthrough: watch real traffic live
The smallest way to see this working end to end uses a published test server, so there is nothing of your own to write. @modelcontextprotocol/server-everything is the protocol maintainers’ own reference server — it exercises most of the spec, and its tools have no client-native equivalent, so anything you see genuinely went over MCP.
Wrap a stdio server
Put mcpsnoop on your PATH — via Go, Homebrew, or a prebuilt binary from the project’s releases page — then wrap the launch command in Claude Code:
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest
claude mcp add everything -- mcpsnoop -- npx -y @modelcontextprotocol/server-everythingFor Claude Desktop, wrap the same command in claude_desktop_config.json instead:
{
"mcpServers": {
"everything": {
"command": "mcpsnoop",
"args": ["--", "npx", "-y", "@modelcontextprotocol/server-everything"]
}
}
}Now watch it:
- Run
mcpsnoopwith no arguments in a separate terminal. The TUI opens and waits. - Start a new client session — MCP servers load at session start, so an already-open session won’t show up.
- Ask the client to exercise the tools: “Use the everything MCP server to echo a short message, add 40 and 2, then run its long-running operation.”
- Drill into a frame with
enter, filter with/, inspect negotiated capabilities withc, replay a call withr.
The quick calls return OK almost immediately. The long-running one sends progress notifications and runs long enough to trip a SLOW flag with a live elapsed timer — the first time most people see, rather than infer, that a call is still in flight.
Reverse-proxy an HTTP server
Stdio servers get wrapped directly. A Streamable HTTP server sits behind a reverse proxy instead:
mcpsnoop http --target http://localhost:3000/mcp --listen :7000Point your client at :7000 instead of the server directly, and every frame mirrors the same way. One asymmetry worth flagging now, because it matters later: HTTP mode intercepts JSON-RPC messages, not their headers — so a bearer token never lands in a captured trace, but a header-level failure (a 401, for instance) is invisible to it too. More on that in common failure signatures below.
Deep dive: filter, replay, export
Filter the stream
Press / inside a session and combine space-separated tokens, ANDed together. Plain text matches the method, tool, id, and payload:
tool:search status:slow # slow calls to one search tool
method:tools/call status:error # tool calls that failed
dir:s2c kind:req # server-initiated requests (sampling, roots)The full token set: tool:, method:, id:, dir: (c2s or s2c), kind: (req, resp, notify, stderr), and status: (ok, error, slow, pending). Takeaway: treat this as a live grep over the protocol, not a raw log to scroll through — stack tokens until only the frames you care about remain.
Replay a call
Press r on a captured tool call to send it again against a fresh server process, without re-driving the client that originally triggered it. Takeaway: replay turns “I saw it fail once, an hour ago” into “watch it fail again, right now” — the difference between describing a bug and reproducing one.
Export and post-mortem review
Sessions persist automatically as JSONL under ~/.local/state/mcpsnoop/sessions/ (or $MCPSNOOP_HOME / $XDG_STATE_HOME), so running mcpsnoop with no arguments backfills every past session next to any live ones — you can review yesterday’s bug without reproducing it today. Turn any session into a portable file:
mcpsnoop export -T html -o out.html # self-contained, searchable HTML
mcpsnoop export -T text server.py-48213 # one session, as plain text
mcpsnoop export -T json | jq # newest session, piped to jqFor a specific bug report, write straight to a known path with --trace-file ./mcpsnoop-session.jsonl, and add --redact-secrets to scrub common secret field names before anything touches disk. Takeaway: export before you ask a teammate to look at something — a session file is a far better bug report than a description of what you think you saw.
What we got wrong
Three assumptions worth correcting before you build a workflow around any of this — each one grounded in what the docs and the maintainer actually say, not what the pitch implies.
We assumed a tool that shows “every JSON-RPC frame” would show us a 401. It won’t. A request rejected at the HTTP transport layer, before it’s ever parsed as JSON-RPC, never becomes a frame at all. Asked directly about this on Hacker News, mcpsnoop’s maintainer confirmed that HTTP mode intercepts the JSON-RPC messages but not their headers — by design, so a bearer token never gets logged, but it also means a header-level auth failure is invisible to it. For that layer, the official docs point you at curl -v or your browser’s Network panel instead.
We assumed a server that passes Inspector is debugged, period. It only means the server works with Inspector’s own session. Inspector connects as its own client and negotiates its own capabilities, which can legitimately differ from what Cursor or Claude Code declares. A server can pass every tab in Inspector and still misbehave against your actual client.
We assumed --redact-secrets makes a captured session safe to paste into a public issue. It’s explicitly best-effort: the README states it only scrubs values under matching JSON object keys, so a token embedded in a stderr line, or nested under a differently-named field, passes through untouched. Read an export before you share it — don’t assume the flag did that for you.
Common failure signatures
Two habits prevent most wasted debugging time:
Wrong
Stare at the chat transcript, guess which call failed, retry the whole conversation and hope it works this time.
Right
Wrap the server once, reproduce the failure, and read the one frame that actually failed.
Wrong
Test only in Inspector, ship, and treat every field report as user error.
Right
Keep Inspector for pre-integration testing, then verify one real client session before you ship — the two can legitimately disagree.
With that out of the way, here is what four common problems actually look like on the wire.
Bad schema: rejected before the first call
Signature: the tool never becomes usable, or the client throws a validation error at connect time. Root cause: the input schema uses a feature not every client’s validator accepts — the most common real case is oneOf/anyOf at the top level of input_schema, which at least one popular client has rejected outright with a 400 invalid_request_error (documented in the wild in our Todoist MCP guide). Fix: flatten the schema, or check your target client’s supported JSON Schema subset before leaning on advanced features.
Tool not registered: nothing to call
Signature: the agent says it has no matching tool, or falls back to a worse method, even though you know the tool exists in your code. Root cause: the server errored during startup before registering it, or the client is still holding a tool list cached from before your last change — a plain reconnect isn’t always a full restart. Fix: check the tools/list response from the initialize handshake directly, in Inspector’s Tools tab or a captured frame. If it’s not there, fully quit and reopen the client — a plain reconnect won’t refresh it.
Auth 401: invisible above the JSON-RPC layer
Signature: the connection fails with a generic error, and a JSON-RPC-level tool shows nothing useful. Root cause: the rejection happened at the HTTP transport layer, before any JSON-RPC framing — the exact gap described above, where a bad or missing bearer token never becomes a payload you can inspect. Fix: drop to HTTP-level tooling for this layer specifically — curl -v against the endpoint, or your browser’s Network panel, both explicitly recommended in the official debugging guide for Streamable HTTP.
Timeout: a request with no response
Signature: the client appears to freeze on one step, far longer than the operation should take. Root cause: the tool handler is blocked on something external — a slow API, a lock, an infinite loop — and if it never sends a progress notification either, most tools give you zero signal anything is in flight. Fix: watch it live. A transparent proxy shows the request with no matching response and a running timer, which is exactly how mcpsnoop flags a call as SLOW.
Limits & safety
- It runs the command you already wrap — nothing more. That adds no new execution surface, but the project’s own security notes are explicit: only wrap servers you trust, and run untrusted ones in a container.
- Captured frames can contain anything your server exchanges — prompts, tool arguments, results, and any credentials embedded in them. Redaction is opt-in (
--redact-secrets,--redact-key) and matched by JSON key name only; it is not a guarantee. - HTTP mode never sees headers. Good for not accidentally logging a bearer token, bad for diagnosing anything that fails at the header level — see the auth-401 signature above.
- No remote transport of its own. To watch traffic on a remote host, the docs point you at an SSH tunnel or streaming the session log over SSH, keeping the tool itself entirely local.
- It is early. A single-maintainer, MIT-licensed Go project created in late June 2026, actively responding to feedback in public. Treat it as a useful, current tool for real in-path visibility — not as a fixture the way Inspector is.
Who this is for
For: anyone building or maintaining an MCP server — including any of the servers listed in our directory — who has hit “it works in Inspector but not in my real client.” Anyone triaging a bug report where a session export beats another round of “can you describe what happened?” Anyone running a server over Streamable HTTP who needs to watch a reverse-proxied session without touching client or server code.
Not for: a first walkthrough of a brand-new server — Inspector’s guided tabs are the friendlier starting point, and the official docs say so directly. Auditing which servers or configs are exposed across a fleet you don’t operate day to day — that is a different job, closer to what AgentScan or mcp-audit do, and closer to what we cover in our MCP security guide. Fleet-wide, always-on observability — this is a local, point-in-time terminal tool, not a hosted monitoring product.
Community signal
mcpsnoop’s Show HN thread (63 points, July 3, 2026) is a better read for how it landed with working developers than any pitch would be — including the parts that pushed back.
The core framing resonated. One commenter wrote: “ ‘MCP Inspector […] never sees the traffic between your client and your server.’ This line resonates a lot, what you’re building makes sense to me!”
Not everyone agreed a dedicated tool is necessary. One reply offered the general-purpose alternative: “You can just get your agent to run tshark :)” — to which the maintainer, Semyon Usachev (kerlenton), gave an unusually direct answer: “Ha, fair. tshark in a shell works fine, the only reason to wrap it is typed output and not handing the agent a raw shell.” A second commenter made the broader version of the same point: building a custom proxy, they said, is “really simple” with an LLM doing the scaffolding.
Both are fair objections. A packet sniffer or a hand-rolled proxy will show you bytes. What a purpose-built tool adds is MCP-specific structure on top of those bytes — frames correlated into calls, a SLOW flag on hung ones, a filter language keyed to tool names and call status instead of IPs and ports. Whether that structure is worth a dedicated binary depends entirely on how often you’re debugging MCP specifically, versus network traffic generally. Tech press coverage picked up the Inspector-comparison framing within a day of the Show HN post — a sign of good timing and a clear pitch, not evidence the underlying idea (a transparent shim) is new to systems debugging in general.
The verdict
Our take
Reach for the official MCP Inspector first, every time — it’s free, install-free (npx @modelcontextprotocol/inspector), and catches most bugs before a real client is even involved. Reach for a transparent proxy like mcpsnoop specifically when a bug only reproduces with your actual client, or when you need a session you can filter, replay, and hand to a teammate. Skip mcpsnoop if a general-purpose tool you already trust — tshark, a hand-rolled proxy — gives you equivalent visibility; even its own maintainer will tell you that’s a legitimate call. Don’t skip having some layer of real-traffic visibility — guessing from a chat transcript is the actual anti-pattern here.
mcpsnoop itself was created in late June 2026, is MIT-licensed, and is built by one developer who is answering questions in public. That is not a knock — plenty of durable developer tools started exactly this way — but the honest framing is “a useful, actively-developed tool for this job,” not “the standard.” The idea underneath it — watch the real data path instead of a simulated one — is the part worth keeping regardless of which specific binary you reach for next year.
The bigger picture
MCP tooling is splitting into two lanes that solve different problems and get confused for each other constantly. One lane is runtime debugging — watching one server misbehave while you fix it, which is everything in this guide. The other is exposure and inventory auditing — scanning which servers and configs exist across a fleet, and whether any of them leak secrets or expose shadow APIs. Tools like AgentScan and mcp-audit live in that second lane; we cover the exposure and CVE side of that world separately in our MCP security guide.
Both lanes exist for the same underlying reason: MCP traffic and MCP configuration are invisible by default, at every layer, until you add a tool that makes them visible. The official Inspector remains the right first stop for interactive testing, by the protocol’s own recommendation. Live, in-path visibility into a real session is the part of that gap tools like mcpsnoop are trying to fill — and it would not be surprising to see this specific lane consolidate around a small number of tools, official or not, over the next year or two.
Frequently Asked Questions
How do I debug an MCP server?
Get visibility into the actual JSON-RPC traffic instead of guessing from a client's chat window. Start with the official MCP Inspector to test the server in isolation, check server-side logs (stderr for stdio), and if a bug only shows up with your real client, wrap the connection with a transparent proxy like mcpsnoop to watch the live session.
MCP Inspector vs mcpsnoop — which should I use?
Use Inspector first: it's the official, transport-agnostic UI for testing a server in isolation, and the protocol's own docs call it your first stop. Reach for mcpsnoop when a bug only reproduces with your actual client, since Inspector connects as its own client and can't see that traffic.
Why isn't my agent calling my MCP tool?
Three usual causes: the tool never registered (check the initialize handshake's tools list), its schema uses something your client's validator rejects, such as top-level oneOf or anyOf, or the model didn't choose it. A live frame view or Inspector's Tools tab shows which case you're in immediately.
What does a capability negotiation error mean?
JSON-RPC error -32602, Invalid params, often means your server sent a request type — like sampling or elicitation — that the client never declared support for during initialize. Inspect the initialize exchange on both sides; client and server need to agree on capabilities before anything built on top of them works.
How do I debug a remote or Streamable HTTP MCP server?
A JSON-RPC-level tool, like mcpsnoop's HTTP reverse-proxy mode, shows message traffic but not transport failures — a request rejected before it's parsed as JSON-RPC, such as a 401, never becomes a visible frame. Pair it with curl -v or your browser's Network panel for that layer, exactly as the official docs recommend.
Is it safe to run a proxy like mcpsnoop against a production server?
It only runs the server command you already wrap, so it adds no new execution risk. But captured frames can include prompts, arguments, and credentials — use its opt-in secret redaction, know that it's best-effort and keyed to JSON field names, and avoid wrapping any server command you don't already trust.
What's the difference between mcpsnoop and a security scanner like mcp-audit?
Different jobs. mcpsnoop watches live traffic on one server you're actively debugging, in real time. mcp-audit and similar scanners inventory MCP configs across servers you may not even be running yet, checking for exposed secrets or shadow APIs — closer to the exposure scanning we cover in our MCP security guide.
Glossary
JSON-RPC
The request/response message format MCP uses to carry every tool call, prompt, and resource read over any transport.
stdio transport
A local subprocess transport; the client launches the server and talks over its stdin/stdout, leaving stderr free for logs.
Streamable HTTP transport
MCP's remote transport; JSON-RPC rides an HTTP POST plus an SSE stream, tracked by a session ID.
MCP Inspector
The official, browser-based testing tool that connects to a server as its own standalone client for interactive testing.
Transparent proxy (shim)
A process inserted into a real client-server connection that forwards every byte while mirroring a copy for inspection.
Capability negotiation
The initialize handshake step where client and server each declare which optional protocol features they support.
Frame
One complete JSON-RPC message — a request, a response, or a notification — the unit a debugging tool captures and displays.
Hung call
A tool call that never returns a response frame; visible as a request with no match and a climbing elapsed timer.
Session replay
Resending a previously captured request against a fresh server process, to reproduce a bug without redriving the whole client.
Redaction
Scrubbing known-sensitive fields, like tokens or API keys, from a captured trace before it's shared, usually matched by JSON key name.
Tool registration
The step where a server lists a tool's name and schema in its tools/list response, making it callable at all.
Error -32602
The standard JSON-RPC code for Invalid params; in MCP, a frequent cause is a capability mismatch surfacing mid-request.
TUI (terminal UI)
An interactive, keyboard-driven interface rendered inside the terminal, as opposed to a browser-based UI like Inspector's.
Post-mortem debugging
Reviewing a session log after the fact, from disk, instead of watching the traffic live as it happens.
Sources
- Primary — mcpsnoop README, install, and security notes: github.com/kerlenton/mcpsnoop (MIT, Go)
- Primary — trying mcpsnoop end to end: docs/TRY_IT.md
- Primary — reviewing past sessions: docs/POST_MORTEM.md
- Primary — official MCP Inspector repo: github.com/modelcontextprotocol/inspector
- Primary — official MCP debugging guide: modelcontextprotocol.io/docs/tools/debugging
- Primary — official MCP Inspector guide: modelcontextprotocol.io/docs/tools/inspector
- Community — Show HN thread (Jul 3, 2026): news.ycombinator.com/item?id=48777144
- Web — tech press coverage: TechTimes: MCP Debugging Goes Transparent
- Adjacent tools — AgentScan · mcp-audit
- MCP.Directory — Browse MCP servers · Claude Code client guide
- Related guides — What is MCP? · MCP Security 2026 · Todoist MCP guide