Skip to content

MCP servers spawned 8-10x per instance on Windows, consuming ~3.5 GB RAM on startup #26336

Closed as not planned

Description

@Zeal29

Description

On Windows, the OpenCode desktop app spawns 8-10 duplicate instances of every local MCP server immediately on startup, instead of one. This causes ~3.5 GB of MCP-related process memory at idle, with 82+ child processes under opencode-cli.exe.

Environment

  • OpenCode version: 1.3.13 (desktop)
  • OS: Windows 11
  • RAM: 32 GB total
  • Local MCPs configured: 9 enabled, 4 disabled
  • System idle RAM: ~41% (12.8 GB used)
  • OpenCode idle RAM: ~70% (21.8 GB used) — 9 GB attributed to OpenCode

Evidence

After a fresh restart with no sessions active, process tree under opencode-cli.exe:

MCP Server               Instances   Process Type
─────────────────────    ─────────   ────────────
zai-mcp-server           10x         cmd.exe -> node.exe (via npx)
exa-search               9x          cmd.exe -> node.exe (via npx)
replicant                9x          cmd.exe -> node.exe (via npx)
chrome-mcp               9x          node.exe
cdp-browser              9x          node.exe
edge-tts                 8x          uvx.exe
windows-mcp              9x          uvx.exe
flaui-mcp                9x          dotnet.exe
playwright (DISABLED)    7x          cmd.exe -> node.exe (via npx)

Total: 82 direct children, ~3465 MB RAM

Key observations:

  • Disabled MCPs still spawn (playwright is enabled: false but spawns 7 instances)
  • Each MCP is spawned via a separate cmd.exe wrapper (from npx/uvx)
  • The multiplier varies between 8-10 but is consistently much greater than 1

Root Cause Analysis

Bug 1: descendants() is a no-op on Windows

In packages/opencode/src/mcp/index.ts, the cleanup function that kills child processes during instance disposal:

const descendants = Effect.fnUntraced(
  function* (pid: number) {
    if (process.platform === "win32") return [] as number[]  // <- Returns empty on Windows
    // Uses pgrep -P (Unix-only command)
    const pids: number[] = []
    // ...
  },
)

On Windows, this returns [], so the finalizer never kills descendant processes (cmd.exe, node.exe, uvx.exe, dotnet.exe). When instances are reloaded or disposed, old MCP processes are left as orphans. client.close() only closes the MCP protocol connection, not the OS processes.

Fix: Use taskkill /T /F /PID <pid> on Windows to kill the entire process tree, or implement Windows-native descendant enumeration via WMI (Win32_Process parent-child traversal).

Bug 2: ScopedCache race condition or multi-initialization

The MCP layer creates state via InstanceState.make() which uses Effect's ScopedCache, keyed by instance directory. The InstanceStore in packages/opencode/src/project/instance-store.ts has proper deduplication with deferred entries.

However, on the desktop app, the initial burst of connections (WebSocket, HTTP polling, UI initialization) appears to trigger multiple concurrent ScopedCache lookups before the first one completes, or the MCP layer is rebuilt multiple times during startup. This needs investigation in the Effect ScopedCache behavior under concurrent access or the desktop app's initialization sequence.

Bug 3: enabled: false MCPs are not respected

MCP servers with enabled: false in config are still spawned. The create() function checks mcp.enabled === false and returns DISABLED_RESULT, but the process tree shows disabled MCPs still spawning. This suggests the config check may be bypassed in certain initialization paths.

Impact

  • Memory: ~3.5 GB wasted on duplicate MCP processes at idle
  • Performance: 82+ background processes competing for CPU and I/O
  • Startup time: Each duplicate MCP must be independently initialized via npx/uvx/dotnet run
  • Stability: Orphan processes accumulate across restarts (due to Bug 1), progressively consuming more RAM

Activity

  1. github-actions commented on May 8, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If the core issue (the Windows descendants() no-op and the over-spawning on startup) is already tracked in one of those, consider closing this as a duplicate or linking them. If this report adds new context (especially the disabled MCP still spawning and the 8–10x multiplier data), it may be worth keeping open separately.

  2. github-actions commented on Jul 9, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions