fix(mcp): per-project registries, registry-independent rendering, per-server ${VAR} handling - #193
Merged
Merged
Conversation
McpToolOutput is self-describing, so a recorded MCP execution can be deserialized without a live server or registry entry. Expose that as deserialize_mcp_output() (McpTool::deserialize_output delegates to it) plus an is_mcp_tool_name() predicate for the reserved mcp__ prefix.
Session rendering and agent session state resolved recorded tool executions through whichever registry the instance happened to hold — so a parallel session in another project (or a second code-assistant instance viewing the session) silently dropped MCP tool calls whose server the current registry had not connected. MCP executions now always deserialize from their self-describing JSON via deserialize_tool_execution(); only native tools still require a registry entry. A session renders identically wherever it is viewed.
The registry cache held a single slot, so sessions running in different projects at the same time invalidated each other on every run start: each switch rebuilt the registry and the eviction pass shut down the other project's local MCP servers. The cache is now a map keyed by the included local-MCP directory (None = global-only; requests without trusted local MCP normalize to it), so each project's registry stays valid while others build. Cache and connection pool hold Weak references: a registry lives exactly as long as a session instance or in-flight run holds it, connections live as long as some registry references them (dropping the last Arc terminates the server child). This replaces the explicit building_keys/evict_unreferenced machinery with ownership-driven lifetimes and answers the previously open eviction question: a project's servers run while a session that uses them is open.
refresh_tool_registry broadcast every fresh registry to all active session instances, so starting a run in one project rewired every other session's rendering to that project's registry — a session viewed while another project's agent ran turns lost its own MCP tool executions. The refresh now updates the manager-level registry (this run's working registry and the default for not-yet-run instances) and the requesting session's instance alone. Other sessions keep the registry of their own last run; MCP executions render registry-independently anyway.
Rework the design note into feature documentation: .mcp.json format and merge semantics, the trust flow, per-session server toggles, the per-project registry cache with ownership-driven connection lifetimes, the registry-independent rendering of recorded MCP executions, and the known limitations. Cross-reference from mcp-client-mode.md and AGENTS.md.
Variable substitution was all-or-nothing: one unset variable failed the whole configuration — the global mcp-servers.json registered nothing, and a project's .mcp.json was dropped entirely. A server missing its secret cannot connect anyway, but it must not take the other servers down with it. McpServersConfig::substitute_env_values now substitutes per server, drops the failing ones, and returns them as (name, error) for the loaders to log.
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.
Follow-up to #189. With sessions running in parallel across projects, the single-slot registry cache and the registry broadcast caused real misbehavior; this PR makes registries per-project and makes session rendering independent of connection state.
Problems
refresh_tool_registry), so a session viewed while another project's agent was running silently dropped its MCP tool calls. The same failure hit a session viewed in a second code-assistant instance that never launched (or never trusted) the producing servers.${VAR}substitution was all-or-nothing. One unset variable failed the wholemcp-servers.json(nothing registered) or dropped the whole.mcp.json.Changes
McpToolOutputis self-describing; recordedmcp__*executions now always deserialize viamcp_client::deserialize_mcp_output(tools::mcp::deserialize_tool_execution), never through a registry lookup. A session renders identically wherever it is viewed. Only native tools still require a registry entry.ConfigToolRegistrykeys registries by the included local-MCP directory (None= global-only). Cache and connection pool holdWeakreferences: a registry lives while a session instance or in-flight run holds it; connections live while some registry references them (dropping the lastArcterminates the server child). This removes thebuilding_keys/evict_unreferencedmachinery and answers the eviction question deterministically: a project's servers run while a session that uses them is open. Parallel sessions in different projects no longer invalidate each other or relaunch each other's servers.refresh_tool_registryassigns the fresh registry to the requesting session's instance only (and catches an instance up when the provider returns an already-cachedArcfor a second session in the same project).${VAR}degradation.McpServersConfig::substitute_env_valuesdrops only the servers whose variables cannot be resolved and returns them for the loaders to log; the other servers keep working. Applies to bothmcp-servers.jsonand.mcp.json.docs/project-scoped-mcp-servers.mdreworked from a design note into feature documentation (format, merge, trust, toggles, lifecycle, rendering guarantee, known limitations); cross-referenced frommcp-client-mode.mdandAGENTS.md.Notes
SessionServiceneeded no changes — provider signature andRegistryRequestare unchanged.session_mode_returns_session_id_while_running(PTY execute_command) is flaky under full-suite load onmainas well — unrelated to this branch.