[BUG] Claude Desktop spawns duplicate MCP server processes on every launch — 6/10 servers doubled within 45 seconds

Resolved 💬 4 comments Opened Mar 20, 2026 by Vasiliy-b Closed Apr 17, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Claude Desktop spawns duplicate MCP server processes during normal startup. Within 45 seconds of a fresh launch, 6 out of 10 configured stdio-transport MCP servers are running in duplicate (2× each via the disclaimer wrapper binary), wasting ~550 MB of RAM. No user interaction is required — the duplication happens automatically during the initial MCP initialization sequence.

Environment

  • Claude Desktop: 1.1.7714
  • Electron: 40.4.1
  • macOS: Darwin 25.3.0 (Sequoia 26.1, Apple Silicon)
  • MCP servers configured: 10 (all stdio transport)

Bug Description

Observed behavior

After a fresh launch (clean quit → reopen), Claude Desktop spawns 16 disclaimer wrapper processes instead of the expected 10-12 (10 user-configured MCP servers + 2 built-in extensions):

DUPLICATION CHECK (45s after fresh launch):
   2  npx -y firecrawl-mcp@latest          ← DUPLICATE
   2  npx @playwright/mcp@latest            ← DUPLICATE
   2  docker run ... github-mcp-server      ← DUPLICATE
   2  npx -y mcp-image                      ← DUPLICATE
   2  npx -y @modelcontextprotocol/server-google-maps  ← DUPLICATE
   2  uvx mcp-server-docker                 ← DUPLICATE
   1  npx -y @notionhq/notion-mcp-server
   1  uv ... houdini_mcp_server.py
   1  node .../Claude Extensions/.../server/index.js  (notes extension)
   1  node .../Claude Extensions/.../server/index.js  (osascript extension)

6 out of 10 configured MCP servers are doubled. The duplication is progressive during startup:

  • T+15s: 11 processes (1 already duplicated)
  • T+45s: 16 processes (6 duplicated)
Process hierarchy evidence

All processes share the same parent (Claude Desktop main process) and same process group — this is not an orphan/reparenting issue:

PPID    PID     PGID    COMMAND
31231   31304   31231   disclaimer npx -y firecrawl-mcp@latest       ← Set 1
31231   31307   31231   disclaimer npx @playwright/mcp@latest        ← Set 1
31231   31309   31231   disclaimer docker run ... github-mcp-server  ← Set 1
31231   31310   31231   disclaimer npx -y @notionhq/notion-mcp-server
31231   31311   31231   disclaimer npx -y mcp-image                  ← Set 1
31231   31314   31231   disclaimer uvx mcp-server-docker             ← Set 1
31231   31317   31231   disclaimer npx -y @.../server-google-maps    ← Set 1
31231   31322   31231   disclaimer uv ... houdini_mcp_server.py
31231   31496   31231   disclaimer node .../notes/server/index.js
31231   31498   31231   disclaimer node .../osascript/server/index.js
31231   31500   31231   disclaimer npx -y firecrawl-mcp@latest       ← Set 2 (DUPLICATE)
31231   31537   31231   disclaimer npx @playwright/mcp@latest        ← Set 2 (DUPLICATE)
31231   31556   31231   disclaimer docker run ... github-mcp-server  ← Set 2 (DUPLICATE)
31231   31560   31231   disclaimer npx -y mcp-image                  ← Set 2 (DUPLICATE)
31231   31579   31231   disclaimer npx -y @.../server-google-maps    ← Set 2 (DUPLICATE)
31231   31598   31231   disclaimer uvx mcp-server-docker             ← Set 2 (DUPLICATE)

Two distinct PID ranges (31304-31322 vs 31500-31598) confirm the sets were spawned at different times during startup.

Resource Impact

Disclaimer wrappers:  16 processes × ~1.3 MB = 21.3 MB
MCP server children:
  - npm-based servers:    ~97-127 MB each
  - docker containers:    ~30 MB each  
  - uv/node servers:      ~26-48 MB each

Duplicate (excess) processes: 6 disclaimer + 6 children
Estimated excess memory: ~550 MB
Total Claude Desktop footprint: 3,219 MB (including duplicates)

For users with many MCP servers, this compounds significantly — especially for Docker-based MCP servers that spawn full containers.

Steps to Reproduce

# 1. Ensure Claude Desktop is running with ≥5 stdio MCP servers configured
# 2. Clean quit
osascript -e 'quit app "Claude"'
sleep 5

