- 新增依赖: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)
85 lines
2.4 KiB
Go
85 lines
2.4 KiB
Go
package mcp
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
|
||
"github.com/google/jsonschema-go/jsonschema"
|
||
)
|
||
|
||
// schemaMap 把 SDK 传来的 input schema 规整成 map[string]any;nil 或无法识别时
|
||
// 退化为「无约束的对象 schema」,保证 Parameters() 永远能返回可用形状。
|
||
func schemaMap(v any) map[string]any {
|
||
switch s := v.(type) {
|
||
case nil:
|
||
return emptyObjectSchema()
|
||
case map[string]any:
|
||
return s
|
||
case json.RawMessage:
|
||
return decodeSchema(s)
|
||
case []byte:
|
||
return decodeSchema(s)
|
||
default:
|
||
data, err := json.Marshal(v)
|
||
if err != nil {
|
||
return emptyObjectSchema()
|
||
}
|
||
return decodeSchema(data)
|
||
}
|
||
}
|
||
|
||
// emptyObjectSchema 返回无约束的空对象 schema。
|
||
func emptyObjectSchema() map[string]any {
|
||
return map[string]any{"type": "object", "properties": map[string]any{}}
|
||
}
|
||
|
||
// decodeSchema 把 JSON 文本解成 schema map,失败时退回空对象 schema。
|
||
func decodeSchema(data []byte) map[string]any {
|
||
out := map[string]any{}
|
||
if err := json.Unmarshal(data, &out); err != nil {
|
||
return emptyObjectSchema()
|
||
}
|
||
return out
|
||
}
|
||
|
||
// resolveSchema 把原始 schema 解析成可校验、可补默认值的已解析 schema。
|
||
func resolveSchema(raw map[string]any) (*jsonschema.Resolved, error) {
|
||
data, err := json.Marshal(raw)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("marshal input schema: %w", err)
|
||
}
|
||
var s jsonschema.Schema
|
||
if err := json.Unmarshal(data, &s); err != nil {
|
||
return nil, fmt.Errorf("parse input schema: %w", err)
|
||
}
|
||
resolved, err := s.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("resolve input schema: %w", err)
|
||
}
|
||
return resolved, nil
|
||
}
|
||
|
||
// applySchema 按已解析的 input schema 先补默认值、再校验参数。
|
||
// resolved 为 nil 表示 schema 不可解析,此时降级为不校验。
|
||
// ApplyDefaults 对病态默认值会 panic,这里兜住转成错误,避免拖垮调用方。
|
||
func applySchema(resolved *jsonschema.Resolved, args map[string]any) (err error) {
|
||
if resolved == nil {
|
||
return nil
|
||
}
|
||
if args == nil {
|
||
args = map[string]any{}
|
||
}
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
err = fmt.Errorf("input schema default application panicked: %v", r)
|
||
}
|
||
}()
|
||
if err = resolved.ApplyDefaults(&args); err != nil {
|
||
return fmt.Errorf("apply input schema defaults: %w", err)
|
||
}
|
||
if err = resolved.Validate(args); err != nil {
|
||
return fmt.Errorf("arguments do not match input schema: %w", err)
|
||
}
|
||
return nil
|
||
}
|