Files
Agent-Native/runtime/mcp/protocol.go
T
weichenandCursor e4020d297c
ci / vet-and-test (pull_request) Successful in 49s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Add a script host, deferred tools, and background MCP connect
Why: models need a capped way to call several tools from one script, and a slow MCP server must not block the first prompt.
Evidence: GOWORK=off go test ./runtime/codeorch/ ./runtime/tools/ ./runtime/mcp/ ./runtime/policy/ ./adapters/native/
Impact: run_tool_script stays unregistered until the caller turns it on. Deferred tools stay out of the model list. An unready MCP server connects in the background.
Refs: feature/code-orchestration
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 18:02:53 +00:00

155 lines
4.5 KiB
Go
Raw 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 实现 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 协议版本。
const ProtocolVersion = "2024-11-05"
// 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"`
}
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)
ListTools(ctx context.Context) ([]toolInfo, error)
CallTool(ctx context.Context, name string, args json.RawMessage) (callToolResult, error)
Close() error
}