Claude Media
gRPCサービスをMCP化してClaudeに叩かせる2つの経路

gRPCサービスをMCP化してClaudeに叩かせる2つの経路

protoファイルからMCPサーバーを自動生成するprotoc-gen-go-mcpとprotoc-gen-mcpの違いを比較し、社内gRPCサービス群をClaude Codeから呼び出す手順を解説します。

gRPCサービスは.protoファイルにメソッドとメッセージ型がすでに定義されています。この定義をそのままMCPツールのJSON Schemaに変換できれば、REST APIのように1エンドポイントずつツール定義を手書きする手間がかかりません。この発想を実装したprotocプラグインが2種類公開されており、この記事ではその選び方と、生成したサーバーをClaude Codeに登録して権限を絞るところまでを扱います。

MCPプロトコル自体の仕組みはMCPとは、Go向けのMCPサーバー実装全般はGoでMCPサーバーを書く公式SDKの使い方が対応します。

gRPCをMCP化する2つのアプローチ

.protoファイルからMCPサーバーを生成するプラグインには、既存のgRPCサーバーをそのまま包む方式と、proto定義にMCP用の注釈を足す方式の2種類があります。

くらべる

protoc-gen-go-mcpとprotoc-gen-mcp

既存サーバーをそのまま包む

protoc-gen-go-mcp

Redpanda Dataが公開しています。対応言語はGoで、gRPCとConnectRPCのサービスをラップするMCPハンドラを生成します。

  • proto側の変更は不要で、既存の.protoをそのまま使えます
  • MCPライブラリは公式go-sdkとmark3labs/mcp-goをアダプタ経由で選べます
  • 向くのは、既にあるgRPCサーバー群を手早くツール化したいケースです
protoに注釈を足して設計する

protoc-gen-mcp

The Protobuf Projectが公開しています。対応言語はGo・Rust・C++で、Tools・Prompts・Resources・Elicitationを含むMCPサーバー一式を生成します。

  • (mcp.v1.tool)等のオプションをprotoに注釈する必要があります
  • MCPライブラリは公式go-sdk(Go)とrmcp(Rust)です
  • 向くのは、プロンプトやリソースまで含めてMCPサーバーを設計したいケースです

Elicitationハンドラの生成はGoとRustだけで、C++は対応していません。

protoc-gen-go-mcp: 既存のgRPCサーバーをそのままツール化

protoc-gen-go-mcpはRedpanda Dataが公開しているOSSのprotocプラグインです。.protoファイルのサービス定義ごとに*.pb.mcp.goを生成し、gRPCサーバーの各RPCメソッドをそのままMCPツールとして登録できるようにします。ツールの入力スキーマは、RPCメソッドの入力メッセージ型からJSON Schemaとして自動導出されます。

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

生成コードはruntime.MCPServerという共通インターフェース越しに動くため、実際のMCPサーバー実装(公式go-sdkかmark3labs/mcp-goか)は生成後に選べます。

import (
    "github.com/modelcontextprotocol/go-sdk/mcp"
    "github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime/gosdk"
)
 
raw, s := gosdk.NewServer("my-server", "1.0.0")
testdatamcp.RegisterTestServiceHandler(s, &srv)
raw.Run(ctx, &mcp.StdioTransport{})

すでに動いているgRPCクライアントがあるなら、実装を書かずにMCPツール呼び出しをそのまま転送する経路もあります。

testdatamcp.ForwardToTestServiceClient(s, myGrpcClient)

このForwardTo*Client系の関数が、社内マイクロサービス群を機械的にツール化できるこのプラグインを使う最大の利点です。サービスの数だけ.protoがあれば、ハンドラのコードを1行も書かずにMCPサーバー化が完了します。ConnectRPCのクライアントにはForwardToConnect*Client系の関数が用意されています。

再帰するメッセージのスキーマは3階層までで、それ以降はJSON文字列として扱われます。

proto定義に無い値をツールに追加で渡したい場合は、WithExtraPropertiesで拡張プロパティを定義できます。たとえば複数のバックエンドを切り替えるbase_urlのようなフィールドを、protoスキーマを変更せずにツールの入力に追加できます。

