Protoc Gen Go Mcp について
protoc-gen-go-mcp は、gRPC または ConnectRPC サービス向けに MCP サーバーを生成する Protocol Buffers コンパイラープラグインです。既存の protobuf サービスを MCP クライアントに公開したい Go 開発者を対象としています。
主な機能
- 01proto サービス定義からの MCP ハンドラー生成
- 02メソッド入力から生成される JSON Schema
- 03gRPC および ConnectRPC クライアントへのツール呼び出し転送
- 04go-sdk と mark3labs/mcp-go の両ライブラリに対応
- 05実行時に標準 MCP と OpenAI 互換スキーマを選択
活用シーン
- 01既存の gRPC サービスを MCP ツールとして公開
- 02buf のビルドワークフローでの MCP コード生成
- 03同一サービスの複数インスタンスでのツール名プレフィックス付与
MCP 対応
生成されたコードは各 RPC メソッドを MCP サーバー上のツールとして登録し、JSON Schema によってツールの入力を MCP クライアントに伝えます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
protoc-gen-go-mcp
protoc-gen-go-mcp は、gRPC または ConnectRPC API 用の Model Context Protocol (MCP) サーバーを生成する、Protocol Buffers コンパイラープラグインです。
各 protobuf サービスの *.pb.mcp.go ファイルを生成し、ハンドラーを gRPC サーバーまたはクライアントに直接委譲できるようにします。MCP は内部でツール入力に JSON Schema を使用します。protoc-gen-go-mcp は、メソッドの入力ディスクリプターからこれらのスキーマを自動生成します。
生成されるコードは MCP ライブラリに依存しません。薄いアダプターを通じて、サーバー作成時に modelcontextprotocol/go-sdk(公式 SDK)または mark3labs/mcp-go を選択できます。再生成は不要です。
機能
.protoサービスから MCP ハンドラーを自動生成- メソッドの入力に対する JSON Schema を出力
- gRPC または ConnectRPC サーバー/クライアントに接続
bufと簡単に統合- 実行時の LLM プロバイダー選択 — 標準の MCP または OpenAI 互換スキーマ
- 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 サービス定義を含むパッケージごとに新しいフォルダーが作成されます。
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 ツールになります。
実行時の LLM プロバイダー選択
コードを再生成せずに、実行時に LLM との互換性を選択できます。
// 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 ハンドラーがクライアントに直接接続され、定型コードが一切不要になります。
追加プロパティ
proto に定義されていない追加プロパティを MCP ツールに追加できます。これらはコンテキストに書き込まれます。
// 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_ セグメントを基準に判定します)を通過しても変更されません。無効な値や、サービス内のツール名の重複がある場合、コード生成は失敗します(1つのサービスは1つの 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 関数は、*server.MCPServer ではなく runtime.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() は、*mcp.CallToolResult ではなく *runtime.CallToolResult を返すようになりました。カスタムハンドラーから呼び出している場合は、戻り値の型を更新してください。
4. トランスポート
トランスポートの設定に変更はありません。手順1の未加工のサーバーを使用してください。
// mark3labs stdio
server.ServeStdio(raw)
// mark3labs streamable HTTP
mcpserver.NewStreamableHTTPServer(raw)
// go-sdk stdio
raw.Run(ctx, &mcp.StdioTransport{})
LLM プロバイダーとの互換性
ジェネレーターは、標準 MCP 用と OpenAI 互換用のハンドラーを自動的に生成します。実行時に使用するものを選択してください。
標準 MCP
- 完全な JSON Schema サポート(additionalProperties、anyOf、oneOf)
- マップは JSON オブジェクトとして表現されます
- 既知の型はネイティブの JSON 表現を使用します
OpenAI 互換
- 制限付き JSON Schema(additionalProperties、anyOf、oneOf なし)
- マップはキーと値のペアの配列に変換されます
- 既知の型(Struct、Value、ListValue)は JSON 文字列としてエンコードされます
- すべてのフィールドは、nullable ユニオンを使って必須としてマークされます
開発とテスト
コマンド
すべてのコマンドで 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
ゴールデンファイルテスト
ジェネレーターは、出力の一貫性を確保するためにゴールデンファイルテストを使用します。テストでは、コンパイル済みディスクリプターからプロセス内でジェネレーターを再実行し、チェックイン済みの *.pb.mcp.go ファイルと比較します。
新しいテストを追加するには: pkg/testdata/proto/testdata/ に .proto ファイルを配置し、just generate を実行します。ゴールデンテストは生成されたすべてのファイルを自動的に検出し、比較します。
Proto から JSON Schema へのマッピング
このプラグインは、protobuf 型を JSON Schema にマッピングします。LLM が目にするのは 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 による制約(たとえば、整数キーには pattern、ブール値キーには enum)を持つ JSON オブジェクトになります。
OpenAI モード: map<K, V> は、{key, value} オブジェクトの配列になります。キーの制約(パターン、列挙値)は key フィールドに保持されます。実行時に FixOpenAI が、proto のアンマーシャリング前にこれらの配列をオブジェクトに戻します。
Well-known 型
| Proto 型 | 標準スキーマ | OpenAI スキーマ |
|---|---|---|
google.protobuf.Timestamp | format: date-time を持つ string | 同じ |
google.protobuf.Duration | 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 など) | null 許容のスカラー(例:["string", "null"]) | 同じ |
OpenAI モードでは、実行時に FixOpenAI が逆変換を処理します。JSON エンコードされた文字列は再度パースされ、ラッパーオブジェクト {"value": X} は X に展開され、マップ配列はオブジェクトに変換されます。
Oneof フィールド
標準モード: JSON Schema の anyOf を使用し、各 oneof グループに対してエントリーを1つ設けます。各エントリーには、許可される選択肢が1つ指定されます。
OpenAI モード: oneof フィールドは、oneof の制約を説明する記述を付けた null 許容フィールド(例:type: ["string", "null"])にフラット化されます。実行時に FixOpenAI が null の oneof 選択肢を取り除き、各グループで最初の 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 オブジェクトに戻します。実際のデータは任意の深さまでネストできます。深さの制限は LLM が参照するスキーマにのみ適用され、送信できるデータには適用されません。
ツール名の変換
完全修飾 RPC 名(ドットをアンダースコアに置き換えたもの)が64文字を超える場合、名前は切り詰められます。先頭部分を10文字の SHA-256 ハッシュ接頭辞に置き換え、末尾(最も具体的な部分で、通常は ServiceName_MethodName)を保持します。64文字の上限があるのは、Claude デスクトップでこの制限が適用されるためです。
制限事項
- インターセプターはまだサポートされていません。gRPC サーバーに登録すると、インターセプターを経由しません。
- 再帰的なメッセージスキーマは、ネストが3レベルを超えるとフィールドレベルの詳細が失われます(上記参照)。LLM は、それより深いレベルを JSON 文字列としてエンコードする必要があります。
WithExtraPropertiesで追加するプロパティは、proto フィールド名と重複しないようにしてください。重複する場合、追加プロパティの値はコンテキストに抽出されますが、proto メッセージにも漏れ込みます。
フィードバック
フィードバック、バグ報告、PR をお待ちしています!ぜひ議論に参加して、Go と Protobuf の MCP ツールの未来づくりにご協力ください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。