Skip to content

fix(desktop): toggle a connector through its per-server enabled endpoint - #135694

Closed
Yicheng-Mark wants to merge 1 commit into
NousResearch:mainfrom
Yicheng-Mark:fix/desktop-connectors-mcp-toggle
Closed

Yicheng-Mark wants to merge 1 commit into
NousResearch:mainfrom
Yicheng-Mark:fix/desktop-connectors-mcp-toggle

Conversation

@Yicheng-Mark

Copy link
Copy Markdown

Symptom

The Desktop Connectors per-server enable/disable switch does nothing: the state never changes, nothing persists to mcp_servers.<name>.enabled in config, and no error is ever shown. Meanwhile the CLI equivalent (hermes config set mcp_servers.<name>.enabled false) works against the same server, so the failure reads as "the toggle is dead". As reported in #135014 (first symptom).

Cause

The toggle went through the whole-map replace: setServerEnabled → writeEntry → persist() → saveMcpServers → PUT /api/mcp/servers with the entire mcp_servers map.

Two layers turn a backend rejection into silence:

  1. The endpoint is the wrong shape for a toggle. replace_mcp_servers validates the whole map and 400s if any entry fails — one bad entry among eleven poisons every toggle, while the single-key CLI write path never touches that validation.
  2. The failure is invisible. persist() awaited saveMcpServers but never read its { ok } outcome, then painted the React Query cache unconditionally. The optimistic paint flips the switch, the staleTime: 0 background refetch returns the untouched config and flips it back, and neither writeEntry's false return nor setServerEnabled notifies — the reporter sees "the state never changes (and does not persist to config)", with no error anywhere.

The per-server endpoint for exactly this toggle already exists (PUT /api/mcp/servers/{name}/enabled, exposed by setMcpServerEnabled), but only in ambient-scope form — the Capabilities surface needs the capabilityScoped(profile) routing every other desktop MCP write uses.

Fix

setServerEnabled stops round-tripping the whole map:

  • New setMcpServerEnabledFor(name, enabled, profile?) in api/mcp.ts, scoped like saveMcpServers but hitting the per-server endpoint.
  • setServerEnabled calls it, and:
    • on rejection (ok !== true or a thrown transport error) calls notifyError and returns — no cache paint, so the switch reflects reality;
    • after success, paints the config cache with withEnabled, mirrors into the draft, reloads, and probes when enabling — same post-write steps persist() performed, minus the map round-trip.
    • keeps writeEntry's guards: unknown server name and profilePending stay silent no-ops, and the profileEpoch check still fences cross-profile races.

The second symptom in the issue (clicking a row opening what looks like a different server's details) is deliberately not addressed here: it traces into how merged hosted+local cards pick their dialog and default way, which is a design question rather than a wiring bug, and belongs in its own discussion on the issue.

Regression tests (use-mcp-servers.test.tsx, new): the toggle PUTs the per-server endpoint and never the whole map; a rejected toggle notifies and leaves the cache unpainted; re-enabling drops the enabled key (the documented on-by-default) and probes; unknown server names short-circuit.

Fixes #135014 (toggle symptom)

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds a functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — TS files; local verification was transpile-level (tsc, clean of syntax errors); suite runs in CI
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • I've updated relevant documentation — N/A, toggle persistence path only
  • I've updated cli-config.yaml.example — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md — N/A
  • I've considered cross-platform impact — request/reply path is platform-neutral
  • I've updated tool descriptions/schemas — N/A

The Connectors toggle wrote the whole mcp_servers map via PUT
/api/mcp/servers. That replace 400s when any single entry in the map
fails validation, so one bad entry made every enable/disable a silent
no-op: persist() ignored the {ok:false} outcome and painted the cache
anyway, the staleTime:0 refetch flipped the switch back, and nothing
ever surfaced an error (135014).

Route setServerEnabled through PUT /api/mcp/servers/{name}/enabled --
the endpoint the CLI-parity surface already exposes -- notify on
rejection, and paint the config cache only after the backend accepted
the write.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) tool/mcp MCP client and OAuth area/config Config system, migrations, profiles labels Oct 9, 2026
@Yicheng-Mark

Copy link
Copy Markdown
Author

Superseded by #137863 — same fix and the same new test file, re-posted as a ready PR rather than a draft parked behind the open-PR limit.

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

Labels

area/config Config system, migrations, profiles comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists tool/mcp MCP client and OAuth type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop Connectors UI — MCP enable/disable toggle does nothing, and clicking Composio opens a different server's details

2 participants