Files
coder/agent/agentsocket/proto/agentsocket.proto
T
Kyle Carberry 27ecd17991 refactor: consolidate agent MCP onto a single persistent engine (#26599)
Two MCP code paths both spawned the servers declared in a workspace's
`.mcp.json`: the persistent engine in `agent/x/agentmcp` (which owns
tool-call execution via `CallTool`) and an ephemeral one-shot runner in
`agent/agentcontext` (`mcprunner.go`) that connected, listed tools, and
immediately closed each server purely for discovery. Every declared
server was launched twice, and the discovery path duplicated the
engine's `.mcp.json` parse, transport-build, env-resolve, and connect
logic.

This makes `agent/x/agentmcp` the single persistent MCP engine. The
`agentcontext` manager now reads that engine's per-server catalog
in-process through an injected `MCPCatalog` option and surfaces each
server as a `KindMCPServer` resource. The engine wires `SetOnReload` to
the manager's `Trigger`, so a reload (startup connect or `.mcp.json`
edit) re-resolves and re-pushes the pinned resources. Tool-call
execution is unchanged: it still flows through the engine's `CallTool`
over `POST /api/v0/mcp/call-tool`.

The now-dead HTTP discovery surface is removed: the agent `GET
/api/v0/mcp/tools` route with `agentmcp.API.handleListTools`, and
`workspacesdk.AgentConn.ListMCPTools` with `ListMCPToolsResponse` (mock
regenerated). The change nets roughly `-1370` lines, mostly the deleted
duplicate runner and its tests.

<details>
<summary>Decision log</summary>

The merge of #26585 made pinned `chat_context_resources` the sole source
of workspace context, which surfaced the duplicate spawning. Two options
were considered:

- **Option A + dependency injection (chosen):** keep `agent/x/agentmcp`
as the single persistent engine; `agentcontext` consumes its catalog
in-process and stays the orchestrator/owner at the API boundary (it
still pushes `KindMCPServer` resources). This is low-risk because
`agentcontext` already exposed the `resolver.MCPResources` seam, so the
change just rebinds it from the ephemeral runner to the shared engine.
- **Option B (rejected):** reimplement persistent pooling, reconnect,
singleflight, and race handling inside `agentcontext` and delete
`agentmcp`. Too broad, and it discards the engine's tested lifecycle for
no behavioral gain.

`agentcontext`'s discovery was never what kept servers alive; its runner
closed each server immediately after listing tools. The component
holding persistent connections was always `agentmcp`, which is why
execution already lived there. Consolidating onto it removes the
duplicated stack rather than a whole package: both packages survive with
distinct roles (`agentmcp` is the engine, `agentcontext` is the
orchestrator/owner).

</details>

Coder Agents generated on behalf of @kylecarbs
2026-06-22 22:21:58 -06:00

178 lines
4.9 KiB
Protocol Buffer

