Skip to content

service: v2 service spawns one local MCP server pair per cataloged project directory at startup #43845

Open

Description

@Kosyakssss

Summary

On startup, the opencode V2 background service (opencode2 serve --service) spawns one full set of local stdio MCP server processes per project directory discovered by its project catalog, instead of one set total. With ~53 directories in the catalog and 2 local MCP servers configured, this produced 51 duplicate MCP process pairs (~153 processes including grandchildren) in a ~70-second burst. The extra processes are never cleaned up and keep running until the service is manually restarted.

Environment

  • opencode version: opencode2 v0.0.0-beta-17793 (@opencode-ai/cli, npm)
  • OS: macOS 26.x / Darwin Kernel Version 27.0.0, arm64 (Apple Silicon M1)
  • Terminal: Ghostty (xterm-256color, truecolor)
  • Shell: fish
  • Install/channel: beta (npm global)
  • Active plugins: none found in config

Reproduction

  1. Configure two local stdio MCP servers in ~/.config/opencode/opencode.jsonc (one uv run ... Python server, one native binary):
    {
      "$schema": "https://opencode.ai/config.json",
      "mcp": {
        "servers": {
          "telegram": {
            "type": "local",
            "command": ["uv", "--directory", "/path/to/telegram-mcp", "run", "--locked", "python", "main.py"]
          },
          "cua-driver": {
            "type": "local",
            "command": ["cua-driver", "mcp"]
          }
        }
      }
    }
  2. Have many directories under $HOME so the project catalog discovers dozens of projects (in this setup the catalog was rooted at /).
  3. Start or restart the service (opencode2 service restart, or a fresh boot/login that starts it).
  4. Watch the process table:
    ps -eo pid,ppid,lstart,command | grep -E 'telegram-mcp|cua-driver'

Expected Behavior

One process tree per configured local MCP server, created lazily and reused across projects/sessions; unused trees terminated when no longer needed.

Actual Behavior

Immediately after service start, one (uv telegram-mcp + cua-driver) pair was spawned roughly every 1–2 seconds for ~70 seconds, then spawning stopped — but all spawned pairs stayed alive indefinitely, all as direct children of the service PID:

51 × uv --directory .../telegram-mcp run --locked python main.py
51 × cua-driver mcp
51 × .venv/bin/python3 main.py   (grandchildren)

Counts verified stable 15+ minutes later; only restarting the service removes them (same leak observed on 2026-08-18 with 43 pairs / 116 connects in 20s on an earlier beta).

Correlation evidence from ~/.local/share/opencode/log/opencode.log: right after service start it emitted 3,061 catalog.updated events covering exactly ~53 unique directories (/, /Users, /Users/kote, /Users/kote/Notes, home subdirectories...), matching the ~51 leaked pairs almost 1:1. No MCP errors are logged during the burst. A remote MCP server configured alongside these (Composio) is unaffected, consistent with only stdio servers being spawned.

Additional Context

