MCP servers spawn duplicates on session reconnect — breaks OAuth callback flows
Description
Claude Code spawns MCP server child processes on session start but does not kill them on session end, crash recovery, or before spawning replacements on reconnect. This results in duplicate MCP server instances running simultaneously. For MCP servers that use localhost OAuth callback flows (e.g., workspace-mcp on port 8000), the duplicate processes create an unrecoverable OAuth state mismatch.
Environment
| Field | Value |
|-------|-------|
| Claude Code | Desktop app, current as of 2026-04-02 |
| OS | macOS Darwin 25.5.0 |
| Shell | bash |
| Model | claude-opus-4-6 |
Steps to reproduce
- Start a Claude Code session with MCP servers configured (e.g.,
workspace-mcp,mcp-server-github,mcp-server-filesystem, a custom MCP server) - Open a second Code window, or let the session reconnect after a disconnect/crash
- Run
ps aux | grep mcp— observe duplicate instances of every MCP server
What happened
Every MCP server in the stack was duplicated:
PID 6421 workspace-mcp --single-user --tools gmail drive calendar docs sheets (started 8:35 PM)
PID 6580 workspace-mcp --single-user --tools gmail drive calendar docs sheets (started 8:40 PM)
PID 6440 mcp-server-github (started 8:35 PM)
PID 6578 mcp-server-github (started 8:40 PM)
PID 6447 mcp-server-filesystem (started 8:35 PM)
PID 6569 mcp-server-filesystem (started 8:40 PM)
PID 6418 library-index-mcp/server.py (started 8:35 PM)
PID 6560 library-index-mcp/server.py (started 8:40 PM)
For workspace-mcp specifically, this is fatal to OAuth:
- The older process (PID 6421) holds the listener on port 8000
- The newer process (PID 6580) generates OAuth URLs with state tokens it stores in its own memory
- The OAuth callback hits port 8000, which routes to PID 6421
- PID 6421 doesn't recognize PID 6580's state tokens
- Result: "Invalid or expired OAuth state parameter" on every auth attempt, regardless of how fast the user completes the flow
Manually killing the stale process (PID 6421) freed port 8000, allowed the surviving process to claim it, and OAuth succeeded on the next attempt.
Expected behavior
Claude Code should manage MCP server process lifecycle:
- On session end / window close: Kill child MCP server processes
- Before spawning a new instance: Check if an existing instance of the same MCP server is already running (by PID file, port check, or process name match) and either reuse it or kill it before spawning a replacement
- On crash recovery / reconnect: Clean up orphaned MCP processes before restarting them
Impact
- OAuth-based MCP servers are completely broken after any session reconnect until the user manually kills the stale process. The error message ("Invalid or expired OAuth state parameter") gives no indication that duplicate processes are the cause.
- Non-OAuth MCP servers silently double resource usage (memory, CPU, file handles) with no user-visible benefit.
- The problem compounds — if the user reconnects multiple times without restarting, they accumulate more zombie MCP processes.
Suggested fix
Any of these would work:
- PID tracking: Claude Code records PIDs of spawned MCP servers and kills them on session end / before respawn
- Port guard: Before launching an MCP server, check if its configured port is already in use; if so, kill the occupant or skip the spawn
- Process name dedup: Before spawning, check
psfor an existing process with the same command signature; reuse or kill - MCP spec guidance: Recommend that MCP servers implement a PID/lock file pattern so the platform doesn't have to solve this for every server independently
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