关于 Protoc Gen Go Mcp
protoc-gen-go-mcp 是一个 Protocol Buffers 编译器插件,可为 gRPC 或 ConnectRPC 服务生成 MCP 服务代码。适合希望将现有 Go 服务直接暴露给 MCP 客户端的开发者。
主要功能
- 01从 .proto 服务定义生成 MCP 处理代码
- 02自动从输入描述生成 JSON Schema
- 03可与 gRPC 与 ConnectRPC 的客户端和服务端对接
- 04支持 go-sdk 与 mark3labs/mcp-go 两种 MCP 库
- 05运行时可选择标准 MCP 或 OpenAI 兼容的 Schema
适用场景
- 01将已有 gRPC 服务快速暴露为 MCP 工具
- 02通过 buf 在构建流程中生成 MCP 代码
- 03为同一服务的多个实例添加工具名前缀以区分
MCP 相关
该插件生成的代码会把每个 RPC 方法注册为 MCP 服务上的工具,并通过 JSON Schema 向 MCP 客户端描述工具输入。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
protoc-gen-go-mcp
protoc-gen-go-mcp 是一个 Protocol Buffers 编译器插件,可为你的 gRPC 或 ConnectRPC API 生成 Model Context Protocol(MCP) 服务。
它会为每个 protobuf 服务生成 *.pb.mcp.go 文件,让你可以将处理程序直接委托给 gRPC 服务端或客户端。MCP 在底层使用 JSON Schema 描述工具输入,protoc-gen-go-mcp 会根据方法输入描述符自动生成这些 Schema。
生成的代码与 MCP 库无关:创建服务时,通过一个轻量级适配器选择 modelcontextprotocol/go-sdk(官方 SDK)或 mark3labs/mcp-go,无需重新生成代码。
功能
- 根据
.proto服务自动生成 MCP 处理程序 - 为方法输入生成 JSON Schema
- 连接到 gRPC 或 ConnectRPC 服务端/客户端
- 轻松集成
buf - 运行时选择大模型提供商——支持标准 MCP 或 OpenAI 兼容 Schema
- 与 MCP 库无关——同时支持官方 go-sdk 和 mark3labs/mcp-go
用法
生成代码
在你的 buf.gen.yaml 中添加条目:
...
plugins:
- local:
- go
- run
- github.com/redpanda-data/protoc-gen-go-mcp/cmd/protoc-gen-go-mcp@latest
out: ./gen/go
opt: paths=source_relative
你还需要生成标准的 *.pb.go 文件。protoc-gen-go-mcp 默认会使用单独的子文件夹 {$servicename}mcp,并导入 *pb.go 文件——这与 connectrpc-go 类似。
运行 buf generate 后,每个包含 protobuf Service 定义的包都会出现一个新文件夹:
tree pkg/testdata/gen/
gen
go
testdata
test_service.pb.go
testdataconnect/
test_service.connect.go
testdatamcp/
test_service.pb.mcp.go
设置 MCP 服务
生成的代码面向 runtime.MCPServer 接口。导入相应的适配器包,即可选择底层使用的 MCP 库。
使用官方 go-sdk(推荐)
import (
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime/gosdk"
)
// Create server -- raw is *mcp.Server for transport, s is runtime.MCPServer for tools.
raw, s := gosdk.NewServer("my-server", "1.0.0")
testdatamcp.RegisterTestServiceHandler(s, &srv)
// Serve over stdio
raw.Run(ctx, &mcp.StdioTransport{})
使用 mark3labs/mcp-go
import (
"github.com/mark3labs/mcp-go/server"
"github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime/mark3labs"
)
// Create server -- raw is *server.MCPServer for transport, s is runtime.MCPServer for tools.
raw, s := mark3labs.NewServer("my-server", "1.0.0")
testdatamcp.RegisterTestServiceHandler(s, &srv)
// Serve over stdio
server.ServeStdio(raw)
配置处理程序
进程内注册示例:
srv := testServer{} // your gRPC implementation
// Register all RPC methods as tools on the MCP server
testdatamcp.RegisterTestServiceHandler(s, &srv)
protobuf 服务中的每个 RPC 方法都会成为一个 MCP 工具。
运行时选择大模型提供商
无需重新生成代码,即可在运行时选择大模型兼容模式:
// Option 1: Use convenience function with runtime provider selection
testdatamcp.RegisterTestServiceHandlerWithProvider(s, &srv, runtime.LLMProviderOpenAI)
// Option 2: Register specific handlers directly
testdatamcp.RegisterTestServiceHandler(s, &srv) // Standard MCP
testdatamcp.RegisterTestServiceHandlerOpenAI(s, &srv) // OpenAI-compatible
连接 gRPC 和 connectrpc 客户端
将 MCP 工具调用直接转发给 gRPC 客户端:
testdatamcp.ForwardToTestServiceClient(s, myGrpcClient)
connectrpc 也同样适用:
testdatamcp.ForwardToConnectTestServiceClient(s, myConnectClient)
这样即可将 MCP 处理程序直接连接到客户端,无需编写任何样板代码。
额外属性
可以为 MCP 工具添加 proto 中未定义的额外属性。这些属性会写入上下文。
// Enable URL override with custom field name and description
option := runtime.WithExtraProperties(
runtime.ExtraProperty{
Name: "base_url",
Description: "Base URL for the API",
Required: true,
ContextKey: MyURLOverrideKey{},
},
)
// Use with any generated function
testdatamcp.RegisterTestServiceHandler(s, &srv, option)
testdatamcp.ForwardToTestServiceClient(s, client, option)
工具名称前缀
重复注册同一服务时(例如,使用不同的数据库实例),可使用 WithNamePrefix 为工具添加命名空间前缀:
sqlv1mcp.RegisterSQLServiceHandler(s, postgresHandler, runtime.WithNamePrefix("postgres"))
sqlv1mcp.RegisterSQLServiceHandler(s, clickhouseHandler, runtime.WithNamePrefix("clickhouse"))
// Tools: postgres_SQLService_Query, clickhouse_SQLService_Query, ...
工具命名
默认情况下,工具以方法的完整 proto 名称命名,并将点替换为下划线(redpanda_mcps_sql_v1_SQLService_Query)。名称超过 64 个字符(最严格的提供方限制)时会截断:保留完整名称的 10 个字符哈希,以及尽可能多的名称尾部(最具体的部分)。哈希会被强制以字母开头,因为 Gemini 会拒绝以数字开头的函数名称。
若要改用稳定且由人工指定的名称发布方法,请设置 (mcp.v1.tool_name) 方法选项:
import "mcp/v1/annotations.proto";
rpc GetEquityResearchReport(GetEquityResearchReportRequest) returns (GetEquityResearchReportResponse) {
option (mcp.v1.tool_name) = "get_equity_research_report";
}
值必须匹配 ^[a-z_][a-z0-9_-]{0,63}$——使用小写蛇形或短横线命名法,并符合所有提供方的函数命名规则;小写可确保配置的名称在经过使用者的反混淆启发式处理后保持不变(这些启发式会根据派生名称中的 UpperCamel Service_ 片段进行判断)。值无效或服务内存在重复工具名称时,代码生成会失败(一个服务对应一个 MCP 服务注册,不同服务可以合法地重复使用同一名称)。动态注册路径(gen.RegisterService)也会应用相同的选项;如果配置的名称无效或发生冲突,则会回退到派生名称,确保工具不会被静默丢弃。
从仅使用 mark3labs 迁移(v0.2 之前)
生成的代码不再直接导入 mark3labs/mcp-go。它通过 runtime.MCPServer 接口进行操作,并由你选择要通过适配器包使用的 MCP 库。
1. 创建服务端
// Before
s := server.NewMCPServer("name", "1.0", server.WithToolCapabilities(true))
// After -- mark3labs
raw, s := mark3labs.NewServer("name", "1.0", server.WithToolCapabilities(true))
// raw is *server.MCPServer for transport (ServeStdio, NewStreamableHTTPServer, etc.)
// s is runtime.MCPServer for tool registration
// After -- official go-sdk
raw, s := gosdk.NewServer("name", "1.0")
2. 注册工具
生成的 Register*Handler 函数现在接收 runtime.MCPServer,而不是 *server.MCPServer。直接传入第 1 步中的 s 即可。
如果你曾使用 mark3labs 类型手动调用 s.AddTool() 来添加名称前缀,请删除这段代码,并改用 WithNamePrefix:
// Before: 60 lines of manual register.go per service
sql.RegisterTools(s, "postgres", handler) // custom wrapper around s.AddTool(mcp.Tool, ...)
// After: one line
sqlv1mcp.RegisterSQLServiceHandler(s, handler, runtime.WithNamePrefix("postgres"))
3. HandleError 返回类型
runtime.HandleError() 现在返回 *runtime.CallToolResult,而不是 *mcp.CallToolResult。如果你在自定义处理器中调用它,请更新返回类型。
4. 传输
传输设置不变——使用第 1 步中的原始服务端:
// mark3labs stdio
server.ServeStdio(raw)
// mark3labs streamable HTTP
mcpserver.NewStreamableHTTPServer(raw)
// go-sdk stdio
raw.Run(ctx, &mcp.StdioTransport{})
大模型提供方兼容性
生成器会自动创建标准 MCP 和兼容 OpenAI 的处理器。运行时选择使用哪一种。
标准 MCP
- 完整支持 JSON Schema(additionalProperties、anyOf、oneOf)
- 将映射表示为 JSON 对象
- 已知类型使用原生 JSON 表示
兼容 OpenAI
- 受限的 JSON Schema(不支持 additionalProperties、anyOf、oneOf)
- 将映射转换为键值对数组
- 已知类型(Struct、Value、ListValue)编码为 JSON 字符串
- 所有字段都标记为必填,并使用可空联合类型
开发与测试
命令
所有命令都使用 just:
just build # Build everything (Bazel)
just test-unit # Unit + golden tests (no API keys needed)
just test # All tests including conformance/integration (needs API keys)
just test-cover # Tests with coverage report
just generate # Regenerate proto code + descriptor set
just lint # Run golangci-lint
just fmt # Format code
just install # Install binary to GOPATH/bin
just gazelle # Sync BUILD files from go.mod
# View all available commands
just --list
开发工作流
# 1. Edit proto or generator code
# 2. Regenerate
just generate
# 3. Run tests
just test-unit
Golden 文件测试
生成器使用 Golden 文件测试来确保输出一致性。测试会在进程内根据编译后的描述符重新运行生成器,并与已纳入版本控制的 *.pb.mcp.go 文件进行比较。
添加新测试: 将 .proto 文件放入 pkg/testdata/proto/testdata/,然后运行 just generate。Golden 测试会自动发现所有生成的文件并进行比较。
Proto 到 JSON Schema 的映射
该插件会将 protobuf 类型映射为 JSON Schema。了解这些映射很重要,因为大模型看到的是 Schema,而不是你的 proto 定义。
标量类型
| Proto 类型 | JSON Schema 类型 | 说明 |
|---|---|---|
double、float | number | |
int32、uint32、sint32、fixed32、sfixed32 | integer | |
int64、uint64、sint64、fixed64、sfixed64 | string | JSON 无法精确表示 64 位整数 |
bool | boolean | |
string | string | |
bytes | string | contentEncoding: base64 |
enum | string | enum 数组包含所有值名称 |
消息和映射
嵌套消息会变为嵌套的 object 架构。映射取决于模式:
标准模式: map<K, V> 会变为带有 propertyNames 约束的 JSON 对象(例如,整数键使用 pattern,布尔键使用 enum)。
OpenAI 模式: map<K, V> 会变为由 {key, value} 对象组成的数组。键约束(模式、枚举)会保留在 key 字段上。运行时,FixOpenAI 会在 proto 反序列化前将这些数组转换回对象。
众所周知的类型
| Proto 类型 | 标准架构 | OpenAI 架构 |
|---|---|---|
google.protobuf.Timestamp | 带有 format: date-time 的 string | 相同 |
google.protobuf.Duration | 带有时长模式的 string | 相同 |
google.protobuf.Struct | 带有 additionalProperties: true 的 object | string(JSON 编码) |
google.protobuf.Value | 任意类型(无类型约束) | string(JSON 编码) |
google.protobuf.ListValue | array | string(JSON 编码) |
google.protobuf.FieldMask | string | 相同 |
google.protobuf.Any | 带有 @type 和 value 的 object | 相同,但带有 additionalProperties: false |
包装类型(StringValue 等) | 可空标量(例如,["string", "null"]) | 相同 |
在 OpenAI 模式下,FixOpenAI 会在运行时处理反向转换:将 JSON 编码的字符串解析回来,将包装对象 {"value": X} 解包为 X,并将映射数组转换为对象。
Oneof 字段
标准模式: 使用 JSON Schema anyOf,每个 oneof 组对应一个条目。每个条目指定一个允许的选项。
OpenAI 模式: Oneof 字段会展平为可空字段(例如,type: ["string", "null"]),并添加说明来注明 oneof 约束。运行时,FixOpenAI 会移除 oneof 选项中的 null 值,并为每个组仅保留第一个非 null 字段。
验证约束
buf.validate 注解会映射为 JSON Schema 关键字:
| 验证 | JSON Schema |
|---|---|
string.uuid | format: uuid |
string.email | format: email |
string.pattern | pattern |
string.min_len/max_len | minLength/maxLength |
int32.gte/lte | minimum/maximum |
int32.gt/lt | minimum+1 / maximum-1 |
float.gt/lt | exclusiveMinimum/exclusiveMaximum |
double.gte/lte | minimum/maximum |
递归消息
支持自引用和相互递归的消息(例如,树节点、链表)。架构会展开至最多 3 层深度,并提供完整字段详情。超过此深度后,递归字段会变为 {"type": "string", "description": "JSON-encoded TreeNode. Provide a JSON object as a string."}——这与 OpenAI 模式下对 Struct/Value/ListValue 使用的模式相同。
运行时,FixOpenAI 会在 proto 反序列化前将这些字符串编码的字段解析回 JSON 对象。实际数据可以任意嵌套;深度限制仅适用于大模型看到的架构,而不限制它可以发送的数据。
工具名称处理
如果 RPC 完全限定名称(将点替换为下划线后)超过 64 个字符,则会截断名称:将开头替换为 10 个字符的 SHA-256 哈希前缀,同时保留结尾(最具体的部分,通常为 ServiceName_MethodName)。设定 64 字符限制是因为 Claude 桌面端对此有限制。
限制
- 暂不支持拦截器。在 gRPC 服务端注册会绕过拦截器。
- 递归消息架构在嵌套超过 3 层后会丢失字段级详情(见上文)。大模型必须将更深层级编码为 JSON 字符串。
- 通过
WithExtraProperties添加的额外属性不得与 proto 字段名称冲突。如果发生冲突,额外属性值会被提取到上下文中,但也会泄漏到 proto 消息中。
反馈
欢迎提供反馈、提交 Bug 报告或 PR!加入讨论,一起塑造 Go 和 Protobuf MCP 工具的未来。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。