Activity

  1. github-actions commented on Aug 21, 2026

    @github-actions
    Contributor

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

    You already noted these as "possibly related" — they may in fact be the same underlying bug or closely related root causes worth consolidating.

  2. firefoxmmx2 commented on Sep 12, 2026

    @firefoxmmx2

    Same symptom reproduced on the v2.0.2 stable line — one full local MCP set per directory, and the #46828 browse fix is not present in 2.0.x

    Environment

    • opencode v2.0.2 (@opencode/cli, pnpm global), running opencode serve --service
    • Linux 6.18.49-2-lts x86_64
    • User-scope config with 3 local stdio MCP servers (mysql + postgres via @bytebase/dbhub, jetbrains via supergateway) plus 1 remote (context7)

    Deterministic reproduction (one request)

    Reading any directory through the location-scoped fs endpoint used by the picker materializes a full Location runtime and starts a complete set of local MCP servers for that directory.

    1. Record the baseline of MCP-related processes under the service.
    2. Call the picker's endpoint for a single directory:
      GET /api/fs/list?location[directory]=/tmp/opencode
    3. Within ~8 seconds the service spawns a new set of every local MCP server:
    BEFORE: 8 MCP-related processes
    GET /api/fs/list?location[directory]=/tmp/opencode -> 200
    AFTER:  14 MCP-related processes (delta +6)
    
    627672 579141 220180  npm exec @bytebase/dbhub@latest --dsn mysql://...***...
    627673 579141 197480  npm exec supergateway --sse http://127.0.0.1:64343/sse
    627674 579141 193580  npm exec @bytebase/dbhub@latest --dsn postgres://...***...
    628094 627672 150616  node .../dbhub --dsn mysql://...***...
    628101 627673 100344  node .../supergateway --sse http://127.0.0.1:64343/sse
    628107 627674 150452  node .../dbhub --dsn postgres://...***...
    

    That is roughly 1 GB RSS added per directory visited. Browsing a directory tree with N children starts N of these sets. All spawned processes are direct children of the service PID and are not cleaned up.

    The fix for #46826 is not in v2.0.2

    #46828 (which closed #46826 and #46679) added GET /api/browse/list so the workspace picker and /cd navigate without resolving a Location. On v2.0.2:

    • GET /api/browse/list returns 404
    • the shipped binary contains no browse route at all (only api/fs/list, api/fs/find, api/fs/read/)
    • the full /openapi.json (122 routes) has no browse entry

    So on 2.0.x, directory browsing still goes through fs/list -> LocationMiddleware, the exact mechanism described in #46826 and #37844.

    Ask

    Was #46828 intended to land in the 2.0.x stable line? If yes, could it be picked/re-landed? If 2.0.x deliberately uses a different navigation path, what is the expected behavior when a browsed directory inherits user-scope local MCP servers?

    Related: #46826, #46828, #46679, #37844

  3. yudaiyan commented on Sep 22, 2026

    @yudaiyan

    Same symptom on v2.0.12 (Linux/WSL2): one local MCP set per touched directory, kept alive while idle

    Environment:

    • opencode v2.0.12 (@opencode-ai/cli)
    • Linux 6.6.87.2-microsoft-standard-WSL2, x86_64
    • Global config with 3 local stdio MCP servers, all npm/npx-based: tavily-mcp@latest, @playwright/mcp@latest, @fangjunjie/ssh-mcp-server
    • No plugins involved

    Setup: one background service (opencode serve --service) and TUIs opened across a few project directories.

    The service spawned 5 full MCP sets, one per cwd, including directories with no active session:

    Location (cwd) npm exec parents set started
    /opt/study/opencode 3 09:54
    /root 3 09:54
    /opt/work/hermes-session-manager 3 09:55
    /opt/study 3 09:57
    /tmp/opencode 3 10:04

    Each set is npm exec <pkg> (~105–111 MB) → sh -c → the actual node server (67–105 MB). Just the 15 npm exec parents accounted for 1.6 GB RSS; the full MCP tree was ≈ 2.8 GB while all sessions were idle. The service itself was 519 MB.

    Sample from one set:

    PPID   PID    RSS     COMMAND
    28166  28269  106 MB  npm exec tavily-mcp@latest
    28166  28270  108 MB  npm exec @playwright/mcp@latest
    28166  28271  108 MB  npm exec @fangjunjie/ssh-mcp-server --host ... 
    28269  28378   76 MB  node .../node_modules/.bin/tavily-mcp
    28270  28370  105 MB  node .../node_modules/.bin/playwright-mcp
    28271  28362   67 MB  node .../node_modules/.bin/ssh-mcp-server
    

    Nothing is torn down when a Location goes idle — the processes only go away after a service restart. /root (the service's own cwd) had no active session at all but still held a full set.

    Looks like the same root cause as #37844 (cached locations multiply MCP subprocesses). The multiplication is especially expensive with npm-based servers, where each copy costs ~190 MB including the npm exec wrapper. Idle-Location teardown, or one shared set per resolved config, would help a lot.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions