Files
Agent-Native/runtime/mcp/fakeserver.go
weichenandCursor 694b08cdbe
ci / vet-and-test (pull_request) Failing after 2s
ci / docker-checkout-hostgateway (pull_request) Successful in 2s
Pair skipped tool calls and close the stdio and fetch gaps
Why: a tool-budget stop left tool calls without results, host callbacks raced, a canceled MCP read stole the next response, and fetch re-resolved after the SSRF check.
Evidence: GOWORK=off go vet ./... && go test -count=1 ./... && go test -race ./runtime/... ./adapters/native/... passed, including TestLoopBudgetToolCallCap, TestParallelHostCallbacksAreSerial, TestStdioSlowCancelThenEcho, TestPresetsAndClientForChatCompletions, TestFetchResolveChangesToLoopbackRejected, and TestFetchHostCheckRedirectStillRejectsPrivate.
Impact: over-budget calls get an error result and no tool event; one Run serializes host callbacks; MCP dispatches by id; anthropic and google presets use OpenAI-compatible chat endpoints; fetch dials the address it just checked. deps/agent-intent is 3130190; error-code-system and logging-system are a3ee67b and 49b5d8b.
Refs: N1, N2, N3, N4, N13, N14
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 01:10:29 +00:00

104 lines
2.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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":
return respond(initializeResult{
ProtocolVersion: ProtocolVersion,
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"}}})
default:
return fail(-32602, "unknown tool "+p.Name)
}
default:
return fail(-32601, "method not found: "+req.Method)
}
}
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":{}}`),
},
}
}
// FakeServerEnv 是拉起 fake server 的环境变量名。
const FakeServerEnv = "AGENT_NATIVE_TEST_MCP_STDIO"