diff options
| author | Claude <noreply@anthropic.com> | 2026-05-24 09:45:16 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-05-24 09:45:16 +0000 |
| commit | 952b7623ee9dceec15099043086622aa2aab4741 (patch) | |
| tree | 1cee247252bb9401139cb81f090b228e56c0f428 /internal/api/server.go | |
| parent | 54f6631c28a8b85f6f874e17822549faba916a38 (diff) | |
feat(executor,api): wire agent MCP into ContainerRunner + mount /mcp (Phase 2)
ContainerRunner now mints a per-task MCP token (from an injected Registry),
writes a claude mcp-config into the workspace pointing at the host agent MCP
server over host.docker.internal with that bearer, and adds --mcp-config to the
in-container claude invocation. The token is revoked when the run ends. After
the run, a buffered ask_user (PendingQuestion on the channel) is converted into
a BlockedError — the MCP path to BLOCKED — with the file-based question.json
kept as a fallback for in-flight tasks started on the old wire.
The API server mounts the StreamableHTTP MCP handler at POST/GET/DELETE /mcp
when a registry is provided; serve.go constructs one Registry shared by the
runners (mint) and the server (resolve). Minting is skipped for gemini agents.
Tests: writeMCPConfig output shape, buildInnerCmd flag presence/absence, and an
api-level end-to-end MCP tool call through NewServer/Handler proving the route
is mounted (and absent without a registry).
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
Diffstat (limited to 'internal/api/server.go')
| -rw-r--r-- | internal/api/server.go | 13 |
1 files changed, 12 insertions, 1 deletions
diff --git a/internal/api/server.go b/internal/api/server.go index ff3a111..1522b72 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -39,6 +39,7 @@ type Server struct { taskLogStore taskLogStore // injectable for tests; defaults to store questionStore questionStore // injectable for tests; defaults to store pool *executor.Pool + registry *executor.Registry // per-task agent MCP token registry; mounts /mcp when set hub *Hub logger *slog.Logger mux *http.ServeMux @@ -100,7 +101,7 @@ func (s *Server) SetLLM(c *llm.Client) { } -func NewServer(store *storage.DB, pool *executor.Pool, logger *slog.Logger, claudeBinPath, geminiBinPath string) *Server { +func NewServer(store *storage.DB, pool *executor.Pool, registry *executor.Registry, logger *slog.Logger, claudeBinPath, geminiBinPath string) *Server { wd, _ := os.Getwd() s := &Server{ ctx: context.Background(), @@ -109,6 +110,7 @@ func NewServer(store *storage.DB, pool *executor.Pool, logger *slog.Logger, clau taskLogStore: store, questionStore: store, pool: pool, + registry: registry, hub: NewHub(), logger: logger, mux: http.NewServeMux(), @@ -180,6 +182,15 @@ func (s *Server) routes() { s.mux.HandleFunc("GET /api/drops", s.handleListDrops) s.mux.HandleFunc("GET /api/drops/{filename}", s.handleGetDrop) s.mux.HandleFunc("POST /api/drops", s.handlePostDrop) + if s.registry != nil { + mcpHandler := executor.NewAgentMCPHandler(s.registry) + // The streamable HTTP transport uses POST (messages), GET (SSE stream), + // and DELETE (session end). Register them explicitly so the patterns are + // more specific than the "GET /" catch-all and don't conflict. + s.mux.Handle("POST /mcp", mcpHandler) + s.mux.Handle("GET /mcp", mcpHandler) + s.mux.Handle("DELETE /mcp", mcpHandler) + } s.mux.Handle("GET /", http.FileServerFS(webui.Files)) } |
