feat(mcp): /mcp reconnect <name> for identity drift (closes part of #110)#115
Merged
feat(mcp): /mcp reconnect <name> for identity drift (closes part of #110)#115
Conversation
C2b implementation per RFC #110. Identity-drift only — append / edit / reorder / remove drift cases surface a clear "restart Reasonix to apply" message instead of mutating the registry or prefix mid-session. The graduated permissive policy from the empirical spike (#113) needs API work on `ImmutablePrefix` (replaceTool / removeTool) before the other drift kinds can take effect mid-session; that's a follow-up PR. Touch: - `src/mcp/registry.ts`: new `McpClientHost = { client: McpClient }` indirection on BridgeOptions. Tool closures resolve the live client via `host.client` at call time, so reconnect can swap the underlying socket without re-bridging tools. - `src/mcp/reconnect.ts` (new): `reconnectMcpServer({ host, spec, beforeTools })` re-handshakes a fresh transport, classifies drift, swaps `host.client` only on identity, closes the new client cleanly on refusal so the old one stays untouched. - `src/cli/ui/slash/handlers/mcp.ts`: third subcommand `reconnect <name>`, fires async, reports via `ctx.postInfo` with the lifecycle `↻ reconnect…` / `✓ connected` / `✖ failed` formatter. - `src/cli/ui/mcp-lifecycle.ts`: `reconnect` state added to the union. - `src/cli/ui/slash/types.ts`: `McpServerSummary.client?` replaced by `host: McpClientHost`. `McpClient` import dropped (now via host). - `src/cli/ui/mcp-browse.ts`: `/resource` and `/prompt` read through `server.host.client`. Disconnected-server warnings dropped — host always carries a client now. - `src/cli/commands/chat.tsx`: builds the host at bridge time, stores it on the summary. Tests: - `tests/mcp-reconnect.test.ts`: 2 cases for spec_parse early returns. - `tests/mcp-integration.test.ts`: live test that bridge + host indirection routes a swapped client correctly through registry.dispatch. - `tests/slash.test.ts`: 3 cases for the slash dispatch (lifecycle line emission, unknown-name rejection with hint, no-arg usage). - `tests/mcp-browse.test.ts`: server() helper updated to accept `client` and wrap in host shape. Closes part of #110 (identity case only). Append/edit/reorder/remove mid-session handling deferred — needs ImmutablePrefix surgery.
This was referenced May 2, 2026
esengine
added a commit
that referenced
this pull request
May 2, 2026
Stage E2 of the C2b follow-ups: when /mcp reconnect finds the server added new tools at the END of its tool list, register them mid-session and addTool the prefix instead of refusing. Drift kinds the reconnect now accepts: - identity → free swap, ~95% cache hit (was already shipped in #115) - append → register new tools, ~95% cache hit (the new chunks land in cache too, per benchmarks/spike-mcp-reconnect data) - edit / reorder / remove → still refused with "restart Reasonix" Touch: - src/mcp/registry.ts — extract `registerSingleMcpTool(mcpTool, env)` + new `BridgeEnv` type (resolved bridge environment captured at first-bridge time). bridgeMcpTools' return shape gains `env` so reconnect can re-use the same options. - src/cli/ui/slash/types.ts — McpServerSummary gains `bridgeEnv: BridgeEnv` so the append handler has everything it needs to register a new tool without redoing the whole bridge. - src/cli/commands/chat.tsx — captures bridge.env onto the summary. - src/mcp/reconnect.ts — accepts identity (always) plus append (if caller passes accept: ["identity", "append"]). On append, returns addedTools so the caller can register them. Identity is forced- accepted regardless of `accept` because it's free. - src/cli/ui/mcp-append.ts (new) — `applyMcpAppend(loop, target, addedTools)` calls registerSingleMcpTool + prefix.addTool + refreshes target.report. Accepted-tools-only counting handles the unnamed-tool defensive case. - src/cli/ui/mcp-reconnect-kickoff.ts — gains optional `applyAppend` callback. When set, opts the reconnect into ["identity", "append"]; when missing, behaviour is unchanged from #115. - src/cli/ui/slash/handlers/mcp.ts + src/cli/ui/McpBrowser.tsx + src/cli/ui/App.tsx — both surfaces wire applyMcpAppend through. - tests/mcp-append.test.ts (new) — 4 cases: registry registration, fingerprint invalidation, summary refresh, defensive skip on unnamed tools. Closes most of #110. Edit / reorder / remove mid-session remain as follow-up issues — each requires new ImmutablePrefix API (replaceTool / removeTool) + cache-reset announcement.
This was referenced May 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes part of #110.
Scope
/mcp reconnect <name>for the identity-drift case only. Append / edit / reorder / remove drift surface a clear "restart Reasonix to apply" message instead of mutating the registry or prefix mid-session.Why split this way: the graduated-permissive policy from the empirical spike (#113) needs API work on
ImmutablePrefix(replaceTool / removeTool) before the other drift kinds can take effect mid-session. That's a follow-up PR. This one delivers the most common reconnect case (server crashed / restarted with same tool surface, user wants to retry without restarting Reasonix).Touch
Indirection layer:
src/mcp/registry.ts— newMcpClientHost = { client: McpClient }exposed viaBridgeOptions.host. Tool closures resolvehost.clientat call time, so swapping the client doesn't require re-bridging tools.New module:
src/mcp/reconnect.ts—reconnectMcpServer({ host, spec, beforeTools })re-handshakes a fresh transport, classifies drift viaclassifyToolListDrift(feat(mcp): tool-list drift classifier — groundwork for #110 C2b #114), swapshost.clientonly on identity, closes the new client cleanly on refusal so the old one stays untouched.Slash:
src/cli/ui/slash/handlers/mcp.ts— third subcommandreconnect <name>, fires async, reports viactx.postInfowith↻ reconnect…/✓ connected/✖ failed.src/cli/ui/mcp-lifecycle.ts—reconnectstate added to the union.Type / consumer churn:
src/cli/ui/slash/types.ts—McpServerSummary.client?: McpClientreplaced byhost: McpClientHost. The mutable wrapper IS the swap point.src/cli/ui/mcp-browse.ts—/resourceand/promptread throughserver.host.client. Dropped the "server not connected" branches — host always carries a client now.src/cli/commands/chat.tsx— builds host at bridge time, stores on summary.Tests
tests/mcp-reconnect.test.ts(new) — 2 cases for spec_parse early returnstests/mcp-integration.test.ts— live test that bridge + host indirection routes a swapped client correctly throughregistry.dispatchtests/slash.test.ts— 3 cases for slash dispatch (lifecycle line emission, unknown-name rejection with hint, no-arg usage)tests/mcp-browse.test.ts—server()helper updated to acceptclientand wrap in host shapenpm run verifygreen: 1778 tests.Deferred to follow-up
ImmutablePrefix.addToolto be safely callable from outside the loop, plusToolRegistrymutation that doesn't break the prefix invariantImmutablePrefix.replaceTool(doesn't exist yet)ImmutablePrefix.removeTool+ handling the cache reset announcementrkeybind inMcpBrowser— reuse this slash once landed--strictflag — opt-in to refusing even identity reconnects (specialised use case)Each of those is its own PR with its own design call.
Test plan
npm run verifypasses (1778 tests, +6 new)spec_parsereason/mcp reconnect <name>, confirm the lifecycle lines appear and tools keep working