package mcp import ( "context" "github.com/google/uuid" mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/yan1h/agent-coding-workflow/internal/infra/errs" ) // OrchestratorBridge 是 mcp 调用 orchestrator 服务的窄桥接接口(orchestrator → // mcp 的既有依赖边由 orchestrator 实现该接口,mcp 不反向 import orchestrator,避免循环)。 type OrchestratorBridge interface { // RequestSubtask 让运行中的 agent 为其当前编排 step 派生一个子 step。 // parentACPSessionID 是调用方所在的 acp session(从 AuthSession 解析); // 返回新建子 step 的 id。 RequestSubtask(ctx context.Context, parentACPSessionID uuid.UUID, phase string, prompt string, agentKindID *uuid.UUID) (subtaskID string, err error) } // registerOrchestratorTools 注册编排器相关 MCP 工具。 func registerOrchestratorTools(srv *mcpsdk.Server, deps ServerDeps) { registerRequestSubtask(srv, deps) } // ===== request_subtask ===== type requestSubtaskIn struct { Phase string `json:"phase,omitempty" jsonschema:"target phase for the subtask (defaults to the parent step phase)"` Prompt string `json:"prompt" jsonschema:"prompt for the child agent"` AgentKindID string `json:"agent_kind_id,omitempty" jsonschema:"optional agent kind UUID for the subtask"` } type requestSubtaskOut struct { OK bool `json:"ok"` StepID string `json:"step_id"` } func registerRequestSubtask(srv *mcpsdk.Server, deps ServerDeps) { mcpsdk.AddTool(srv, &mcpsdk.Tool{ Name: "request_subtask", Description: "Ask the orchestrator to spawn a child step (sub-agent) for the current run, subject to depth and fan-out limits. Only callable from within an active agent session.", }, func(ctx context.Context, _ *mcpsdk.CallToolRequest, in requestSubtaskIn) (*mcpsdk.CallToolResult, requestSubtaskOut, error) { if err := CheckToolFromCtx(ctx, "request_subtask"); err != nil { return nil, requestSubtaskOut{}, err } if deps.Orchestrator == nil { return nil, requestSubtaskOut{}, errs.New(errs.CodeNotFound, "orchestrator not enabled") } // request_subtask 是 create_acp_session 的逆向闸门:只有在 session 内的 // agent(system token)可调,普通用户/admin token 不可(它们走 StartRun)。 sess, ok := FromContext(ctx) if !ok { return nil, requestSubtaskOut{}, errs.New(errs.CodeInternal, "mcp: AuthSession missing") } if sess.Issuer != IssuerSystem { return nil, requestSubtaskOut{}, errs.New(errs.CodeForbidden, "request_subtask is only callable from within an agent session") } if sess.ACPSessionID == nil || *sess.ACPSessionID == uuid.Nil { return nil, requestSubtaskOut{}, errs.New(errs.CodeForbidden, "calling token is not bound to an acp session") } if in.Prompt == "" { return nil, requestSubtaskOut{}, errs.New(errs.CodeInvalidInput, "prompt is required") } var akID *uuid.UUID if in.AgentKindID != "" { id, perr := uuid.Parse(in.AgentKindID) if perr != nil { return nil, requestSubtaskOut{}, errs.Wrap(perr, errs.CodeInvalidInput, "agent_kind_id parse") } akID = &id } stepID, err := deps.Orchestrator.RequestSubtask(ctx, *sess.ACPSessionID, in.Phase, in.Prompt, akID) if err != nil { return nil, requestSubtaskOut{}, err } return nil, requestSubtaskOut{OK: true, StepID: stepID}, nil }) }