summaryrefslogtreecommitdiff
path: root/internal/api/server.go
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-24 09:45:16 +0000
committerClaude <noreply@anthropic.com>2026-05-24 09:45:16 +0000
commit952b7623ee9dceec15099043086622aa2aab4741 (patch)
tree1cee247252bb9401139cb81f090b228e56c0f428 /internal/api/server.go
parent54f6631c28a8b85f6f874e17822549faba916a38 (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.go13
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))
}