anomalyco / anomalyco/opencode
Runtime MCP mutations can return before tool registry reconciliation
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Problem
Runtime MCP lifecycle endpoints can return before the MCP tool adapter has reconciled the new state into ToolRegistry and the Code Mode catalog.
MCP.add, remove, connect, and disconnect publish mcp.tools.changed. McpTool consumes that event on an asynchronously forked stream. A client can therefore mutate MCP state and immediately start a session whose first tool snapshot still reflects the previous registry state.
This is a general read-after-write consistency gap. It was discovered while investigating a voice plugin tool-routing failure, but it did not cause that specific failure.
Expected
After a runtime MCP mutation request returns, a subsequent session/tool snapshot should reflect the mutation.
Actual
The MCP service state is current when the request returns, while ToolRegistry and Code Mode may still be reconciling asynchronously.
Deterministic reproduction
A controlled test can:
- Start with an empty mocked MCP catalog.
- Publish
mcp.tools.changedafter addingvoice.list_open_tabsto the catalog. - Block
McpToolafter it begins reading the updated catalog but before registration completes. - Snapshot ToolRegistry after publication has completed.
- Observe that
voice.list_open_tabsis absent. - Release reconciliation and observe that the path appears.
This reproduces without sleeps or scheduler luck.
Practical impact
Normal HTTP/session work usually gives reconciliation time to finish, so the observed incidence is likely low. Repeated real-world probes did not reproduce a missing tool. The API nevertheless has no ordering guarantee, especially under load or for clients that issue a session request immediately after MCP mutation.
Potential direction
Expose the existing serialized McpTool reconciliation operation as a Location-scoped fence. Runtime MCP add/remove/connect/disconnect handlers can await it after mutating MCP state. Server-originated tool-list updates can remain asynchronous.
A prototype and regression test were implemented in #39898, then closed so the broader API behavior can be considered separately rather than bundled into the voice plugin investigation.
Notes
Avoid making all event-bus subscribers synchronous. A synchronous listener can deadlock because MCP tool discovery publishes before the server startup deferred settles, while reconciliation reads mcp.tools() and waits for that deferred.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the MCP.add, remove, connect, and disconnect handlers, the serialized McpTool reconciliation operation, and the prototype and regression test referenced in #39898. Add a deterministic regression test for the read-after-write gap; done means a mutation response is followed by a session or tool snapshot that reflects the updated registry and Code Mode catalog.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100