option := runtime.WithExtraProperties(
    runtime.ExtraProperty{
        Name:        "base_url",
        Description: "Base URL for the API",
        Required:    true,
        ContextKey:  MyURLOverrideKey{},
    },
)
testdatamcp.RegisterTestServiceHandler(s, &srv, option)

渡された値はGoのcontext.Contextに格納され、ハンドラ側の実装からContextKeyで取り出せます。protoファイルを触らずにMCP側だけの都合でパラメータを増やしたいときに使う仕組みです。

protoc-gen-mcp: proto定義にMCPオプションを注釈する

protoc-gen-mcpはThe Protobuf Projectが公開しているプラグインで、.protoファイルに(mcp.v1.tool)等のオプションを注釈し、Go・Rust・C++向けにMCPサーバーのコードを生成します。プロトコルバージョン2026-07-28に準拠しており、Tools(呼び出し可能な関数)に加えてPrompts(構造化された対話テンプレート)・Resources(取得可能なコンテキスト)・Elicitation(実行前の入力確認)まで生成対象に含む点が、protoc-gen-go-mcpとの大きな違いです。

import "mcp/v1/annotations.proto";
 
service TodoService {
  option (mcp.v1.service) = {
    app: { display_name: "Todo App" version: "1.0.0" }
  };
 
  rpc CreateTodo(CreateTodoRequest) returns (Todo) {
    option (mcp.v1.tool) = {
      description: "Creates a new todo item."
    };
    option (mcp.v1.elicitation) = {
      message: "Please confirm the todo details before creating."
      schema: "todo.v1.CreateTodoConfirmation"
      required: true
    };
  }
}

elicitationオプションを付けたRPCは、実行前にクライアントへ入力を求めます。schemaを指定するとフォーム、urlを指定するとURLを開く確認になり、required: trueを付けるとユーザーが辞退した時点で呼び出しが中止されます。Claude Codeは追加の設定なしでこのダイアログを表示します。一方で、クライアントがElicitationに対応していない場合は、確認なしでツールが実行されます。さらにClaude CodeではElicitationフックでダイアログを出さずに自動応答させることもできるため、削除やデータ変更を伴うメソッドの歯止めは、後述の権限ルールと組み合わせて考えます。

導入にはprotoファイル側でMCPアノテーションの依存を追加する準備が要ります。

# buf.yaml
version: v2
deps:
  - buf.build/googleapis/googleapis
  - buf.build/the-protobuf-project/mcp
buf dep update
buf generate

生成後は、Go・Rust・C++それぞれの実装にサーバーロジックを書き込みます。protoのメッセージにgoogle.api.resourceアノテーションを付けておくと、MCPのResourcesとして自動的に検出される点も、protoc-gen-go-mcpには無い特徴です。長時間かかる処理はgRPCのサーバーストリーミングとmcp.v1.MCPProgress型を組み合わせることで、MCPのProgress通知(進捗の逐次報告)としてクライアントに届けられます。この進捗通知は、生成されたForwardTo*MCPClientでgRPCサーバーへ転送する構成のときに使えます。

MCP Inspectorで動作確認する

Claude Codeに繋ぐ前に、生成したMCPサーバーが正しくツールを公開できているかをMCP Inspectorで確認しておくと切り分けが早くなります。protoc-gen-mcpの例では、生成したバイナリをstdioで起動し、Inspectorから接続します。

cd examples/go/stdio && go run .
npx @modelcontextprotocol/inspector -- go run .

Inspectorの画面ではツール一覧・入力スキーマ・実際の呼び出し結果をブラウザ上で確認できます。ツール名が想定と違う、入力スキーマのフィールドが抜けている、といった問題はこの段階で見つけたほうが、Claude Code側の権限設定と混同せずに切り分けられます。

