Skip to content

feat: Allow disabling failed MCP servers & persist the toggle - #56452

Open
VlexWest wants to merge 12 commits into
anomalyco:devfrom
VlexWest:mcp-disable-failed
Open

VlexWest wants to merge 12 commits into
anomalyco:devfrom
VlexWest:mcp-disable-failed

Conversation

@VlexWest

@VlexWest VlexWest commented Oct 17, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #56451
Related: #62868 (fixes the toggle half; losing a stateful process on eviction is separate)

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Space in /mcps now disables a server in any state (failed, pending, connected). On a disabled server it enables it again. Before, failed servers could only be retried & pending ones ignored space.

The toggle is saved now. Disconnect writes "disabled": true (or "enabled": false for v1 entries) into the config file that defines the server, connect removes it. Edits go through jsonc-parser, so comments & formatting stay. Servers without a config file (runtime add, remote config) or defined by managed config keep the old runtime-only behavior.

  • core: Config.setMcpDisabled finds the highest-priority json/jsonc file with the server & edits it
  • core: disconnect interrupts a running connect attempt, so disabling a pending server is immediate instead of waiting for the startup timeout
  • server: connect/disconnect call it; connect still connects after the edit, so a server that the merged config keeps disabled doesn't end up as a silent no-op
  • tui: toggle logic & labels (enable / disable)

I know #17833 got pushback for writing to opencode.json. This only writes when the user toggles a server & only touches that one key, same as Claude Code does.

How did you verify your code works?

  • new core test: native & v1 entries, jsonc comments kept, enable removes the flag
  • new core test: disconnect on a server that never answers returns right away & ends disabled (times out on the old code)
  • new server test: failed local server gets disabled over HTTP & ends up disabled in config & status
  • new server test: a disabled v1 server stays disabled after /api/location/reload, which rebuilds the Location like an idle eviction (MCP servers disabled with /mcps reconnect after an idle Location eviction #62868)
  • new tui tests for failed, pending, connected & disabled, they fail on the old code
  • bun typecheck in core, server, tui, protocol
  • tried it locally with bun run dev:live on a failing server

Screenshots / recordings

1 2 3

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as described. In /mcps, space on a failed server now goes to Disabled (on the base it stayed Failed and only offered retry), and the core/server tests show the flag written to and removed from the defining config file with comments kept. I checked it by driving the TUI on the base and on this branch, and by running the core, server and tui tests plus typecheck for core, server, tui and protocol.

Public API: no new routes or fields, but the meaning of POST /api/experimental/mcp/{server}/connect and /disconnect changes. They now also write disabled: true / enabled: false into (or remove it from) the config file that defines the server. The desktop/web app (packages/app, gui-extensions) call these endpoints too, so their toggles will now save to config as well. The request and response shapes are unchanged. Config.Interface gains an optional setMcpDisabled.

Concerns:

  • packages/server typecheck and the new server test fail: test/mcp-toggle.test.ts imports jsonc-parser, which isn't a dependency of packages/server. JSON.parse is enough there.
  • The toggle can write to admin-managed config, and connect can silently do nothing (see inline comments).
  • Writing to user config files at all is a maintainer call, given the earlier pushback on #17833.

Before

After

Comment thread packages/server/test/mcp-toggle.test.ts Resolved
Comment thread packages/core/src/config.ts Resolved
Comment thread packages/server/src/handlers/mcp.ts Resolved
- add Config.setMcpDisabled for native and v1 MCP entries
- add test for jsonc comments and enable/disable roundtrip
- change connect to clear the disabled flag in config
- change disconnect to set the disabled flag in config
- change route descriptions
- add test for a failed server
- change space to disable failed, pending and connected servers
- change space to enable disabled servers
- add tests for every status
- change disconnect to interrupt a running connect attempt
- change interrupted connect to skip the failed status
- add test for a server that never answers
- change setMcpDisabled to stay runtime-only when managed config defines the server
- add test for a managed server
- change connect to start the server even after a config edit
- change toggle test to JSON.parse
@VlexWest VlexWest changed the title fix: Allow disabling failed MCP servers & persist the toggle feat: Allow disabling failed MCP servers & persist the toggle Oct 17, 2026
@eriktisme

Copy link
Copy Markdown

@VlexWest Do you mind taking this PR over the finish line? 🙏

- fix conflicts with the MCP connection refactor
- change disconnect to interrupt pending starts through startConnection
- add pooled variant of the pending disconnect test
@VlexWest

VlexWest commented Nov 5, 2026 •

Copy link
Copy Markdown
Author

@eriktisme Done, merged dev & fixed the conflicts, should be good to go now.

Now let's hope a maintainer sees & merges it 🙂 (@rekram1-node @kitlangton)

@kfunigeneral

Copy link
Copy Markdown

@VlexWest I think this would also fix the V2 location-eviction path in #62868.

On 2.2.6 and dev, MCP.disconnect only sets status: "disabled" in memory. After a location services evicted sweep, the Location rebuilds from config and the server reconnects with a new connectionID, so the /mcps choice is silently undone. Writing the flag into the defining config file is what's missing for config-defined servers; mine use the V1 mcp. shape, so enabled: false is the relevant path. It also survives /reload, unlike the runtime toggle.

This covers the toggle half of #62868. Would you mind mentioning it in the description as Related: #62868 (rather than Fixes), since the stateful-process loss is separate? It would also be good to add a regression test for the eviction path: disable via the mcp.disconnect endpoint, call LocationServiceMap.invalidate, boot the Location again, and assert the server is still disabled and the config still has the flag. I'm happy to help with that, or open a follow-up test-only PR after this lands if you'd rather keep this one more focused.

- fix conflict in the MCP dialog test fixture
- add test that a disabled V1 server stays disabled after a location reload
- change toggle tests to share one setup
@VlexWest

VlexWest commented Nov 9, 2026

Copy link
Copy Markdown
Author

@kfunigeneral Ah yes, good catch, thanks! I've added "Related: #62868" to the description & a regression test: a disabled v1 server (mcp.) stays disabled after the Location is being rebuild + the config keeps enabled: false.

@kfunigeneral

Copy link
Copy Markdown

@kfunigeneral Ah yes, good catch, thanks! I've added "Related: #62868" to the description & a regression test: a disabled v1 server (mcp.) stays disabled after the Location is being rebuild + the config keeps enabled: false.

Thanks, that's the exact test I was after. The test shape matches my config.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't disable an MCP server that failed or is stuck connecting

3 participants