Files
2026-06-22 08:55:57 +08:00

86 lines
3.3 KiB
Go

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
})
}