進捗通知を使うストリーミングRPCでは、Inspector側の待ち時間にも注意が要ります。Inspectorの既定は合計60秒で、超えるとMCP error -32001: Maximum total timeout exceededが出ます。MCP_REQUEST_MAX_TOTAL_TIMEOUT=300000(ミリ秒、5分)のように引き上げます。進捗のたびに待ち時間を延ばすMCP_REQUEST_TIMEOUT_RESET_ON_PROGRESSも使えます。生成されるHTTPサーバー側はReadTimeout: 0とWriteTimeout: 0が既定で、進捗が途中で切られない設計です。

Claude Codeに登録して権限を絞る

Inspectorで確認できたら、Claude Codeに登録します。以下は、Claude Code v2.1.289での出力です。サーバー自身のフラグや引数は--の後ろに置き、-eの環境変数は名前の後ろ、--の前に置きます。

手順

登録から権限設定まで

  1. 1

    stdioサーバーとして追加する

    プロジェクトスコープなら、チーム全員が同じ.mcp.jsonを共有できます。

    claude mcp add -s project grpc-tools -e BACKEND_ADDR=localhost:50051 -- go run ./cmd/mcpserver
    # Added stdio MCP server grpc-tools with command: go run ./cmd/mcpserver to project config
  2. 2

    HTTPで公開したサーバーも同じファイルに足す

    streamable-httpで起動したサーバーは--transport httpでURLを渡します。

    claude mcp add -s project --transport http grpc-http http://localhost:8080/mcp
    # Added HTTP MCP server grpc-http with URL: http://localhost:8080/mcp to project config
  3. 3

    書き込まれた.mcp.jsonを確かめる

    2回の実行で、次の内容が作業ディレクトリの.mcp.jsonに書かれました。

    {
      "mcpServers": {
        "grpc-tools": {
          "type": "stdio",
          "command": "go",
          "args": ["run", "./cmd/mcpserver"],
          "env": { "BACKEND_ADDR": "localhost:50051" }
        },
        "grpc-http": {
          "type": "http",
          "url": "http://localhost:8080/mcp"
        }
      }
    }

ツールはmcp__<サーバー名>__<ツール名>の形で権限ルールに現れます。サーバー名grpc-toolsのツールを丸ごと対象にするならmcp__grpc-toolsかmcp__grpc-tools__*です。更新系だけを止めるなら、ツール名を個別にdenyかaskへ書きます。protoc-gen-go-mcpの既定のツール名は、完全修飾されたRPC名をアンダースコアでつないだもので、次の例もこの規則で組んでいます。(mcp.v1.tool_name)で名前を固定しておけば、権限ルールもprotoのパッケージ名の変更に引きずられません。

{
  "permissions": {
    "allow": ["mcp__grpc-tools__todo_v1_TodoService_GetTodo"],
    "ask": ["mcp__grpc-tools__todo_v1_TodoService_CreateTodo"],
    "deny": ["mcp__grpc-tools__todo_v1_TodoService_DeleteTodo"]
  }
}

長時間RPCを呼ぶサーバーには、.mcp.jsonのエントリにtimeout(ミリ秒)を足せます。たとえば"timeout": 600000で10分です。このタイムアウトは1回のツール呼び出しの壁時計の上限で、サーバーが進捗通知を送っても延びません。Progress通知は「動いている」ことをクライアントに見せる機能で、待ち時間の延長には使えない点に注意が要ります。

入力スキーマの側にも、Claude Code固有の関門があります。トップレベルのプロパティ名は、1〜64文字のASCII英数字と_・.・-に限られます。外れるツールはそのツールだけが除外され、同じサーバーの他のツールは使えます(除外はClaude Codeが取得する機能フラグで有効になります)。protoのフィールド名は通常この範囲に収まりますが、WithExtraPropertiesで足す名前も同じ制約を受けます。

ツール名の衝突とプレフィックス

どちらの実装でも、同じgRPCサービスを複数のインスタンス(本番用DBと検証用DBのSQLサービスなど)に対して登録すると、ツール名が衝突します。protoc-gen-go-mcpではWithNamePrefixオプションでツール名に接頭辞を付けられます。

sqlv1mcp.RegisterSQLServiceHandler(s, postgresHandler, runtime.WithNamePrefix("postgres"))
sqlv1mcp.RegisterSQLServiceHandler(s, clickhouseHandler, runtime.WithNamePrefix("clickhouse"))
// postgres_SQLService_Query, clickhouse_SQLService_Query

既定のツール名はプロトコルの完全修飾名(パッケージ名_サービス名_メソッド名)をアンダースコア区切りにしたものです。64文字を超える名前は、10文字のハッシュ値とメソッド名末尾を組み合わせて自動的に短縮されます。これはClaude Desktopが課している64文字上限(対応プロバイダーの中でもっとも厳しい制約)に合わせた仕様です。

よくあるつまずき

インターセプターを迂回してしまう

生成ハンドラをgRPCサーバーへ直接登録する経路では、通常のgRPCリクエストに挟んでいる認可・ロギングなどのインターセプターを迂回します。既存のgRPCサーバーに設定済みのインターセプターに依存した認可チェックがある場合、MCP経由の呼び出しではその処理が実行されない前提で設計する必要があります。

WithExtraPropertiesの名前衝突で値が漏れる

WithExtraPropertiesで追加したプロパティ名が、対象のprotoメッセージが持つフィールド名と衝突すると、渡した値がcontext.Contextに格納されると同時にproto側のメッセージにもそのまま渡ってしまいます。拡張プロパティを定義する際は、対象protoの既存フィールド名と重複しないことを確認してから追加します。

64文字制限で意図しないツール名になる

サービス名やパッケージ名が長いprotoでは、自動生成されたツール名が64文字制限にかかりハッシュ付きの読みにくい名前になることがあります。protoc-gen-go-mcpでは(mcp.v1.tool_name)メソッドオプションで人間が読める名前を明示的に指定できます。指定する値は^[a-z_][a-z0-9_-]{0,63}$のパターンに従う必要があり、違反するとコード生成自体が失敗します。同じサービス内で名前が重複した場合も生成が失敗します。

OpenAI互換モードとの違いに気づかない

protoc-gen-go-mcpは標準MCPのJSON Schemaに加えて、OpenAI互換のスキーマも自動生成します。標準MCPはadditionalProperties・anyOf・oneOfをフルに使ったJSON Schemaで、マップ型はJSONオブジェクトとして表現されます。OpenAI互換モードはこれらの表現を使わない制限版で、マップ型はキーと値のペアの配列に変換され、Struct等のwell-knownな型はJSON文字列にエンコードされ、すべてのフィールドがnullable扱いの必須項目になります。

Claude向けに実装したつもりが誤ってOpenAI互換ハンドラを登録していると、複雑な型を持つフィールドの表現がクライアント側の期待と食い違います。どちらのハンドラを使うかはコード生成時ではなく登録時に選べます。

testdatamcp.RegisterTestServiceHandler(s, &srv)        // 標準MCP
testdatamcp.RegisterTestServiceHandlerOpenAI(s, &srv)  // OpenAI互換

プロバイダーを引数で渡すRegisterTestServiceHandlerWithProviderでも切り替えられます。実装レビューでは、登録関数の末尾にOpenAIが付いていないかを確かめます。

int64フィールドが文字列として返る

protoc-gen-go-mcpのJSON Schemaマッピングでは、int64・uint64系の型はJSON Schema上string型になります。これはJSONが64ビット整数を精度を落とさず表現できないための仕様で、バグではありません。Claudeが返ってきた値を数値として扱う前提のコードを書くと、型が食い違います。

まとめ

既存のgRPCサーバー群を早く試すならprotoc-gen-go-mcpのForwardTo*Client系関数、Prompts・Resources・ElicitationやGo以外の言語まで含めて設計するならprotoc-gen-mcpが選択肢になります。どちらを選んでも、更新系のRPCは生成側の注釈だけに頼らず、Claude Code側のaskやdenyルールで二重に止める構成が堅実です。

接続方法の全体像はClaude Code MCP設定ガイドにまとめています。

この記事を共有:XはてブLinkedIn