Skip to content

Retract top-level MCP customization once its child id resolves - #328349

Draft
jruales wants to merge 1 commit into
mainfrom
jruales/mcp-orphaned-top-level-customization
Draft

Retract top-level MCP customization once its child id resolves#328349
jruales wants to merge 1 commit into
mainfrom
jruales/mcp-orphaned-top-level-customization

Conversation

@jruales

@jruales jruales commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Authored by Copilot.

McpCustomizationController publishes a bare top-level customization for MCP servers whose child customization cannot be resolved. Child customizations register asynchronously, so the first state update for a plugin-derived server can arrive before its child id exists.

The controller then kept the server top-level for the rest of the session, so once the child id resolved the same server was surfaced twice — under the plugin child id and under the orphaned mcp-top-level:<provider>:<session>:<name> id. Acting on the orphan failed with Cannot start/stop unknown MCP server customization.

Now the published top-level entry is retracted with SessionCustomizationRemoved when the child id resolves. The "stay top-level" rule existed to avoid orphaning the published id, which the explicit removal handles.

Repro

  1. Install an agent plugin that contributes MCP servers via .mcp.json.
  2. Start an agent session so the agent host reports MCP state while the plugin child customizations are still registering (most reliable with a server that settles into authRequired rather than ready, so the duplicate stays visible).
  3. Open the MCP server list for the session.

Before: the server appears twice, once with a plugin.file://... id and once with mcp-top-level:.... Start/stop on the second logs Cannot start unknown MCP server customization mcp-top-level:....

After: the top-level entry is retracted and the server appears once.

Validation

  • ./scripts/test.sh --run src/vs/platform/agentHost/test/node/shared/mcpCustomizationController.test.ts (17 passing, incl. new retraction test)
  • ./scripts/test.sh --runGlob "**/agentHost/test/node/**/*.test.js" (3238 passing, 0 failing)
  • npm run compile, hygiene, diagnostics clean

`McpCustomizationController` publishes a bare top-level customization for
MCP servers whose child customization cannot be resolved. Child
customizations are registered asynchronously, so the first state update
for a plugin-derived server can arrive before its child id exists.

The controller then kept the server top-level for the rest of the
session, so when the child id later resolved the same server was
surfaced twice: once under the plugin-derived child id and once under the
orphaned `mcp-top-level:<provider>:<session>:<name>` id. Start/stop on
the orphan failed with "Cannot start/stop unknown MCP server
customization".

Retract the published top-level entry with `SessionCustomizationRemoved`
once the child id resolves, then continue on the child path. The
"stay top-level" rule existed to avoid orphaning the published id, which
the explicit removal now handles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 31, 2026 03:57
@jruales
jruales marked this pull request as draft July 31, 2026 03:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Retracts fallback top-level MCP customizations after their plugin child becomes resolvable.

Changes:

  • Migrates MCP state updates to resolved child IDs.
  • Adds a regression test for top-level retraction.
Show a summary per file
File Description
mcpCustomizationController.ts Removes fallback entries during subsequent MCP updates.
mcpCustomizationController.test.ts Tests migration from top-level to child customization.

Review details

Suppressed comments (1)

src/vs/platform/agentHost/node/shared/mcpCustomizationController.ts:291

  • This only notices a newly resolvable child during the next MCP state update, not when the child ID actually resolves. SessionPluginController publishes the asynchronously resolved plugin child independently (copilotAgent.ts:4355-4389), and that path neither mutates _live nor calls applyOne; if the server has already settled (notably authRequired), no later state event is guaranteed, so the orphaned top-level entry can remain. The new test masks this by explicitly calling applyOne after adding the child. Please reconcile existing live entries when plugin customizations publish/settle (and test resolution without a subsequent server-state update).
		const childId = this._options.resolveChildId(server.name);
		if (childId !== undefined) {
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +283 to +289
// A top-level entry is only a fallback for servers whose child
// customization could not be resolved yet — child customizations are
// registered asynchronously, so the first state update for a
// plugin-derived server can arrive before its child id exists. Once the
// child id resolves, retract the previously published top-level entry
// and continue as a child, otherwise the same server stays surfaced
// twice for the rest of the session.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants