Protoc plugin + runtime library for generating MCP (Model Context Protocol) server handlers from gRPC service definitions.
just build # bazelisk build //...
just test-unit # bazelisk test //... (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 conformancetest # Run conformance tests against LLM providers (needs API keys)
just integrationtest # Run all integration tests (needs API keys)
just gazelle # Sync BUILD files from go.modcmd/protoc-gen-go-mcp/ Entry point for protoc plugin
pkg/gen/ Core library (THE important package):
schema.go JSON schema generation from protoreflect descriptors
register.go Dynamic MCP tool registration at runtime
pkg/generator/ Protoc plugin: Go template output, delegates to pkg/gen
pkg/runtime/ MCPServer interface, FixOpenAI, error handling, extra properties
server.go MCPServer interface + Tool/CallToolRequest/CallToolResult types
mark3labs/ Adapter: mark3labs/mcp-go -> runtime.MCPServer
gosdk/ Adapter: modelcontextprotocol/go-sdk -> runtime.MCPServer
pkg/testdata/ Proto files + generated code for testing
conformancetest/ E2E tests against real LLM providers (Gemini, OpenAI, Anthropic)
- Static (codegen):
protoc-gen-go-mcpgenerates*.pb.mcp.gowith pre-computed schemas - Dynamic (runtime):
gen.RegisterService()creates MCP tools from anyprotoreflect.ServiceDescriptorat runtime - no codegen needed. This is the proxy/gateway mode.
// Official go-sdk
raw, s := gosdk.NewServer("name", "1.0") // s is runtime.MCPServer
raw.Run(ctx, &mcp.StdioTransport{})
// mark3labs/mcp-go
raw, s := mark3labs.NewServer("name", "1.0") // s is runtime.MCPServer
server.ServeStdio(raw)testdatamcp.RegisterTestServiceHandler(s, myServiceImpl)
testdatamcp.RegisterTestServiceHandlerOpenAI(s, myServiceImpl)gen.RegisterService(s, serviceDescriptor, handler, gen.RegisterServiceOptions{
Provider: runtime.LLMProviderOpenAI,
NewMessage: func(md protoreflect.MessageDescriptor) proto.Message { ... },
})schema := gen.MessageSchema(msgDescriptor, gen.SchemaOptions{OpenAICompat: true})
standard, openAI := gen.ToolForMethod(methodDescriptor, "description")- Two schema modes: standard MCP and OpenAI-compatible (
gen.SchemaOptions) - OpenAI mode: maps -> arrays of KV pairs, all fields required, additionalProperties: false
- Well-known types (Struct, Value, ListValue) become JSON strings in OpenAI mode
- Tool names > 64 chars get hash-mangled (Claude desktop limit)
pkg/genis fully independent of protoc - works with any protoreflect descriptor- Generated code and runtime are MCP-library-agnostic via runtime.MCPServer interface
- Adapter packages (runtime/mark3labs, runtime/gosdk) bridge to concrete MCP libraries
- Golden test re-runs generator in-process from compiled descriptors, no shell/buf at test time
- Unit tests:
go test ./pkg/...(with -race, always) - Conformance tests:
go test ./conformancetest/(needs API keys, tests skip if missing) - Golden test: in-process generator re-run vs checked-in
gen/go/*.pb.mcp.go - Edge case protos:
pkg/testdata/proto/testdata/edge_cases.proto - Fuzz tests:
pkg/runtime/fix_fuzz_test.go,pkg/gen/schema_fuzz_test.go
Uses buf. Test protos in pkg/testdata/proto/.
After changing protos: just generate.
- Edit proto or generator code
just generate(regenerates test proto Go code + descriptor set)just test-unit(runs unit tests, including golden comparison)