在 Go 项目里加一个 MCP 工具,第一步经常被跳过:客户端、SDK 和协议到底采用哪一版?把几个 JSON-RPC 方法拼起来,最多得到一个局部演示,离完整服务器还有一段距离。
官方在 2026 年 7 月 28 日发布的规范说明调整了无状态请求、元数据和传输交互。这是七月的变化,拿来检查十月的项目很合适,不能写成十月刚发布。旧客户端是否还能接入,要看实际 SDK 的兼容表。
先写出三层责任
业务函数负责“这个请求能不能做”;SDK 适配负责把结果变成工具调用响应;协议层负责版本、传输、取消和错误。三层可以在同一进程,验收时仍要分开。
以只读报价为例,工具名叫 quote,并不代表它天然安全。SKU 是否属于当前租户、数量是否合理,都得由业务代码核验。下面只实现参数边界,不自称 MCP SDK:
package main import ( "context" "encoding/json" "errors" "fmt" "io" "strings" ) type QuoteRequest struct { SKU string `json:"sku"` Qty int `json:"qty"` } func parse(ctx context.Context, raw string) (QuoteRequest, error) { var q QuoteRequest if err := ctx.Err(); err != nil { return q, err } dec := json.NewDecoder(strings.NewReader(raw)) dec.DisallowUnknownFields() if err := dec.Decode(&q); err != nil { return q, err } var extra any if err := dec.Decode(&extra); err != io.EOF { return q, errors.New("expected one JSON value") } if q.SKU == "" || q.Qty < 1 || q.Qty > 99 { return q, errors.New("invalid quote request") } return q, nil } func main() { for _, raw := range []string{ `{"sku":"book","qty":2}`, `{"sku":"book","qty":2,"role":"admin"}`, } { q, err := parse(context.Background(), raw) fmt.Printf("sku=%q qty=%d accepted=%t\n", q.SKU, q.Qty, err == nil) } }保存成 main.go,执行 go run main.go。第一条应被接受,第二条因未知字段被拒绝。拒绝 role 字段只减少参数歧义,不能代替身份认证:真实角色应从可信会话取得,不能从模型入参读取。
接 SDK 时,别把局部通过当互通通过
官方 Go SDK提供服务器、客户端和授权相关包,并维护协议版本兼容关系。选择 tag 以后,至少检查四件事:
| 检查对象 | 需要留下的证据 |
|---|---|
| 客户端与服务器版本 | 精确 tag、协议版本和协商/拒绝行为 |
| 输入与输出契约 | 必填项、未知字段、错误结果、文本内容类型 |
| 调用生命周期 | 取消是否传给业务,资源是否及时释放 |
| 业务权限 | 同工具不同租户、只读与写入是否独立授权 |
前面的程序只覆盖输入契约。它没有启动 MCP 服务器,没有完成 SDK 互通,也没有测试 OAuth。把这个边界写清楚,比贴“生产级”三个字更有用。
泛型可以简化调用,不能替你证明性能
Go 1.27 发布说明确认加入了方法独立类型参数。现有服务器若仍用旧工具链,是否升级要单独决定。本文示例使用普通结构体,本地验证环境为 Go 1.26.5,不依赖这一新语法。
结构体也不意味着反序列化没有反射,更不能顺手推出 CPU 降六成、连接零泄漏。真要比较 SDK 与自研适配,应使用同样的工具、负载、硬件和错误比例,再看分配、尾延迟及维护成本。没有测过,就别先把结论写进封面。
本文本地实验只验证标准库参数解析;协议互通、权限集成和性能仍需在项目实际版本中验证。