anomalyco / anomalyco/opencode

Runtime MCP mutations can return before tool registry reconciliation

Open
#39,902 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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:

  1. Start with an empty mocked MCP catalog.
  2. Publish mcp.tools.changed after adding voice.list_open_tabs to the catalog.
  3. Block McpTool after it begins reading the updated catalog but before registration completes.
  4. Snapshot ToolRegistry after publication has completed.
  5. Observe that voice.list_open_tabs is absent.
  6. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.