MCP Server Connection Issues
An MCP server that fails to connect in Claude Code, Codex or Cursor almost always has one of five causes: a malformed server entry, a launcher missing from the client’s PATH, a missing or expired credential, a startup timeout, or a remote network or protocol problem. Check them in that order and prove each fix with a tools/list call.
You added the Postgres server, restarted the agent, and the tool list is empty. claude mcp list says Failed to connect, Cursor shows the server in red, and a teammate with the same config gets Connection closed. This page is for the developer staring at that error and for the tech lead who owns the team’s shared .mcp.json. It covers the diagnosis order, the command-line traps that write a broken entry without an error, and a smoke test that proves every server answers before an agent depends on it.
Checked on 2026-09-26 against Claude Code 2.1.283, codex-cli 0.157.1 and the MCP Inspector CLI 2.8.0; every Claude Code trap below also reproduces on 2.1.286. Cursor details come from vendor documentation and are marked where they could not be tested.
What you get from this MCP troubleshooting guide
Section titled “What you get from this MCP troubleshooting guide”- A symptom table that maps the error you see to its cause and the first command to run.
- The seven CLI traps that produce a server entry which looks fine and never connects.
- Fixes for
spawn ENOENT, startup timeouts, expired OAuth tokens and ignored headers. - A
tools/listsmoke test you can run locally or in CI, so “it connects” is a check, not a feeling. - Three copy-paste prompts that make the agent find the real error for you.
If you have not added a server yet, start with the setup page for your tool: MCP setup in Claude Code, MCP setup in Codex or MCP setup in Cursor.
Which MCP connection failure do you have?
Section titled “Which MCP connection failure do you have?”Match the symptom first. Each row names the cause that produces it most often and the command that confirms it.
| Symptom | Most likely cause | First check |
|---|---|---|
spawn npx ENOENT, Connection closed right after start | The client cannot find npx, node or uvx on its PATH | Run the exact command from the entry in a terminal; which npx |
A remote URL listed as a stdio server, Failed to connect | claude mcp add without --transport http | claude mcp get <name> shows Type: stdio and the URL as the command |
⏸ Pending approval in claude mcp list | A project .mcp.json server nobody approved yet | Start claude in the repo and approve it |
Needs authentication, 401, or “connected but no tools” on a remote server | OAuth never completed or the token expired | /mcp in Claude Code, codex mcp login <name> |
401 in Codex although config.toml has a token | Codex ignored an unknown headers key | codex mcp get <name> --json shows "http_headers": null |
| Works on the second try, fails on the first call of the day | First-run package download exceeds the startup timeout | Warm the cache in a terminal; raise the timeout |
| Connects, zero tools | The server started but registered no tools, usually because a required variable or token scope is missing (many servers only register tools once configured) | Read its stderr by running the command by hand |
| Works for you, fails for a teammate | A local PATH, a global install, or a variable only you have | Diff claude mcp get or codex mcp get --json output between machines |
Diagnose an MCP server in five checks
Section titled “Diagnose an MCP server in five checks”Each check rules out a whole class of causes, so run them in order and stop at the first one that fails.
- Read what the client stored. Print the entry the client actually uses, not the one you think you wrote:
claude mcp get <name>,codex mcp get <name> --json, or themcpServersblock in.cursor/mcp.json. Most broken servers are broken here. - Run the launch command by hand. Copy
commandandargsinto a terminal. If the server prints an error or exits, the client will fail the same way. A healthy stdio server prints nothing and waits for input; stop it with Ctrl+C. - Check the client’s
PATH. A desktop app or IDE does not load your.zshrc, so a launcher installed by nvm, asdf, mise or Homebrew may be invisible to it. Comparewhich npxin your terminal with what the client can see. - Check credentials. Confirm every variable the server needs is set in the entry’s
env, and that remote OAuth is complete and not expired. - Check the network and protocol. For remote servers, test the URL, the proxy and the protocol version the client negotiates.
Check MCP server status in Claude Code, Codex and Cursor
Section titled “Check MCP server status in Claude Code, Codex and Cursor”The status commands differ per tool. Use them before and after every fix.
claude mcp list # every server with a health check: Connected, Failed to connect, Pending approvalclaude mcp get postgres # scope, type, command or URL, env, and status for one serverclaude --debug "mcp" # start a session with MCP debug outputclaude --debug-file /tmp/mcp.log # the same, written to a file you can attach to an issueInside a session, /mcp shows each server’s state and lets you authenticate or reconnect. Project servers from .mcp.json stay ⏸ Pending approval until someone approves them in an interactive session. To re-ask after a rejection, run claude mcp reset-project-choices.
codex mcp list # servers, command or URL, enabled status, auth typecodex mcp get postgres --json # the parsed entry: shows which keys Codex actually keptcodex doctor # installation, config, auth and runtime healthtail -n 100 ~/.codex/log/codex-tui.log # session log after you reproduce the failureCodex stores servers as [mcp_servers.<name>] in ~/.codex/config.toml. Inside the TUI, /mcp lists active servers and their tools. The --json output is the fastest way to catch a key that Codex parsed and then dropped.
Cursor reads .cursor/mcp.json in the project and ~/.cursor/mcp.json globally, and shows each server’s status in its MCP settings. A red status there does not tell you why, so run step 2 above in a terminal to get the server’s stderr.
A common false failure is a trailing comma, which makes the whole file invalid JSON. Validate the file with node -e "JSON.parse(require('fs').readFileSync('.cursor/mcp.json','utf8'))". If a fix does not take effect, quit Cursor fully rather than closing the window.
cursor.com could not be reached when this page was checked, so confirm current log locations in Cursor’s MCP documentation.
CLI traps that write a broken MCP server entry
Section titled “CLI traps that write a broken MCP server entry”These commands succeed, or fail with an error that points somewhere else. Each one was run on the versions named.
| Trap | What happens | Correct form |
|---|---|---|
claude mcp add sentry https://mcp.sentry.dev/mcp with no --transport | Claude Code 2.1.283 prints a warning and still saves a stdio server whose command is the URL. It fails at connect time | claude mcp add --transport http sentry https://mcp.sentry.dev/mcp |
claude mcp add --url … | error: unknown option '--url' (2.1.283). --url is the Codex flag | Pass the URL as the positional argument after the name |
-e KEY=value right before the name, or between the name and the command without -- | -e is variadic and swallows the next argument, before the name it eats the name and fails with Invalid environment variable format: <name>; between the name and the command it eats the command and fails with missing required argument 'commandOrUrl'. After --, -e silently becomes an argument of the server (2.1.283) | claude mcp add my-server -e API_KEY=… -- npx my-mcp-server: name, then -e, then -- |
headers = { … } under [mcp_servers.x] in Codex | Parsed without an error and dropped (0.157.1). codex mcp get x --json shows "http_headers": null | http_headers, env_http_headers or bearer_token_env_var |
codex mcp add x --url … --env K=V | --env is stdio-only (0.157.1) and the command fails | --bearer-token-env-var GITHUB_PAT_TOKEN, or http_headers in TOML |
.claude/mcp.json | Claude Code never reads it | Project scope is .mcp.json at the repository root |
codex mcp-server | Not a subcommand in codex-cli 0.157.1; Codex can no longer run as an MCP server | Use codex exec or the Codex SDK for programmatic control; Claude Code still offers claude mcp serve |
Package names are the eighth trap. npx -y postgres-mcp installs an unrelated npm package: Postgres MCP Pro is the PyPI package postgres-mcp, so it runs with uvx. Check a name with npm view <package> version or on PyPI before you blame the connection. The MCP security guide covers why a look-alike package is a supply-chain risk, not only a broken config.
Fix spawn ENOENT and Connection closed on stdio servers
Section titled “Fix spawn ENOENT and Connection closed on stdio servers”These errors mean the process never started. The fix is almost always the launcher, not the server.
which npx node uvx # e.g. /Users/you/.nvm/versions/node/v22.14.0/bin/npxIf the path sits inside a version manager’s directory, the desktop client cannot see it. Put the absolute path in the entry instead of the bare name:
{ "mcpServers": { "postgres": { "command": "/Users/you/.local/bin/uvx", "args": ["postgres-mcp", "--access-mode=restricted"], "env": { "DATABASE_URI": "${DATABASE_URI}" } } }}${DATABASE_URI} expansion is Claude Code .mcp.json syntax (it also accepts ${VAR:-default}). Cursor uses its own interpolation syntax; check Cursor’s MCP documentation for the current form. Python servers need uv installed; uvx is part of it.
where.exe node npxOn native Windows (not WSL), Claude Code needs a cmd /c wrapper to run npx:
claude mcp add my-server -- cmd /c npx -y @scope/packageUse double backslashes for Windows paths inside JSON, and run the client as the same user who installed Node.
A half-downloaded package in the npx cache produces the same error on every retry. Clear only the npx cache, then run the command once by hand so the next start is warm:
rm -rf "$(npm config get cache)/_npx"Fix MCP startup timeouts
Section titled “Fix MCP startup timeouts”A server that works on the second attempt is usually downloading itself on the first. The client gives up before npx or uvx finishes. Run the launch command once in a terminal to warm the cache, pin a version in args so the cache stays valid, and raise the startup limit only if the server is genuinely slow:
MCP_TIMEOUT=30000 claude # server startup timeout in millisecondsMCP_TOOL_TIMEOUT sets the limit for a single tool call.
[mcp_servers.postgres]command = "uvx"args = ["postgres-mcp", "--access-mode=restricted"]startup_timeout_sec = 30tool_timeout_sec = 120Cursor’s timeout settings were not verifiable on 2026-09-26. Warm the cache in a terminal and pin the package version in args.
Telling the agent to “wait longer” changes nothing: the client enforces the limit. The MCP best practices page covers timeout tuning for servers that stay slow.
Fix authentication failures on remote MCP servers
Section titled “Fix authentication failures on remote MCP servers”A remote server that returns 401, or connects with no tools, has a credential problem. Most vendor-hosted servers (GitHub, Sentry, Linear, Figma, Atlassian) use OAuth, so the fix is to finish or repeat the browser login rather than to paste a token.
claude mcp add --transport http sentry https://mcp.sentry.dev/mcpclaude mcp login sentry # or /mcp inside a sessionclaude mcp logout sentry # clear a stale token, then log in againFor a server that takes a static token, pass a header in single quotes: claude mcp add --transport http github https://api.githubcopilot.com/mcp/ -H 'Authorization: Bearer ${GITHUB_PAT}'. The quotes keep ${GITHUB_PAT} unexpanded, so the config stores the reference, Claude Code expands it at connect time, and no token lands in your config file or, with -s project, in git. Double quotes would write the token itself into the config.
codex mcp add github --url https://api.githubcopilot.com/mcp/ --bearer-token-env-var GITHUB_PAT_TOKENcodex mcp add sentry --url https://mcp.sentry.dev/mcpcodex mcp login sentry # OAuth; add --no-browser on a remote machinecodex mcp logout sentry # clear stored credentialsFor other headers, use http_headers for literal values or env_http_headers to read a value from an environment variable. codex mcp list shows the auth type Codex detected; Unknown on a server you gave a token to means the key name is wrong.
Remote servers take url and optional headers in mcp.json. Cursor runs the OAuth flow itself on first connect; if nothing opens, check that the browser handoff was not blocked, then reconnect from the MCP settings.
{ "mcpServers": { "sentry": { "url": "https://mcp.sentry.dev/mcp" } }}Two server-specific traps: Sentry’s token header is Authorization: Sentry-Bearer <token>, not Bearer, and Atlassian’s endpoint is https://mcp.atlassian.com/v2/mcp (the old /v1/sse stopped working after 30 June 2026). A 403 on every call with a valid token usually means the token’s scope is too narrow; the MCP security guide lists least-privilege scopes.
Connect a stdio-only client to a remote server with mcp-remote
Section titled “Connect a stdio-only client to a remote server with mcp-remote”Claude Code, Codex and Cursor all speak HTTP to remote servers, so they do not need a bridge. Some older or embedded MCP clients only launch local stdio processes. For those, mcp-remote runs as a local stdio server and forwards to the remote URL, including the OAuth login:
{ "mcpServers": { "atlassian": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.atlassian.com/v2/mcp"] } }}Its usage line is mcp-remote <https://server-url> [callback-port] [--debug]; add --debug when the bridge itself is the suspect. Do not wrap a remote server in mcp-remote inside Claude Code or Codex to “fix” it: that hides the real HTTP error behind a second process.
As of 2026-09-26, mcp-remote is at npm 0.14.3 with ★1.6k on GitHub, and the Atlassian and Tavily READMEs still document it for stdio-only clients (npm registry, GitHub).
Network, proxy and protocol problems with remote MCP servers
Section titled “Network, proxy and protocol problems with remote MCP servers”Remote servers add three failure points that stdio servers do not have.
- Proxy. A stdio server inherits the environment of its entry, not of your shell. Put
HTTPS_PROXYandNO_PROXYin the entry’senvblock. Behind a TLS-inspecting proxy, Node-based servers also needNODE_EXTRA_CA_CERTSpointing at your corporate CA bundle. - Local HTTP servers. For a server you run on your machine, confirm something is listening before blaming the client:
curl -i http://127.0.0.1:3845/mcpreturns an HTTP status when the process is up andConnection refusedwhen it is not.lsof -i :3845shows which process holds the port. - Protocol version. MCP 2026-07-28 removed the
initializehandshake andMcp-Session-Id. Claude Code negotiates 2026-07-28 by default with direct HTTP servers (all installs by v2.1.274); codex-cli 0.157.1 does not (mcp_2026_07_28is under development and off). A server that works in Codex and fails only in Claude Code may reject the newer protocol: test withMCP_PROTOCOL_NEGOTIATION=legacy claudeand, if that fixes it, report it to the server’s maintainer. The MCP 2026-07-28 migration guide covers the server side.
Prove the MCP server works before the agent depends on it
Section titled “Prove the MCP server works before the agent depends on it”“The dot is green” is not a test. A server can connect and expose zero tools, or the wrong ones. Prove each server with a deterministic tools/list call from the MCP Inspector CLI, which reads the same mcpServers JSON the clients use:
# The Inspector does not expand ${VAR}: render a resolved copy outside the repo firstf="$(mktemp "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/mcp.XXXXXX")" # owner-only, unpredictable namenode -e 'const fs=require("fs");const sub=v=>typeof v==="string"?v.replace(/\$\{(\w+)(?::-([^}]*))?\}/g,(m,k,d)=>process.env[k]??d??m):Array.isArray(v)?v.map(sub):v&&typeof v==="object"?Object.fromEntries(Object.entries(v).map(([k,x])=>[k,sub(x)])):v;fs.writeFileSync(process.argv[1],JSON.stringify(sub(JSON.parse(fs.readFileSync(".mcp.json","utf8")))))' "$f"
npx -y @modelcontextprotocol/inspector --cli --config "$f" --server postgres --method tools/list
rm "$f" # delete the resolved copy when doneThe Inspector passes ${VAR} placeholders from --config through literally (checked with 2.8.0): a server started from the committed .mcp.json receives the string ${DATABASE_URI} as its credential and fails, or reaches the wrong target. That is why the script substitutes values on the parsed JSON, so a quote or backslash in a secret cannot break the file, and writes the result to a mktemp file with owner-only permissions outside the repo. Delete it when done (rm "$f") and never commit it. As of 2026-09-26 the Inspector is at npm 2.8.0 with ★11.0k on GitHub (npm registry, GitHub). The tools/list call prints the server’s tools as JSON, or an error such as Connection timed out after 30000 ms. Pass a bare command instead of --config for a quick check (… --cli context7-mcp --method tools/list), but prefer --config: extra flags in a server’s own arguments can be read as Inspector flags.
For a team, turn that into a gate the tech lead owns:
- Commit
.mcp.jsonwith${VAR}placeholders only, never a token. - Add a smoke-test script that renders the resolved copy outside git, runs the
tools/listcall for every server in it, deletes the copy, and fails when a server errors or returns zero tools. - Run it in CI on pushes to the default branch that change
.mcp.json, with only the secrets that job needs, and locally after onboarding. Never give these secrets to a pull request job: a pull request can rewrite.mcp.jsonand the script, and every server entry is a command the job executes. - Check context cost the same way: compare
/contextin Claude Code before and after adding a server, because a server that connects can still crowd out the task. See reducing MCP token cost.
When MCP connections still fail
Section titled “When MCP connections still fail”It works in your terminal and fails in the client. The client has a different PATH or a stale process. Put the absolute launcher path in the entry, clear the npx cache, and quit the client fully.
Claude Code lists the server but never connects to it. It is a project server waiting for approval. Start claude in the repository and approve it; if someone rejected it earlier, run claude mcp reset-project-choices.
The server connects and loads zero tools. It started but registered no tools, usually because a required variable or token scope is missing: many servers only register tools once configured. Run the command by hand with the same env and read the stderr.
A remote server worked yesterday and returns 401 today. The OAuth token expired or was revoked. Run claude mcp logout <name> then claude mcp login <name>, or codex mcp logout <name> then codex mcp login <name>, or reconnect from Cursor’s MCP settings.
It works for you and fails for a teammate. You have a global install, a variable or a PATH entry they do not. Pin versions in args, list every required variable in the repository README, and make the smoke test part of onboarding.
Old server processes pile up. Find them by the server’s own name and stop only those, for example pgrep -fl postgres-mcp then kill <pid>. Avoid pkill -f mcp: it also kills other clients’ servers and any process with “mcp” in its arguments.