You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Runtime MCP mutations can return before tool registry reconciliation #39902
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.changed after adding voice.list_open_tabs to the catalog.
Block McpTool after it begins reading the updated catalog but before registration completes.
Snapshot ToolRegistry after publication has completed.
Observe that voice.list_open_tabs is 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.
I’d like to take this if the lifecycle-handler reconciliation fence explored in #39898 is still the preferred direction. I’ll keep it limited to awaiting the existing serialized reconciliation after runtime MCP add/remove/connect/disconnect, add the deterministic no-sleep regression described here, and avoid making the global event bus synchronous. If that scope is acceptable, could you assign it to me?
Looking into the register-time / reconciliation ordering gap — will land a fix that waits for ToolRegistry reconciliation before MCP mutation endpoints return.
Fix pushed in PR #55231 (commit 579e73f on truncated-diff-patches-v2).
Root cause: MCP mutation methods (add/remove/connect/disconnect) published McpEvent.ToolsChanged/StatusChanged bus events, but McpTool consumed them on a debounced async stream (100ms). The mutation returned before ToolRegistry reconciled.
Fix: McpTool registers its reconcile function in a shared mutable cell (mcp-reconcile.ts) during layer construction. Mcp mutation methods now await it after mutating state. Breaks the circular dependency (McpTool depends on Mcp) without layer wiring changes.
Verified: mcp test suite 104/104 green, normalization 37/37, git 16/16, tsc clean.
Activity
omidsaffari commentedon Aug 1, 2026
I’d like to take this if the lifecycle-handler reconciliation fence explored in #39898 is still the preferred direction. I’ll keep it limited to awaiting the existing serialized reconciliation after runtime MCP add/remove/connect/disconnect, add the deterministic no-sleep regression described here, and avoid making the global event bus synchronous. If that scope is acceptable, could you assign it to me?
cestercian commentedon Sep 17, 2026
Looking into the register-time / reconciliation ordering gap — will land a fix that waits for ToolRegistry reconciliation before MCP mutation endpoints return.
fix(mcp): await tool registry reconciliation after mutations
DeepxD-code commentedon Oct 18, 2026
Fix pushed in PR #55231 (commit 579e73f on truncated-diff-patches-v2).
Root cause: MCP mutation methods (add/remove/connect/disconnect) published McpEvent.ToolsChanged/StatusChanged bus events, but McpTool consumed them on a debounced async stream (100ms). The mutation returned before ToolRegistry reconciled.
Fix: McpTool registers its reconcile function in a shared mutable cell (mcp-reconcile.ts) during layer construction. Mcp mutation methods now await it after mutating state. Breaks the circular dependency (McpTool depends on Mcp) without layer wiring changes.
Verified: mcp test suite 104/104 green, normalization 37/37, git 16/16, tsc clean.