Repository navigation
Description
Summary
When a local (stdio) MCP server process exits unexpectedly while a session is already connected, OpenCode logs a single mcp transport error warning and never restarts it. The server's tools silently disappear from the agent's tool list for the remainder of the session. Only an unrelated event — a write to the OpenCode config file, or starting a new session — causes the server to be spawned again.
Environment
- opencode version: v2.0.22 (channel
latest) - OS: Windows_NT 10.0.26200.9550 (win32 x64)
- Terminal: Windows Terminal (
WT_SESSIONset),TERM=xterm-256color - Shell:
C:\WINDOWS\system32\cmd.exe - Install/channel: latest (npm
@opencode/cli) - Active plugins: none configured in
opencode.json
Reproduction
- Configure a local stdio MCP server that stays alive after
initialize, for example:{ "mcp": { "servers": { "playwright": { "type": "local", "command": ["C:\\Program Files\\nodejs\\node.exe", "C:\\path\\to\\mcp\\cli.js"] } } } } - Have OpenCode sessions open in two or more directories, so several connections to the same server are spawned (one per directory).
- Kill the MCP server process from outside OpenCode. A realistic trigger is another OpenCode session running a broad process cleanup such as:
Any
Get-Process node,msedge -ErrorAction SilentlyContinue | Where-Object { $_.StartTime -gt (Get-Date).AddMinutes(-25) } | ForEach-Object { try { $_.Kill() } catch {} }
taskkill, service restart of a parent process, or OOM kill should behave the same way. - Observe that the MCP tools are no longer offered to the agent, and that nothing brings them back.
Expected Behavior
A local MCP server whose process exits unexpectedly should be restarted automatically (ideally with backoff and a cap on retries), so its tools remain available. At minimum, a crash while connected should be retried the same way an initial connect failure is.
Actual Behavior
One warning is logged per connection and then nothing:
timestamp=2026-10-04T20:48:21.035Z level=WARN message="mcp transport error" server=playwright error="MCP server process exited with code 255" role=server directory="<project-a>" connectionID=<id>
timestamp=2026-10-04T20:48:21.051Z level=WARN message="mcp transport error" server=playwright error="MCP server process exited with code 255" role=server directory="<project-b>" connectionID=<id>
timestamp=2026-10-04T20:48:21.067Z level=WARN message="mcp transport error" server=playwright error="MCP server process exited with code 255" role=server directory="<project-c>" connectionID=<id>
timestamp=2026-10-04T20:48:21.082Z level=WARN message="mcp transport error" server=playwright error="MCP server process exited with code 255" role=server directory="<project-d>" connectionID=<id>
timestamp=2026-10-04T20:48:21.099Z level=WARN message="mcp transport error" server=playwright error="MCP server process exited with code 255" role=server directory="<home>" connectionID=<id>
No mcp connected line follows, and no retry is attempted. The agent's tool list loses every tool from that server and does not recover on its own.
Notes on scope and contrast:
- The shared background service was not restarted during this occurrence (same PID and start time before and after), so this is specific to MCP child processes rather than a service restart.
- Retry-on-startup does appear to exist: an earlier
mcp connect failedwas followed by a successfulmcp connectedroughly 35 seconds later. What is missing is retry-on-crash for a server that was already connected. - Writing to the global config file causes all connections to be torn down and respawned immediately (
mcp connected ... tools=25for every directory within ~2s), which currently works as an accidental recovery trigger.
Additional Context
- Frequency: 4 occurrences within roughly 40 minutes in one session. Always reproducible when the child process is killed.
- Exit code was
255in every occurrence, consistent with an external kill rather than a crash inside the server. The MCP server itself worked correctly when spawned manually:initializesucceeded,tools/listreturned 25 tools, andtools/call browser_navigatesucceeded. - An earlier, separate failure mode was also observed and is believed to be a different issue: when the command was
npx -y <package>, several connections spawned at once raced on the package manager's shared install cache and died withENOTEMPTY ... InstallFailed extracting tarball. Working around it by pre-installing the package and using an absolute path made startup reliable. Mentioned here only for context. - Workarounds currently used: write to the global
opencode.jsonto force a respawn, oropencode service restart. Both are indirect — the first mutates configuration as a side effect. - Suggested direction (if this is a bug rather than intended behavior): treat
mcp transport errorlike a failed connect and re-enter the spawn/retry path, ideally with exponential backoff and a cap so a permanently broken server does not spin.
Activity
github-actions commentedon Oct 4, 2026
This issue might be a duplicate of existing issues. Please check:
ckir commentedon Oct 18, 2026
Confirmed on Linux / v2.0.32 — same failure, plus two new observations and a cleaner workaround
Corroborating this from a different platform and a later version: Ubuntu Linux x86_64 (kernel 7.0.0-38-generic), opencode v2.0.32 (channel
latest), local stdio server configured globally in~/.config/opencode/opencode.jsonc.What happened
Five connections to the same local server (
rtk, one per open directory, per the pattern in the OP) were killed simultaneously by an external signal: all fivemcp transport errorwarnings landed within a ~250 ms window (02:14:01.988Z–02:14:02.231Z), even though the connections had been spawned 6–53 minutes apart. Confirmed external cause, not a crash:run=febb6aed)/var/log/syslog(grep -c "Out of memory"→ 0 in both current and rotated logs)New observation 1: a new session does not heal it in v2.0.32
The OP notes that starting a new session respawns the server. That did not hold once a stored
failedstatus existed for the directory:02:14:02Z02:19Z→ nomcp connectedloggedopencode mcp list(fresh CLI process) at02:27Z→ still✗ rtk failed, re-displaying the old errormcp connectedfinally at02:53:44Z)So the failure is sticky across sessions as well as across tool calls, and
mcp listreports the cached status rather than triggering a connect.New observation 2: the error text embeds stale startup stderr instead of the exit status
v2.0.22 reported an informative
MCP server process exited with code 255. v2.0.32 reports:That blob is whatever happened to be in the child's stderr buffer at kill time — in this case only the ANSI-colored
initializelogs from up to 53 minutes earlier, with no exit code. It reads exactly like a startup/handshake failure and sent the investigation down the wrong path; the server had connected fine (mcp connected server=rtk tools=4) and had served traffic since. Consider including the exit code/signal and not presenting pre-failure buffered output as the failure reason.New observation 3: per-directory reconnect exists without mutating config
A cleaner workaround than the config-touch / service-restart ones in the OP:
opencode api post '/api/experimental/mcp/<server>/connect?location[directory]=<dir>'Reconnects that directory's connection only (
✓ <server> connectedimmediately, tools usable). The endpoint acceptslocation[directory]as a deep-object query param;GET /api/mcplists servers. Looping over the affected directories fully recovered all six connections without restarting the service or touchingopencode.json.Diagnostic note (possibly useful for the retry logic)
The server logs
input stream terminated+serve finished quit_reason=Closedwhen it actually processes stdin EOF (verified by hand-driving aninitialize/tools/listhandshake against the same binary). Those lines being absent from the captured stderr is a reliable signal that the child was signalled rather than disconnected — i.e. exactly the case the retry path should cover.Frequency on this occurrence: once, but permanently sticky until manual intervention.
iaduartec commentedon Oct 24, 2026
Additional data point on v2.1.4 (Linux/WSL2): the same "not restarted" outcome also happens when the local child exits cleanly (code 0), not only after a crash or external kill. That suggests the dispose/reload path, not a server that died.
Timeline from
~/.local/share/opencode/log/opencode.log:No
mcp connectedfor that server afterwards. Later config resolutions (one per request in this build) did not retry.State stayed like that for 45+ minutes:
The server itself is healthy. Spawning the exact same command with piped stdio and sending
initializeanswers in ~200 ms, lists 11 tools, and stays alive:Recovery is immediate with the experimental endpoint:
Note: it is not deterministic. A later explicit config edit plus reload left the server connected, so this looks like a race between dispose and respawn rather than a fixed repro.
Two things would help:
failedforever.mcp listdistinguishdisposedfromfailed(or show the failure time), so a healthy binary isn't mistaken for a broken one.Related: #50725 (
mcp listvs/api/mcpmismatch), #52237 (remote servers not retried).Environment: opencode v2.1.4, Linux 6.18.54.1-microsoft-standard-WSL2 (x64),
TERM=xterm-256color, bash, installed at~/.local/bin/opencode, 4 local plugins (adaptive_router,agy_delegate,context-mode.v2,rtk). Paths redacted to/home/user.opencodejr commentedon Oct 27, 2026
Thanks for the detailed report, @TitanLion72! In short: you'd like OpenCode to restart a local (stdio) MCP server automatically, with backoff and a retry limit, when its process exits after it has connected, so its tools don't silently vanish for the rest of the session.
The current v2 behaviour is intentional, if not ideal. When a connected server's process exits, OpenCode marks the server as
failedand removes its tools. Recovery is meant to be manual: tool calls to it return "Reconnect it from /mcps." The only automatic retries are short ones (about 250 ms and 1 s) inside a single connect attempt. Nothing restarts a server that crashes after it has connected. So I've filed this as a feature request rather than a bug, and maintainers will consider it.Until then, reconnecting the server from
/mcpsshould be a more direct workaround than editingopencode.jsonor restarting the service.Related issues:
enzoayala32 commentedon Nov 9, 2026
Adding wrapper-level evidence from Windows / v2.2.6 that may help identify what kills the node children. This comes from a separate report (#62841) that was marked as a duplicate of this one.
Setup: three local stdio servers spawned as
node <script>, one connection per open directory (2 directories → 6 processes), plus one non-node local server (a Rust.exe) and one remote server. I put a diagnostic wrapper between OpenCode and each node server that logs stdin EOF, stdout/stderr EPIPE, the child's exit code and its stderr, and watched the process table in parallel.Observations:
mcp transport error ... exited with code 1lines with no stderr attached.end, no pipe error, no child error. The processes were terminated from outside, not crashed. Windows Event Viewer has noApplication Error/Application Hangentry fornode.exeat those timestamps either.STDIN END (parent closed stdin)→CHILD EXIT code=0. So the silent exit-1 deaths are not the graceful reload path (consistent with @iaduartec's observation about the dispose/reload path)..exeserver survived the same events and the remote server was unaffected — only the node-spawned children died. The OpenCode server process itself kept the same run id across the deaths, so it was not a service restart (and no desktop app was running, so not the desktop: supervisor respawns a live local service every ~60s (reason: 'missing'), aborting every tool call #61061 supervisor path either).The node-selectivity is the part I have not seen in this thread: whatever kills these processes appears to target the node-based MCP children rather than all local MCP processes. Happy to run more targeted experiments (ProcMon/ETW capture, or a watchdog that respawns and dumps stack info) if that would help.
enzoayala32 commentedon Nov 9, 2026
Follow-up with a reproducible trigger, from more experiments today (Windows, v2.2.6).
The trigger is OpenCode's own
location services reloadedpath — not an external killer.POST /api/experimental/mcp/<server>/connect?location[directory]=..., no reload): 6 node processes across 2 directories stayed connected for 40+ minutes (still alive when writing this). This is reproducible: three separate reconnects, all healthy./reloadand through a config-file save (both loglocation services reloaded, which re-boots the per-directory services): every per-directory node connection died together, ~1m40s–4m30s later, silently. Three reload events observed today, all three followed by the deaths within that window. Yesterday's config-save reconnects behaved the same way (100s, 3m26s, 12m).What rules out an external/OS-level killer:
node, stdin/stdout pipes held open, parented outside the OpenCode server) survive indefinitely — 30+ minutes and counting — while the server's node children die.end, no EPIPE, no child error, no stderr): the children are terminated, not crashed.STDIN END (parent closed stdin)→CHILD EXIT code=0.Application Errorentries fornode.exeat the death timestamps.The remaining puzzle is node-selectivity: the same reloads spawn the local Rust
.exeserver too, and its processes survive the entire sequence (same PIDs across the reloads,inJob=True, parent chain to the service). So whatever tears the children down targets the node-spawned processes specifically — e.g. a per-child job/stdio-teardown path that only covers the node spawns, rather than a blanket "kill all local MCP children on reload".Happy to add more data if useful (e.g. per-child job-handle limits, or a respawn watchdog that dumps a terminating-process stack).
enzoayala32 commentedon Nov 9, 2026
Correction + a working user-side mitigation.
Correction to my previous comment: the "40+ minutes, still alive" figure was measured too early. Those node children did eventually die — ~54 minutes after the API reconnect (the
/reloadreconnects died at 1m40s–4m30s, so the API connect path lasts ~10x longer, but it is not immune). Deaths continue in batches: every per-directory node connection dies together, in the same second, minutes after connecting.Working mitigation (user-side watchdog) until #54378 lands: a small loop that polls
opencode mcp listevery 45s and reconnects any failed local server viaPOST /api/experimental/mcp/<server>/connect?location[directory]=...in each affected directory. It recovered all three node servers within ~2 minutes of each death batch, repeatedly, over the last hour. Script (bash, ~40 lines):The reconnect endpoint works and is much faster than waiting for a config write; the only gap is that
mcp listoutput is cached for a moment, so the loop needs the ~45s cadence plus a short settle time for slow servers (rea takes ~20s to initialize).One more observation for whoever picks this up: the deaths are not tied to any single interval today — batches died at 4m, 3m, 12m, 19m, 48m and 54m after connecting — but they always hit every per-directory connection of every node-based server at once, and never the Rust
.exeserver or the remote one. That node-selectivity is still the strongest lead on what is tearing them down.