mirror of
https://github.com/coder/coder.git
synced 2026-09-22 21:22:17 +08:00
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
178 lines
4.9 KiB
Protocol Buffer
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);
|
|
}
|