mcphost

package
v0.0.0-...-8b65ef4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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.

View Source
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

func ListToolNames(ctx context.Context, cfg Config) ([]string, error)

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

func NewServer(cfg Config) *mcp.Server

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

func RequireTools(ctx context.Context, cfg Config, required ...string) error

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

type AdminToolError struct {
	Status int
	Body   string
}

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.

func (*AdminToolError) Error

func (e *AdminToolError) Error() string

Error implements 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.

type MCPServerSpec

type MCPServerSpec struct {
	Command string            `json:"command"`
	Args    []string          `json:"args"`
	Env     map[string]string `json:"env"`
}

MCPServerSpec is one entry in the `mcpServers` map: a stdio MCP server launched as command+args with the given environment.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL