Run claude mcp list first: it prints a health status next to every configured server, which tells you whether the problem is connection, authentication, or an approval you never gave. If a server shows as failed, run its command by hand, because it usually fails visibly. The most common cause for a custom stdio server is writing to stdout, which the transport uses as the protocol channel. After that come pending project approval, relative paths in the command, missing environment variables, and configuration written into a file Claude Code does not read.
- Anything on stdout corrupts the protocol. Log to stderr, always.
claude mcp listprints a health status per server. Read it before anything else.- Project servers need a one-time approval. Dismissed prompt means never connected.
- Relative paths in
commandresolve against your launch directory, not.mcp.json. - Environment variables belong in the server
envblock, not your shell. claude --debug=mcpwrites the server stderr to~/.claude/debug/<session-id>.txt.
Diagnose in order
# 1. what is configured, and what does each one report?
claude mcp list
claude mcp get postgres
# 2. what actually connected, and is anything awaiting approval?
# (inside a session)
/mcp
# 3. run the server command by hand - this finds most problems
npx -y @modelcontextprotocol/server-postgres "$DATABASE_URL"
# 4. read the server stderr from the client side
claude --debug=mcp
# then: ~/.claude/debug/<session-id>.txt
What claude mcp list tells you, and what to do about it.
| Status | Means | Do |
|---|---|---|
| Connected | Working | Nothing |
| Needs authentication | OAuth not completed | Run /mcp and authenticate |
| Failed to connect | The server did not start or was unreachable | Step 3: run it by hand |
| Pending approval | A project server you never approved | Run claude in the repo and approve from /mcp |
| Rejected | You declined it once | Check disabledMcpjsonServers in settings |
| Not listed at all | Wrong scope, or the wrong config file | See the configuration section below |
The stdout trap
console.log("server starting..."); // corrupts the stream
console.log(`connected to ${dbUrl}`); // same
process.stdout.write("ready\n"); // same
console.error("server starting...");
process.stderr.write("connected\n");
# anything on stdout that is not JSON-RPC is a problem
npx -y some-mcp-server 2>/dev/null | head -5
# a well-behaved stdio server prints NOTHING and blocks, waiting for a client.
# a silent hang here is success, not a failure.
Configuration in a file Claude Code does not read
A whole class of "my server never appears" is a correct configuration in the wrong place. There is no error, because nothing read it.
| You did | What happened |
|---|---|
Put .mcp.json inside .claude/ | Project MCP config lives at the repository root |
Added mcpServers to settings.json | settings.json does not read that key. Use .mcp.json or --scope user. |
| Used the Claude Desktop config format | Different file, different shape. Use claude mcp add and let it write the file. |
| Added it in one project and expected it everywhere | Local scope is the default. Re-add with --scope user. |
Committed enableAllProjectMcpServers and cloned the repo | A cloned repository cannot approve its own servers until you trust the workspace |
# local (default): this project only, private to you, in ~/.claude.json
claude mcp add --transport http stripe https://mcp.stripe.com
# project: shared with the team via .mcp.json at the repository root
claude mcp add --transport http shared-server --scope project https://example.com/mcp
# user: all your projects, private to you, in ~/.claude.json
claude mcp add --transport http hubspot --scope user https://mcp.hubspot.com/anthropic
# and if you dismissed the approval prompt once:
claude mcp reset-project-choices
Environment variables and relative paths
Two failures that both look like "it works in my shell but not in Claude Code", for two different reasons.
# the server is a child process. It gets Claude Code's environment minus the
# variables Claude Code strips from subprocesses, and nothing you exported after launch.
claude mcp add postgres \
--env DATABASE_URL="postgres://localhost/dev" \
-- npx -y @modelcontextprotocol/server-postgres
{
"mcpServers": {
"internal": {
"type": "stdio",
"command": "/Users/you/tools/mcp-internal/bin/server",
"args": ["--config", "/Users/you/tools/mcp-internal/config.json"],
"env": { "API_TOKEN": "..." }
}
}
}
Timeouts, reconnection, and the servers that drop later
A server that connects and then stops working is a different problem from one that never connects, and the controls are different too.
The timers, as of August 2026.
| Timer | Default | Control |
|---|---|---|
| Server startup | Set per launch | MCP_TIMEOUT, in milliseconds |
| Tool call wall clock | About 28 hours | Per-server timeout in ms, or MCP_TOOL_TIMEOUT |
| Tool call idle | 5 minutes remote, 30 minutes stdio | CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT, or 0 to disable |
| First response byte, remote only | 60 seconds | Raised by setting timeout or MCP_TOOL_TIMEOUT to 60s or more |
| Backgrounding a long call | 2 minutes | CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS |
# a server that is slow to boot
MCP_TIMEOUT=10000 claude
# a specific server whose tools take minutes
# in its .mcp.json entry: "timeout": 600000
Questions people ask
Run claude mcp list first, since it prints a health status next to each server and distinguishes a failed connection from a missing OAuth login or an approval you never gave. If it shows as failed, run the server command by hand: it usually fails visibly. For custom stdio servers the most common cause is writing to stdout.
Almost certainly a stray print to stdout. The stdio transport uses stdout as the JSON-RPC channel, so a startup banner or a console.log corrupts the stream. Log to stderr instead, and check with npx -y your-server 2>/dev/null | head -5, which should print nothing.
claude mcp list shows what is configured plus a health status for each; /mcp inside a session shows what actually connected and which project servers are awaiting approval. The difference between the two tells you where the problem is.
It started but is not returning a tool list. Select Reconnect from /mcp. If the count stays at zero, run claude --debug=mcp and read the server stderr in the debug log at ~/.claude/debug/<session-id>.txt.
It runs as a child process and receives Claude Code environment minus the variables Claude Code strips from subprocesses, not whatever you exported in your shell afterwards. Pass them in the per-server env block, which does not depend on the launch environment at all.
It was added at local scope, which stores it in ~/.claude.json under your path. Re-add it with --scope project, which writes .mcp.json at the repository root for everyone. Each teammate then approves it once, since project servers require a one-time approval.
A relative path in command or args resolves against the directory you launched Claude Code from, not against the location of .mcp.json. Use an absolute path for local scripts; executables on PATH such as npx work as they are.
Less than they used to. Tool definitions are deferred by default, so only names enter context until a schema is needed. The live cost is tool output: Claude Code warns above 10,000 tokens for a single result and caps results at 25,000 tokens by default. Prune servers you no longer use.
Sources
Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.