feat: push MCP server context and tools from agentcontext - #26533
Merged
Conversation
kylecarbs
force-pushed
the
kylecarbs/agentcontext-mcp-push
branch
4 times, most recently
from
June 21, 2026 19:39
426ed9f to
70b4e21
Compare
sreya
approved these changes
Jun 21, 2026
agentcontext now starts its own MCP servers from the .mcp.json files its watcher discovers, lists their tools, and surfaces them as KindMCPServer resources pushed to coderd alongside instruction files and skills. This runs independently of agent/x/agentmcp (which serves the agent's MCP HTTP API); the two MCP paths share no state during the rollout, so removing the old package later touches nothing here. - agentcontext/mcprunner.go: self-contained one-shot MCP runner (connect, initialize, list tools, close) with its own .mcp.json parser. A Manager goroutine (runMCPSync) reloads it when the discovered KindMCPConfig path:contenthash set changes, then re-resolves to publish the tools. Each per-server connect force-kills its subprocess on close so a server that ignores stdin-close cannot stall the reload (mcp-go's stdio Close blocks on cmd.Wait with no kill). - agentcontext/mcp.go: buildMCPServerResources turns the runner's non-blocking per-server snapshot into KindMCPServer resources (connected->OK with tools, failed->unreadable issue, no-tools-yet skipped). Tool names are emitted exactly as the server reports them; flattening into a single namespace (e.g. server__tool) is left to the control plane, since the resource already carries the server name. - agentcontext/resolve.go: the resolver takes an MCPResources func() []Resource seam (no MCPProvider interface); MCP resources are excluded from the drift/aggregate hash so a server connecting does not dirty hydrated chats; structurally broken .mcp.json is flagged StatusInvalid. - agent.go: wires the runner via ManagerOptions.MCPExecer/MCPUpdateEnv; agent/x/agentmcp is left untouched. - agentcontext/mcpexec_test.go: the runner's end-to-end coverage lives with the package, re-exec'ing the test binary as a fake stdio MCP server to assert tools reach the snapshot and that a server ignoring stdin-close is force-killed instead of stalling the reload.
kylecarbs
force-pushed
the
kylecarbs/agentcontext-mcp-push
branch
from
June 21, 2026 22:01
70b4e21 to
e4d9ac9
Compare
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
What
Live MCP servers and their tools now flow into the
agentcontextsnapshot and are pushed to coderd viaPushContextState, stored alongside instruction files and skills. Previously the resolver's MCP seam was unimplemented, so live MCP tool lists never reached the pushed snapshot.agentcontextis now fully self-contained for MCP: it connects to the MCP servers declared in the.mcp.jsonfiles its own watcher already discovers, lists their tools, and emitsKindMCPServerresources. It does not depend on or modifyagent/x/agentmcp— that package is left pristine and keeps serving the agent's MCP HTTP API. The two MCP paths run independently, which means the legacy package can be deleted later without touching this code.How
agentcontext/mcprunner.go): a one-shot MCP client (connect → initialize → list tools → close) with its own.mcp.jsonparser. A Manager goroutine (runMCPSync) reloads it whenever the discoveredKindMCPConfigpath:contenthashset changes, then re-resolves so the new tools are published. Per-server connects run in parallel (bounded) with a per-server timeout; a server that fails to connect is recorded as a failure rather than aborting the batch. Each connect also force-kills its subprocess on close, because mcp-go's stdioClose()closes stdin and then blocks oncmd.Wait()with no kill — a server that ignores stdin-close would otherwise stall the whole reload loop.agentcontext/mcp.go):buildMCPServerResourcesturns the runner's non-blocking per-server snapshot intoKindMCPServerresources. Connected servers carry their sorted tools (StatusOK); failed servers surface asStatusUnreadableissues instead of vanishing; connected-but-no-tools-yet are skipped until a later reload. The content hash is tool-set sensitive. The resolver consumes this through a plainMCPResources func() []Resourcefield (noMCPProviderinterface).server__tool) is left to the control plane in the next step, since each resource already carries the server name.driftResources). MCP servers connect asynchronously after boot; without this, a server finishing its connect would dirty every hydrated chat even though nothing the user pinned changed.agent.go): the manager is givenManagerOptions.MCPExecer/MCPUpdateEnv;agent/x/agentmcpis untouched..mcp.jsonsurfaces asStatusInvalidrather than silently dropping all its servers.coderd already persists
mcp_server/mcp_configresource bodies (including tools), so no coderd or proto changes were required.Testing
buildMCPServerResources(grouping/sort/skip/failed/hash sensitivity), MCP resources applied via the resolver seam, MCP exclusion from the aggregate hash,.mcp.jsonparsing (transport inference, env expansion),toolInputSchema, andmcpConfigSetchange detection.TestDRPCPusher_HappyPathSerializesAllFields): aKindMCPServerresource (tools + input schema) round-trips throughPushContextStateinto theMCPServerBodywire form, asserting the server name, tool name/description, and the decodedinput_schema.TestManager_MCPServerToolsInSnapshot): a.mcp.jsonpoints at a re-exec'd fake stdio MCP server; the runner connects it and itsechotool surfaces as aKindMCPServerresource in the Manager snapshot — the same snapshot pushed to coderd — exercisingrunMCPSyncand the resolver wiring end to end.TestManager_MCPServerHangingCloseDoesNotStall): the fake server ignores stdin-close; the test asserts its tool still surfaces, proving the runner force-kills the subprocess instead of stalling the reload. Verified to fail without the fix.-race;go build ./...,go vet, andgolangci-lintare clean on the touched packages.Scope / follow-ups
This is the agent-side production+push half. The chatd consumer (reading the pinned MCP resources for prompt/tool injection, including any server-prefix flattening of tool names) and removing the legacy
workspaceMCPToolsCachepull path remain follow-ups, per the RFC rollout. While bothagent/x/agentmcpandagentcontextexist, stdio MCP servers are spawned by both; this is intentional and temporary untilagentmcpis removed.Implementation plan and decisions
Goal: produce live MCP server resources (with tools) from
agentcontextand push them to coderd.Starting state (main): proto (
PushContextState,MCPServerBody,MCPTool), the drpc adapter, coderd storage (workspace_agent_context_resources, body kindmcp_server), and the resolver's MCP seam already existed; nothing implemented the seam or fed live tools into the snapshot.Decision (agentcontext fully separate from agentmcp):
agentcontextstarts and lists its own MCP servers using only the connect-and-list half of an mcp-go client, driven by the.mcp.jsonfiles its existing watcher discovers. It shares no state withagent/x/agentmcpand does not import it. Two earlier revisions of this branch were discarded: (1) relocatingagentmcpintoagentcontext(rejected — it duplicates config parsing and file watchingagentcontextalready does); (2) readingagentmcp's cached server snapshot via new accessors (rejected — unnecessary coupling between two packages that should simply run independently while one is being retired). The temporary double-spawn of stdio servers is the accepted cost of keeping the two paths cleanly separated untilagentmcpis removed.Decision (no tool-name prefixing, no MCPProvider interface): the agent pushes raw, unflattened data — server name plus verbatim tool names — and lets the control plane own any
server__toolflattening. With a single self-contained producer, theMCPProviderinterface was collapsed into afunc() []Resourcefield on the resolver.Invariants held: no secrets (env/headers) in pushed resources, only server/tool metadata; MCP excluded from the drift hash; the seam is non-blocking so the resolver never stalls on MCP I/O.
This PR was created by Coder Agents on behalf of @kylecarbs.