Skip to content

feat: run plugin MCP servers discovered by agentcontext - #29326

Closed
jscottmiller wants to merge 1 commit into
scott/agent-plugins/6-agentmcp-pluginsfrom
scott/agent-plugins/7-agent-wiring
Closed

jscottmiller wants to merge 1 commit into
scott/agent-plugins/6-agentmcp-pluginsfrom
scott/agent-plugins/7-agent-wiring

Conversation

@jscottmiller

@jscottmiller jscottmiller commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

tl;dr: The agent runs the MCP servers of plugins in the workspace working directory, and starts and stops them as plugins appear and disappear. The frontend follows in #30577.

Plugin servers start only for plugins discovered at the working directory (the directory itself, plugins/*, and .agents/plugins/*), and only while CODER_AGENT_EXP_MCP_CONFIG_FILES is unset. Setting it, even to .mcp.json, starts none, so a template that points it outside the project, as the network isolation example does, also keeps plugins in the project from starting servers. The rule is path based, so plugins at other paths, such as ~/.coder, contribute skills only, as does every plugin when the template sets no agent dir. agent-plugins joins ExperimentsSafe, so --experiments='*' enables it, and the extending-agents docs describe plugin MCP servers, their mcp.json fields, the PLUGIN_DATA directory, this rule, and repository trust.

mcpConfigSources builds the MCP source list from the env-configured .mcp.json files followed by one plugin-scoped mcp.json source per such plugin (Snapshot.PluginsAt). The agent registers it with Manager.SetSources, so the startup reload, a snapshot subscriber, the engine's config watcher, and Reconnect on a context resync all compute the current list from one place. ReloadCurrent re-reads the list on every attempt, so concurrent reloads cannot drop each other's servers, and it returns an error if the list is still changing after three reloads. The subscriber reloads on every snapshot change; reloads with unchanged sources are no-ops. Plugins are ordered by name claim, so name collisions resolve deterministically.

Plugin MCP server resources carry the plugin name and a <plugin>/<server> source, so a server that lost a name collision still appears with its error. A plugin server whose <plugin>/<server> equals a legacy server name is not started, and the legacy resource names it in a warning; plugin servers whose source exceeds the 1024-byte limit are counted on a <plugin>/mcp.json row instead of failing the push. When the home directory or a plugin's data directory cannot be determined, an existing plugin mcp.json shows that error on its <plugin>/mcp.json row instead of loading. A plugin server whose ./ command appears after its mcp.json is not retried until a config or snapshot change. Agent tests cover adding a plugin, then its mcp.json, and removing it after startup, plus the working-directory, config-file, and experiment rules.

PR map

scott/agent-plugins/6-agentmcp-plugins..a9f87cef08

Category Files + -
prod (backend) 10 423 92
test (backend) 8 824 103
docs 1 61 0
Total 19 1308 195
  1. agent/x/agentmcp/manager.go (+119/-66): SetSources and ReloadCurrent, which replace Reload and ReloadSources, re-read the registered sources on every attempt and error after maxReloadAttempts. The snapshot records each source's position and LoadError, so a plugin reorder or a changed load error reloads. parseAndDedup does not start a plugin server whose ID equals a legacy name. Reconnect reloads the registered sources and does nothing until the first reload settles.
  2. agent/agentcontext/mcp.go (+113/-11): buildMCPServerResources uses <plugin>/<server> sources and copies PluginName. It omits a plugin server whose ID equals a legacy server name, with a warning on the legacy row, and counts plugin servers whose source is over 1024 bytes on a <plugin>/mcp.json row.
  3. agent/agent.go (+86/-4, concurrency): mcpConfigSources adds one source per plugin at the working directory, and none when the MCP config file list was set explicitly or the manifest has no directory. A tracked goroutine, runPluginMCPSync, subscribes to snapshot changes before Run starts and calls ReloadCurrent.
  4. agent/agentcontext/plugin.go (+54/-0): Snapshot.Plugins() returns OK plugins in name-claim order, and Snapshot.PluginsAt(dir) keeps those a scan root at dir discovers.
  5. agent/agentcontextconfig/api.go (+16/-5): Config.MCPConfigFilesSet, set by ReadEnvConfig when CODER_AGENT_EXP_MCP_CONFIG_FILES is non-empty, and API.MCPConfigFilesSet().
  6. agent/x/agentmcp/config.go, plugindata.go (+26/-3): ConfigSource.LoadError, reported for an existing plugin mcp.json instead of loading it, and PluginSource.
  7. codersdk/deployment.go (+1, API): agent-plugins joins ExperimentsSafe.

This PR was generated by Coder Agents on behalf of @jscottmiller.

@ibetitsmike

Copy link
Copy Markdown
Collaborator

UAT run on this tip (d8f7100b)

Ran a remote acceptance pass on dogfood with CODER_EXPERIMENTS=agent-plugins, an inner docker workspace on this build, and real model turns. Fixtures covered valid plugins with stdio and loopback streamable-http servers, a broad invalid manifest and mcp.json matrix, cross-root plugin and .mcp.json collisions, symlink escapes, and hooks/commands directories.

Worked: discovery on all four roots; manifest and mcp.json validation surfaced as error rows with valid siblings still loading; (plugin: name) index entries, read_skill by bare and plugin/<p>/<s> alias, qualified aliases on collisions, composer triggers with plugin labels; stdio env (PLUGIN_ROOT, PLUGIN_DATA at mode 700, placeholder expansion, no CODER_* in the sandbox) and real HTTP tool calls; hydration lifecycle: new plugin MCP row live in an existing chat and callable next turn without refresh, plugin and skill rows pinned until context refresh, removal and re-add, workspace restart with identical kind/source pairs, experiment OFF hiding every plugin row in old chats without refresh, plugin servers back at the first poll after re-enable.

Does not work: non-OK skill and mcp_server rows lose plugin_name in the chat context listing (details and suggested fix on #29323).

Not covered: SSE transport, custom HTTP headers, user-declared source precedence.

Posted by Xum, an AI coding agent, on behalf of @ibetitsmike.

@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from 7d71863 to bf74037 Compare September 15, 2026 19:11
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from bf74037 to 311b7fd Compare September 17, 2026 17:20
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from 311b7fd to 3511ebb Compare September 17, 2026 21:16
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch 2 times, most recently from e7dca4c to 7e655a8 Compare September 18, 2026 22:02
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from 7e655a8 to 250b756 Compare September 24, 2026 17:01
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from 250b756 to 5ea3704 Compare September 24, 2026 22:10
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from c279941 to f9e9502 Compare October 9, 2026 21:27
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from f9e9502 to 4951489 Compare October 9, 2026 22:27
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from 4951489 to a5c91c5 Compare October 12, 2026 18:15
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from a5c91c5 to 9461fb6 Compare October 12, 2026 19:42
@jscottmiller

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-10-16 02:07 UTC by @jscottmiller

Review history
  • R1 (2026-10-12): 13 reviewers, 7 Nit, 3 P2, 12 P3, 7 P4, COMMENT. Review
  • R2 (2026-10-12), 7 Nit, 3 P2, 12 P3, 7 P4, COMMENT. Review
  • R3 (2026-10-12): 13 reviewers, 8 Nit, 3 P2, 19 P3, 20 P4, COMMENT. Review
  • R4 (2026-10-16), 8 Nit, 3 P2, 19 P3, 20 P4, COMMENT. Review

deep-review v0.13.0 | Round 4 | f76e6bf..d731736

Last posted: Round 4, 50 findings (3 P2, 19 P3, 20 P4, 8 Nit), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed (22cc66d; addition verified R3, removal half is CRF-53) agent/agent.go:581 No test fails when runPluginMCPSync is removed; plugin add/remove after startup untested R1 Netero Yes
CRF-2 P3 Author fixed (22cc66d, code removed) agent/agentcontext/watcher.go:296 mcp.json additions to eventRelevant have no test R1 Netero Yes
CRF-3 P3 Author fixed (22cc66d) agent/x/agentmcp/manager.go:221 ReloadFrom returns nil at the attempt cap while the snapshot still mismatches; caller records the plugin set as applied R1 Netero Yes
CRF-4 Note Note (body) agent/x/agentmcp/reload_internal_test.go:794 Concurrent-caller test does not force the second Reload to join before release R1 Netero No
CRF-5 P2 Author fixed (22cc66d) agent/x/agentmcp/manager.go:207 handleWatchedConfigChange reloads a cached lastSources list, so a watcher reload can stop new plugin servers or restart a removed plugin's servers, and runPluginMCPSync never corrects it R1 Takumi P2, Pariston P2, Meruem P2, Ryosuke P3, Melody P3, Mafuuu P3 Yes
CRF-6 P2 Deferred (CODAGT-1391) agent/agent.go:555 Plugin whose ./-relative command appears after mcp.json stays failed until mcp.json changes or restart R1 Ryosuke Yes
CRF-7 P3 Author fixed (22cc66d) agent/agent.go:588 last/slices.Equal guard duplicates startReloadIfNeeded's SnapshotChanged short circuit and causes the no-retry state R1 Bisky P3, Ryosuke P3 Yes
CRF-8 P3 Author fixed (22cc66d) agent/agentcontext/mcp.go:197 <plugin>/<server> source collides with legacy names containing /; the colliding plugin server keeps running but its resource and tools never reach chatd R1 Meruem P3, Luffy P3, Pariston P4 Yes
CRF-9 P3 Author fixed (22cc66d) agent/agentcontext/mcp.go:97 Collision warning names an internal field, says "hides", and gives no action R1 Leorio Yes
CRF-10 P3 Author fixed (22cc66d; push rejection fixed, fix introduced CRF-52) agent/agentcontext/mcp.go:193 Plugin server Source has no length bound; over 1024 bytes makes coderd reject the whole push R1 Kurapika Yes
CRF-11 P3 Author fixed (22cc66d) agent/x/agentmcp/manager.go:209 No test fails when ReloadFrom stops calling sources() per attempt R1 Bisky Yes
CRF-12 P3 Author fixed (22cc66d) agent/agentcontext/plugin_test.go:1125 No test that a plugin MCP server with no OK plugin resource is filtered by withoutOrphanedPluginResources R1 Bisky Yes
CRF-13 P3 Author fixed (22cc66d) agent/agentcontext/plugin.go:456 HasMCPConfig and hasPluginMCPConfig repeat checks agentmcp already performs R1 Luffy Yes
CRF-14 P3 Author fixed (22cc66d) agent/agentcontext/mcp.go:147 hashMCPServer comment gives a false reason (aggregate hash excludes KindMCPServer) and "identity" is no longer what is hashed R1 Gon P2, Mafuuu P4 Yes
CRF-15 P3 Open (silent R4; R3 reply thread still shows resolved) agent/agentcontext/mcp.go:17 Three names for non-plugin MCP servers; "a scan root's .mcp.json" is wrong R1 Gon Yes
CRF-16 P3 Author fixed (22cc66d) agent/agent.go:500 Comment states an ordering requirement (subscribe before Run) that does not exist R1 Gon P2 Yes
CRF-17 P4 Author fixed (22cc66d) agent/agent.go:564 "mcp.json" literal is a third copy of a constant validated in agentmcp R1 Bisky Yes
CRF-18 P4 Author fixed (22cc66d) agent/agent.go:594 default branch of runPluginMCPSync cannot run; "failed reload is retried" doc is false R1 Meruem Yes
CRF-19 P4 Author fixed (22cc66d, code removed) agent/agentcontext/plugin.go:464 Escaping mcp.json symlink message calls the plugin root a "scan root" R1 Leorio Yes
CRF-20 P4 Author fixed (22cc66d) agent/x/agentmcp/manager.go:208 maxAttempts = 3 has no stated reason; doc repeats the value R1 Gon Yes
CRF-21 P4 Author fixed (22cc66d) agent/agent.go:538 mcpConfigSources doc says "each valid plugin" but only HasMCPConfig plugins are included R1 Gon P2 Yes
CRF-22 P4 Author fixed (22cc66d, code removed) agent/agentcontext/plugin.go:452 hasPluginMCPConfig doc omits that a missing file returns no warning R1 Gon P2 Yes
CRF-23 P4 Author fixed (22cc66d) agent/agentcontext/plugin.go:641 Plugins doc names scan-root priority but sorts by name-claim order; copies manager.go root order R1 Gon P2 Yes
CRF-24 Nit Author fixed (22cc66d) agent/x/agentmcp/manager.go:209 Use for range maxAttempts instead of an open loop with an inner bound check R1 Ging-Go Yes
CRF-25 Nit Author fixed (22cc66d) agent/agentcontext/mcp.go:185 hasKey wraps a comma-ok lookup; package uses the inline form elsewhere R1 Ging-Go Yes
CRF-26 Nit Author fixed (22cc66d) agent/x/agentmcp/manager.go:207 ReloadFrom name does not distinguish it from ReloadSources R1 Gon Yes
CRF-27 Nit Author fixed (22cc66d) agent/agentcontext/mcp.go:96 Variable plugins holds server descriptions R1 Gon Yes
CRF-28 Nit Author fixed (22cc66d) agent/agent.go:595 Same ReloadFrom call logs "plugin MCP servers" here and "workspace MCP servers" at 1513 R1 Leorio Yes
CRF-29 Nit Open (silent R4; R3 reply thread still shows resolved) agent/agentcontext/mcp.go:190 Comments that restate the code or test name (8 locations) R1 Gon P2 Yes
CRF-30 Nit Author fixed (22cc66d, code removed) agent/agentcontext/plugin.go:636 mcp.json acceptance rule repeated in three comments R1 Gon P2 Yes
CRF-31 Note Note (body) agent/agent.go:544 Working-directory plugin stdio commands now auto-start, same trust level as working-directory .mcp.json R1 Kurapika No
CRF-32 Note Note (body) agent/x/agentmcp/manager.go:575 A plugin mcp.json parse failure appears as a KindMCPServer named mcp.json; #30577 must treat it as a file error R1 Leorio No
CRF-33 Note Note (body) agent/x/agentmcp/manager.go:184 Manager.Reload has no production caller after this PR R1 Luffy No
CRF-34 Note Dropped by orchestrator (confirmation, no action requested) agent/agent.go:581 runPluginMCPSync timing converges apart from CRF-3 R1 Takumi No
CRF-35 Note Dropped by orchestrator (confirmation, no action requested) agent/agent.go:588 Reload feedback loop terminates; cost bounded by plugin count R1 Killua No
CRF-36 Note Dropped by orchestrator (confirmation, no action requested) agent/agentcontext/mcp.go:56 Producer/consumer pairs match, including coderd source validation R1 Melody No
CRF-37 Note Dropped by orchestrator (confirmation, no action requested) agent/agentcontext/mcp.go:56 Removed plugin's servers are filtered from the push by withoutOrphanedPluginResources R1 Ryosuke No
CRF-38 Note Dropped by orchestrator (confirmation, no action requested) agent/agent.go:581 Remaining premise holds R1 Pariston No
CRF-39 Note Dropped by orchestrator (positive comment note, no action requested) agent/agent.go:575 runPluginMCPSync doc explains the plugin-set comparison R1 Leorio No
CRF-40 P2 Dropped by orchestrator (subsumed by CRF-7 and CRF-18) agent/agent.go:575 runPluginMCPSync doc narrates the loop R1 Gon No
CRF-41 P2 Dropped by orchestrator (merged into CRF-6; the diff makes this path reachable, so the fix is in scope) agent/x/agentmcp/manager.go:412 SnapshotChanged keys plugin sources only on mcp.json stat, so a failed parse is never retried (root cause of CRF-6) R1 Ryosuke No
CRF-42 OOS Out of scope (body) coderd/agentapi/context.go:483 coderd requires plugin MCP source <plugin>/<server>, so a collision-free separator (root fix for CRF-8) needs a coderd change R1 Luffy, Meruem, Pariston No
CRF-43 OOS Out of scope (body) agent/agentcontext/mcp.go:193 Legacy .mcp.json server name over 1024 bytes rejects the whole push (existed before the PR) R1 Kurapika No
CRF-44 OOS Out of scope (body) agent/x/agentmcp/manager.go:922 Raw ListTools error stored and shown to users may contain HTTP server URL query strings R1 Kurapika No
CRF-45 OOS Out of scope (body) agent/agentcontext/plugin.go:478 readPluginManifest escape message also calls the plugin root a "scan root" R1 Leorio No
CRF-46 P3 Author fixed (d731736) agent/x/agentmcp/manager.go:254 reloadFrom attempt-cap error (CRF-3 fix) has no test R3 Netero Yes
CRF-47 P3 Author fixed (d731736) agent/x/agentmcp/manager.go:379 lastSources fallback and "no sources registered" error never run in production; two ways to pick reload sources R3 Netero Yes
CRF-48 P3 Author fixed (d731736) agent/x/agentmcp/manager.go:367 handleWatchedConfigChange doc and lastSources field doc describe the pre-SetSources behavior R3 Netero Yes
CRF-49 P4 Author fixed (d731736) agent/agentcontext/mcp.go:162 Long-source message joined onto an existing unreadable <plugin>/mcp.json row is untested R3 Netero Yes
CRF-50 P4 Open (author acknowledged R4, no ticket; needs human decision) agent/x/agentmcp/manager.go:659 <plugin>/<server> ID built in two packages, and both implement the legacy-collision rule R3 Netero Yes
CRF-51 P4 Author fixed (d731736) agent/agent.go:546 HomeDir and PluginSource failures log a Warn on every reload attempt R3 Netero Yes
CRF-52 P3 Open (silent R4; R3 reply thread still shows resolved) agent/agentcontext/mcp.go:111 Plugin server with a source over 1024 bytes is still started by parseAndDedup but its resource is dropped, so it runs unlisted R3 Kurapika P3, Luffy P3, Ryosuke P4, Melody P4, Leorio P4 Yes
CRF-53 P3 Open (silent R4; R3 reply thread still shows resolved) agent/agent_context_test.go:307 Removal step of TestAgent_PluginMCPServersFollowPluginChanges passes when the engine never drops the plugin R3 Pariston, Bisky, Razor Yes
CRF-54 P4 Open (author acknowledged R4, no ticket; needs human decision) agent/agentcontext/mcp.go:92 Legacy-collision warning replaces any plugin row with a matching source, hiding its real error R3 Razor Yes
CRF-55 P4 Author fixed (d731736) agent/agentcontext/plugin.go:619 Plugin order decides the shared-name winner, but SnapshotChanged ignores order, so an order-only change keeps the old winner R3 Ryosuke Yes
CRF-56 P4 Author fixed (d731736) agent/agent.go:544 HomeDir or PluginSource failure drops a plugin's servers with no context resource R3 Mafuuu Yes
CRF-57 P3 Author fixed (d731736) agent/agentcontext/mcp.go:93 <plugin>/<server> called "source" and "ID"; "ID" already means Resource.ID in the function R3 Gon Yes
CRF-58 P3 Author fixed (d731736) agent/x/agentmcp/manager.go:209 SetSources doc states an ordering requirement that does not exist and omits the concurrency contract R3 Gon Yes
CRF-59 P4 Author fixed (d731736) agent/x/agentmcp/manager.go:122 Sources function named sources in three places and currentSources in two R3 Gon Yes
CRF-60 P4 Author fixed (d731736) agent/x/agentmcp/manager.go:664 Log "skipping plugin MCP server with a name already in use" also fires for the legacy-ID case R3 Gon, Leorio Yes
CRF-61 P4 Author fixed (d731736) agent/agentcontext/plugin_test.go:1134 Plugins() order called name-claim, discovery, and scan-root order R3 Gon Yes
CRF-62 Nit Author fixed (d731736) agent/agentcontext/mcp.go:80 New comments restate the code (9 locations) R3 Gon P2 Yes
CRF-63 P4 Author fixed (d731736) agent/agentcontext/mcp.go:157 Long-source message uses "source" and says "not listed" for running servers R3 Leorio Yes
CRF-64 P4 Open (silent R4; posted in R3 review body) agent/x/agentmcp/manager.go:396 Watcher reload failure logs "watched reload failed" while agent triggers log "failed to reload MCP servers" R3 Leorio Yes
CRF-65 P4 Open (silent R4; posted in R3 review body) agent/x/agentmcp/manager.go:192 ReloadSources/ReloadCurrent docs omit the attempt-cap error R3 Leorio Yes
CRF-66 P4 Author fixed (d731736) agent/agent.go:497 SetOnReload comment lists only startup and .mcp.json edits as triggers R3 Gon (listed out of scope; reclassified) Yes
CRF-67 OOS Out of scope (body) agent/x/agentmcp/manager.go:457 Legacy sources have the same order-insensitive SnapshotChanged check R3 Ryosuke No
CRF-68 Note Dropped by orchestrator (confirmation, no action requested) agent/x/agentmcp/manager.go:388 CRF-5 fix complete; reload timing converges R3 Takumi No
CRF-69 Note Dropped by orchestrator (confirmation, no action requested) agent/agent.go:539 Plugin servers gated by PluginsSupported R3 Kurapika No
CRF-70 Note Dropped by orchestrator (confirmation, no action requested) agent/agentcontext/mcp.go:91 Other paired rules and formats agree R3 Melody No
CRF-71 Note Dropped by orchestrator (confirmation, no action requested) agent/x/agentmcp/manager.go:388 CRF-5 fix addresses the root cause R3 Pariston No
CRF-72 Note Dropped by orchestrator (confirmation, no action requested) agent/agent.go:565 Remaining premise holds; addition path covered by mutation R3 Pariston No
CRF-73 Note Dropped by orchestrator (confirmation, no action requested) agent/x/agentmcp/manager.go:370 CRF-5 fix complete R3 Mafuuu No
CRF-74 Note Dropped by orchestrator (confirmation, no action requested) agent/agentcontext/mcp.go:194 CRF-14 fix accurate R3 Mafuuu No
CRF-75 Note Dropped by orchestrator (confirmation, no action requested) agent/agentcontext/mcp.go:93 CRF-9 warning names identifier, effect, fix R3 Leorio No
CRF-76 Note Dropped by orchestrator (confirmation, no action requested) agent/agent.go:562 CRF-18 retry claim now true R3 Leorio No

Contested and acknowledged

CRF-6 (P2, agent/agent.go:555) - plugin command created after mcp.json stays failed

  • Finding: A plugin whose ./-relative command or cwd is created after its mcp.json keeps a failed server, because SnapshotChanged stats only mcp.json and nothing else triggers a reparse. Proposed: make SnapshotChanged report a plugin source whose parse failed on a missing referenced file as changed.
  • Author defense: No automatic retry added; a retry trigger tied to snapshot or watched-config changes would still miss this case, because creating bin/server changes neither. The author accepts the gap for this PR: the server stays failed until a config or context change, or an agent restart.
  • Deferred: Tracked in CODAGT-1391 (PRRC_kwDOGkVX1s79f2R7, R3). Do not re-evaluate. Tracked in CODAGT-1391.

CRF-50 (P4, agent/x/agentmcp/manager.go:659) - plugin server ID and legacy-collision rule in two packages

  • Finding: The <plugin>/<server> ID is built in agentmcp and in agentcontext.mcpServerSource, and both packages implement the legacy-name collision rule; a format change in one leaves a server refused without a warning or hidden while running. Proposed: one exported ID function in agentmcp.
  • Author defense (R4, PRRC_kwDOGkVX1s7_tlkD): Accepting as-is for this stack; each side has its own test, nothing ties the formats together, a shared ID function is a reasonable follow-up. No ticket.
  • Status: Not closed. Agent acceptance without a ticket needs a human decision.

CRF-54 (P4, agent/agentcontext/mcp.go:92) - legacy-collision warning hides the plugin row's own error

  • Finding: The warning replaces any plugin row whose source equals a legacy name, regardless of agentmcp's reason, so an invalid-entry error is lost and the suggested rename would not start the server.
  • Author defense (R4, PRRC_kwDOGkVX1s7_tlr0): Needs a plugin server and a legacy server with the same qualified name; the warning points at the rename, after which the plugin row's own error appears. No ticket.
  • Status: Not closed. Agent acceptance without a ticket needs a human decision.

Round log

Round 1

Round type: round 1 (full panel). Netero: 1 P2, 2 P3, 1 Note; no P0, so the panel ran. Law not run (517 effective additions). Panel of 13: specialists ging-go, takumi, ryosuke, kurapika, melody, luffy, killua (7 triggered: Go files, goroutines and shutdown, Go package boundaries, path containment and subprocess launch, paired source lists and wire format, new feature, reload resource lifecycle); core pariston, mafuuu; pool bisky (tests changed), gon, leorio (once-per-PR floor); wildcard meruem (shuf). Size target 9-12 for security-sensitive code exceeded by one because the 7 triggered specialists plus core, Bisky, and the Gon/Leorio floor reach 12 before the wildcard.

Cross-check decisions:

  • CRF-5 merges six reports of the stale lastSources path. Two directions: stopping a just-added plugin's servers (Takumi, Pariston, Meruem, Melody; Meruem 20/20 and Takumi 10/10 with forced interleaving) and restarting a removed plugin's servers (Ryosuke, Mafuuu; inferred). Kept at P2. Upward case for P1: a plugin removal fires both the agentcontext watcher and the agentmcp config watcher on the same event, so the restart direction is correlated rather than coincidental; not raised because neither direction was reproduced in a running agent and the restart case needs the plugin's mcp.json to still exist.
  • CRF-8: Luffy says the colliding plugin server's tools stay callable by the model; Meruem says they never reach chatd. Verified chatd builds workspace MCP tools from pushed OK mcp_server resources (coderd/x/chatd/context_prompt.go:100), so Meruem's framing is used. Separator change needs coderd validateContextPluginRefs (coderd/agentapi/context.go:483) to change, so it is CRF-42 out of scope; the in-PR consequence is the finding.
  • CRF-14: Gon P2, Mafuuu P4. Keep argument for P2: the comment gives a false reason and a maintainer can act on it. Set P3: coderd only stores content_hash (coderd/agentapi/context.go:368), so no behavior depends on the misreading.
  • CRF-16, CRF-21, CRF-22, CRF-23 (Gon P2). Keep argument: these comments state facts that are wrong or incomplete. CRF-16 set P3 because it tells maintainers to preserve a constraint that does not exist; the others set P4 because following them produces correct code.
  • CRF-29 consolidates eight Gon P2 restatement comments (mcp.go:190, plugin.go:29, agent_context_test.go:184 and :244, mcp_internal_test.go:171 and :175, plugin_test.go:1141, reload_internal_test.go:751) and CRF-30 consolidates plugin.go:636 and resolve.go:1127. Keep argument for P2: AGENTS.md requires substantive comments. Set Nit: a project-rule violation where the code works.
  • CRF-40 (Gon, agent.go:575) contradicts Leorio's Note (CRF-39). Dropped as subsumed: CRF-7 shows the loop claim is false (Bisky: push count unchanged without the guard) and CRF-18 shows the retry claim is false, so either fix rewrites the comment.
  • CRF-3, CRF-7, CRF-18 interact: removing last (CRF-7) removes CRF-3's consequence and the dead branch in CRF-18. CRF-13 (drop HasMCPConfig) would remove the code behind CRF-2, CRF-21, CRF-22, CRF-30.
  • CRF-41: Ryosuke listed the SnapshotChanged root cause as out of scope. Reclassified in scope and merged into CRF-6: agentmcp/manager.go is changed by this PR and this PR is the first production caller that passes plugin sources, so the diff changes how that code behaves.
    Reviewed against 1cb2cc2..9461fb6.

Round 2 update

BLOCKED. Churn guard: 28 of 29 open findings addressed in 22cc66d; CRF-6 (P2) deferred by the author ("a follow-up ticket is being filed") with no ticket linked and no stated reason the failed-server state is acceptable. No reviewers, no Netero. Status cells updated from churn-guard classifications; fixes are author claims, not panel-verified. Reviewed against 9461fb6..22cc66d.

Round 3

Churn guard PROCEED: CRF-6 deferred to CODAGT-1391 with explicit acceptance. Round type: restructure (exported API changed: ReloadFrom removed; SetSources, ReloadCurrent, PluginSource added; PluginInfo.HasMCPConfig removed), so full panel. Netero (advisory, post-panel): CRF-46 to CRF-51. Law not run (783 effective additions). Panel of 13: same 7 triggered specialists as R1 (ging-go, takumi, ryosuke, kurapika, melody, luffy, killua), core pariston and mafuuu, bisky, gon and leorio (mandatory on restructure), wildcard razor (shuf). Exceeds 12 by one for the same reason as R1.

Verified fixed by reviewers: CRF-1 addition half (Netero mutation), CRF-5 (Takumi, Pariston, Mafuuu), CRF-7, CRF-12, CRF-17 (Bisky), CRF-8, CRF-13 (Luffy), CRF-9, CRF-18 (Leorio), CRF-11 (Netero), CRF-14 (Mafuuu, Gon), CRF-16, CRF-20, CRF-21, CRF-22, CRF-23, CRF-26, CRF-27, CRF-30 (Gon), CRF-24, CRF-25 (Ging-Go), CRF-2 (Netero, code removed). CRF-3 code fixed, no test (CRF-46).

Cross-check decisions:

  • CRF-52 merges five reports; severities P3 (Kurapika, Luffy) and P4 (Ryosuke, Melody, Leorio's wording note). Higher wins: P3, same consequence class as CRF-8 (process runs, tools never reach chatd). Posted as a new finding on the CRF-10 thread because the CRF-10 fix introduced it. Ryosuke's structural alternative (agentmcp owns the plugin server ID and every run/no-run rule) also removes CRF-50, CRF-49's merge path, and CRF-54; included in the comment.
  • CRF-53 merges Pariston, Bisky, Razor (all P3, all reproduced with a sticky-sources mutation). Posted on the CRF-1 thread.
  • CRF-57 (Gon) contradicts Leorio's CRF-75 note approving "its ID" in the warning. Comment asks for one term in code comments and leaves the user-facing word to the author.
  • CRF-62 consolidates nine Gon P2 restatement comments (mcp.go:80, :83, :158, :190, manager.go:234, plugindata.go:32, agent_context_test.go:291, reload_internal_test.go:844, plugin_test.go:1128). Keep argument for P2: AGENTS.md requires substantive comments and the pattern recurs. Set Nit, as in R1: a project-rule violation where the code works. The two locations already listed in CRF-29 are a re-raise of CRF-29, not part of CRF-62.
  • CRF-66: Gon listed agent.go:497 as out of scope. Reclassified in scope: the diff adds reload triggers, which makes that comment's trigger list wrong.
  • CRF-15 re-raised at Nit level (test variable workspace in mcp_internal_test.go:160) on its thread rather than as a new finding.
    Reviewed against 1cb2cc2..22cc66d.

Round 4 update

BLOCKED. Branch rebased (base 1cb2cc2 to f76e6bf, head d731736). Churn guard: 15 addressed, 2 acknowledged without ticket (CRF-50, CRF-54), 1 deferred (CRF-6), 6 silent: CRF-15, CRF-29, CRF-52, CRF-53 (R3 replies on threads whose unresolve failed with "Resource not accessible by integration", so the threads still show resolved) and CRF-64, CRF-65 (folded into the R3 review body). Orchestrator confirmed CRF-52, CRF-53, CRF-64 unchanged in code. No reviewers, no Netero. Human approval from ethanndickson on the PR. Reviewed against f76e6bf..d731736.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot 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.

This PR starts and stops plugin MCP servers from the agentcontext plugin snapshot and attributes their resources to the plugin. Findings: 3 P2, 12 P3, 7 P4, 7 Nit.

Out of scope (needs a ticket or explicit acceptance by a human):

  • coderd/agentapi/context.go:483: coderd requires a plugin MCP server source of <plugin>/<server>, and legacy server names may contain /, so plugin and legacy sources can collide; a separator that cannot collide needs a coderd change.
  • agent/agentcontext/mcp.go:193: a legacy .mcp.json server name over 1024 bytes makes coderd reject the whole context push; this existed before the PR.
  • agent/x/agentmcp/manager.go:922: the raw ListTools error is stored and shown to users; for HTTP servers it may include the request URL with query-string credentials (unverified).
  • agent/agentcontext/plugin.go:478: an escaping plugin.json symlink is also reported as escaping a "scan root" that is the plugin root.

Notes:

  • Plugin mcp.json stdio commands under <working dir>/plugins/* and <working dir>/.agents/plugins/* now start automatically when the workspace supports plugins. This is the trust level the working-directory .mcp.json already has.
  • A plugin mcp.json that fails to parse appears as a KindMCPServer resource named mcp.json with source <plugin>/mcp.json; #30577 has to show it as a file error.
  • Manager.Reload(paths) has no production caller after this PR.
  • TestReloadSources_ConcurrentCallerAppliesItsSources does not force the second Reload to join the in-flight body before close(release). It failed 20 of 20 runs against the base manager.go, but under heavy scheduler load it can pass against broken code.

🤖 This review was automatically generated with Coder Agents.

Comment thread agent/agent.go Resolved
Comment thread agent/x/agentmcp/manager.go Resolved
Comment thread agent/agent.go Resolved
Comment thread agent/agentcontext/watcher.go Resolved
Comment thread agent/x/agentmcp/manager.go Resolved
Comment thread agent/x/agentmcp/manager.go Resolved
Comment thread agent/agentcontext/mcp.go Resolved
Comment thread agent/agent.go Resolved
Comment thread agent/agentcontext/mcp.go Resolved
Comment thread agent/agentcontext/plugin.go Resolved
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch 2 times, most recently from 8fa7b32 to 22cc66d Compare October 12, 2026 21:17
@jscottmiller

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot 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.

Re-review of 22cc66d. The author reports fixes for 28 of the 29 round 1 findings; these are not verified yet, because this round ran no reviewers.

The finding below is deferred without a ticket, so further review is blocked until it has a response:

  • agent/agent.go:555, P2: a plugin whose ./-relative command or cwd is created after its mcp.json keeps a failed server until mcp.json changes or the agent restarts. The reply says a follow-up ticket is being filed, but no ticket is linked, and the reply does not say why the stuck failed server is acceptable. This needs a human decision: link the ticket in that thread, or state explicitly that the gap is accepted.

The out-of-scope items and notes from round 1 are unchanged and do not block.

🤖 This review was automatically generated with Coder Agents.

@jscottmiller

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from 22cc66d to 55133e9 Compare October 12, 2026 23:00

@coder-agents-review coder-agents-review Bot 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.

Re-review of 22cc66d. This round: 7 P3, 13 P4, 1 Nit, and two prior findings re-raised on their threads.

Reviewers verified these fixes: the watcher now uses the registered source function; the plugin-set guard is gone; the legacy-name collision no longer starts the plugin server; the collision warning text; the attempt-cap error; the per-attempt source test; the orphaned-server test; PluginSource; the removed HasMCPConfig layer; and the round 1 comment and naming fixes, apart from the two re-raised on their threads. The ./-command retry is deferred to CODAGT-1391 with the gap accepted. Two fixes introduced new problems: the 1024-byte source fix and the removal test (replies on those threads).

Out of scope (needs a ticket or explicit acceptance by a human):

  • agent/x/agentmcp/manager.go:457: SnapshotChanged ignores the order of legacy .mcp.json sources, so reordering MCPConfigFiles without a file change keeps the old first-wins winner for a shared legacy name until a file changes or the agent restarts.
  • The four round 1 out-of-scope items are unchanged.

agent/x/agentmcp/manager.go:367

P3 [CRF-48] The handleWatchedConfigChange doc still says it reloads "using the most recently observed path set" through "a singleflight Reload", but it now calls reloadFrom with the function registered by SetSources. (Netero)

A maintainer reading it will expect the watcher to reuse the last reload's list, which is the stale-list behavior the round 1 fix removed. The lastSources field comment at line 116 also says it holds "sources passed to ReloadSources", while line 347 records the sources of every reload. Update both comments.

🤖

agent/x/agentmcp/manager.go:396

P4 [CRF-64] A failed watcher reload logs watched reload failed, while the two agent triggers now log failed to reload MCP servers. (Leorio)

All three call reloadFrom and can return the same new attempt-cap error, so an operator who greps for failed to reload MCP servers misses failures from config file edits. Use failed to reload MCP servers here too; the logger already carries trigger=fsnotify (line 386).

🤖

agent/x/agentmcp/manager.go:192

P4 [CRF-65] The ReloadSources doc says it waits for one reconnect and does not mention the attempt-cap error, and ReloadCurrent refers to it. (Leorio)

Only the unexported reloadFrom doc says a call can reload up to maxReloadAttempts times and then return an error while the Manager is still open. Add that to the ReloadSources doc, including that servers from the last completed reload keep running.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread agent/x/agentmcp/manager.go Resolved
Comment thread agent/x/agentmcp/manager.go Resolved
Comment thread agent/agentcontext/mcp.go Resolved
Comment thread agent/x/agentmcp/manager.go Resolved
Comment thread agent/agent.go Resolved
Comment thread agent/x/agentmcp/manager.go Resolved
Comment thread agent/agentcontext/plugin_test.go Resolved
Comment thread agent/agentcontext/mcp.go Resolved
Comment thread agent/agentcontext/mcp.go Resolved
Comment thread agent/agent.go Resolved
@jscottmiller
jscottmiller marked this pull request as ready for review October 13, 2026 03:01
@jscottmiller
jscottmiller force-pushed the scott/agent-plugins/7-agent-wiring branch from 55133e9 to 1d40c18 Compare October 13, 2026 17:37

@ethanndickson ethanndickson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pr lgtm in relative isolation (i havent spent much time on the rest of the stack) but maybe a $comment-cleanup is due

Comment thread agent/agentcontext/mcp.go Resolved
@jscottmiller

Copy link
Copy Markdown
Contributor Author

@ethanndickson Ran a comment-cleanup pass across the whole stack; see the CRF-62 thread.

(Coder Agents generated)

@jscottmiller

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot 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.

Re-review of d731736. The author reports fixes for 15 of the round 3 findings. They are not verified yet, because this round ran no reviewers.

Further review is blocked until each item below has a fix or a reply. The first four were replies on review threads that the reviewer could not reopen, so those threads still show as resolved. Expand the resolved threads to find them.

  • agent/agentcontext/mcp.go:111, P3: a plugin server whose <plugin>/<server> is over 1024 bytes is still started, and only its resource is dropped. agentmcp has no length check, so the command runs while chatd never receives its tools. Reject the entry in parseAndDedup, next to the legacy-name check. Reply on the thread for the 1024-byte source finding.
  • agent/agent_context_test.go:281, P3: the removal step of TestAgent_PluginMCPServersFollowPluginChanges still calls os.RemoveAll(pluginDir) and checks only the push. It passes when mcpConfigSources keeps returning the removed plugin. Delete only plugin.json and check that the server stopped, not only the pushed resources. Reply on the thread about testing plugin changes after startup.
  • agent/agentcontext/mcp_internal_test.go:160, P3 follow-up: the legacy test server is still named workspace.
  • agent/agent_context_test.go:159 and the doc on TestReloadCurrent_ReadsSourcesOnEachAttempt in reload_internal_test.go, Nit follow-up: both comments still restate the code.
  • agent/x/agentmcp/manager.go:370, P4 (round 3 review body): a failed watcher reload still logs watched reload failed, while the agent triggers log failed to reload MCP servers.
  • ReloadCurrent doc in agent/x/agentmcp/manager.go, P4 (round 3 review body): it still does not say that the call can reload up to maxReloadAttempts times and then return an error.

Accepted without a ticket, so these need a human decision (file a ticket or explicitly accept the gap):

  • agent/x/agentmcp/manager.go:632: the <plugin>/<server> ID and the legacy-collision rule are implemented in both agentmcp and agentcontext, and no test ties them together.
  • agent/agentcontext/mcp.go:87: the legacy-collision warning replaces a plugin row's own error, such as an invalid entry, whatever caused that row.

The out-of-scope items from rounds 1 and 3 are unchanged and do not block.

🤖 This review was automatically generated with Coder Agents.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

@nickvigilante nickvigilante 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.

Approving the docs in docs/ai-coder/agents/extending-agents.md; I did not review the agent code.
I checked the "Plugin MCP servers" section against the agent code at c3570ad.
The claims match the implementation, and markdownlint and Vale are clean.
The inline comments are non-blocking.
The main gaps are the plugin mcp.json rules that differ from .mcp.json, the start and stop behavior without a workspace restart, and the known limitation for late-appearing ./ commands, which matters now that agent-plugins is a safe experiment.
The remaining comments are wording and link nits.

🤖 Review generated with Coder Agents on behalf of @nickvigilante.

Comment on lines +422 to +425
A plugin declares MCP servers in an `mcp.json` file at its root, with `"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json"`.
Each server requires `type`, set to `stdio`, `streamable-http`, or `sse`.
A stdio server runs with `PLUGIN_ROOT` set to the plugin directory and `PLUGIN_DATA` set to a data directory for the plugin under `~/.coder/plugin-data`.
The agent expands `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` in `args`, `env` values, and `cwd`.

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.

This section tells readers how to declare servers but leaves out the rules that make a plugin mcp.json stricter than .mcp.json. A reader copying the .mcp.json example above will hit these errors without explanation:

  • The top level must contain exactly $schema and mcpServers, or the whole file is rejected (agent/x/agentmcp/plugin.go:52).
  • type isn't inferred from command or url, and "http" is rejected in favor of streamable-http (plugin.go:174-181). Line 339 lists http as valid for .mcp.json, so the contrast is worth stating.
  • command must be a bare name on PATH or a ./-relative path inside the plugin (plugin.go:257).
  • cwd defaults to the plugin root, not the workspace working directory, and must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA} (plugin.go:303-323).
  • url must be https, or http on a loopback host (plugin.go:459).
  • env can't set PLUGIN_ROOT or PLUGIN_DATA, and fields from another type are rejected (plugin.go:98-100, plugin.go:239).
  • An invalid entry is skipped while its siblings still load, unlike .mcp.json, which fails as a whole.

A short list or a minimal example mcp.json would cover most of this.

Setting `CODER_AGENT_EXP_MCP_CONFIG_FILES` to a non-empty value, including `.mcp.json`, turns off plugin MCP servers.
A template that points it at a file outside the project, as in [network isolation](./platform-controls/agent-isolation.md), also keeps plugins in the project from starting servers.
Plugins in `~/.coder` or in a declared source contribute skills only, even when the declared source is inside the working directory.
If the template doesn't set `dir` on the `coder_agent` resource, the workspace has no working directory, so no plugin starts MCP servers.

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.

Two gaps after this paragraph:

  1. Live start and stop. The agent starts and stops plugin servers as plugins and their mcp.json files appear or disappear, without a workspace restart. "How discovery works" says this only for .mcp.json. One sentence here, possibly pointing to the existing out-of-date-chat note, would cover it.
  2. Known limitation. Per the PR description, a server whose ./ command file appears after its mcp.json isn't retried until a config or snapshot change. Since this PR adds agent-plugins to ExperimentsSafe, the content guidelines call for documenting known limitations. That change also means --experiments='*' turns the feature on, which "Enable the experiment" could mention.

If the template doesn't set `dir` on the `coder_agent` resource, the workspace has no working directory, so no plugin starts MCP servers.

Plugin servers and `.mcp.json` servers share one set of server names, which prefix their [tool names](#tool-naming).
A plugin server doesn't start when a `.mcp.json` server or another plugin's server already uses its name, and its context entry shows the conflict.

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.

As written, "already" doesn't say who wins. parseAndDedup (manager.go:638-672) gives .mcp.json servers priority over every plugin server, and between plugins the first to claim the name (resolver name-claim order) wins. This also uses "context list" to match line 411 instead of introducing "context entry".

Suggested change
A plugin server doesn't start when a `.mcp.json` server or another plugin's server already uses its name, and its context entry shows the conflict.
A plugin server doesn't start when another server already uses its name, and it appears in the chat's context list with the conflict.
A `.mcp.json` server always keeps its name over a plugin server, and between plugins, the first plugin the agent discovers keeps the name.

- `CODER_AGENT_EXP_MCP_CONFIG_FILES` is not set on the agent.

Setting `CODER_AGENT_EXP_MCP_CONFIG_FILES` to a non-empty value, including `.mcp.json`, turns off plugin MCP servers.
A template that points it at a file outside the project, as in [network isolation](./platform-controls/agent-isolation.md), also keeps plugins in the project from starting servers.

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.

Nit: the env var is set in the "1. Configure the template" section, so a deep link lands readers on it.

Suggested change
A template that points it at a file outside the project, as in [network isolation](./platform-controls/agent-isolation.md), also keeps plugins in the project from starting servers.
A template that points it at a file outside the project, as in [Network isolation](./platform-controls/agent-isolation.md#1-configure-the-template), also keeps plugins in the project from starting servers.

Comment on lines +429 to +432
- The plugin is the workspace working directory, or is in its `plugins/` or `.agents/plugins/` directory.
- `CODER_AGENT_EXP_MCP_CONFIG_FILES` is not set on the agent.

Setting `CODER_AGENT_EXP_MCP_CONFIG_FILES` to a non-empty value, including `.mcp.json`, turns off plugin MCP servers.

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.

Nit: the second bullet and line 432 say nearly the same thing, and "The plugin is the workspace working directory" reads oddly. Possible tightening:

Suggested change
- The plugin is the workspace working directory, or is in its `plugins/` or `.agents/plugins/` directory.
- `CODER_AGENT_EXP_MCP_CONFIG_FILES` is not set on the agent.
Setting `CODER_AGENT_EXP_MCP_CONFIG_FILES` to a non-empty value, including `.mcp.json`, turns off plugin MCP servers.
- The plugin is the working directory itself, or is in its `plugins/` or `.agents/plugins/` directory.
- `CODER_AGENT_EXP_MCP_CONFIG_FILES` is unset on the agent.
Setting `CODER_AGENT_EXP_MCP_CONFIG_FILES` to any non-empty value, even `.mcp.json`, turns off plugin MCP servers.

The agent now feeds the mcp.json of each valid plugin discovered at the
working directory (the directory itself, plugins/*, and .agents/plugins/*)
into the MCP engine, but only while CODER_AGENT_EXP_MCP_CONFIG_FILES is
unset. Setting it, even to .mcp.json, starts no plugin servers;
agentcontextconfig.Config.MCPConfigFilesSet records whether it was set.
Plugins found through other scan roots, including declared sources
nested under the working directory, start no servers.
mcpConfigSources composes the env-configured .mcp.json files followed
by one plugin-scoped source per plugin, built by agentmcp.PluginSource
with PLUGIN_DATA under ~/.coder/plugin-data. A plugin without an
mcp.json contributes no servers, and the engine's config watcher picks
the file up when it appears. When the home directory or a plugin's data
directory cannot be determined, the plugin is still passed as a
ConfigSource with LoadError set, which an existing mcp.json reports on
the plugin's <plugin>/mcp.json resource; only the first such failure
logs at warn level. LoadError is kept out of PluginScope, so it is not
part of server identity; the file snapshot records it, and a change to
it reloads. Plugin sources come only from the context snapshot, so no
plugin server starts while Manifest.AgentPluginsEnabled is off.

The agent registers mcpConfigSources with Manager.SetSources, and the
startup reload, a context snapshot subscriber, and config watcher
reloads all compute the current sources from it. ReloadCurrent is the
only reload entry point besides Reconnect: Reload, ReloadSources, and
the watcher's last-sources fallback are removed. Reconnect, which a
context resync calls to retry failed servers, reloads the current
sources and does nothing until the first reload settles. ReloadCurrent calls the function
on every attempt and re-checks the snapshot after joining a concurrent
reload, so one trigger cannot drop another's servers. It returns an
error if the sources keep changing after three reloads. The subscriber
reloads on every snapshot change once the first resolve has finished;
a reload with unchanged sources is a no-op. The subscription is taken
before the context manager's Run loop starts, and the current snapshot
is examined before the first wait.

Snapshot.Plugins() returns the valid plugins in the order the resolver
claimed their names, so the first plugin to claim a server name wins
deterministically. Snapshot.PluginsAt keeps the ones a scan root at
a given directory discovers. The engine's snapshot records source order, so a
reorder alone triggers a reload. MCPServerStatus gains PluginName, which
buildMCPServerResources copies onto the resource.

MCP server resources declared by a plugin carry the plugin name and use
<plugin>/<server> as their source, so a plugin entry that lost a name
collision with a legacy server still appears with its error. A plugin
server whose <plugin>/<server> ID equals a legacy server name is not
started, and the legacy server's resource carries a warning naming it.
A plugin server whose source exceeds the 1024-byte source limit is
left out and counted on an unreadable <plugin>/mcp.json resource, or
on the plugin's server named mcp.json when there is one.

agent-plugins joins ExperimentsSafe, so --experiments='*' enables it.
The extending-agents docs gain a Plugin MCP servers section covering the
plugin mcp.json, when the agent starts plugin servers, name conflicts
with .mcp.json servers, and repository trust.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale This issue is like stale bread.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants