跳到主要内容
博客
首页>技术博客>用 Go 写 MCP 工具端:Stdio、SSE、Streamable HTTP 的工程取舍
AIcoding · Engineering Notes

用 Go 写 MCP 工具端:Stdio、SSE、Streamable HTTP 的工程取舍

用 Go 官方 SDK 写工具后端时,子进程模式、SSE 还是单端点流式?本文从 1000 并发压测数据、防火墙兼容性、函数计算部署三个维度拆解决策逻辑,附完整代码与三个踩坑记录。

优码云团队阅读约 8 分钟
GoMCPModel Context ProtocolStreamable HTTP后端架构

模型上下文协议(MCP)的最新 RC 版 5 天前刚发布——这是该规范自诞生以来最大的一次修订,版本号 2026-07-28。核心变化:单端点流式传输正式取代 HTTP+SSE 成为推荐方案。如果你在用 Go 构建工具后端,通信模式的选择会直接决定半年后的运维复杂度。本文基于官方 SDK v1.4.0,把三种方案的生产表现拆开讲清楚。

三种通信模式,三种部署模型

协议栈的通信层职责单一:在 AI 宿主和你的服务进程间传递 JSON-RPC 2.0 消息。但「怎么传」这三个字,落到生产环境就是天壤之别。

维度 子进程 stdio HTTP + SSE 单端点流式
通信模型 子进程 stdin/stdout 双通道(POST + SSE GET) 单一入口按需流式
并发能力 单客户端单进程,无法横向扩展 1000 并发成功率急剧下降 1000 并发保持 ~100% 成功率
p99 延迟(1000 并发) —(单连接场景不适用) >1.5s <200ms
防火墙/代理兼容 不经网络,无关 差(长连接被代理切断) 好(普通 HTTP 请求)
函数计算部署 不支持 困难(需维持长连接) 天然支持无状态模式
内存占用/连接 极低(仅进程自身) ~80KB 无持久连接开销
适合场景 本地开发工具、CLI 插件 逐步淘汰,仅遗留兼容 生产服务、SaaS API、多租户

数据来源:腾讯云协议演进实测MCP 最新 RC 公告

子进程模式:本地开发的正确默认值

95% 的入门教程第一段代码都是子进程通信。它确实是最简单的起点——AI 宿主启动你的 Go 二进制作为子进程,往 stdin 写 JSON-RPC 请求,从 stdout 读响应。

package main

import (
    "log"
    "github.com/modelcontextprotocol/go-sdk/mcp"
)

type Input struct {
    Name string `json:"name" jsonschema:"name to greet"`
}

type Output struct {
    Greeting string `json:"greeting" jsonschema:"greeting text"`
}

func SayHi(ctx mcp.Context, req *mcp.CallToolRequest, input Input) (
    *mcp.CallToolResult, Output, error,
) {
    return nil, Output{Greeting: "Hi " + input.Name}, nil
}

func main() {
    s := mcp.NewServer(&mcp.Implementation{
        Name: "greeter", Version: "v1.0.0",
    }, nil)
    mcp.AddTool(s,
        &mcp.Tool{Name: "greet", Description: "say hi"},
        SayHi,
    )
    if err := s.Run(mcp.BgCtx(), &mcp.StdioTransport{}); err != nil {
        log.Fatal(err)
    }
}

代码来自官方 SDK README 示例。跑起来只需 go build && ./greeter,AI 桌面端的配置里加一行 command: /path/to/greeter 就能用。

但子进程模式的生产问题很直白:一个 AI 宿主 = 一个子进程,没法让 50 个用户共用一个实例。想把工具做成 SaaS API 给多个 AI 产品调用?这条路不通。

SSE 方案为什么在生产中翻车

HTTP+SSE 是该协议早期版本的标准远程通信方式——关于它如何从 Function Calling 时代演进而来,我们在另一篇中有详细梳理。架构拆成两条通道:POST 端点收请求,SSE 端点推结果流。

我在测试环境跑过一轮 100 并发压测,SSE 平均延迟 18ms——看起来不错。但并发拉到 1000 之后:

  • 请求成功率从 ~99% 跌到不到 40%
  • p99 延迟飙到 1.5 秒以上
  • 进程维持了上千个 TCP 连接,每连接占 ~80KB 内存

根因不在 Go 的并发——goroutine 处理 1000 连接完全没问题。问题在 SSE 的协议设计:HTTP/1.1 长连接无法复用,遇到企业防火墙/代理还会因长时间无数据被强制断开。写再完美的重连逻辑,也架不住代理在 60 秒空闲后直接 RST。

社区在 2025 年初通过变更提案引入了新通信方案,并在这次 RC 中将其标为推荐模式。

单端点模式:按需流式 + HTTP 直连

新方案的核心思路是「回归 HTTP 本身的能力」——不引入 SSE 那种额外的长连接语义,用普通的 HTTP 请求 + 按需升级为流式响应。

协议层面三个关键设计:

  1. 端点统一:不再分 POST 和 SSE 两个 URL,所有通信走单一入口
  2. 协商式流化:后端根据响应内容决定返回完整 JSON(同步)还是事件流(异步),客户端无需预先建连接
  3. 会话标识抽象:通过请求头在无状态 HTTP 之上实现有状态交互

Go 官方 SDK 的新模式用法——与上面同样的初始化逻辑,差别仅在于最后一步不调 s.Run(),而是挂到 HTTP mux:

// ... 初始化 s 同上(NewServer + AddTool),然后:

mux := http.NewServeMux()
_ = mcp.NewStreamableServerTransport(mux, "/mcp", s, nil)
http.ListenAndServe(":8080", mux)

关键变化:进程不再「拥有」网络层——它变成一个普通的 HTTP handler。可以挂到已有的 Gin/Echo/Chi 路由里,和其他业务 API 共享端口。对已有 HTTP 基础设施的团队,这比另开端口干净太多。

三个生产踩坑记录

坑 1:struct tag 写错导致 tool 注册成功但调用失败

官方 SDK 用 jsonschema 标签生成 tool 的输入参数定义。如果 tag 格式有误——比如字段类型用了 int 而非 float64——AddTool 不会报错,但 AI 宿主拿到参数描述后无法正确构造请求。排查技巧:启动后先调 tools/list 看返回的定义与预期是否一致。

坑 2:子进程模式的 stdout 被日志污染

子进程模式下,进程的 stdout 就是 JSON-RPC 通道。如果在代码里用了 log.Println()fmt.Println(),这些输出会混进响应流,客户端解析直接崩溃。解决:所有日志走 log.SetOutput(os.Stderr),或用 slog 包显式设 slog.NewJSONHandler(os.Stderr, nil)

坑 3:HTTP+SSE 在企业网关后面频繁断连

最难排查的一个。SSE 连接在 nginx proxy_read_timeout 默认 60s 且未对事件流做特殊配置时被静默切断。客户端看到 EOF,后端看到 write: broken pipe。短期修复:调高超时 + proxy_buffering off。长期方案就是迁到新模式。

常见问题

问:新项目直接用单端点方案还是先上子进程模式?

工具只给本地 IDE/AI 客户端用,子进程模式最省事。但凡需要远程访问、多用户共享、或未来可能做成 SaaS——从第一天就用新模式,后期零迁移成本。最新 RC 已将单端点流式标为推荐方式,新写的 SSE 服务基本属于技术债。

问:官方 Go SDK 和社区实现怎么选?

官方库由 Google 协作维护,当前 v1.4.0,覆盖最新协议规范,Apache 2.0 许可。社区版 mark3labs 是先驱——官方 README 里专门致谢了其作者 Ed Zynda。追求与规范同步和长期维护保障,选官方;需要 MIT 许可或依赖社区版特有功能,mark3labs 仍是可行选项。关于 Go 与 Python 两种语言 SDK 的详细对比,可参考这篇选型分析

问:Tool handler 里可以做长时间阻塞操作吗?

可以但要设超时。handler 的参数第一个就是 ctx(携带客户端 deadline)。长时间操作——比如调用外部 LLM API、大批量数据处理——必须检查 ctx.Done() 并在超时前返回部分结果。完全不理会 deadline,客户端断开后进程还在跑,浪费算力。

问:单端点模式的会话怎么管理?

后端返回会话标识响应头,客户端后续请求都带这个头。Go SDK 内部做了完整生命周期管理——创建、超时清理、并发隔离。无状态部署(如 Lambda)场景下,会话状态需外存(Redis/DynamoDB),官方库目前未内置此能力,需自行实现存储接口。

选型路线图

  • 本地工具 / IDE 插件 → 子进程模式,零配置,零网络依赖
  • 内部微服务 / 已有 K8s 集群 → 单端点方案,复用现有网关、负载均衡、健康检查
  • SaaS / 多租户 / 函数计算 → 单端点无状态 + 外部会话存储
  • 遗留 HTTP+SSE → 制定迁移计划,最新 RC 后社区重心已明确转向

如果你的团队正在评估 Go 后端技术栈的整体选型,MCP 工具服务的通信模式只是其中一个决策维度——但它往往是最容易被低估的那个。

参考

  1. 官方 Go SDK — GitHub
  2. 2026-07-28 Specification Release Candidate
  3. 协议演进:从 SSE 到 Streamable HTTP — 腾讯云
  4. How to Build an MCP Server in Go — OneUptime
  5. 流式传输支持 — go-sdk issue #10
分享到
Go MCP Server 实战:Stdio/S… - 优码云博客