Documentation
¶
Overview ¶
config.go (v2.7 b3-ii, ADR-0049) — the pure `--mcp-config` generation helper. claude's `--mcp-config` expects a JSON object with a top-level `mcpServers` map; each entry is launched as `command` + `args` with the given `env`. This mirrors the canonical stdio MCP server config shape (the same {mcpServers:{<name>:{command,args,env}}} shape claude consumes, and the generic JSON the worker daemon's MCPInjector walks in home_dir/mcp_config.json).
This is a PURE function: it takes the launch command + args + binding params and returns the bytes. It does NO I/O and hard-codes NO binary path (the caller / D2-c supplies command + args). D2-c writes the bytes to disk and passes --mcp-config to claude.
The env keys are the exact ones runMCPHost (handlers_mcphost.go) reads:
AC_MCP_AGENT_ID operating agent id (process-fixed) AC_MCP_ADMIN_URL admin endpoint (unix:/path or tcp://host:port) AC_MCP_WORKER_TOKEN worker bearer token (owner worker:<id>) AC_MCP_SERVER_FINGERPRINT pinned cert fingerprint (required for tcp://) AC_MCP_AGENT_ROOT agent workspace root (file-tool containment) AC_MCP_RUNTIME_SOCKET local agent-runtime control socket AC_MCP_TIER_TOOLS optional bool; false exposes the full catalog AC_MCP_GENERATION supervisor generation that owns this host process
files.go (v2.7 b3-ii, ADR-0049) — the per-agent MCP file tools. Two of the three move BYTES (upload_file / download_file) through the FileMover seam (the daemon-side FileTransferClient, which enforces workspace path containment); attach_file is a pure JSON passthrough to the admin endpoint.
Security spine (same as every other tool): agentRoot + agentID are process-fixed — taken from Config, NEVER from tool args — so the model can neither move files for another agent nor reach outside the agent's workspace. The args structs deliberately carry NO agent_id / agent_root.
Error mapping: the FileMover returns PLAIN errors (workspace path-escape, not-reachable, admin non-2xx, etc.) rather than the typed *AdminToolError that callAdmin understands. To let claude see the actual reason (e.g. "path escapes workspace root") and self-correct rather than treating it as a transport failure, file-mover errors are surfaced as an IsError CallToolResult carrying err.Error() (see fileError). attach_file, being a callAdmin passthrough, reuses callAdmin's existing *AdminToolError → IsError mapping.
orchestration_tools.go — MCP handler factories for the orchestration engine tools (P2-T2). Every handler follows the b3-i pattern EXACTLY: a typed args struct with NO agent_id field, a handler that injects the process-fixed agent_id from cfg.AgentID and forwards via callAdmin to the matching /admin/agent-tools/<tool> endpoint.
Package mcphost implements the per-agent stdio MCP server (v2.7 b3-i, ADR-0049). One `mcp-host` process is bound to exactly ONE agent: it bridges MCP tool calls from a claude process to the center's admin agent-tool HTTP endpoints (/admin/agent-tools/<tool>).
Security spine (mirrors internal/admin/api requireAgentOnWorker): the operating agent_id is PROCESS-FIXED — it comes from Config.AgentID (sourced from the AC_MCP_AGENT_ID env by the subcommand), and is injected into every admin call body. It is NEVER taken from the model's tool args, so the model cannot act as another agent. The worker bearer token (owner worker:<id>) rides the AdminCaller transport; the center re-checks requireAgentOnWorker + per-agent domain authz on every call.
Built on the official MCP Go SDK (github.com/modelcontextprotocol/go-sdk @v1.6.1). b3-i ships 2 representative tools (get_my_work + post_task_message) to prove the shape; the full tool set + file tools + config generation are b3-ii.
tools.go (v2.7 b3-ii, ADR-0049) — the remaining OQ4 JSON passthrough tools completing the per-agent MCP surface on top of the locked b3-i form (server.go). Every handler follows the b3-i pattern EXACTLY: a typed In struct with NO agent_id field, a handler that injects the process-fixed agent_id from cfg.AgentID and forwards via callAdmin to the matching /admin/agent-tools/<tool> endpoint. The MCP tool name equals the admin route's <tool> segment (callAdmin POSTs to /admin/agent-tools/<tool>), so the registration name and the callAdmin tool string must stay in lockstep.
Admin request field names are matched VERBATIM to the handlers in internal/admin/api/agent_tools_passthrough.go + agent_tools_write.go:
- assign_task / reassign_task : {task_id, assignee}
- subscribe / unsubscribe : {task_id, identity?} (defaults to self)
- fail_task : {task_id, reason}
- retry_failed_task : {task_id}
- heartbeat : {task_id}
- complete_task : {task_id, summary?}
- discard_task : {task_id, reason}
- create_task : {project_id, title, description?, derived_from_issue?, assignee?, dispatch?, dispatch_mode?}
- update_task : {task_id, title?, description?, clear_description?}
- fork_executor : runtime-local {task_id, model?, context?}
- get_task : {task_id}
- get_issue : {issue_id}
- verify_task : {task_id}
Index ¶
- Variables
- func GenerateMCPConfig(p MCPConfigParams) ([]byte, error)
- func ListToolNames(ctx context.Context, cfg Config) ([]string, error)
- func NewServer(cfg Config) *mcp.Server
- func RequireTools(ctx context.Context, cfg Config, required ...string) error
- type AdminCaller
- type AdminToolError
- type Config
- type FileMover
- type MCPConfig
- type MCPConfigParams
- type MCPServerSpec
Constants ¶
This section is empty.
Variables ¶
var AgentFacingToolNames = []string{}/* 107 elements not displayed */
AgentFacingToolNames is the SOURCE-OF-TRUTH canonical set of MCP tool names the per-agent catalog (NewServer) exposes to the agent LLM. It exists to anchor the full-parity guard (TestAgentFacingToolParity): the guard asserts the live ListTools name-set EQUALS this list, so a tool added to the registration without being added here (or vice versa) fails CI — forcing a DELIBERATE decision about whether a new capability should be agent-facing.
This closes the whole CLASS of the #285/#299 seam (a plan/admin handler written but never registered in the agent catalog → the agent LLM can't see it). The per-tool integration guards (TestPlanToolsRegistered) catch specific families; this catches ANY drift in either direction.
When adding/removing an agent-facing tool: update BOTH the NewServer registration AND this list (and FilesSeamTools below if it moves bytes via the FileMover seam instead of the /admin/agent-tools/<name> proxy). The guard will tell you if you miss one.
var FilesSeamTools = []string{
"download_file",
"list_my_execution_state",
}
FilesSeamTools are the agent-facing tools that use a local daemon/runtime seam rather than proxying to an /admin/agent-tools/<name> HTTP endpoint via callAdmin. They are the legitimate EXCEPTION to the reverse-lockstep half of the parity guard: every other AgentFacingToolNames entry maps 1:1 to a /admin/agent-tools/<name> admin route, but these do not (download_file proxies to GET /admin/files/{ulid}; list_my_execution_state reads the local runtime socket). Keep this list minimal and explicit.
Functions ¶
func GenerateMCPConfig ¶
func GenerateMCPConfig(p MCPConfigParams) ([]byte, error)
GenerateMCPConfig builds and marshals the --mcp-config document. The bytes are the file content D2-c writes to disk and hands to claude via --mcp-config.
func ListToolNames ¶
ListToolNames enumerates the tool names NewServer(cfg) exposes over a real MCP tools/list exchange. Handler dependencies may be nil because this only lists metadata; it never invokes a tool.
func NewServer ¶
NewServer builds the per-agent MCP server, registers the b3-i tools, and returns it WITHOUT running it. The caller runs it (srv.Run with a transport) so tests can attach an in-process transport.
func RequireTools ¶
RequireTools fails loud when the agent-center MCP catalog does not expose every required default tool. This is a runtime preflight guard against a supervisor continuing without the center communication surface it is required to use.
Types ¶
type AdminCaller ¶
type AdminCaller interface {
CallAgentTool(ctx context.Context, tool string, body any, out *json.RawMessage) error
}
AdminCaller is the seam the MCP tool handlers call to reach the center's admin agent-tool endpoints. Implementations POST `body` (a JSON object that already carries the process-fixed agent_id) to /admin/agent-tools/<tool> and write the raw admin JSON response into *out. On a non-2xx response they MUST return an error; ideally a typed *AdminToolError exposing the status + body so the handler can surface it to the model as an IsError CallToolResult.
internal/workerdaemon.AdminClient satisfies this (see AdminClient.CallAgentTool).
type AdminToolError ¶
AdminToolError is the typed error an AdminCaller returns on a non-2xx admin response. The handler unwraps it to build an IsError CallToolResult carrying the body, so claude sees the failure verbatim instead of a silent protocol error.
type Config ¶
type Config struct {
// AgentID is the process-fixed operating agent. Injected into every
// admin call body as agent_id; never read from tool args.
AgentID string
// Admin is the transport seam to the center's admin agent-tool
// endpoints.
Admin AdminCaller
// AgentRoot is the agent's workspace root, passed to the FileMover for
// path containment on every file tool. Process-fixed; never from args.
// May be empty (file tools then fail containment with a clear error).
AgentRoot string
// Files is the byte-mover seam for the upload/download file tools. May
// be nil if the host is built without file support (the tools then
// return an IsError result explaining files are not wired).
Files FileMover
// TierTools (WS5, #issue-e346e5ec) enables tool TIERING: the default tool
// set is the small high-frequency core; low-frequency management tools are
// DEFERRED (removed from the default ListTools) and loaded on demand via the
// search_tools meta-tool. Off (default) registers the FULL set — used by the
// docs export and parity tests. The production per-agent host turns it ON.
TierTools bool
// Generation is the supervisor generation that launched this MCP host. It is
// stamped onto the frozen plan-rule snapshot so audits can distinguish a
// reused planning session from a fresh supervisor generation reload.
Generation int
// RuntimeSocket is the local agent-runtime control socket. Tools that need
// runtime authority, such as list_my_execution_state, read through this instead
// of querying executor state from the center.
RuntimeSocket string
// contains filtered or unexported fields
}
Config carries everything NewServer needs. It is intentionally transport-agnostic (an AdminCaller + FileMover, not concrete HTTP/FS clients) so the server is testable with fakes.
type FileMover ¶
type FileMover interface {
UploadFile(ctx context.Context, agentRoot, agentID, localPath, scope, scopeID string) (string, error)
DownloadFile(ctx context.Context, agentRoot, agentID, ulidOrURI, destPath string) error
}
FileMover is the seam the file tools (upload_file/download_file) call to move bytes between the agent's local workspace and the center, behind the daemon-side workspace path-containment guardrail. agentRoot + agentID are supplied by the handler from Config (process-fixed) — NEVER from tool args — so the model cannot move files for another agent or outside the workspace.
internal/workerdaemon.*FileTransferClient satisfies this.
type MCPConfig ¶
type MCPConfig struct {
MCPServers map[string]MCPServerSpec `json:"mcpServers"`
}
MCPConfig is the top-level `--mcp-config` document claude consumes.
func BuildMCPConfig ¶
func BuildMCPConfig(p MCPConfigParams) MCPConfig
BuildMCPConfig builds the typed --mcp-config document for a single `worker mcp-host` server bound to one agent. Pure; no I/O.
type MCPConfigParams ¶
type MCPConfigParams struct {
ServerName string
Command string
Args []string
AgentID string
AdminURL string
WorkerToken string
ServerFingerprint string
AgentRoot string
RuntimeSocket string
Generation int
// DisableToolTiering sets AC_MCP_TIER_TOOLS=false for clients that already
// have their own deferred-tool mechanism. Codex is one such client: it only
// indexes tools present in the MCP startup catalog, so mcp-host-side dynamic
// AddTool/RemoveTools can double-hide tools from Codex.
DisableToolTiering bool
}
MCPConfigParams are the inputs to GenerateMCPConfig. Command + Args are the launch vector for `worker mcp-host` (supplied by the caller — never hard-coded here). The remaining fields become the per-server env.