README
ΒΆ
protoc-gen-go-mcp
protoc-gen-go-mcp is a Protocol Buffers compiler plugin that generates Model Context Protocol (MCP) servers for your gRPC APIs.
It generates *.pb.mcp.go files for each protobuf service, enabling you to delegate handlers directly to gRPC servers or clients. Under the hood, MCP uses JSON Schema for tool inputsβprotoc-gen-go-mcp auto-generates these schemas from your method input descriptors.
β οΈ Currently supports mark3labs/mcp-go as the MCP server runtime. Future support is planned for official Go SDKs and additional runtimes.
β¨ Features
- π Auto-generates MCP handlers from your
.protoservices - π§ AI-Friendly Schemas - Clean JSON schemas using modern JSON Schema 2020-12 specification
- π JSON Schema $defs Support - Message types defined once in
$defsand referenced via$ref - π Recursive Structure Support - Handles circular and recursive message references with cycle detection
- π Advanced OneOf Support - Handles protobuf oneOf with discriminated unions and automatic transformation
- π¬ Field Comments as Descriptions - Preserves protobuf comments in tool schemas (including nested messages)
- π¦ JSON Schema Generation for method inputs with proper validation
- π Flexible Integration - Wire up to gRPC servers or clients
- π§© Easy
bufIntegration - β‘ Well-Known Types - Proper handling of Google protobuf well-known types
- π― Gemini Compliant - Tool names follow Google's restrictions
- π‘οΈ Production Ready - Robust error handling, no panics, safe type assertions
π§ Usage
Generate code
Add entry to your buf.gen.yaml:
...
plugins:
- local:
- go
- run
- github.com/shaders/protoc-gen-go-mcp/cmd/protoc-gen-go-mcp@latest
out: ./gen/go
opt: paths=source_relative
You need to generate the standard *.pb.go files as well. protoc-gen-go-mcp by defaults uses a separate subfolder {$servicename}mcp, and imports the *pb.go files - similar to connectrpc-go.
After running buf generate, you will see a new folder for each package with protobuf Service definitions:
tree pkg/testdata/gen/
gen
βββ go
βββ testdata
βββ test_service.pb.go
βββ testdataconnect/
β βββ test_service.connect.go
βββ testdatamcp/
βββ test_service.pb.mcp.go
Advanced Schema Generation
JSON Schema Structure
protoc-gen-go-mcp generates modern JSON schemas with $defs for better organization:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"user": {"$ref": "#/$defs/User"},
"settings": {"$ref": "#/$defs/Settings"}
},
"required": ["user"],
"$defs": {
"User": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "User's full name"},
"email": {"type": "string"}
},
"required": ["name", "email"]
},
"Settings": {
"type": "object",
"properties": {
"theme": {"type": "string"}
},
"required": []
}
}
}
OneOf Support with Discriminated Unions
protoc-gen-go-mcp generates AI-friendly schemas for protobuf oneOf fields using discriminated unions with object_type field:
// Proto definition
message Item {
oneof item_type {
Product product = 1;
Service service = 2;
}
}
Generates schema:
{
"item_typeOneOfType": {
"oneOf": [
{
"type": "object",
"properties": {
"object_type": {"const": "product", "type": "string"},
"price": {"type": "number"}
},
"required": ["object_type"]
},
{
"type": "object",
"properties": {
"object_type": {"const": "service", "type": "string"},
"duration": {"type": "string"}
},
"required": ["object_type"]
}
]
}
}
Recursive Structure Support
Handles complex recursive structures without stack overflow:
message FilterExpression {
message Operation {
repeated FilterExpression operands = 1; // Recursive reference
}
oneof kind {
Operation operation = 1;
string value = 2;
}
}
Annotation: zero_based_pagination
If your gRPC API uses 0-based pagination (page=0 is the first page), LLM clients tend to send page=1 for the first page anyway. The (mcp.options.zero_based_pagination) = true annotation lets you keep your protobuf 0-based for production gRPC traffic while presenting an LLM-friendly 1-based view through the MCP wrapper.
import "mcp/options/options.proto";
message ListItemsRequest {
// Page number (0-based).
int32 page = 1 [(mcp.options.zero_based_pagination) = true];
}
This makes the generator:
- Set
"minimum": 1on the field in the generated JSON schema and rewrite the description to read as 1-based. - Decrement the value by 1 inside the generated MCP handler before forwarding the request to gRPC. Values <= 0 are clamped to 0 so a non-compliant client never produces a negative index.
The annotation is defined in proto/mcp/options/options.proto and the Go counterpart in github.com/shaders/protoc-gen-go-mcp/pkg/options. Other clients (REST, control panels, regular gRPC consumers) see the protobuf untouched.
Wiring up with gRPC client
It is also possible to directly forward MCP tool calls to gRPC clients. Follows gRPC-Gateway pattern. Connect to gRPC server, then:
testdatamcp.ForwardToTestServiceClient(mcpServer, myGrpcClient)
This directly connects the MCP handler to the gRPC client, requiring zero boilerplate. Each RPC method in your protobuf service becomes an MCP tool.
Extra properties
It's possible to add extra properties to MCP tools, that are not in the proto. These are written into context.
// 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.ForwardToTestServiceClient(mcpServer, client, option)
π§ͺ Development & Testing
Quick Commands
# Run all tests
task test
# Build the binary
task build
# Install to GOPATH/bin
task install
# Update golden test files
task generate-golden
# View all available commands
task --list
Manual Commands
# Run tests
go test ./...
# Update golden files
./tools/update-golden.sh
# Or manually for specific packages
go test ./pkg/generator -update-golden
# Build from source
go build -o protoc-gen-go-mcp ./cmd/protoc-gen-go-mcp
# Run integration tests (requires OPENAI_API_KEY)
# Either export OPENAI_API_KEY or add to .env file
export OPENAI_API_KEY="your-api-key"
task integrationtest
Development Workflow
# Format code
task fmt
# Run linting
task lint
# Generate protobuf files for testdata
task generate
Golden File Testing
The generator uses golden file testing to ensure output consistency. The test structure in pkg/generator/testdata/ is organized as:
testdata/
βββ *.proto # Input proto files (just drop new ones here!)
βββ buf.gen.yaml # Generates into actual/
βββ buf.gen.golden.yaml # Generates into golden/
βββ actual/ # Current generated output (committed to track changes)
βββ golden/ # Expected output (committed as test baseline)
To add new tests: Simply drop a .proto file in pkg/testdata/proto/testdata/ and run the tests. The framework automatically:
- Discovers all
.protofiles - Generates code using
task generate - Compares with expected output
- Creates missing golden files on first run
To update golden files after generator changes:
# Update all golden files
task generate-golden
# Or update specific package
go test ./pkg/generator -update-golden
ποΈ Recent Improvements
v0.2.0 (Latest)
- JSON Schema 2020-12: Modern schema generation with
$defsand$ref - Cycle Detection: Prevents stack overflow with recursive message structures
- Robust Error Handling: Replaced panics with proper error propagation
- Code Quality: Eliminated code duplication, added safe type assertions
- Performance: Optimized memory allocation and string operations
- Documentation: Comprehensive documentation for all public APIs
β οΈ Limitations
- Tool name mangling for long RPC names: If the full RPC name exceeds 64 characters, the head of the tool name is mangled to fit.
- Streaming RPCs are not yet supported (unary only)