Files
Cyrene_v2/internal/capability/mcp/schema.go
T
AskaEth a1083a322f
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
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)
2026-10-04 13:53:47 +08:00

85 lines
2.4 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
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
}