e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
411 lines
11 KiB
Markdown
411 lines
11 KiB
Markdown
---
|
|
layout: default
|
|
---
|
|
|
|
# Building AI-Native Services
|
|
|
|
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).
|
|
|
|
## What You'll Build
|
|
|
|
A **task management service** with full CRUD operations that:
|
|
- Exposes every endpoint as an MCP tool automatically
|
|
- Has rich documentation that agents can read
|
|
- Includes auth scopes for write operations
|
|
- Works with Claude Code, the agent playground, and any MCP client
|
|
|
|
## Prerequisites
|
|
|
|
```bash
|
|
go install go-micro.dev/v6/cmd/micro@latest
|
|
```
|
|
|
|
## Step 1: Create the Service
|
|
|
|
```bash
|
|
micro new tasks
|
|
cd tasks
|
|
```
|
|
|
|
## Step 2: Define Your Types
|
|
|
|
Design your request/response types with `description` tags. These tags become parameter descriptions that agents read:
|
|
|
|
```go
|
|
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.
|
|
|
|
## Step 3: Write the Handler with Doc Comments
|
|
|
|
Write standard Go doc comments on every handler method. The MCP gateway extracts these automatically at registration time.
|
|
|
|
```go
|
|
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.
|
|
|
|
## Step 4: Register with Scopes
|
|
|
|
Use `server.WithEndpointScopes()` to control which agents can call which endpoints:
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"go-micro.dev/v6"
|
|
"go-micro.dev/v6/server"
|
|
)
|
|
|
|
func main() {
|
|
service := micro.NewService("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()
|
|
}
|
|
```
|
|
|
|
## Step 5: Run with MCP
|
|
|
|
There are three ways to run your service with MCP enabled.
|
|
|
|
### Option A: `micro run` (Recommended for Development)
|
|
|
|
```bash
|
|
micro run
|
|
```
|
|
|
|
Your service is now available at:
|
|
- **Web Dashboard:** http://localhost:8080/
|
|
- **Agent Playground:** http://localhost:8080/agent
|
|
- **MCP Tools:** http://localhost:8080/mcp/tools
|
|
- **WebSocket:** ws://localhost:3000/mcp/ws
|
|
- **API Gateway:** http://localhost:8080/api/tasks/TaskService/Create
|
|
|
|
### Option B: `WithMCP` (One-Liner for Library Users)
|
|
|
|
Add MCP to your service with a single option:
|
|
|
|
```go
|
|
import "go-micro.dev/v6/gateway/mcp"
|
|
|
|
func main() {
|
|
service := micro.NewService("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.
|
|
|
|
### Option C: Standalone MCP Gateway
|
|
|
|
For production, run the MCP gateway as a separate process that discovers all services:
|
|
|
|
```bash
|
|
micro-mcp-gateway \
|
|
--registry consul \
|
|
--registry-address consul:8500 \
|
|
--address :3000 \
|
|
--auth jwt \
|
|
--rate-limit 10
|
|
```
|
|
|
|
See the [standalone gateway docs](../deployment.md) for more.
|
|
|
|
### Use with Claude Code
|
|
|
|
```bash
|
|
# Start MCP server for Claude Code (stdio transport)
|
|
micro mcp serve
|
|
```
|
|
|
|
Add to your Claude Code config:
|
|
|
|
```json
|
|
{
|
|
"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".
|
|
```
|
|
|
|
### Use with WebSocket Clients
|
|
|
|
For real-time bidirectional communication (e.g., streaming agent frameworks):
|
|
|
|
```javascript
|
|
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: {}
|
|
}));
|
|
```
|
|
|
|
## Step 6: Test Your Tools
|
|
|
|
Use the CLI to verify tools work:
|
|
|
|
```bash
|
|
# 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
|
|
```
|
|
|
|
## Step 7: Add Observability (Optional)
|
|
|
|
Enable OpenTelemetry tracing to see every agent tool call as a distributed trace:
|
|
|
|
```go
|
|
import (
|
|
"go.opentelemetry.io/otel"
|
|
"go-micro.dev/v6/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 called
|
|
- `mcp.transport` — HTTP, WebSocket, or stdio
|
|
- `mcp.account.id` — who called it
|
|
- `mcp.auth.allowed` — whether it was permitted
|
|
|
|
Trace 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.
|
|
|
|
## Step 8: Use the AI Package (Optional)
|
|
|
|
If your service needs to call AI models directly:
|
|
|
|
```go
|
|
import (
|
|
"go-micro.dev/v6/ai"
|
|
_ "go-micro.dev/v6/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",
|
|
})
|
|
```
|
|
|
|
## Checklist
|
|
|
|
Before shipping an AI-native service:
|
|
|
|
- [ ] Every handler method has a doc comment explaining what it does
|
|
- [ ] Every method has an `@example` tag with realistic JSON input
|
|
- [ ] Request struct fields have `description` tags
|
|
- [ ] Write/delete operations have auth scopes
|
|
- [ ] You've tested with `micro mcp test` to verify tools work
|
|
- [ ] You've tested with Claude Code or the agent playground
|
|
|
|
## What Happens Under the Hood
|
|
|
|
```
|
|
1. 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
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
- [MCP Security Guide](mcp-security.md) - Configure auth and scopes for production
|
|
- [Tool Description Best Practices](tool-descriptions.md) - Write comments that make agents smarter
|
|
- [Agent Integration Patterns](agent-patterns.md) - Multi-agent workflows
|
|
- [MCP Documentation](../mcp.md) - Full MCP reference
|