feat(mcp): 协议层换成官方 Model Context Protocol SDK,删自研实现(裁决 10.4.6 + AGENTS §9.5)
- 新增依赖:github.com/modelcontextprotocol/go-sdk v1.8.0(direct)+ github.com/google/jsonschema-go v0.4.3 (SDK 自带依赖,直接用于 schema 校验/默认值,避免再造一份);go.sum 纯新增 - 删除自研协议层:rpc.go / stdio.go / http.go / sse.go 及其测试 - 用 SDK 的成熟逻辑:命令传输(stdio 子进程)/ SSE / Streamable HTTP、协议版本协商与 -32022 回退、keepalive(含失败阈值)、tools/list_changed 订阅、能力协商(InitializeResult.Capabilities) - 保留自研的四件事(其余零自研):配置→传输、发现→registry 注册、能力族推断、schema 缓存(Prepare 命中不重复解析) - 能力闸诚实化:服务器声明 capabilities 但不含 tools 时 ListTools/DiscoverTools 返回 ErrCapabilityNotDeclared(不静默空集);完全不声明 capabilities 的老式服务器仍按 legacy 处理 - 复核:build 绿 + capability 全包测试绿(mcp 42 用例,含真子进程 stdio / httptest Streamable HTTP / 内存传输三类假 server,不依赖外网)+ `-race` 干净;变异 2 条(featuresFrom 恒 true / 删 applySchema 调用)已还原 - 无 REST/WS/env/数据目录变更;包内 API 有增删(见回报),仓外调用方签名未变(未改 cmd/cyrene 与 plugin/manager)
This commit is contained in:
1 parent
7b05b4bfa9
commit
a1083a322f
26 files changed
+1903
-3011
No files matched your search
@@ -0,0 +1,98 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// newTransport 按 cfg 选传输。显式 TransportType 优先(stdio / sse / http,http 与
|
||||
// streamable 等价,对齐 design/09 §2.1 的枚举);为空则沿用历史推断:
|
||||
// 有 Command 走 stdio,有 URL 走 SSE(既有 URL 配置多为 /sse 端点)。
|
||||
// APIKey 通过自定义 RoundTripper 以 Authorization: Bearer 发送。
|
||||
func newTransport(cfg MCPConfig) (mcpsdk.Transport, error) {
|
||||
switch cfg.TransportType {
|
||||
case "http", "streamable":
|
||||
return httpURLTransport(cfg, true)
|
||||
case "sse":
|
||||
return httpURLTransport(cfg, false)
|
||||
case "stdio":
|
||||
return commandTransport(cfg)
|
||||
}
|
||||
switch {
|
||||
case cfg.Command != "":
|
||||
return commandTransport(cfg)
|
||||
case cfg.URL != "":
|
||||
return httpURLTransport(cfg, false)
|
||||
default:
|
||||
return nil, fmt.Errorf("mcp: %w", ErrNoTransport)
|
||||
}
|
||||
}
|
||||
|
||||
// httpURLTransport 构建 URL 型传输:streamable=true 走 Streamable HTTP(2025-11-25
|
||||
// 规范首选),否则走旧式 SSE。
|
||||
func httpURLTransport(cfg MCPConfig, streamable bool) (mcpsdk.Transport, error) {
|
||||
if cfg.URL == "" {
|
||||
return nil, fmt.Errorf("mcp transport %s: %w", cfg.TransportType, ErrNoTransport)
|
||||
}
|
||||
client := httpClientFor(cfg)
|
||||
if streamable {
|
||||
return &mcpsdk.StreamableClientTransport{Endpoint: cfg.URL, HTTPClient: client}, nil
|
||||
}
|
||||
return &mcpsdk.SSEClientTransport{Endpoint: cfg.URL, HTTPClient: client}, nil
|
||||
}
|
||||
|
||||
// commandTransport 拉起 stdio 子进程,按换行分隔的 JSON-RPC 通信。
|
||||
func commandTransport(cfg MCPConfig) (mcpsdk.Transport, error) {
|
||||
if cfg.Command == "" {
|
||||
return nil, fmt.Errorf("mcp transport stdio: %w", ErrNoTransport)
|
||||
}
|
||||
cmd := exec.Command(cfg.Command, cfg.Args...)
|
||||
cmd.Env = envWith(os.Environ(), cfg.Env)
|
||||
return &mcpsdk.CommandTransport{Command: cmd}, nil
|
||||
}
|
||||
|
||||
// httpClientFor 返回带鉴权头的 HTTP 客户端;没有 APIKey 时返回 nil(用 SDK 默认)。
|
||||
func httpClientFor(cfg MCPConfig) *http.Client {
|
||||
if cfg.APIKey == "" {
|
||||
return nil
|
||||
}
|
||||
return &http.Client{Transport: &bearerTransport{base: http.DefaultTransport, token: cfg.APIKey}}
|
||||
}
|
||||
|
||||
// bearerTransport 给每个请求加 Authorization: Bearer。凭据不写日志、不进错误消息。
|
||||
type bearerTransport struct {
|
||||
base http.RoundTripper
|
||||
token string
|
||||
}
|
||||
|
||||
// RoundTrip 实现 http.RoundTripper。
|
||||
func (t *bearerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
clone := req.Clone(req.Context())
|
||||
clone.Header.Set("Authorization", "Bearer "+t.token)
|
||||
return t.base.RoundTrip(clone)
|
||||
}
|
||||
|
||||
// envWith 返回 base 叠加 extra 后的环境变量(同名以 extra 为准);extra 为空时原样返回。
|
||||
func envWith(base []string, extra map[string]string) []string {
|
||||
if len(extra) == 0 {
|
||||
return base
|
||||
}
|
||||
out := make([]string, 0, len(base)+len(extra))
|
||||
for _, kv := range base {
|
||||
if k, _, ok := strings.Cut(kv, "="); ok {
|
||||
if _, dup := extra[k]; dup {
|
||||
continue
|
||||
}
|
||||
}
|
||||
out = append(out, kv)
|
||||
}
|
||||
for k, v := range extra {
|
||||
out = append(out, k+"="+v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user