// Package mcp 实现 Model Context Protocol 客户端(stdio 与 Streamable HTTP // 两种传输),并把 server 工具桥接为 runtime/tools.Registry 的普通工具。 // // 边界声明:server 提供的描述与 schema 视为不可信输入(长度受限、不解释); // 工具调用的策略(allowlist/确认)仍由 tools.Registry 策略层统一执行。 package mcp import ( "context" "encoding/json" "fmt" "time" ) // ProtocolVersion 是本客户端声明的 MCP 协议版本(2026-07-28 RC: // resultType 判别、input_required 协议级结果、tasks/* 长任务面)。 const ProtocolVersion = "2026-07-28" // LegacyProtocolVersion 是旧版协议(无 resultType/tasks),协商回落时用。 const LegacyProtocolVersion = "2024-11-05" // SupportsResultType 报告协商版本是否支持 2026-07-28 的 resultType 语义 // (ISO 日期版本按字典序可比)。 func SupportsResultType(negotiated string) bool { return negotiated >= "2026-07-28" } // ServerConfig 声明一个 MCP server 接入点:Command(stdio)或 URL(HTTP)二选一。 type ServerConfig struct { Name string `json:"name"` Command string `json:"command,omitempty"` Args []string `json:"args,omitempty"` Env []string `json:"env,omitempty"` URL string `json:"url,omitempty"` Headers map[string]string `json:"headers,omitempty"` // Timeout 单次请求上限(默认 30s)。 Timeout time.Duration `json:"timeout,omitempty"` // Enabled 为 false 时跳过这台服务器。nil 表示启用。 Enabled *bool `json:"enabled,omitempty"` // Exposure 为 direct 或 deferred。空则工具多时延迟暴露。 Exposure string `json:"exposure,omitempty"` } // Validate 校验配置完整性(fail-closed:命名不规范或双传输/无传输都拒绝)。 func (c ServerConfig) Validate() error { if !validName(c.Name) { return fmt.Errorf("mcp server name %q must be [a-zA-Z0-9_-]+", c.Name) } hasCmd := c.Command != "" hasURL := c.URL != "" if hasCmd == hasURL { return fmt.Errorf("mcp server %q: exactly one of command or url is required", c.Name) } switch c.Exposure { case "", "direct", "deferred": default: return fmt.Errorf("mcp server %q: exposure must be direct or deferred", c.Name) } return nil } // IsEnabled 报告这台服务器是否要连接。 func (c ServerConfig) IsEnabled() bool { return c.Enabled == nil || *c.Enabled } func validName(s string) bool { if s == "" { return false } for _, r := range s { switch { case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-', r == '_': default: return false } } return true } // ---- JSON-RPC 2.0 envelope ---- type rpcRequest struct { JSONRPC string `json:"jsonrpc"` ID int64 `json:"id,omitempty"` Method string `json:"method"` Params any `json:"params,omitempty"` } type rpcNotification struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` } type rpcResponse struct { JSONRPC string `json:"jsonrpc"` ID *int64 `json:"id"` Result json.RawMessage `json:"result,omitempty"` Error *rpcError `json:"error,omitempty"` } type rpcError struct { Code int `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data,omitempty"` } func (e *rpcError) Error() string { return fmt.Sprintf("mcp rpc error %d: %s", e.Code, e.Message) } // ---- MCP 语义类型 ---- type initializeParams struct { ProtocolVersion string `json:"protocolVersion"` Capabilities json.RawMessage `json:"capabilities,omitempty"` ClientInfo clientInfo `json:"clientInfo"` } type clientInfo struct { Name string `json:"name"` Version string `json:"version"` } type initializeResult struct { ProtocolVersion string `json:"protocolVersion"` Capabilities json.RawMessage `json:"capabilities"` ServerInfo clientInfo `json:"serverInfo"` Instructions string `json:"instructions,omitempty"` } type toolInfo struct { Name string `json:"name"` Description string `json:"description,omitempty"` InputSchema json.RawMessage `json:"inputSchema,omitempty"` } type toolsListResult struct { Tools []toolInfo `json:"tools"` NextCursor string `json:"nextCursor,omitempty"` } type callToolParams struct { Name string `json:"name"` Arguments json.RawMessage `json:"arguments,omitempty"` } type callToolResult struct { Content []contentBlock `json:"content"` IsError bool `json:"isError,omitempty"` // ResultType 是 2026-07-28 的协议级结果判别(空 = 普通结果)。 // "input_required" 表示服务端需要用户输入:客户端经 ask 端口取得 // 应答后(有 TaskID 时先 tasks/update)用原参数重试 tools/call。 ResultType string `json:"resultType,omitempty"` TaskID string `json:"taskId,omitempty"` } // taskResult 是 tasks/get 的返回(2026-07-28 长任务面;字段集按 RC 常用 // 形态解析,未识别字段忽略)。 type taskResult struct { TaskID string `json:"taskId"` Status string `json:"status"` Detail string `json:"detail,omitempty"` } type contentBlock struct { Type string `json:"type"` Text string `json:"text,omitempty"` } // Client 是一个已连接 MCP server 的最小面(initialize / tools/list / tools/call)。 type Client interface { Initialize(ctx context.Context) (initializeResult, error) // NegotiatedVersion 是 initialize 后服务端确认的协议版本(未握手为空)。 NegotiatedVersion() string ListTools(ctx context.Context) ([]toolInfo, error) CallTool(ctx context.Context, name string, args json.RawMessage) (callToolResult, error) // TasksGet/Update/Cancel 是 2026-07-28 的长任务面。 TasksGet(ctx context.Context, taskID string) (taskResult, error) TasksUpdate(ctx context.Context, taskID, input string) error TasksCancel(ctx context.Context, taskID string) error Close() error }