Migration Guides
Step-by-step guides for migrating to Go Micro from other frameworks.
Migration Guides
Step-by-step guides for migrating to Go Micro from other frameworks.
Available Guides
Coming Soon
We’re working on additional migration guides:
- From go-kit - Migrate from Go kit microservices framework
- From Standard Library - Upgrade from net/http and net/rpc
- From Gin/Echo - Transition from HTTP-only frameworks
- From Micro v3 - Upgrade from older Go Micro versions
Why Migrate to Go Micro?
- Pluggable Architecture - Swap components without changing code
- Zero Configuration - Works out of the box with sensible defaults
- Progressive Enhancement - Start simple, add complexity when needed
- Unified Abstractions - Registry, transport, broker, store all integrated
- Active Development - Regular updates and community support
Need Help?
1 - Add MCP to Existing Services
Add MCP to Existing Services
You have a working go-micro service and want to make it accessible to AI agents via MCP. This guide covers the three approaches, from simplest to most flexible.
Option 1: One-Line Setup (Recommended)
Add a single option to your service constructor:
import "go-micro.dev/v6/gateway/mcp"
func main() {
service := micro.NewService("myservice",
mcp.WithMCP(":3001"), // Add this line
)
service.Init()
// ... register handlers as before
service.Run()
}
That’s it. Your service now exposes all registered handlers as MCP tools at http://localhost:3001/mcp/tools.
Option 2: Standalone MCP Gateway
If you want the MCP gateway to run separately from your services (e.g., in production with multiple services):
import "go-micro.dev/v6/gateway/mcp"
// Start MCP gateway alongside your service
go mcp.ListenAndServe(":3001", mcp.Options{
Registry: service.Options().Registry,
})
This discovers all services in the registry and exposes them as tools.
Option 3: CLI (No Code Changes)
If you don’t want to modify your service code at all:
# Start your service normally
go run .
# In another terminal, start the MCP gateway
micro mcp serve --address :3001
The CLI approach uses the same registry to discover running services.
Improving Agent Experience
Once MCP is enabled, improve how agents interact with your service by adding documentation.
Before:
func (s *Users) Get(ctx context.Context, req *GetRequest, rsp *GetResponse) error {
After:
// Get retrieves a user by their unique ID. Returns the full user profile
// including email, display name, and account status.
//
// @example {"id": "user-123"}
func (s *Users) Get(ctx context.Context, req *GetRequest, rsp *GetResponse) error {
The MCP gateway automatically extracts these comments and presents them to agents as tool descriptions.
Step 2: Add Struct Tag Descriptions
type GetRequest struct {
ID string `json:"id" description:"User ID in UUID format"`
}
type GetResponse struct {
Name string `json:"name" description:"Display name"`
Email string `json:"email" description:"Primary email address"`
Active bool `json:"active" description:"Whether the account is active"`
}
Step 3: Add Auth Scopes (Optional)
Restrict which agents can call which endpoints:
handler := service.Server().NewHandler(
new(Users),
server.WithEndpointScopes("Users.Delete", "users:admin"),
server.WithEndpointScopes("Users.Get", "users:read"),
)
Then configure the MCP gateway with auth:
mcp.ListenAndServe(":3001", mcp.Options{
Registry: service.Options().Registry,
Auth: authProvider,
Scopes: map[string][]string{
"myservice.Users.Delete": {"users:admin"},
"myservice.Users.Get": {"users:read"},
},
})
Using with Claude Code
Once your service is running with MCP, connect it to Claude Code:
# Option A: stdio transport (recommended for local dev)
micro mcp serve
# Option B: Add to Claude Code settings
{
"mcpServers": {
"my-services": {
"command": "micro",
"args": ["mcp", "serve"]
}
}
}
Verify It Works
# List all tools the MCP gateway exposes
curl http://localhost:3001/mcp/tools | jq
# Test a specific tool
curl -X POST http://localhost:3001/mcp/call \
-H 'Content-Type: application/json' \
-d '{"tool": "myservice.Users.Get", "arguments": {"id": "user-123"}}'
What Doesn’t Need to Change
- Handler signatures - No changes needed to your RPC handlers
- Proto definitions - Existing protos work as-is
- Client code - Services calling each other still use the normal RPC client
- Tests - Existing tests continue to work
- Deployment - Add a port for MCP, everything else stays the same
Next Steps
2 - Migrating from gRPC
Step-by-step guide to migrating existing gRPC services to Go Micro.
Why Migrate?
Go Micro adds:
- Built-in service discovery
- Client-side load balancing
- Pub/sub messaging
- Multiple transport options
- Unified tooling
You keep:
- Your proto definitions
- gRPC performance (via gRPC transport)
- Type safety
- Streaming support
Migration Strategy
Phase 1: Parallel Running
Run Go Micro alongside existing gRPC services
Phase 2: Gradual Migration
Migrate services one at a time
Phase 3: Complete Migration
All services on Go Micro
Step-by-Step Migration
1. Existing gRPC Service
// proto/hello.proto
syntax = "proto3";
package hello;
option go_package = "./proto;hello";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
// Original gRPC server
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "myapp/proto"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + req.Name}, nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Fatal(s.Serve(lis))
}
2. Generate Go Micro Code
Update your proto generation:
# Install protoc-gen-micro
go install go-micro.dev/v6/cmd/protoc-gen-micro@latest
# Generate both gRPC and Go Micro code
protoc --proto_path=. \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
--micro_out=. --micro_opt=paths=source_relative \
proto/hello.proto
This generates:
hello.pb.go - Protocol Buffers typeshello_grpc.pb.go - gRPC client/server (keep for compatibility)hello.pb.micro.go - Go Micro client/server (new)
3. Migrate Server to Go Micro
// Go Micro server
package main
import (
"context"
"go-micro.dev/v6"
"go-micro.dev/v6/server"
pb "myapp/proto"
)
type Greeter struct{}
func (s *Greeter) SayHello(ctx context.Context, req *pb.HelloRequest, rsp *pb.HelloReply) error {
rsp.Message = "Hello " + req.Name
return nil
}
func main() {
svc := micro.NewService("greeter",
)
svc.Init()
pb.RegisterGreeterHandler(svc.Server(), new(Greeter))
if err := svc.Run(); err != nil {
log.Fatal(err)
}
}
Key differences:
- No manual port binding (Go Micro handles it)
- Automatic service registration
- Returns error, response via pointer parameter
4. Migrate Client
Original gRPC client:
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := pb.NewGreeterClient(conn)
rsp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "John"})
Go Micro client:
svc := micro.NewService("client")
svc.Init()
client := pb.NewGreeterService("greeter", svc.Client())
rsp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "John"})
Benefits:
- No hardcoded addresses
- Automatic service discovery
- Client-side load balancing
- Automatic retries
5. Keep gRPC Transport (Optional)
Use gRPC as the underlying transport:
import (
"go-micro.dev/v6"
"go-micro.dev/v6/client"
"go-micro.dev/v6/server"
grpcclient "go-micro.dev/v6/client/grpc"
grpcserver "go-micro.dev/v6/server/grpc"
)
svc := micro.NewService("greeter",
micro.Client(grpcclient.NewClient()),
micro.Server(grpcserver.NewServer()),
)
This gives you:
- gRPC performance
- Go Micro features (discovery, load balancing)
- Compatible with existing gRPC clients
Streaming Migration
Original gRPC Streaming
service Greeter {
rpc StreamHellos (stream HelloRequest) returns (stream HelloReply) {}
}
func (s *server) StreamHellos(stream pb.Greeter_StreamHellosServer) error {
for {
req, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
stream.Send(&pb.HelloReply{Message: "Hello " + req.Name})
}
}
Go Micro Streaming
func (s *Greeter) StreamHellos(ctx context.Context, stream server.Stream) error {
for {
var req pb.HelloRequest
if err := stream.Recv(&req); err != nil {
return err
}
if err := stream.Send(&pb.HelloReply{Message: "Hello " + req.Name}); err != nil {
return err
}
}
}
Service Discovery Migration
Before (gRPC with Consul)
// Manually register with Consul
config := api.DefaultConfig()
config.Address = "consul:8500"
client, _ := api.NewClient(config)
reg := &api.AgentServiceRegistration{
ID: "greeter-1",
Name: "greeter",
Address: "localhost",
Port: 50051,
}
client.Agent().ServiceRegister(reg)
// Cleanup on shutdown
defer client.Agent().ServiceDeregister("greeter-1")
After (Go Micro)
import "go-micro.dev/v6/registry/consul"
reg := consul.NewConsulRegistry()
svc := micro.NewService("greeter",
micro.Registry(reg),
)
// Registration automatic on Run()
// Deregistration automatic on shutdown
svc.Run()
Load Balancing Migration
Before (gRPC with custom LB)
// Need external load balancer or custom implementation
// Example: round-robin DNS, Envoy, nginx
After (Go Micro)
import "go-micro.dev/v6/selector"
// Client-side load balancing built-in
svc := micro.NewService("greeter",
micro.Selector(selector.NewSelector(
selector.SetStrategy(selector.RoundRobin),
)),
)
Gradual Migration Path
1. Start with New Services
New services use Go Micro, existing services stay on gRPC.
// New Go Micro service can call gRPC services
// Configure gRPC endpoints directly
grpcConn, _ := grpc.Dial("old-service:50051", grpc.WithInsecure())
oldClient := pb.NewOldServiceClient(grpcConn)
2. Migrate Read-Heavy Services First
Services with many clients benefit most from service discovery.
3. Migrate Services with Fewest Dependencies
Leaf services are easier to migrate.
4. Add Adapters if Needed
// gRPC adapter for Go Micro service
type GRPCAdapter struct {
microClient pb.GreeterService
}
func (a *GRPCAdapter) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
return a.microClient.SayHello(ctx, req)
}
// Register adapter as gRPC server
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &GRPCAdapter{microClient: microClient})
Checklist
Common Issues
Port Already in Use
gRPC: Manual port management
lis, _ := net.Listen("tcp", ":50051")
Go Micro: Automatic or explicit
// Let Go Micro choose
svc := micro.NewService("greeter")
// Or specify
svc := micro.NewService("greeter",
micro.Address(":50051"),
)
Service Not Found
Check registry:
# Consul
curl http://localhost:8500/v1/catalog/services
# Or use micro CLI
micro services
Different Serialization
gRPC uses protobuf by default. Go Micro supports multiple codecs.
Ensure both use protobuf:
import "go-micro.dev/v6/codec/proto"
svc := micro.NewService("greeter",
micro.Codec("application/protobuf", proto.Marshaler{}),
)
| Scenario | gRPC | Go Micro (HTTP) | Go Micro (gRPC) |
|---|
| Simple RPC | ~25k req/s | ~20k req/s | ~24k req/s |
| With Discovery | N/A | ~18k req/s | ~22k req/s |
| Streaming | ~30k msg/s | ~15k msg/s | ~28k msg/s |
Go Micro with gRPC transport performs similarly to pure gRPC
Next Steps
Need Help?
3 - Migrating from v5 to v6
v6 is a small, mechanical upgrade. The bulk of it is the Go module path; the
behavioral changes are two, both with a one-line fix.
1. Module path: go-micro.dev/v6
Go puts the major version in the import path, so every import changes:
// before
import "go-micro.dev/v5"
import "go-micro.dev/v5/server"
// after
import "go-micro.dev/v6"
import "go-micro.dev/v6/server"
A repo-wide find/replace does it:
grep -rl 'go-micro.dev/v5' --include='*.go' . \
| xargs sed -i 's|go-micro.dev/v5|go-micro.dev/v6|g'
go mod tidy
Update the CLI too:
go install go-micro.dev/v6/cmd/micro@latest
2. TLS is verified by default
In v5, TLS certificate verification was off by default (you opted in with
MICRO_TLS_SECURE=true). In v6 it is on by default — the safe choice now
that an agent, not just a human on a trusted network, can reach an endpoint.
- Production: nothing to do. Verification is on.
MICRO_TLS_SECURE is gone — remove it; it’s the default now.- Self-signed certs (local/dev): opt out with
MICRO_TLS_INSECURE=true, or
call tls.InsecureConfig() directly.
3. NewService is the service constructor
The service constructor is now symmetric with NewAgent and NewFlow:
service := micro.NewService("greeter", micro.Address(":8080"))
agent := micro.NewAgent("task-mgr", micro.AgentServices("task"))
flow := micro.NewFlow("onboard", micro.FlowTrigger("events.user.created"))
micro.New("greeter", ...) still works as a deprecated alias — no rush,
but prefer NewService.- The old name-less form
micro.NewService(micro.Name("greeter"), ...) is
removed; pass the name positionally: micro.NewService("greeter", ...).
Generated services already use NewService — re-running micro new or
micro run --prompt emits the v6 form.
That’s it
No other API changed. Agents, services, flows, the registry/broker/store
interfaces, MCP, A2A, and x402 all work as they did — just under
go-micro.dev/v6 and secure by default.