internal/website/docs/guides/ai-native-services.md
This guide walks you through building a Go Micro service that is AI-native from the start — meaning AI agents can discover, understand, and call your service automatically via the Model Context Protocol (MCP).
A task management service with full CRUD operations that:
go install go-micro.dev/v5/cmd/[email protected]
micro new tasks
cd tasks
Design your request/response types with description tags. These tags become parameter descriptions that agents read:
package main
import "context"
// Request types with description tags for AI agents
type Task struct {
ID string `json:"id" description:"Unique task identifier"`
Title string `json:"title" description:"Short task title (max 100 chars)"`
Description string `json:"description" description:"Detailed task description"`
Status string `json:"status" description:"Task status: todo, in_progress, or done"`
Assignee string `json:"assignee,omitempty" description:"Username of assigned person"`
}
type CreateRequest struct {
Title string `json:"title" description:"Task title (required, max 100 chars)"`
Description string `json:"description" description:"Detailed description of the task"`
Assignee string `json:"assignee,omitempty" description:"Username to assign the task to"`
}
type CreateResponse struct {
Task *Task `json:"task" description:"The newly created task"`
}
type GetRequest struct {
ID string `json:"id" description:"Task ID to retrieve"`
}
type GetResponse struct {
Task *Task `json:"task" description:"The requested task"`
}
type ListRequest struct {
Status string `json:"status,omitempty" description:"Filter by status: todo, in_progress, done (optional)"`
}
type ListResponse struct {
Tasks []*Task `json:"tasks" description:"List of matching tasks"`
}
type UpdateRequest struct {
ID string `json:"id" description:"Task ID to update"`
Status string `json:"status" description:"New status: todo, in_progress, or done"`
}
type UpdateResponse struct {
Task *Task `json:"task" description:"The updated task"`
}
type DeleteRequest struct {
ID string `json:"id" description:"Task ID to delete"`
}
type DeleteResponse struct {
Deleted bool `json:"deleted" description:"True if the task was deleted"`
}
Key point: The description tags are parsed by the MCP gateway and shown to agents as parameter documentation. Be specific about formats, constraints, and valid values.
Write standard Go doc comments on every handler method. The MCP gateway extracts these automatically at registration time.
type TaskService struct {
tasks map[string]*Task
nextID int
}
// Create creates a new task with the given title and description.
// Returns the created task with a generated ID and initial status of "todo".
//
// @example {"title": "Fix login bug", "description": "Users can't log in with SSO", "assignee": "alice"}
func (t *TaskService) Create(ctx context.Context, req *CreateRequest, rsp *CreateResponse) error {
t.nextID++
task := &Task{
ID: fmt.Sprintf("task-%d", t.nextID),
Title: req.Title,
Description: req.Description,
Status: "todo",
Assignee: req.Assignee,
}
t.tasks[task.ID] = task
rsp.Task = task
return nil
}
// Get retrieves a task by its unique ID.
// Returns an error if the task does not exist.
//
// @example {"id": "task-1"}
func (t *TaskService) Get(ctx context.Context, req *GetRequest, rsp *GetResponse) error {
task, ok := t.tasks[req.ID]
if !ok {
return fmt.Errorf("task %s not found", req.ID)
}
rsp.Task = task
return nil
}
// List returns all tasks, optionally filtered by status.
// If no status filter is provided, returns all tasks.
// Valid status values: "todo", "in_progress", "done".
//
// @example {"status": "todo"}
func (t *TaskService) List(ctx context.Context, req *ListRequest, rsp *ListResponse) error {
for _, task := range t.tasks {
if req.Status == "" || task.Status == req.Status {
rsp.Tasks = append(rsp.Tasks, task)
}
}
return nil
}
// Update changes the status of an existing task.
// Valid status transitions: todo -> in_progress -> done.
// Returns an error if the task does not exist.
//
// @example {"id": "task-1", "status": "in_progress"}
func (t *TaskService) Update(ctx context.Context, req *UpdateRequest, rsp *UpdateResponse) error {
task, ok := t.tasks[req.ID]
if !ok {
return fmt.Errorf("task %s not found", req.ID)
}
task.Status = req.Status
rsp.Task = task
return nil
}
// Delete removes a task by ID. This action is irreversible.
// Returns an error if the task does not exist.
//
// @example {"id": "task-1"}
func (t *TaskService) Delete(ctx context.Context, req *DeleteRequest, rsp *DeleteResponse) error {
if _, ok := t.tasks[req.ID]; !ok {
return fmt.Errorf("task %s not found", req.ID)
}
delete(t.tasks, req.ID)
rsp.Deleted = true
return nil
}
What agents see: Each method's doc comment becomes the tool description. The @example tag provides a valid JSON input that agents can reference.
Use server.WithEndpointScopes() to control which agents can call which endpoints:
package main
import (
"context"
"fmt"
"go-micro.dev/v5"
"go-micro.dev/v5/server"
)
func main() {
service := micro.New("tasks", micro.Address(":8081"))
service.Init()
service.Handle(
&TaskService{tasks: make(map[string]*Task)},
// Read operations: any authenticated agent
server.WithEndpointScopes("TaskService.Get", "tasks:read"),
server.WithEndpointScopes("TaskService.List", "tasks:read"),
// Write operations: agents with write scope
server.WithEndpointScopes("TaskService.Create", "tasks:write"),
server.WithEndpointScopes("TaskService.Update", "tasks:write"),
// Delete: admin only
server.WithEndpointScopes("TaskService.Delete", "tasks:admin"),
)
service.Run()
}
There are three ways to run your service with MCP enabled.
micro run (Recommended for Development)micro run
Your service is now available at:
WithMCP (One-Liner for Library Users)Add MCP to your service with a single option:
import "go-micro.dev/v5/gateway/mcp"
func main() {
service := micro.New("tasks",
mcp.WithMCP(":3000"), // MCP gateway starts automatically
)
service.Init()
// register handlers...
service.Run()
}
This starts the MCP gateway on port 3000 alongside your service. All registered handlers are automatically exposed as MCP tools.
For production, run the MCP gateway as a separate process that discovers all services:
micro-mcp-gateway \
--registry consul \
--registry-address consul:8500 \
--address :3000 \
--auth jwt \
--rate-limit 10
See the standalone gateway docs for more.
# Start MCP server for Claude Code (stdio transport)
micro mcp serve
Add to your Claude Code config:
{
"mcpServers": {
"tasks": {
"command": "micro",
"args": ["mcp", "serve"]
}
}
}
Now Claude can manage your tasks:
You: "Create a task to fix the login bug and assign it to alice"
Claude: [calls tasks.TaskService.Create with {"title": "Fix login bug", ...}]
Created task-1: "Fix login bug" assigned to alice.
You: "What tasks does alice have?"
Claude: [calls tasks.TaskService.List]
Alice has 1 task: "Fix login bug" (status: todo)
You: "Mark it as in progress"
Claude: [calls tasks.TaskService.Update with {"id": "task-1", "status": "in_progress"}]
Updated task-1 to "in_progress".
For real-time bidirectional communication (e.g., streaming agent frameworks):
const ws = new WebSocket("ws://localhost:3000/mcp/ws", {
headers: { "Authorization": "Bearer <token>" }
});
// JSON-RPC 2.0 over WebSocket
ws.send(JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/list",
params: {}
}));
Use the CLI to verify tools work:
# List all available tools
micro mcp list
# Test a specific tool
micro mcp test tasks.TaskService.Create
# Generate documentation
micro mcp docs
# Export for LangChain
micro mcp export --format langchain
Enable OpenTelemetry tracing to see every agent tool call as a distributed trace:
import (
"go.opentelemetry.io/otel"
"go-micro.dev/v5/gateway/mcp"
)
go mcp.ListenAndServe(":3000", mcp.Options{
Registry: service.Options().Registry,
TraceProvider: otel.GetTracerProvider(),
})
Each tool call generates a span with attributes:
mcp.tool.name — which tool was calledmcp.transport — HTTP, WebSocket, or stdiomcp.account.id — who called itmcp.auth.allowed — whether it was permittedTrace context is propagated downstream via metadata headers (Mcp-Trace-Id, Mcp-Tool-Name, Mcp-Account-Id), so you get full distributed traces from agent through gateway to service.
If your service needs to call AI models directly:
import (
"go-micro.dev/v5/ai"
_ "go-micro.dev/v5/ai/anthropic"
)
m := ai.New("anthropic",
ai.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")),
)
resp, err := m.Generate(ctx, &ai.Request{
Prompt: "Summarize these tasks: " + taskJSON,
SystemPrompt: "You are a project manager assistant",
})
Before shipping an AI-native service:
@example tag with realistic JSON inputdescription tagsmicro mcp test to verify tools work1. You write Go comments on handler methods
2. micro registers the handler and extracts docs via go/ast
3. Docs are stored in the service registry as endpoint metadata
4. MCP gateway discovers services via the registry
5. Gateway generates JSON Schema tools with descriptions
6. AI agents query the tools endpoint and see rich descriptions
7. Agents call tools via JSON-RPC, gateway routes to your handler