Treating the Model Context Protocol (MCP) as a compilation target for Protocol Buffers. Shift development from "dynamic, messy JSON handling" to a "contract-first, type-safe" paradigm.
- Contract-First: Define your MCP tools and resources in Protocol Buffers.
- Type-Safe: Go code generation ensures your implementation matches the spec.
- Automatic Schemas: JSON Schemas for MCP tools are automatically generated from your Protobuf messages.
- Zero Boilerplate: Focus on your logic, not on JSON-RPC wiring.
- Go (1.22+)
- Protocol Buffers Compiler (
protoc) protoc-gen-go:go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install github.com/coder/protomcp/cmd/protoc-gen-mcp@latestYou will need the api/mcp.proto file to define your services. You can copy it from this repository to your project (e.g., api/mcp.proto or proto/mcp.proto).
Create a .proto file (e.g., agent.proto). Import mcp.proto to access the extensions.
syntax = "proto3";
package agent;
import "api/mcp.proto"; // Adjust path as necessary
option go_package = "github.com/your/repo/agent";
service Agent {
// Define a Tool
// The method comment becomes the tool description.
rpc GetWeather(GetWeatherRequest) returns (mcp.ToolResult) {
option (mcp.tool) = true;
}
// Define a Resource
rpc ReadLogs(ReadLogsRequest) returns (mcp.Content) {
option (mcp.resource_uri) = "logs/{service}/{date}";
}
}
message GetWeatherRequest {
string city = 1 [(mcp.desc) = "The city to get weather for"];
bool include_forecast = 2 [(mcp.desc) = "Include 7-day forecast"];
}
message ReadLogsRequest {
// Fields matching URI parameters will be populated automatically
string service = 1;
string date = 2;
}Run protoc to generate the Go code and the MCP adapter.
protoc --go_out=. --go_opt=paths=source_relative \
--mcp_out=. --mcp_opt=paths=source_relative \
agent.protoImplement the generated interface and start the server.
package main
import (
"context"
"fmt"
"github.com/coder/protomcp/pkg/mcp"
"github.com/coder/protomcp/api"
// Import your generated package
"github.com/your/repo/agent"
)
type AgentImpl struct{}
func (s *AgentImpl) GetWeather(ctx context.Context, req *agent.GetWeatherRequest) (*api.ToolResult, error) {
return &api.ToolResult{
Content: []*api.Content{
{
Type: "text",
Text: fmt.Sprintf("Weather in %s is sunny", req.City),
},
},
}, nil
}
func (s *AgentImpl) ReadLogs(ctx context.Context, req *agent.ReadLogsRequest) (*api.Content, error) {
return &api.Content{
Type: "text",
Text: fmt.Sprintf("Logs for %s on %s...", req.Service, req.Date),
}, nil
}
func main() {
// Create your implementation
impl := &AgentImpl{}
// Wrap it with the generated adapter
adapter := agent.NewAgentAdapter(impl)
// Serve via Stdio (standard for MCP agents)
if err := mcp.ServeStdio(adapter); err != nil {
panic(err)
}
}Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
If you find this repository helpful and would like to support its development, consider making a donation:
Your support helps maintain and improve this collection of development tools and templates. Thank you for contributing to open source!
