Skip to content

MCP error -32000: Connection closed

Something on the other end of an MCP connection went away, and the transport reported it. This failure happens entirely between your client and an MCP server — a local process or a remote endpoint you configured — so it is not an API error, not your API key, not a quota, and not the model. No request reached the model provider and nothing was billed. Your prompt is irrelevant here: the tools that server was supposed to provide simply are not available.

Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.

The number is a category, not a cause

-32000 sits in the block JSON-RPC 2.0 reserves for implementation-defined server errors. It is the client saying “the channel is gone” in the only vocabulary the protocol gives it. It is emitted by the transport layer, which knows the pipe closed and knows nothing else — not the exit code, not the stack trace, not whether the server printed a reason on its way out.

That has one consequence worth internalizing before you touch any config: the message is phase-ambiguous, and recovering the phase is the entire job. The identical string appears when a server dies during startup, when it exits three seconds later, and when a server that worked fine for twenty minutes gets killed and the next tool call finds a dead pipe. Those are three different problems with three different fixes, and the error text discriminates between none of them.

This is the practical difference between this message and the Codex handshake failure, which names the phase in the string itself. When a message says the connection closed while waiting for the initialize response, you know the server never became usable. -32000 promises you nothing of the kind.

Recovering the phase

Three observations, in the order that costs you the least time:

  • Did the server ever come up in this session? /mcp and claude mcp list show a health status per server: ✔ Connected, ! Needs authentication, ✘ Failed to connect, ⏸ Pending approval, ✘ Connection error, or ✘ Rejected. A server showing ✘ Failed to connect never started. One that you watched connect and that is now failing died mid-session.
  • Ask for the failure detail instead of guessing it. On ✘ Failed to connect, claude mcp list appends the detail and claude mcp get <name> prints it on an Issue: line — the HTTP status or error code plus the server’s own error text, with credential-like text redacted. Note the asymmetry: no detail is appended to ✘ Connection error. If that is the status you have, the CLI has nothing more to give you and the next section is where you go.
  • Time the failure. A server that dies in well under a second is crashing on its own startup path — a missing runtime, a bad argument, an import error. A server that takes noticeably longer and then reports closed is more likely failing on something it does at startup: reading a credential, resolving a host, opening a database.

Is retrying useful?

No — and unlike most retry questions, this one has already been answered for you automatically before the message reached your screen.

The reconnection behavior is not something you can improve by hitting the up arrow. On a first connection over HTTP or SSE, a transient error (5xx, connection refused, timeout) is retried up to 3 times before the server is marked failed. When a remote server drops mid-session, the client reconnects with exponential backoff, up to 5 attempts, starting at a 1-second delay and doubling, showing the server as pending while it tries. By the time you read -32000, that budget is spent. Re-sending your prompt re-runs a decision that has already been made.

For stdio servers the answer is even more final: stdio servers are local processes and are never auto-reconnected. Once that process is gone, the session holds a dead handle for the rest of its life. No amount of retrying inside the session brings it back — you restart the server’s configuration and start a session that will spawn it again.

The useful version of “try again” is therefore a different action: fix something, then start a new session. If the second session fails identically, you are looking at a deterministic configuration problem, which is what the rest of this page is about.

Where the real reason is actually written

A stdio server is a child process, and a child process that dies has usually said why — on stderr, not through the protocol. That output does not appear in the error string, which is why this error reads as informationless while the explanation sits a few lines away in a log.

Run the exact command from your configuration by hand in a terminal, with the same arguments and the same environment. Most startup crashes reproduce in one second at a shell prompt and print the reason in plain text. When the command works by hand but not from the client, you have learned something specific: the difference is the environment, not the server. A client launched from a desktop icon does not necessarily inherit the PATH your shell profile builds, so npx, uv, or a version-managed runtime can be missing from exactly one of the two contexts.

For the client’s own view of the same event, enable debug logging and read ~/.claude/debug/<session-id>.txt. On Windows that is %USERPROFILE%\.claude\debug\.

Fix by what you observed

  • Never connected, dies instantly — the command is wrong or the runtime is missing. Fix it in the config entry and verify by running it by hand first. Absolute paths to the interpreter remove an entire class of PATH problems.
  • Never connected, dies after doing some work — it is failing on a credential or a network dependency it needs at startup. Check that the environment variables the server documents are actually reaching it.
  • Connected, then died — something killed the process: an OOM kill, a crash under a specific tool call, a laptop suspend, or a supervisor. If it dies reproducibly on one tool, that tool is the trigger and the server’s own logs will show it.
  • Remote server, drops repeatedly — you are on a network path with an intermediary that closes idle connections. If the failure instead names a status rather than a closed connection, you want the SSE stream 404 page instead, because a server that answers with a status is a routing problem, not a dead one.
  • The entry has a url but no type — that one is reported explicitly (MCP server "<name>" has a "url" but no "type"; add "type": "http" (or "sse" / "ws") to this entry), so if you are reading -32000 it is not your problem.

A note on the credential half of this, because MCP servers run with your privileges and usually hold tokens. Project-scoped servers live in .mcp.json at the project root, which is a file people commit. Keep secrets out of it — reference an environment variable rather than pasting a value, and when you share a config in an issue or a chat, mask it (ghp_****). A server that fails to start is a bad reason to paste a working token into a shared file.

Confirming it is really fixed

✔ Connected in /mcp is necessary and not sufficient — it means the handshake finished, not that the server survives use. Start a fresh session, confirm the status, then call one of that server’s tools twice, and prefer a call that does real work over a trivial one. A server that connects and dies on first use passes the status check and fails the only test that matters. If the server is stdio, do this in a session started after your change, since the old session can never respawn the process.

If the server connects and the failure arrives after a successful tool call, you are in a different part of the pipeline: an oversized result gets rejected with MCPContentTooLargeError rather than a closed connection, and that is a budget problem, not a transport one. If several servers do connect and requests then fail before the model runs, check whether two of them collide on a tool name — that error blocks requests while every server reports healthy, which is a confusing combination if you are not expecting it. For the general question of which layer a given message comes from, the AI coding error triage tool sorts client-side transport failures from API-side ones, which is the first fork on this page.