Claude Code says your MCP server “Failed to connect” — it might not even be trying

"Failed to connect" isn't the only status claude mcp list can show. Here's why project-scoped MCP servers sit at Pending approval in a freshly cloned repo, the missing-type gotcha that silently drops remote servers, and how to tell which one you actually have.

You added the server. claude mcp add printed its Added ... confirmation, or it’s sitting right there in a committed .mcp.json. But the tools it should provide never show up in your session, or Claude Code just reports the server “Failed to connect” — no stack trace, nothing in your terminal pointing at what’s actually broken.

Before debugging the server process itself, run claude mcp list. It prints a health status next to every configured server, and that status is the fork in the road — “Failed to connect,” “Pending approval,” and “Needs authentication” are three different problems with three different fixes, and only one of them means the server is actually unreachable.

The mechanism: most “failed to connect” reports were never attempted

claude mcp list shows one of several distinct states per server: ✔ Connected, ! Needs authentication, ✘ Failed to connect, or the one people miss entirely, ⏸ Pending approval (run 'claude' to approve). That last state means Claude Code hasn’t tried to reach the server at all — it’s sitting there waiting on you, not failing.

Project-scoped servers declared in a committed .mcp.json need explicit approval before Claude Code will run them, and that requirement doesn’t go away just because the repo also commits enableAllProjectMcpServers (or a specific enabledMcpjsonServers list) into .claude/settings.json. Those settings are only honored once you’ve trusted the workspace, and a folder you just cloned has never been trusted. So the first time anyone — you, a teammate, a fresh CI checkout — opens the project, every project-scoped server starts at “Pending approval,” which looks a lot like “broken” if you’re going by whether the tools appeared rather than by what claude mcp list actually says.

A second, separate cause hits remote HTTP/SSE servers specifically: a .mcp.json entry that has a url but no type field. With nothing to tell it otherwise, Claude Code’s default assumption is that the entry describes a local stdio process, not a URL — so it treats the entry as malformed and drops it rather than connecting. Older versions surfaced this as a cryptic command: expected string, received undefined, which doesn’t point anywhere near the real cause; current versions report it directly as the server having a url but no type.

There’s also a real version of “Failed to connect” — a stdio server whose startup is genuinely slow gets aborted mid-initialization before it can finish, which is an actual timeout, not a config or trust gap.

How to tell which one you’ve got

  1. Run claude mcp list before touching anything else. The word next to the server’s name — not whether tools appeared — tells you which category you’re in.
  2. “Pending approval” means never contacted. Run claude interactively (not a scripted -p invocation) inside the project and accept the workspace trust prompt when it appears. That’s what flips a committed .mcp.json server from pending to active.
  3. A remote server that’s “Failed to connect,” or missing from the list altogether, is worth a .mcp.json check. Confirm the entry has an explicit "type": "http" (or "sse" / "ws") next to its url — a URL alone doesn’t imply the transport, and Claude Code won’t guess it for you.
  4. A local stdio server that’s slow but not misconfigured is a timeout, not a trust or schema problem — confirm by running the exact command/args from the entry directly in your own shell and timing how long it actually takes to come up.

The fix

For the trust gap, there’s no setting to change: run claude inside the project directory and accept the workspace trust dialog. It’s a one-time action per clone, not something you redo every session.

For the missing transport, add "type" explicitly to the entry rather than relying on Claude Code to infer it from the presence of a url — it treats an untyped entry as stdio, every time.

For a stdio server that’s slow but otherwise fine, raise the startup allowance with the MCP_TIMEOUT environment variable, in milliseconds — MCP_TIMEOUT=10000 claude gives a server 10 seconds to come up instead of whatever the default cutoff is currently costing it.

Verify it actually connected

Re-run claude mcp list after any of the above and confirm the specific server now shows ✔ Connected — not just that the command you ran exited cleanly. A clean exit and a reachable server aren’t the same claim; the status word next to the server’s name is the one that settles it.

Comments