
随着LLM和MCP协议的爆火,把现有的服务接口暴露为MCP server的tool正在成为趋势。我们可以做一个简单对应,grpc 的service对应的是mcp server,grpc 每个service的方法,对应的是mcp server的tool列表,因此如果我们写好proto注释,加上详细的方法说明,我们就可以通过proto直接生成MCP server,有了这个想法后,搜索发现竟然已经有大佬实现了这个功能,那就是
https://github.com/redpanda-data/protoc-gen-go-mcp不过呢,这个项目文档缺乏,没有使用细节,我这里根据自己的使用情况,先介绍下如何使用,再分析下它的源码实现。首先安装下这个插件
export GOSUMDB=sum.golang.org && go install github.com/redpanda-data/protoc-gen-go-mcp/cmd/protoc-gen-go-mcp@latest安装后确认下
% ls $GOPATH/bin |grep mcp
protoc-gen-go-mcp它的工作还依赖下面几个插件,如果前面已经安装就可以忽略,其中比较生僻的报protc-gen-connect-go,前面已经介绍过:golang源码分析:connectrpc/connect-go golang源码分析:connectrpc/connect-go原理
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
go install github.com/bufbuild/buf/cmd/buf@latest准备完成后我们初始化buf项目
buf config init定义我们的buf.gen.yaml,具体实现如下
version: v2
managed:
enabled: true
override:
- file_option: go_package_prefix
value: learn/langchain/protoc_gen_mcp/exp2/gen/go
disable:
- file_option: go_package
module: buf.build/googleapis/googleapis
plugins:
# - remote: buf.build/protocolbuffers/go
- local: protoc-gen-go
out: ./gen/go
opt: paths=source_relative
# - remote: buf.build/grpc/go:v1.5.1
- local: protoc-gen-go-grpc
out: ./gen/go
opt:
- paths=source_relative
# - remote: buf.build/connectrpc/go:v1.18.1
- local: protoc-gen-connect-go
out: ./gen/go
opt:
- paths=source_relative
# - local: ["go","run","../protoc-gen-go-mcp/cmd/protoc-gen-go-mcp"]
- local: protoc-gen-go-mcp
out: ./gen/go
opt: paths=source_relative其中注释的部分可以使用远程依赖,既然本地安装了,我们就改为本地的。做完上述准备工作后,我们定义自己的proto文件:example/v1/example.proto
syntax = "proto3";
package example.v1;
message CreateExampleRequest {
int32 some_int32 = 1;
int64 some_int64 = 2;
string some_string = 3;
enum SomeEnum {
SOME_ENUM_UNSPECIFIED = 0;
SOME_ENUM_FIRST = 1;
SOME_ENUM_SECOND = 2;
}
SomeEnum some_enum = 4;
message Nested {
string some_field = 1;
message Nested2 {
string some_nested_field = 1;
message Nested3 {
string some_nested_in_nested_field = 1;
optional string optional_string = 2;
}
Nested3 nested3 = 2;
}
Nested2 nested2 = 2;
}
Nested nested = 5;
optional string optional_string = 6;
repeated string repeated_string = 7;
map<string, Nested> map_with_nested_val = 8;
map<int32, Nested> map_with_nested_val_no_string_key = 9;
oneof some_oneof {
string first_item = 10;
int32 second_item = 11;
}
}
message CreateExampleResponse {
string some_string = 1;
}
service ExampleService {
// @ignore-comment Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations.
// buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
// buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE
// CreateCluster create a Redpanda cluster. The input contains the spec, that describes the cluster.
// A Operation is returned. This task allows the caller to find out when the long-running operation of creating a cluster has finished.
rpc CreateExample(CreateExampleRequest) returns (CreateExampleResponse);
}
接着我们运行buf生成代码
buf generate可以看到在gen目录下生成了下面的代码
.
|____go
| |____example
| | |____v1
| | | |____example_grpc.pb.go
| | | |____examplev1connect
| | | | |____example.connect.go
| | | |____examplev1mcp
| | | | |____example.pb.mcp.go
| | | |____example.pb.go我们一一介绍下,首先是.pb.go文件,他是普通的pb文件,无需介绍,_grpc.pb.go是我们的grpc协议相关代码也无需介绍,.connect.go就是前文介绍的connectrpc/connect-go生成的代码也不是新面孔,最后就是本插件生成的代码.pb.mcp.go。
生成的代码如何使用呢?正如茴香豆的茴字有五种写法,这里也有三种用法。首先我们可以不用经过rpc协议,直接在本地使用,就只需使用proto生成的pb文件就行了
// Copyright 2025 Redpanda Data, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"fmt"
examplev1 "learn/langchain/protoc_gen_mcp/exp2/gen/go/example/v1"
"learn/langchain/protoc_gen_mcp/exp2/gen/go/example/v1/examplev1connect"
"learn/langchain/protoc_gen_mcp/exp2/gen/go/example/v1/examplev1mcp"
"github.com/mark3labs/mcp-go/server"
)
// Ensure our interface and the official gRPC interface are grpcClient
var (
grpcClient examplev1.ExampleServiceClient
mcpClient = examplev1mcp.ExampleServiceClient(grpcClient)
)
// Ensure our interface and the official connect-go interface are compatible
var (
connectClient examplev1connect.ExampleServiceClient
connectMcpClient = examplev1mcp.ConnectExampleServiceClient(connectClient)
)
func main() {
// Create MCP server
s := server.NewMCPServer(
"Example auto-generated gRPC-MCP",
"1.0.0",
)
srv := exampleServer{}
examplev1mcp.RegisterExampleServiceHandler(s, &srv)
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
type exampleServer struct {
}
func (t *exampleServer) CreateExample(ctx context.Context, in *examplev1.CreateExampleRequest) (*examplev1.CreateExampleResponse, error) {
return &examplev1.CreateExampleResponse{
SomeString: "HAHA " + in.GetNested().GetNested2().GetNested3().GetOptionalString(),
}, nil
}定义完server后,我们测试下是否可用,首先编译下代码
% go build -o stdio main.go
% chmod +x stdio
% ./stdio 然后定义一个mcp客户端
package main
import (
"fmt"
"log"
"github.com/mark3labs/mcp-go/client"
langchaingo_mcp_adapter "github.com/i2y/langchaingo-mcp-adapter"
)
func main() {
// Create an MCP client using stdio
mcpClient, err := client.NewStdioMCPClient(
"./stdio", // Path to an MCP server
nil, // Additional environment variables if needed
)
if err != nil {
log.Fatalf("Failed to create MCP client: %v", err)
}
defer mcpClient.Close()
// Create the adapter
adapter, err := langchaingo_mcp_adapter.New(mcpClient)
if err != nil {
log.Fatalf("Failed to create adapter: %v", err)
}
// Get all tools from MCP server
mcpTools, err := adapter.Tools()
if err != nil {
log.Fatalf("Failed to get tools: %v", err)
}
for _, tool := range mcpTools {
fmt.Println(tool.Name)
}
}测试下能够输出结果,说明我们的mcp server定义成功
go run mcp-client/stdio/main.go
0x91a4fc0
0x91a4fc0这里只介绍了最简单的用法,剩下的两种用法呢?我们下回分解
本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!