Claude Code MCP Not Working: Fix Failed to Connect
Claude Code MCP not working? A decision tree for the Failed to connect error: read /mcp and claude mcp list, turn on claude --debug=mcp, then run the server by itself to find the real cause.
"Failed to connect" in Claude Code means one of two things: the server process never started, or the URL never answered. Everything else is detail. The fastest path to the cause is always the same three steps, in order: read the status detail in /mcp or claude mcp list, turn on claude --debug=mcp, and run the server by itself outside Claude Code.
Most MCP bugs are not MCP bugs. They are a stray console.log, a path with a space in it, a config edited in the wrong file, or a session that was never restarted.
The decision tree in one table
| Step | Command | What it tells you |
|---|---|---|
| 1. Read the status | claude mcp list, claude mcp get <name>, or /mcp | Which failure you have, plus the HTTP status or error code |
| 2. Read the logs | claude --debug=mcp | The server's stderr and the handshake, in ~/.claude/debug/ |
| 3. Isolate the server | Run the configured command or curl the URL | Whether the server works at all without Claude Code |
If step 1 names the problem (a 401, a 404, a pending approval), fix it and stop. If it only says ✘ Connection error, skip straight to steps 2 and 3, because that status never carries detail.
Step 1: Read the status before you touch anything
claude mcp list runs from your normal shell and health checks every configured server. claude mcp get <name> shows one server's full config plus an Issue: line when it failed. Inside a session, /mcp shows the same thing with buttons to reconnect or authenticate.
The status strings are specific, and each one points at a different fix:
| Status | What it actually means |
|---|---|
✔ Connected | Working. If tools still seem missing, jump to the no-tools section |
! Connected · tools fetch failed | The handshake worked, listing tools did not. claude mcp get has the error |
! Needs authentication | Reachable, wants a browser sign-in or a token |
✘ Failed to connect | Server did not start or URL did not respond. Detail is appended |
✘ Connection error | The attempt threw an error. No detail is ever appended |
⏸ Pending approval | A .mcp.json server you have not approved yet |
⊘ Disabled for this project | You toggled it off in /mcp. Toggle it back on there |
On recent versions, Failed to connect carries the HTTP status and any error text the server returned. That detail arrived in Claude Code v2.1.219. If you only see a bare status, run claude update first. A lot of old troubleshooting advice exists only because older versions hid the real error.
claude mcp list also prints configuration warnings, and two of them explain a surprising number of failures:
- Hidden whitespace. A token pasted with a trailing newline. The warning reads like
Leading or trailing whitespace in: headers.Authorization. Claude Code does not trim it for you. - Missing environment variable. A
${VAR}reference with no value and no default. The server still loads, with the literal${VAR}text passed through, which then fails in whatever way that server fails.
Step 2: Turn on the MCP debug log
When the status is vague, restart with MCP debugging on:
claude --debug=mcpThe category filter only binds in the = form. claude --debug mcp with a space turns on debug mode for everything, which is noisier but still works. The log lands in ~/.claude/debug/ as a file named after the session ID. If you want it somewhere predictable:
claude --debug-file /tmp/claude-debug.logWhat you are looking for is the server's stderr. A stdio server that crashes on import, cannot find a binary, or throws on a missing environment variable writes that to stderr, and the debug log is where it surfaces.
Step 3: Run the server alone
This is the step people skip, and it is the one that resolves the most cases.
For a stdio server, copy the exact command and arguments from claude mcp get <name> and run them in your terminal:
npx -y @playwright/mcp@latestTwo outcomes, two meanings:
- It starts and sits there silently. Good. A stdio server talks over stdin and stdout, so a blocked, quiet terminal means it is running and waiting for a client. The server works. The problem is in how Claude Code launches it, so compare what you ran against what
claude mcp getshows. - It errors. The message usually names what is missing: Node.js, Python, a browser binary, an API key.
If it prints a friendly banner like "Server started on stdio!" before going quiet, you have just found your bug. Read the next section.
For a remote server, check the URL is reachable from your machine:
curl -I https://mcp.example.com/mcpIn PowerShell, call curl.exe so you hit real curl instead of the Invoke-WebRequest alias. Read the response like this:
- 404 or 405: the server is up. Many MCP endpoints only answer POST, so a GET getting rejected still proves reachability.
- 401 or 403: the server is up and wants authentication.
- Nothing: wrong URL, DNS, VPN, proxy, or firewall. Not an MCP problem.
Failure mode 1: stdout pollution
This is the classic stdio bug, and it is invisible until you know the rule.
In the stdio transport, the server reads JSON-RPC messages on stdin and writes them on stdout, one message per line. The MCP specification says the server MUST NOT write anything to stdout that is not a valid MCP message. It MAY write anything it likes to stderr for logging.
So a single console.log("ready") in a Node server, or a print() in a Python one, puts a line of plain text into the channel where Claude Code expects JSON. The handshake breaks, and you get a failed connection from a server that "works fine when I run it."
The fix is to log to stderr:
// Node: console.log writes to stdout, console.error writes to stderr
console.error("server ready");import sys
print("server ready", file=sys.stderr)Watch for indirect polluters too: a dependency that prints a banner on import, or a wrapper script that echoes before exec. Running the server alone (step 3) shows every line it writes. Anything human-readable there is suspect.
Failure mode 2: paths, spaces, and the launch directory
Three separate path bugs look identical from the outside.
Paths with spaces. Everything after -- in claude mcp add is passed to the server untouched, but your shell still splits it first. An unquoted path with a space becomes two arguments before Claude Code ever sees it:
# Broken: the shell splits "My Projects" into two args
claude mcp add docs -- node /Users/me/My Projects/docs-server/index.js
# Fixed: quote the path
claude mcp add docs -- node "/Users/me/My Projects/docs-server/index.js"Then run claude mcp get docs and confirm the arguments look like what you intended. In a hand-written .mcp.json, keep command as the executable alone and put each argument, spaces included, in its own args element:
{
"mcpServers": {
"docs": {
"command": "node",
"args": ["/Users/me/My Projects/docs-server/index.js"]
}
}
}Do not put a whole command line like node /path/to/server.js into command and expect it to be split for you.
Relative paths. A relative path in command or args resolves against the directory you launched Claude Code from, not the location of .mcp.json. Start Claude Code from a subfolder and ./server/index.js points somewhere else. Use absolute paths, or reference the project root. Claude Code sets CLAUDE_PROJECT_DIR in the server's environment, but to expand it inside command or args of a .mcp.json entry you need a default, because it is not set in Claude Code's own environment:
{
"mcpServers": {
"docs": {
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR:-.}/tools/docs-server/index.js"]
}
}
}The missing --. Without the separator, Claude Code tries to parse the server's flags as its own. claude mcp add myserver npx -y some-server is not the same command as claude mcp add myserver -- npx -y some-server. If claude mcp get shows a command that differs from what you typed, this is why. Remove the server and add it again with -- in place.
Slow first start. A stdio server launched through npx can fail its first check while the package downloads. The default startup timeout is 30 seconds. Raise it for that launch:
MCP_TIMEOUT=60000 claudeFailure mode 3: you edited the config and nothing changed
Claude Code reads .mcp.json at session start. If you edit it while a session is running, the running session does not see the change. Exit, start a new session, then run claude mcp list to confirm.
If the server still does not appear after a restart, one of three things is true:
- The entry is malformed. Claude Code skips that one entry and loads the rest.
claude mcp listprints a parse warning naming the bad field. - You edited the wrong file. Claude Code reads
~/.claude.jsonand<project>/.mcp.json. It does not read~/.claude/.mcp.json,~/.claude/mcp.json,~/.claude/config/mcp.json, or%APPDATA%\Claude\mcp.json. Every one of those is a file people create by hand and then wonder why nothing happens. - The entry has a
urlbut notype. Claude Code reads an entry with notypeas a stdio server, so a remote server copied from another client's docs without"type": "http"gets skipped with a message telling you to add the type. JSON configs using"type": "streamable-http"are fine, since that is accepted as an alias forhttp.
For project servers, there is also approval. A server in .mcp.json shows ⏸ Pending approval until you run claude interactively and approve it. If you rejected it once, reset your choices:
claude mcp reset-project-choicesSince v2.1.196, approvals committed to the repository's own .claude/settings.json are ignored until you trust the workspace by running claude there and accepting the trust dialog. A freshly cloned repo cannot approve its own servers.
Failure mode 4: the scope mix-up
MCP servers live at three scopes, and the default is the one that surprises people.
| Scope | Loads in | Stored in |
|---|---|---|
| Local (default) | Only the project where you added it | ~/.claude.json, under that project's path |
| Project | Only this project, shared via git | .mcp.json at the project root |
| User | Every project on your machine | ~/.claude.json, top level |
The trap: you run claude mcp add in one repo, switch to another, and the server is gone. It is not broken. Local scope ties it to the directory you added it from (the repository root, or the exact directory outside a git repo). Add it again in the new project, or use --scope user if you want it everywhere.
The second trap is duplicates. When the same name exists at more than one scope, Claude Code connects once, using the highest-precedence definition: local, then project, then user, then plugin servers, then claude.ai connectors. The whole entry from the winning scope is used. Fields are not merged. So a stale local entry with an old URL silently beats the correct one your team committed to .mcp.json.
claude mcp list warns when the same name points at different endpoints across scopes. Keep the one you want and remove the rest explicitly:
claude mcp remove github --scope localIf remove answers exists in multiple scopes, that is the same problem announcing itself.
Failure mode 5: remote servers returning 401, 403, or 404
Remote failures are usually easier, because the status code tells you the category.
404. Claude Code shows MCP endpoint not found at <origin>. Check the URL in your MCP config. The message deliberately shows only the origin, not the path, so run claude mcp get <name> to see the full URL. The usual cause is a wrong path: /sse where the server wants /mcp, or a missing trailing segment. Remove the server and add it back with the documented endpoint.
401 or 403 on an OAuth server. Claude Code marks it as needing authentication. Open /mcp, pick the server, and sign in. From a plain shell, claude mcp login <name> runs the same flow. Over SSH or on a machine with no browser, it prints the URL for you to open elsewhere, and --no-browser forces that mode.
401 or 403 when you set the header yourself. Here Claude Code does not fall back to OAuth. It reports a failed connection, because the credential to fix is the one you configured. Check, in order:
- The token has trailing whitespace.
claude mcp listflags it. - The token is valid, but not for this endpoint or with the scopes the tool needs.
- The header references a variable Claude Code refuses to expand toward a remote server. In a remote server's
urlandheaders, credential variables such asANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN,AWS_BEARER_TOKEN_BEDROCK, andNPM_TOKENalways read as empty, soBearer ${ANTHROPIC_AUTH_TOKEN}arrives asBearerand gets a 401. This is on purpose, so a project file cannot ship your credentials to a server it names. Copy the value into a variable with your own name and reference that instead.
Also worth knowing: claude mcp add saves the config without validating credentials. A placeholder token is accepted at add time and fails later, which makes it look like the server broke on its own.
Transient remote failures (5xx, connection refused, timeouts) are retried up to three times on the first connection. Auth and not-found errors are not retried, since only a config change fixes them.
Failure mode 6: connected, but no tools
A green ✔ Connected with nothing usable is its own category.
The server returned an empty list. Select the server in /mcp to see its tools. Zero tools after a clean start usually means a missing environment variable, like an API key the server checks before registering anything. Pass it with --env KEY=value on claude mcp add (with another option, such as --transport stdio, between --env and the server name) or in the entry's env field. Then choose Reconnect in /mcp. If the count stays at zero, read the server's stderr in the --debug=mcp log.
Listing tools failed. That shows as ! Connected · tools fetch failed. claude mcp get <name> has the error.
Individual tools were dropped. Claude Code checks each tool's input schema before sending it to the API, and excludes tools that would make the API reject the request, such as top-level property names outside 1 to 64 characters of letters, digits, _, ., and -. The other tools from that server keep working. The reason is recorded in the server's log, and Claude is told which tools were excluded, so you can simply ask Claude why a tool is missing.
The tools are there, just deferred. Tool search is on by default: only tool names and server instructions load at startup, and full definitions load when Claude searches for them. So a tool can be available without being visible in the way you expected. If a small server's tools should always be in context, set "alwaysLoad": true on that server's entry. Tool search also turns itself off when ANTHROPIC_BASE_URL points at a non-first-party host, which changes how tools load behind a proxy.
What changes in claude -p and CI
Headless runs remove the interactive safety nets, which produces a separate family of "works on my machine" bugs:
- There is no
/mcppanel, so OAuth cannot run there. Sign in once interactively with/mcporclaude mcp login <name>. - Project servers from
.mcp.jsonload without an approval prompt. UsedisabledMcpjsonServers, or--strict-mcp-configwith--mcp-config, to control exactly which servers load. - With
--mcp-configand-p, Claude Code waits for pending servers up toMCP_TIMEOUT(30 seconds by default) before the first turn. - Stdio servers are not reconnected automatically if they die. Remote servers are, with up to five backoff attempts.
Claude Code MCP FAQs
Why does my MCP server say Failed to connect in Claude Code?
Either the process never started or the URL never answered. Run claude mcp get <name> and read the Issue: line for the status code or error. Then run the configured command yourself for a stdio server, or curl -I the URL for a remote one.
Do I need to restart Claude Code after editing .mcp.json?
Yes. .mcp.json is read at session start. Exit, start a new session, and run claude mcp list to check for a parse warning on the entry you changed.
Why is my MCP server connected but showing no tools?
It started but returned an empty tool list, usually because a required environment variable is missing. Add it with --env or the entry's env field, then Reconnect from /mcp. If it says tools fetch failed, claude mcp get has the error.
Where are Claude Code MCP debug logs?
Run claude --debug=mcp. The log, including your server's stderr, goes to ~/.claude/debug/ under the session ID. Use --debug-file <path> to choose the location.
Why does my remote MCP server return 401 even though I set the token?
Look for hidden whitespace (flagged by claude mcp list), a header that references a protected credential variable Claude Code reads as empty, or a token that is not valid for that endpoint. A header you configured yourself never falls back to OAuth.
For the broader picture of how Claude Code decides what loads into a session, see Claude Code session context, and for how MCP servers fit next to skills and plugins, Claude Code plugins explained.
Posted by @speedy_devv

