gateway/mcp/DOCUMENTATION.md
This document explains how to document your go-micro services so that AI agents can understand them better.
The MCP gateway automatically exposes your microservices as tools that AI agents (like Claude) can call. By adding proper documentation to your service handlers, you help agents understand:
go-micro automatically extracts documentation from your Go doc comments at registration time. You don't need to write any extra code!
Just write standard Go documentation comments on your handler methods:
// GetUser retrieves a user by ID from the database. Returns full profile including email, name, and preferences.
//
// @example {"id": "user-1"}
func (s *UserService) GetUser(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
// implementation
}
When you register the handler, go-micro automatically:
@example tag for example inputsSupported Tags:
@example <json> - Example JSON input (highly recommended for AI agents)That's it! No extra registration code needed:
// Documentation is extracted automatically from method comments
handler := service.Server().NewHandler(new(UserService))
service.Server().Handle(handler)
For more control or to override auto-extracted docs, use server.WithEndpointDocs():
handler := service.Server().NewHandler(
new(UserService),
server.WithEndpointDocs(map[string]server.EndpointDoc{
"UserService.GetUser": {
Description: "Custom description that overrides the comment",
Example: `{"id": "user-123"}`,
},
}),
)
Manual metadata takes precedence over auto-extracted comments.
Use server.WithEndpointScopes() to declare the auth scopes required for each
endpoint. The MCP gateway reads these from the registry and enforces them when
an Auth provider is configured.
handler := service.Server().NewHandler(
new(BlogService),
server.WithEndpointScopes("Blog.Create", "blog:write"),
server.WithEndpointScopes("Blog.Delete", "blog:write", "blog:admin"),
server.WithEndpointScopes("Blog.Read", "blog:read"),
)
Scopes are stored as comma-separated values in endpoint metadata ("scopes" key)
and are propagated through the service registry just like descriptions and examples.
An operator can also define or override scopes at the MCP gateway without modifying individual services. This is useful for centralized policy management:
mcp.Serve(mcp.Options{
Registry: reg,
Auth: authProvider,
Scopes: map[string][]string{
"blog.Blog.Create": {"blog:write"},
"blog.Blog.Delete": {"blog:admin"},
},
})
Gateway-level scopes take precedence over service-level scopes.
Add descriptions to struct fields using the description tag:
type User struct {
ID string `json:"id" description:"User's unique identifier (UUID format)"`
Name string `json:"name" description:"User's full name"`
Email string `json:"email" description:"User's email address"`
Age int `json:"age,omitempty" description:"User's age (optional)"`
}
The description tag is used to generate parameter descriptions in the JSON Schema.
1. Handler Registration (Your Service)
├─> You write Go doc comments on methods
├─> Call service.Server().NewHandler(yourHandler)
└─> go-micro automatically parses source files using go/ast
2. Documentation Extraction (Automatic)
├─> Read Go doc comments from handler method source
├─> Parse @example tags for sample inputs
├─> Extract struct tag descriptions
└─> Merge with any manual metadata (manual wins)
3. Service Registry
├─> Store endpoint metadata in registry.Endpoint.Metadata
├─> Metadata distributed with service information
└─> Available to all components (gateway, discovery, etc.)
4. MCP Gateway Discovery
├─> Query registry for services and endpoints
├─> Read description and example from endpoint.Metadata
└─> Generate JSON Schema with documentation
5. Tool Creation
└─> Create MCP tool with rich description for AI agents
For a documented handler, the MCP gateway generates:
{
"name": "users.UserService.GetUser",
"description": "GetUser retrieves a user by ID from the database. Returns full profile including email, name, and preferences.",
"inputSchema": {
"type": "object",
"description": "This endpoint fetches a user's complete profile...",
"properties": {
"id": {
"type": "string",
"description": "User ID in UUID format (e.g., \"123e4567-e89b-12d3-a456-426614174000\")"
}
},
"required": ["id"],
"examples": [
"{\"id\": \"user-1\"}"
]
}
}
AI agents parse your documentation literally. Be explicit:
✅ Good:
// GetUser retrieves a user by their unique ID from the database.
// Returns the user's full profile including name, email, and preferences.
// If the user doesn't exist, returns an error with status 404.
//
// @param id {string} User ID in UUID v4 format (e.g., "123e4567-e89b-12d3-a456-426614174000")
// @return {User} User object with all profile fields populated
❌ Bad:
// Gets a user
func GetUser(...) // No details, no context
Tell agents exactly what format you expect:
✅ Good:
// @param email {string} Email address in RFC 5322 format (must contain @ and domain)
// @param age {number} User's age (integer between 0-150)
// @param phone {string} Phone number in E.164 format (e.g., "+14155552671")
❌ Bad:
// @param email {string} The email
// @param age {number} Age
Show agents actual valid inputs:
✅ Good:
// @example
// {
// "name": "Alice Smith",
// "email": "[email protected]",
// "age": 30,
// "phone": "+14155552671"
// }
❌ Bad:
// @example
// {
// "name": "string",
// "email": "string"
// }
Tell agents what can go wrong:
// GetUser retrieves a user by ID.
//
// Returns error if:
// - User ID is not a valid UUID
// - User does not exist (404)
// - Database is unavailable (503)
//
// @param id {string} User ID in UUID format
Field names should be self-explanatory:
✅ Good:
type CreateUserRequest struct {
FullName string `json:"full_name" description:"User's complete name"`
EmailAddress string `json:"email_address" description:"Primary email for contact"`
DateOfBirth string `json:"date_of_birth" description:"Birth date in YYYY-MM-DD format"`
}
❌ Bad:
type CreateUserRequest struct {
N string `json:"n"` // What is n?
E string `json:"e"` // What is e?
D string `json:"d"` // What is d?
}
Agent: "I need to call GetUser but I don't know what format the ID should be.
Is it a number? A string? A UUID? Let me try..."
❌ Calls with: {"id": 123}
❌ Calls with: {"id": "user123"}
❌ Calls with: {"id": "abc"}
✅ Calls with: {"id": "550e8400-e29b-41d4-a716-446655440000"} (after 4 attempts)
Agent: "GetUser needs an ID in UUID format. The example shows the format.
I'll use a valid UUID."
✅ Calls with: {"id": "550e8400-e29b-41d4-a716-446655440000"} (first attempt)
Result:
The MCP gateway uses several parsers:
parseServiceDocs)ToolDescription structParseStructTags)description tags from struct fieldsParseGoDocComment)reflectTypeToJSONType)See complete examples in:
examples/mcp/documented/ - Fully documented serviceexamples/auth/ - Auth service with documentationexamples/hello-world/ - Basic servicecurl http://localhost:3000/mcp/tools | jq '.tools[0]'
Verify the description and schema are correct.
Add to your Claude Code config and ask Claude to use your service. Claude will show you how it interprets your documentation.
Try the examples from your @example tags:
curl -X POST http://localhost:3000/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "users.UserService.GetUser",
"input": <your-example-json>
}'
Planned improvements:
Q: Do I need to document every field? A: Document fields that are ambiguous or have constraints. Self-explanatory fields can rely on the field name.
Q: Will this slow down my service? A: No. Documentation is parsed once at startup when the MCP gateway discovers services.
Q: Can I use OpenAPI/Swagger specs instead? A: Not yet, but it's planned. For now, use Go comments and struct tags.
Q: What if I don't document my handlers? A: The MCP gateway will still work, generating basic descriptions from method names and types. But agents will perform better with documentation.
Q: How do I know if my documentation is good? A: Test it with Claude Code. If Claude understands your service and calls it correctly on the first try, your documentation is good!
Q: How do I add auth scopes to my endpoints?
A: Use server.WithEndpointScopes() when registering your handler:
handler := service.Server().NewHandler(
new(MyService),
server.WithEndpointScopes("MyService.Create", "write"),
)
Or define scopes at the gateway level using Scopes in mcp.Options.
Q: Can I set scopes at the gateway without changing services?
A: Yes. Use the Scopes option on mcp.Options to define or override scopes for any tool at the gateway layer. This is useful for centralized policy management.
Apache 2.0