package mcp // 本文件是跨测试二进制复用的最小 MCP fake server(stdio JSON-RPC)。 // 测试以「自身可执行文件 + AGENT_NATIVE_TEST_MCP_STDIO=1」re-exec 方式拉起, // 供真实传输链路测试使用;非测试代码不得调用。 import ( "bufio" "encoding/json" "os" "time" ) // RunFakeServer 在 stdin/stdout 上运行最小 MCP server(echo / boom / 未知方法), // 直到 stdin 关闭。测试二进制在 TestMain 的 re-exec 分支调用。 func RunFakeServer() { scanner := bufio.NewScanner(os.Stdin) scanner.Buffer(make([]byte, 1<<20), 1<<20) w := bufio.NewWriter(os.Stdout) defer w.Flush() for scanner.Scan() { var req rpcRequest if err := json.Unmarshal(scanner.Bytes(), &req); err != nil { continue } if req.ID == 0 { continue // 通知帧 } resp := fakeServerHandle(req) body, _ := json.Marshal(resp) w.Write(body) w.WriteByte('\n') w.Flush() } } func fakeServerHandle(req rpcRequest) rpcResponse { respond := func(result any) rpcResponse { raw, _ := json.Marshal(result) return rpcResponse{JSONRPC: "2.0", ID: &req.ID, Result: raw} } fail := func(code int, msg string) rpcResponse { return rpcResponse{JSONRPC: "2.0", ID: &req.ID, Error: &rpcError{Code: code, Message: msg}} } switch req.Method { case "initialize": // 协商:取(客户端请求, 自身上限)的较小者(ISO 日期字典序可比); // AGENT_NATIVE_TEST_MCP_VERSION 可压低上限以测回落路径。 own := ProtocolVersion if v := os.Getenv("AGENT_NATIVE_TEST_MCP_VERSION"); v != "" { own = v } negotiated := own var p initializeParams if req.Params != nil { raw, _ := json.Marshal(req.Params) _ = json.Unmarshal(raw, &p) if p.ProtocolVersion != "" && p.ProtocolVersion < negotiated { negotiated = p.ProtocolVersion } } return respond(initializeResult{ ProtocolVersion: negotiated, ServerInfo: clientInfo{Name: "fake-mcp", Version: "0"}, }) case "tools/list": return respond(toolsListResult{Tools: fakeServerTools()}) case "tools/call": var p callToolParams if req.Params != nil { raw, _ := json.Marshal(req.Params) _ = json.Unmarshal(raw, &p) } switch p.Name { case "echo": var args struct { Text string `json:"text"` } _ = json.Unmarshal(p.Arguments, &args) return respond(callToolResult{Content: []contentBlock{{Type: "text", Text: "echo: " + args.Text}}}) case "boom": return respond(callToolResult{ Content: []contentBlock{{Type: "text", Text: "boom failed"}}, IsError: true, }) case "slow": time.Sleep(2 * time.Second) return respond(callToolResult{Content: []contentBlock{{Type: "text", Text: "slow done"}}}) case "need_input": // 2026-07-28 协议级 input_required:tasks/update 投递后重试得终结果。 if input, ok := fakeTaskInputs()["task-1"]; ok { fakeTaskInputs()["task-1"] = "" return respond(callToolResult{Content: []contentBlock{{Type: "text", Text: "答案已收到: " + input}}}) } return respond(callToolResult{ Content: []contentBlock{{Type: "text", Text: "要答案"}}, ResultType: "input_required", TaskID: "task-1", }) case "legacy_ask": return respond(callToolResult{Content: []contentBlock{{Type: "input_required", Text: "选一个颜色"}}}) default: return fail(-32602, "unknown tool "+p.Name) } case "tasks/update": var p struct { TaskID string `json:"taskId"` Input string `json:"input"` } if req.Params != nil { raw, _ := json.Marshal(req.Params) _ = json.Unmarshal(raw, &p) } fakeTaskInputs()[p.TaskID] = p.Input return respond(map[string]any{"ok": true}) case "tasks/get": var p struct { TaskID string `json:"taskId"` } if req.Params != nil { raw, _ := json.Marshal(req.Params) _ = json.Unmarshal(raw, &p) } return respond(taskResult{TaskID: p.TaskID, Status: "running"}) case "tasks/cancel": var p struct { TaskID string `json:"taskId"` } if req.Params != nil { raw, _ := json.Marshal(req.Params) _ = json.Unmarshal(raw, &p) } delete(fakeTaskInputs(), p.TaskID) return respond(map[string]any{"ok": true}) default: return fail(-32601, "method not found: "+req.Method) } } // fakeTaskInputs 是 need_input 工具的任务输入存储(进程内)。 func fakeTaskInputs() map[string]string { return taskInputStore } var taskInputStore = map[string]string{} func fakeServerTools() []toolInfo { return []toolInfo{ { Name: "echo", Description: "Echo the text back", InputSchema: json.RawMessage(`{"type":"object","required":["text"],"properties":{"text":{"type":"string"}}}`), }, { Name: "boom", Description: "Always fails", InputSchema: json.RawMessage(`{"type":"object","properties":{}}`), }, { Name: "slow", Description: "Responds after a delay", InputSchema: json.RawMessage(`{"type":"object","properties":{}}`), }, { Name: "need_input", Description: "Requires user input (2026-07-28 resultType)", InputSchema: json.RawMessage(`{"type":"object","properties":{}}`), }, { Name: "legacy_ask", Description: "Requires user input (legacy content block)", // 故意无 InputSchema:触发 manager 的合成默认 schema 路径。 }, } } // FakeServerEnv 是拉起 fake server 的环境变量名。 const FakeServerEnv = "AGENT_NATIVE_TEST_MCP_STDIO"