Cursor 3.4.20 kills stdio MCP servers ~1.5s after successful initialize (SIGKILL, V2 FSM race)

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

All user-defined stdio MCP servers configured in ~/.cursor/mcp.json fail to stay connected on macOS Cursor 3.4.20. They reach the “connected” state after a successful initialize + tools/list handshake, then are killed by Cursor itself ~1.5–2 seconds later with SIGKILL (exit 137) — not a graceful stdin close. The state machine transitions are none → initializing → connected → error. Tools never become callable. The built-in cursor-ide-browser MCP is unaffected because it does not go through the stdio child-process path.

The same servers work perfectly when launched standalone in a terminal and fed JSON-RPC over stdin.

Steps to Reproduce

  1. Use this minimal ~/.cursor/mcp.json (or any other stdio MCP):
{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "mcp-postgres@latest"],
      "env": { "DATABASE_URL": "postgresql://USER:PASS@HOST:5432/postgres" }
    }
  }
}
  1. Launch Cursor with any workspace open.
  2. Open ~/Library/Application Support/Cursor/logs/<session>/window1/exthost/anysphere.cursor-mcp/MCP user-postgres.log.
  3. Observe the server reaches Successfully connected to stdio server and then transport_closed ~1.5s later.
  4. Verify the same mcp-postgres@latest package works fine when started manually from a terminal with the same JSON-RPC initialize + tools/list fed over stdin — it responds correctly and stays alive.

Expected Behavior

After a successful initialize + tools/list, the stdio MCP server should stay alive until the user (or Cursor on a clean shutdown) explicitly disables/removes it. Tools should remain callable for the entire session, just like the built-in MCPs.

Operating System

MacOS

Version Information

Cursor 3.4.20 (Electron 39.8.1)
Darwin 24.3.0 (macOS)
Node.js (system): v23.11.0 (/usr/local/bin/node)

Same behavior was observed on 3.3.27 before the auto-upgrade.

Additional Information

Direct evidence that Cursor sends SIGKILL

With a thin bash wrapper that records signals instead of execing immediately, Cursor’s kill -9 becomes visible:

/Users/me/.cursor/mcp-launcher-debug.sh: line 30:
  12685 Killed: 9               /usr/local/bin/npx -y "$@"

Race condition in V2 FSM

workbench.mcp.allowlist.log + per-server log shows CreateClient followed by DeleteClient only 15 ms later, then another CreateClient. The earlier process is then killed mid-startup:

11:42:33.683 createClient: identifier="user-postgres"
11:42:33.688 [V2] Handling CreateClient action
11:42:33.703 [V2] Handling DeleteClient action, reason: config_server_modified  ← 15ms later
11:42:35.372 [error] Connected to database: ... (still starting)
11:42:37.076 createClient: identifier="user-postgres"
11:42:37.079 [V2] Handling CreateClient action
11:42:38.806 [error] Connected to database: ...
11:42:39.683 [error] line 30: 12685 Killed: 9
11:42:39.691 Connection failed: MCP error -32000: Connection closed

The reason: config_server_modified fires even when mcp.json content is unchanged, suggesting an overly eager file-watcher trigger or duplicated CreateClient calls.

Separate but related: environment pollution leaks to MCP children

Cursor injects /Applications/Cursor.app/Contents/Resources/app/resources/helpers/ at the head of PATH when spawning the MCP child process. That directory contains a 226 MB binary named node, which is an Electron-as-Node helper, and Cursor also sets ELECTRON_RUN_AS_NODE=1 in the child environment.

which node inside the MCP wrapper returns:

/Applications/Cursor.app/Contents/Resources/app/resources/helpers/node
/usr/local/bin/npx

As a result, npx ends up executing the MCP server under the Electron-as-Node interpreter, which corrupts the stdio protocol enough that some servers cannot complete initialize at all (firebase-tools mcp, @mobilenext/mobile-mcp — both work standalone, both fail under Cursor unless ELECTRON_RUN_AS_NODE is unset and PATH is repaired).

Workaround for the env pollution (NOT for the SIGKILL bug):

#!/bin/bash
unset ELECTRON_RUN_AS_NODE
export PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"
exec /usr/local/bin/npx -y "$@"

With this wrapper, initialize succeeds — but the SIGKILL bug still kills the server ~1.5 s later.

Impact

All user-defined stdio MCP servers (postgres / gitlab / apifox / firebase / mobile-mcp in my case) are unusable. Only the built-in cursor-ide-browser works.

Suggested investigation areas

  1. The duplicate CreateClientDeleteClient race in the V2 FSM (reason: config_server_modified firing without config changes).
  2. Use SIGTERM + graceful stdin close before SIGKILL; respect a settle window after initialize.
  3. Stop injecting helpers/ into child PATH and stop leaking ELECTRON_RUN_AS_NODE=1 to MCP child processes.
  4. Make the initialize timeout configurable per-server for heavy bootstraps like firebase-tools mcp.

