Skip to content

Desktop v2.1.3: MCP toggle in Settings does nothing — server stays connected, config not updated #58392

Open

Description

@zx2021210538

Description

In the Desktop app, switching an MCP server off in Settings → Extensions → MCP only flips the switch in the UI. Nothing else happens:

  • the MCP server keeps running and is still reported as connected
  • its tools remain callable in the current session
  • the global config is not modified (no disabled: true, file mtime unchanged)
  • the service log shows no config.updated event

A toggle-off is effectively a no-op, and because nothing is written to disk, restarting the app doesn't help either.

This looks like the same family as #25928 / #18664 / #13763, but reproducible on the v2 desktop build.

Plugins

None.

OpenCode version

v2.1.3 (Desktop; the bundled CLI reports opencode v2.1.3)

Steps to reproduce

  1. Configure two stdio MCP servers in the global config (mcp.servers.*), both enabled.
  2. Open Desktop → Settings → Extensions → MCP and switch both off.
  3. The switches show as off.
  4. Check opencode mcp list → both are still connected. opencode debug config → both servers still present without disabled. opencode.json mtime is unchanged.
  5. In an active session the MCP tools still execute normally.
  6. Restart the Desktop app → still connected, because the config was never changed.

Expected behavior

Toggling off should stop the server (at least for the session) and/or persist disabled: true to the config, and the switch should reflect the real state.

Additional context

Operating System

Windows 11

Terminal

N/A (Desktop app)

Activity

  1. opencode-agent commented on Oct 23, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced the part where the server keeps running, on v2.1.3 and on the latest dev (a5dddc507e4f). I tested the same server API calls the Settings page makes, not the Windows Desktop app itself.

    What happens: the Settings → Extensions → MCP switch sends its disconnect request without a project location. That stops the servers only in the background server's default location, so the Settings list shows them as off. Sessions in your project directory, and opencode mcp list run there, use a separate MCP instance that stays connected, so the tools still work. The script below shows this with two stdio servers: after a disconnect with no location, the default location reports disabled, the project location still reports connected, and the server processes keep running.

    On the config file: at v2.1.3 the toggle is runtime-only by design. It never writes disabled: true, so it isn't a failing JSONC write. Saving the toggle to config is being worked on separately in #56452.

    Related:

    Until this is fixed, adding "disabled": true and running opencode reload, as you found, is the reliable way to turn a server off.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces #58392 at the server API level: the Desktop Settings → Extensions → MCP switch
    # calls mcp.list / mcp.disconnect WITHOUT a location (see packages/app/src/settings/providers/extensions.tsx).
    # Set OC to override the opencode command (e.g. running from source).
    set -u
    OC=${OC:-opencode}
    export HOME=$(mktemp -d /tmp/opencode/home.XXXX)
    export XDG_CONFIG_HOME=$HOME/.config XDG_DATA_HOME=$HOME/.local/share XDG_STATE_HOME=$HOME/.local/state XDG_CACHE_HOME=$HOME/.cache
    mkdir -p $XDG_CONFIG_HOME/opencode
    DIR=$HOME
    cat > $DIR/mcp-server.mjs <<'MJS'
    // Minimal stdio MCP server exposing one tool "ping"
    import readline from "node:readline"
    const rl = readline.createInterface({ input: process.stdin })
    const send = (m) => process.stdout.write(JSON.stringify(m) + "\n")
    rl.on("line", (line) => {
      let msg; try { msg = JSON.parse(line) } catch { return }
      if (msg.id === undefined) return
      if (msg.method === "initialize") return send({ jsonrpc: "2.0", id: msg.id, result: { protocolVersion: msg.params?.protocolVersion ?? "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "mock", version: "1.0.0" } } })
      if (msg.method === "tools/list") return send({ jsonrpc: "2.0", id: msg.id, result: { tools: [{ name: "ping", description: "ping", inputSchema: { type: "object", properties: {} } }] } })
      if (msg.method === "tools/call") return send({ jsonrpc: "2.0", id: msg.id, result: { content: [{ type: "text", text: "pong" }] } })
      send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "not found" } })
    })
    MJS
    cat > $XDG_CONFIG_HOME/opencode/opencode.jsonc <<JSON
    {
      // global config with comments
      "mcp": { "servers": {
        "alpha": { "type": "local", "command": ["node", "$DIR/mcp-server.mjs"] },
        "beta":  { "type": "local", "command": ["node", "$DIR/mcp-server.mjs"] }
      } }
    }
    JSON
    PROJECT=$(mktemp -d /tmp/opencode/project.XXXX); (cd $PROJECT; git init -q); cd $HOME
    PORT=4599
    export OPENCODE_PASSWORD=repro
    $OC serve --port $PORT > $HOME/serve.log 2>&1 & SRV=$!
    trap 'kill $SRV 2>/dev/null' EXIT
    for i in $(seq 60); do curl -sf -u opencode:repro localhost:$PORT/api/mcp >/dev/null 2>&1 && break; sleep 1; done
    B=http://localhost:$PORT
    LOC="location%5Bdirectory%5D=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" $PROJECT)"
    st() { curl -s -u opencode:repro "$B/api/mcp$1" | python3 -c "import json,sys;d=json.load(sys.stdin);d=d.get('data',d);print([(s['name'],s['status']['status']) for s in d])"; }
    sleep 3
    echo "== before: list (no location, as Settings page does)"; st ""
    echo "== before: list for project location (sessions in project)"; st "?$LOC"
    for s in alpha beta; do curl -s -u opencode:repro -o /dev/null -w "disconnect $s (no location): HTTP %{http_code}\n" -X POST "$B/api/experimental/mcp/$s/disconnect"; done
    sleep 1
    echo "== after: list (no location)"; st ""
    echo "== after: list for project location"; st "?$LOC"
    echo "== ping processes still running:"; pgrep -af mcp-server.mjs | wc -l

    Run it: bash repro.sh

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions