You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MCP servers disabled with /mcps reconnect after an idle Location eviction #62868
Disabling MCP servers with /mcps is undone after a Location is evicted and rebuilt. On 2.2.6 the log still shows location services evicted, then on the next request location services booted and mcp connected for every server I had disabled, with a new connectionID. A rebuilt Location initializes MCP from config only, so the runtime disconnect is discarded.
This also kills stateful local stdio MCP servers: rea keeps its project in memory, so the next call starts a new process and a 3–4 hour analysis has to be redone.
This is not the 2.2.6 shutdown-event bug from #60668; that stops MCP events during shutdown, but the next boot still reconnects. It is also not #13763 (V1 restart, closed not planned). #57902 is the same mechanism for ACP-added servers, but an ACP fix would re-add those servers, not preserve a /mcps override.
I reproduced it in the v2.2.6 source (tag dc2ffcf) with a scratch test: MCP.disconnect → status "disabled", close the Location scope as eviction does, rebuild → status "connected" and a second mcp connected with a new connectionID.
Plugins
opencode-loop (from ~/.config/opencode/plugins), not MCP-related.
OpenCode version
2.2.6 (tested at tag v2.2.6 / dc2ffcf; also reproduced on 2.2.5)
Steps to reproduce
Configure a stateful local stdio MCP server (in my case rea with npx -y [email protected] mcp) plus other MCP servers.
Open /mcps and disable all of them.
Use a stateful tool in a session (e.g. rea.open_binary + rea.binary_overview).
Leave the location idle for more than 60 minutes (or run /reload).
Thanks for the detailed report. I could reproduce this on 2.2.6 and on the latest dev (12a661e).
The script below uses a minimal local stdio MCP server that logs every process start, and sets experimental.location_idle_timeout to 5 seconds so eviction happens quickly. It:
Waits for the server to connect.
Disables it with the same API call /mcps makes. opencode mcp list shows disabled.
Waits for location services evicted in the log.
Sends another request.
That request logs location services booted and mcp connected server=stateful with a new connectionID. The status is back to connected and a second server process is started, so any in-memory state in the server is lost.
As a workaround for now, experimental.location_idle_timeout: false should keep the Location loaded so it isn't evicted for idleness. It won't help with /reload.
Related: #57902 is the same eviction problem for MCP servers added through ACP. The open PR #56452 would save the /mcps toggle to the config file, which would also avoid this for servers defined there.
Reproduction script (repro.sh)
#!/usr/bin/env bash# Repro for #62868: an MCP server disconnected at runtime (what /mcps does) reconnects# after the Location is evicted for idleness. Needs node and bun. Usage: OC="opencode" ./repro.sh (takes ~2-3 min)set -u
OC=${OC:-opencode}
ROOT=$(mktemp -d /tmp/opencode/repro-XXXX)export HOME=$ROOT/home XDG_CONFIG_HOME=$ROOT/home/.config XDG_DATA_HOME=$ROOT/home/.local/share XDG_STATE_HOME=$ROOT/home/.local/state XDG_CACHE_HOME=$ROOT/home/.cache
export SPAWN_LOG=$ROOT/spawn.log
mkdir -p $XDG_CONFIG_HOME/opencode $ROOT/project;:>$SPAWN_LOG
DIR=$ROOT
cat >$ROOT/mcp-server.mjs <<'MJS'// Minimal stateful stdio MCP server: logs each process start to $SPAWN_LOGimport { appendFileSync } from "node:fs"import { createInterface } from "node:readline"appendFileSync(process.env.SPAWN_LOG, `spawn pid=${process.pid} ${new Date().toISOString()}\n`)const send = (m) => process.stdout.write(JSON.stringify(m) + "\n")createInterface({ input: process.stdin }).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: "stateful", 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 === "ping") return send({ jsonrpc: "2.0", id: msg.id, result: {} }) send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "method not found" } })})MJS
cat >$XDG_CONFIG_HOME/opencode/opencode.json <<JSON{ "experimental": { "location_idle_timeout": "5 seconds" }, "mcp": { "servers": { "stateful": { "type": "local", "command": ["node", "$DIR/mcp-server.mjs"], "environment": { "SPAWN_LOG": "$SPAWN_LOG" } } } } }JSONcd$ROOT/project
$OC service start >/dev/null 2>&1foriin$(seq 30);do$OC mcp list 2>&1| grep -q "stateful connected"&&break; sleep 1;doneecho"== initial status";$OC mcp list 2>&1echo"== disconnect (same call /mcps makes)"$OC api POST "/api/experimental/mcp/stateful/disconnect?location%5Bdirectory%5D=$ROOT/project"2>&1| head -5
$OC mcp list 2>&1echo"== waiting for idle eviction (sweep runs every minute)"foriin$(seq 40);do sleep 5; grep -rqs "location services evicted"$XDG_DATA_HOME$XDG_STATE_HOME&&break;done
sleep 2
echo"== status after eviction (this request boots the location again)"foriin$(seq 20);do out=$($OC mcp list 2>&1);echo"$out"| grep -q stateful &&break; sleep 1;done;echo"$out"
sleep 2
echo"== spawn log (each line = a new MCP server process)"; cat $SPAWN_LOGecho"== service log"; grep -rhE "location services (evicted|booted)|mcp connected"$XDG_DATA_HOME$XDG_STATE_HOME2>/dev/null | cut -c1-220 | tail -10
$OC service stop >/dev/null 2>&1echo"EXPECTED: stateful stays disabled and only one spawn line"
Activity
opencodejr commentedon Nov 9, 2026
Thanks for the detailed report. I could reproduce this on 2.2.6 and on the latest
dev(12a661e).The script below uses a minimal local stdio MCP server that logs every process start, and sets
experimental.location_idle_timeoutto 5 seconds so eviction happens quickly. It:/mcpsmakes.opencode mcp listshowsdisabled.location services evictedin the log.That request logs
location services bootedandmcp connected server=statefulwith a new connectionID. The status is back toconnectedand a second server process is started, so any in-memory state in the server is lost.As a workaround for now,
experimental.location_idle_timeout: falseshould keep the Location loaded so it isn't evicted for idleness. It won't help with/reload.Related: #57902 is the same eviction problem for MCP servers added through ACP. The open PR #56452 would save the
/mcpstoggle to the config file, which would also avoid this for servers defined there.Reproduction script (repro.sh)
Run it:
bash repro.sh