A log bundle (Cursor MCP logs + wrapper-captured stdio I/O + the SIGKILL stderr) is attached.

Does this stop you from using Cursor

No - Cursor works, but with this issue

Here is a complete log bundle reproducing the issue on my machine.

Contents:

  • `cursor-logs/` — raw Cursor MCP logs for this session (per-server `.log` files + `workbench.mcp.allowlist.log` + `workbench.mcp.oauth.log` showing the `connected → error` transitions and the `reason: config_server_modified` DeleteClient race).
  • `wrapper-debug/` — the bash wrapper I used to capture stdin/stdout/stderr/exit codes, including the line that shows `Killed: 9` being delivered by Cursor to the npx child.
  • `README.md` — same content as this report, in self-contained Markdown.

Happy to provide additional traces, run other diagnostics, or test a patched build.

cursor-mcp-bug-bundle.zip (12.3 KB). 补充信息

Thanks for the incredibly detailed report and log bundle. This is a known bug with the MCP server lifecycle on startup. Our engineering team is actively working on MCP reliability improvements, and this race condition is on their radar.

For the environment pollution issue (ELECTRON_RUN_AS_NODE + PATH injection), the bash wrapper approach you found is the right workaround for now:

#!/bin/bash
unset ELECTRON_RUN_AS_NODE
export PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"
exec /usr/local/bin/npx -y "$@"

For the SIGKILL/race condition itself, there’s unfortunately no client-side workaround at the moment.

We’ll be tracking this thread as we work on the fix. If you’re able to test a future update and report back whether the behavior changes, that would be very helpful.

Still happening on 3.15.6, so this survived several minor versions past the original 3.4.20 report. Wanted to add a data point since it rules out a few things the fix might otherwise assume.

Setup: AWS’s own mcp-proxy-for-aws (stdio, launched via uvx), configured exactly per AWS’s docs with multi-profile support. Nothing exotic:

"aws-mcp": {

  "type": "stdio",

  "command": "uvx",

  "args": ["mcp-proxy-for-aws==1.6.4", "https://aws-mcp.us-east-1.api.aws/mcp",

  "--profile", "profile1", "profile2", "profile3",

  "--metadata", "AWS_REGION=eu-west-2"]

}

What I’m seeing — same shape as the OP, just with a slower server (takes ~5-5.5s to complete initialize+tools/list instead of ~1.5s):

createClient: identifier="user-aws-mcp-v2", serverName="aws-mcp-v2"

createClient completed for server: user-aws-mcp-v2, statusType=initializing, success=true

createClient: identifier="user-aws-mcp-v2", serverName="aws-mcp-v2"   ← second createClient, 250ms later

createClient completed for server: user-aws-mcp-v2, statusType=initializing, success=true

Successfully connected to stdio server

connection:connect_success: conn=connecting,auth=unknown -> conn=connected,auth=unknown

stopped connection: user-aws-mcp-v2                                   ← torn down ~1-4s later

connecting stdio for "aws-mcp-v2"

listOfferingsForUI sub-call failed (count=3): tools: MCP error -32000: Connection closed | prompts: Not connected | resources: Not connected

Successfully connected to stdio server

connection:connect_success

stopped connection: user-aws-mcp-v2   ← and again

Loops indefinitely, tool count stuck at 0.

Things I ruled out before assuming it was Cursor-side, in case useful for narrowing the repro:

  • Standalone (initialize+tools/list fed directly over stdin outside Cursor) works 100% reliably, every single run, ~5-5.7s total — so it’s not the server crashing or the SigV4 signing being slow enough to actually time out.
  • Not profile count (1 profile vs 4 profiles: identical timing/behavior).
  • Not uvx resolution overhead (pre-installed the binary via uv tool install and pointed command straight at it — same result).
  • Not stale server/client state — renamed aws-mcpaws-mcp-v2 for a totally fresh identifier, same exact loop.
  • Not a full-app-restart artifact — reproduces identically after Cmd+Q + relaunch, not just window reload.
  • Not resource contention from other MCP servers — disabled several other heavy servers (Docker-based one, a few npx-based ones) before reloading, no change.
  • Confirmed via direct tool call (not just the UI badge) that the server genuinely disappears from the available server list mid-cycle — so it’s a real connection drop, not a stale UI count.

Given it survives a fresh identifier + full restart + reduced load, my guess is it’s purely the config_server_modified-style duplicate CreateClient race the OP found, just intermittently winning/losing depending on timing — and a server that naturally takes ~5s to start just gives the race more chances to trigger than one that starts in under a second.

Happy to grab a full log bundle or test a patched/nightly build if that’d help.