首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >proto直接生成MCP-server:protoc-gen-go-mcp(1)

proto直接生成MCP-server:protoc-gen-go-mcp(1)

作者头像
golangLeetcode
发布2026-03-18 17:43:19
发布2026-03-18 17:43:19
1080
举报

随着LLM和MCP协议的爆火,把现有的服务接口暴露为MCP server的tool正在成为趋势。我们可以做一个简单对应,grpc 的service对应的是mcp server,grpc 每个service的方法,对应的是mcp server的tool列表,因此如果我们写好proto注释,加上详细的方法说明,我们就可以通过proto直接生成MCP server,有了这个想法后,搜索发现竟然已经有大佬实现了这个功能,那就是

代码语言:javascript
复制
https://github.com/redpanda-data/protoc-gen-go-mcp

不过呢,这个项目文档缺乏,没有使用细节,我这里根据自己的使用情况,先介绍下如何使用,再分析下它的源码实现。首先安装下这个插件

代码语言:javascript
复制
export GOSUMDB=sum.golang.org && go install github.com/redpanda-data/protoc-gen-go-mcp/cmd/protoc-gen-go-mcp@latest

安装后确认下

代码语言:javascript
复制
%  ls $GOPATH/bin |grep mcp
protoc-gen-go-mcp

它的工作还依赖下面几个插件,如果前面已经安装就可以忽略,其中比较生僻的报protc-gen-connect-go,前面已经介绍过:golang源码分析:connectrpc/connect-go golang源码分析:connectrpc/connect-go原理

代码语言:javascript
复制
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项目

代码语言:javascript
复制
buf config init

定义我们的buf.gen.yaml,具体实现如下

代码语言:javascript
复制
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

代码语言:javascript
复制

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生成代码

代码语言:javascript
复制
buf generate

可以看到在gen目录下生成了下面的代码

代码语言:javascript
复制
.
|____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文件就行了

代码语言:javascript
复制
// 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后,我们测试下是否可用,首先编译下代码

代码语言:javascript
复制
 % go build -o stdio main.go 
 % chmod +x stdio 
% ./stdio 

然后定义一个mcp客户端

代码语言:javascript
复制
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定义成功

代码语言:javascript
复制
go run mcp-client/stdio/main.go
0x91a4fc0
0x91a4fc0

这里只介绍了最简单的用法,剩下的两种用法呢?我们下回分解

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档