# 3. Verify all processes are gone (should be 0)
ps aux | grep 'disclaimer' | grep -v grep | wc -l

# 4. Relaunch
open -a Claude

# 5. Wait 15s, check for early duplicates
sleep 15
ps -axo command | grep disclaimer | grep -v grep | sed 's|.*/disclaimer ||' | sort | uniq -c | sort -rn

# 6. Wait another 30s, check for full duplication
sleep 30
ps -axo command | grep disclaimer | grep -v grep | sed 's|.*/disclaimer ||' | sort | uniq -c | sort -rn
# Expected: several servers showing count ≥ 2

Root Cause Analysis

1. The disclaimer binary is NOT the problem

Full disassembly of /Applications/Claude.app/Contents/Helpers/disclaimer (universal Mach-O, linked only to libSystem.B.dylib) reveals a well-behaved wrapper:

Symbol table:
  _main                    — entry point
  _signal_handler          — forwards signals to child
  _child_pid               — global storing child PID
  
Imported symbols:
  _posix_spawnattr_init, _posix_spawnattr_destroy, _posix_spawnp
  _responsibility_spawnattrs_setdisclaim   ← macOS TCC responsibility API
  _sigaction, _kill, _waitpid

Execution flow:

  1. posix_spawnattr_init()responsibility_spawnattrs_setdisclaim(attrs, 1)posix_spawnp(argv[1], ...)
  2. Stores child PID in global _child_pid
  3. Registers sigaction handlers for 4 signals: SIGINT(2), SIGTERM(15), SIGHUP(1), SIGQUIT(3)
  4. All handlers forward the signal to child: kill(child_pid, signo)
  5. waitpid() loop (handles EINTR correctly)
  6. Propagates child's exit status faithfully

Key negative findings:

  • No setsid() — does NOT create a new session (not detaching)
  • No setpgid() — does NOT create a new process group
  • No kqueue/EVFILT_PROC — no parent-death monitoring (not needed since it doesn't detach)

All disclaimer processes share PGID with the parent Claude Desktop process. If the Electron app sends SIGTERM to any disclaimer process, it will forward it to its child and exit cleanly. The cleanup mechanism works — it's just never invoked for the first set before the second set is spawned.

2. Graceful quit works correctly

Positive finding: osascript -e 'quit app "Claude"' does properly terminate all 16 disclaimer processes (drops to 0). The before-quit/will-quit Electron handler appears to clean up correctly on application exit.

3. The bug is in MCP server initialization

The Electron app spawns MCP servers, then for reasons unknown, spawns a subset of them again without terminating the first set. This happens within the first 45 seconds of startup, before any user interaction. Possible triggers:

  • MCP transport reconnection/retry logic that spawns new processes instead of reusing existing ones
  • Multiple initialization paths (e.g., main window + renderer both triggering MCP init)
  • Race condition between initial spawn and a health check / reconnection timer

Workaround

Kill the duplicates manually:

# Nuclear: kill all disclaimer processes (Claude Desktop will respawn them)
pkill -f "Claude.*disclaimer"

# Targeted: find and kill only duplicates by PID
ps -axo pid,command | grep disclaimer | grep -v grep
# Manually identify higher-PID duplicates and: kill <PID>

Expected Behavior

Each configured MCP server should have exactly one disclaimer wrapper process. The app should track spawned processes and never spawn a duplicate without first terminating the existing instance.

Related Issues

  • #15423 — Claude Desktop Windows leaves orphaned renderer processes after quit (closed/stale — focuses on renderer processes, not MCP)
  • #28126 — Task tool subagents spawn duplicate MCP servers on Windows (different trigger — subagents, not main app startup)
  • #36308 — MCP servers should auto-reconnect when disconnected (feature request that may share code path with this bug)

Claude Model

Opus

Is this a regression?

Unknown — may have been present since MCP support was added

Claude Code Version

Claude Desktop 1.1.7714

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

N/A (Claude Desktop, not CLI)

Additional Information

Investigation methodology: Process hierarchy analysis (ps -axo ppid,pid,pgid,command), binary reverse engineering (objdump -d, nm -u, otool -L, codesign -dvvv), Electron app bundle extraction (npx asar extract), and systematic quit/relaunch reproduction with timestamped process counts.

Not affected: Claude Code CLI (claude) — this is specific to Claude Desktop's Electron process management of disclaimer-wrapped MCP servers.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