Go SDK for developing mcpd middleware plugins.
This SDK provides Go types and gRPC interfaces for building middleware plugins that integrate with mcpd.
Plugins can process HTTP requests and responses, implementing capabilities like authentication, rate limiting,
content transformation, and observability.
go get github.com/mozilla-ai/mcpd-plugins-sdk-go@latestThen in your project:
go mod tidyThe SDK provides two approaches: with helpers (recommended) for convenience, and explicit for full control.
The SDK provides the Serve() helper and BasePlugin struct to minimize boilerplate:
package main
import (
"context"
"log"
pluginv1 "github.com/mozilla-ai/mcpd-plugins-sdk-go/pkg/plugins/v1/plugins"
"google.golang.org/protobuf/types/known/emptypb"
)
type MyPlugin struct {
pluginv1.BasePlugin // Provides sensible defaults for all methods.
}
func (p *MyPlugin) GetMetadata(ctx context.Context, _ *emptypb.Empty) (*pluginv1.Metadata, error) {
return &pluginv1.Metadata{
Name: "my-plugin",
Version: "1.0.0",
Description: "Example plugin that does something useful",
}, nil
}
func (p *MyPlugin) GetCapabilities(ctx context.Context, _ *emptypb.Empty) (*pluginv1.Capabilities, error) {
return &pluginv1.Capabilities{
Flows: []pluginv1.Flow{pluginv1.FlowRequest},
}, nil
}
func (p *MyPlugin) HandleRequest(ctx context.Context, req *pluginv1.HTTPRequest) (*pluginv1.HTTPResponse, error) {
// Custom request processing logic here.
return &pluginv1.HTTPResponse{
Continue: true,
StatusCode: 0,
Headers: req.Headers,
Body: req.Body,
}, nil
}
func main() {
if err := pluginv1.Serve(&MyPlugin{}); err != nil {
log.Fatal(err)
}
}What BasePlugin provides:
CheckHealth()- returns OKCheckReady()- returns OKConfigure(),Stop()- no-opsHandleRequest(),HandleResponse()- pass through unchanged
Override only the methods you need!
For full control over the server lifecycle:
package main
import (
"context"
"flag"
"log"
"net"
"os"
pluginv1 "github.com/mozilla-ai/mcpd-plugins-sdk-go/pkg/plugins/v1/plugins"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
)
type MyPlugin struct {
pluginv1.UnimplementedPluginServer
}
func (p *MyPlugin) GetMetadata(ctx context.Context, _ *emptypb.Empty) (*pluginv1.Metadata, error) {
return &pluginv1.Metadata{
Name: "my-plugin",
Version: "1.0.0",
Description: "Example plugin",
}, nil
}
func (p *MyPlugin) GetCapabilities(ctx context.Context, _ *emptypb.Empty) (*pluginv1.Capabilities, error) {
return &pluginv1.Capabilities{
Flows: []pluginv1.Flow{pluginv1.FlowRequest},
}, nil
}
func (p *MyPlugin) CheckHealth(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (p *MyPlugin) CheckReady(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (p *MyPlugin) Configure(ctx context.Context, cfg *pluginv1.PluginConfig) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (p *MyPlugin) Stop(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (p *MyPlugin) HandleRequest(ctx context.Context, req *pluginv1.HTTPRequest) (*pluginv1.HTTPResponse, error) {
return &pluginv1.HTTPResponse{
Continue: true,
Headers: req.Headers,
Body: req.Body,
}, nil
}
func (p *MyPlugin) HandleResponse(ctx context.Context, resp *pluginv1.HTTPResponse) (*pluginv1.HTTPResponse, error) {
return &pluginv1.HTTPResponse{
Continue: true,
StatusCode: resp.StatusCode,
Headers: resp.Headers,
Body: resp.Body,
}, nil
}
func main() {
var address, network string
flag.StringVar(&address, "address", "", "gRPC address (socket path for unix, host:port for tcp)")
flag.StringVar(&network, "network", "unix", "Network type (unix or tcp)")
flag.Parse()
if address == "" {
log.Fatal("--address flag is required")
}
lis, err := net.Listen(network, address)
if err != nil {
log.Fatalf("failed to listen on %s %s: %v", network, address, err)
}
if network == "unix" {
defer func() { _ = os.Remove(address) }()
}
grpcServer := grpc.NewServer()
pluginv1.RegisterPluginServer(grpcServer, &MyPlugin{})
log.Printf("Plugin server listening on %s %s", network, address)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}Following Kubernetes conventions (e.g., corev1, appsv1), this SDK uses pluginv1 as the recommended import alias:
import pluginv1 "github.com/mozilla-ai/mcpd-plugins-sdk-go/pkg/plugins/v1/plugins"The SDK follows the versioning of mcpd-proto:
- API Version:
plugins/v1/(in proto repo) maps topkg/plugins/v1/plugins/(in SDK) - Release Version: Proto repo tags like
v0.0.1,v0.0.2, etc. - SDK Version: This repo's tags track SDK releases and may differ from proto versions
Current proto version: v0.0.2
mcpd-plugins-sdk-go/
├── README.md # This file.
├── LICENSE # Apache 2.0 license.
├── Makefile # Proto fetching and code generation.
├── go.mod # Go module definition.
├── go.sum # Dependency checksums.
├── .gitignore # Ignores tmp/ directory.
├── tmp/ # Downloaded protos (gitignored).
└── pkg/
└── plugins/
└── v1/
└── plugins/
├── base.go # BasePlugin helper.
├── server.go # Serve() helper.
├── plugin.pb.go # Generated protobuf types.
└── plugin_grpc.pb.go # Generated gRPC service.
- Go 1.25.1 or later
- protoc (Protocol Buffer Compiler)
- protoc-gen-go and protoc-gen-go-grpc plugins
Install protoc plugins:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestThe SDK uses a Makefile-based approach to fetch proto definitions and generate Go code:
Fetch protos and generate:
make all
go mod tidyUpdate to a new proto version:
- Edit
PROTO_VERSIONin the Makefile (e.g.,v0.0.2) - Run
make clean all - Run
go mod tidy - Commit the updated generated files
Run linter:
make lintClean generated files:
make cleanNote: The clean target only removes generated .pb.go files, preserving helper files like base.go and server.go.
The SDK follows gRPC Health Checking Protocol conventions:
rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty);
rpc CheckReady(google.protobuf.Empty) returns (google.protobuf.Empty);Apache 2.0 - See LICENSE file for details.
This is an early PoC. Contribution guidelines coming soon.