Files
Agent-Native/runtime/mcp/protocol.go
T
weichen b2638da64c Add an MCP client (stdio and streamable HTTP) with namespaced tool bridging
Why: external tool ecosystems speak MCP; the kernel had no way to
mount them, so every external capability needed a bespoke builtin.

Evidence: go vet ./... clean; go test ./... 11 packages ok. Tests run
against a REAL stdio server (test binary re-exec via
mcp.RunFakeServer) and httptest HTTP servers: initialize handshake,
tools/list with pagination cap, tools/call success / tool-level
isError / unknown-tool rpc error, JSON and SSE response modes,
Mcp-Session-Id capture, manager registration as mcp__<server>__<tool>,
namespace wildcard allowlist (mcp__fake__* keeps only that namespace,
unknown wildcard fails closed), unreachable server failing the step,
hung server bounded by per-request timeout, config validation
(duplicate names, dual/missing transport, bad name chars), and an
adapter end-to-end execution calling an MCP tool.

Impact: new runtime/mcp package; native.Options gains MCP *mcp.Manager
and Close() for process cleanup; tools.Registry.Restrict gains
prefix/* namespace entries; server-provided metadata is capped (64
tools/server, 1 KiB descriptions, 64 KiB text results) and treated as
untrusted input.

Refs: plan B5; agent-execution standard (tool providers get
authorized capability only).
2026-10-01 05:43:28 +00:00

141 lines
4.0 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"`
}
// 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)
}
return nil
}
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
}