syntax = "proto3";
option go_package = "github.com/coder/coder/v2/agent/agentsocket/proto";
package coder.agentsocket.v1;
import "agent/proto/agent.proto";
message PingRequest {}
message PingResponse {}
message SyncStartRequest {
string unit = 1;
}
message SyncStartResponse {}
message SyncWantRequest {
string unit = 1;
string depends_on = 2;
}
message SyncWantResponse {}
message SyncCompleteRequest {
string unit = 1;
}
message SyncCompleteResponse {}
message SyncReadyRequest {
string unit = 1;
}
message SyncReadyResponse {
bool ready = 1;
}
message SyncStatusRequest {
string unit = 1;
}
// DependencyInfo represents a directed edge in the dependency graph from one unit
// to a unit it depends on, along with the required and current status of the dependency.
message DependencyInfo {
string unit = 1;
string depends_on = 2;
string required_status = 3;
string current_status = 4;
bool is_satisfied = 5;
}
message SyncStatusResponse {
string status = 1;
bool is_ready = 2;
repeated DependencyInfo dependencies = 3;
}
message SyncListRequest {}
// UnitInfo represents a single unit vertex in the dependency graph.
// Includes the state of the unit itself.
message UnitInfo {
string unit = 1;
string status = 2;
bool is_ready = 3;
}
message SyncListResponse {
repeated UnitInfo units = 1;
}
// ContextSource is a user-declared scan root the agent watches for
// workspace context (instruction files, skills, MCP configs) in
// addition to its built-in defaults. Identity is the canonical path.
message ContextSource {
string path = 1;
}
message ContextSourcesRequest {}
message ContextSourcesResponse {
repeated ContextSource sources = 1;
}
message GetContextSourceRequest {
string path = 1;
}
message GetContextSourceResponse {
ContextSource source = 1;
}
message AddContextSourceRequest {
string path = 1;
}
message AddContextSourceResponse {
ContextSource source = 1;
}
message RemoveContextSourceRequest {
string path = 1;
}
message RemoveContextSourceResponse {}
// ContextResource is the on-wire form of a resolved context resource.
// Payload bytes are never sent over the socket; they ship to coderd via
// the drpc PushContextState path. Mirrors agentcontext.Resource minus
// the payload and the per-server MCP tool list, which ship to coderd via
// the same PushContextState path.
message ContextResource {
string id = 1;
string kind = 2;
string source = 3;
string source_path = 4;
string content_hash = 5;
uint64 size_bytes = 6;
string status = 7;
string error = 8;
string name = 9;
string description = 10;
}
// ContextSnapshot is the agent's resolved context state.
message ContextSnapshot {
uint64 version = 1;
string aggregate_hash = 2;
repeated ContextResource resources = 3;
uint64 payload_bytes = 4;
string snapshot_error = 5;
}
message ContextSnapshotRequest {}
message ContextSnapshotResponse {
ContextSnapshot snapshot = 1;
}
message ResyncContextRequest {}
message ResyncContextResponse {
ContextSnapshot snapshot = 1;
}
// AgentSocket provides direct access to the agent over local IPC.
service AgentSocket {
// Ping the agent to check if it is alive.
rpc Ping(PingRequest) returns (PingResponse);
// Report the start of a unit.
rpc SyncStart(SyncStartRequest) returns (SyncStartResponse);
// Declare a dependency between units.
rpc SyncWant(SyncWantRequest) returns (SyncWantResponse);
// Report the completion of a unit.
rpc SyncComplete(SyncCompleteRequest) returns (SyncCompleteResponse);
// Request whether a unit is ready to be started. That is, all dependencies are satisfied.
rpc SyncReady(SyncReadyRequest) returns (SyncReadyResponse);
// Get the status of a unit and list its dependencies.
rpc SyncStatus(SyncStatusRequest) returns (SyncStatusResponse);
// List all registered units and their current statuses.
rpc SyncList(SyncListRequest) returns (SyncListResponse);
// Update app status, forwarded to coderd.
rpc UpdateAppStatus(coder.agent.v2.UpdateAppStatusRequest) returns (coder.agent.v2.UpdateAppStatusResponse);
// List the workspace context sources registered on the agent.
rpc ContextSources(ContextSourcesRequest) returns (ContextSourcesResponse);
// Get a single registered context source by path.
rpc GetContextSource(GetContextSourceRequest) returns (GetContextSourceResponse);
// Register a new context source (additional scan root).
rpc AddContextSource(AddContextSourceRequest) returns (AddContextSourceResponse);
// Remove a previously-registered context source.
rpc RemoveContextSource(RemoveContextSourceRequest) returns (RemoveContextSourceResponse);
// Return the agent's current resolved context snapshot without forcing a re-walk.
rpc GetContextSnapshot(ContextSnapshotRequest) returns (ContextSnapshotResponse);
// Force a re-walk and synchronous push, returning the resulting snapshot (barrier).
rpc ResyncContext(ResyncContextRequest) returns (ResyncContextResponse);
}