Files
micro--go-micro/internal/website/docs/guides/mcp-security.md
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:40:33 +08:00

363 lines
10 KiB
Markdown

---
layout: default
---
# MCP Security Guide
This guide covers how to secure your MCP gateway for production use, including authentication, per-tool scopes, rate limiting, and audit logging.
## Overview
The MCP gateway provides four layers of security:
1. **Authentication** - Verify the caller's identity via bearer tokens
2. **Scopes** - Control which tools each token can access
3. **Rate Limiting** - Prevent abuse with per-tool rate limits
4. **Audit Logging** - Record every tool call for compliance and debugging
## Authentication
### Bearer Token Auth
The MCP gateway uses bearer token authentication. Tokens are validated by the configured `auth.Auth` provider.
```go
import (
"go-micro.dev/v6/gateway/mcp"
"go-micro.dev/v6/auth"
)
gateway := mcp.ListenAndServe(":3000", mcp.Options{
Registry: service.Options().Registry,
Auth: authProvider, // auth.Auth implementation
})
```
Agents pass tokens in the `Authorization` header:
```bash
curl -X POST http://localhost:3000/mcp/call \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"tool": "tasks.TaskService.Create", "input": {"title": "New task"}}'
```
### Using micro run / micro server
When using `micro run` or `micro server`, authentication is handled automatically:
- **Development mode (`micro run`):** Auth is disabled by default for easy development
- **Production mode (`micro server`):** JWT auth is enabled with user management at `/auth`
Create tokens with specific scopes via the dashboard at `/auth/tokens`.
## Per-Tool Scopes
Scopes control which tools a token can access. There are two ways to set scopes.
### Service-Level Scopes
Set scopes when registering your handler. These travel with the service through the registry:
```go
handler := service.Server().NewHandler(
new(TaskService),
server.WithEndpointScopes("TaskService.Get", "tasks:read"),
server.WithEndpointScopes("TaskService.List", "tasks:read"),
server.WithEndpointScopes("TaskService.Create", "tasks:write"),
server.WithEndpointScopes("TaskService.Update", "tasks:write"),
server.WithEndpointScopes("TaskService.Delete", "tasks:admin"),
)
```
### Gateway-Level Scopes
Override or add scopes at the gateway without modifying services. Gateway scopes take precedence:
```go
mcp.ListenAndServe(":3000", mcp.Options{
Registry: reg,
Auth: authProvider,
Scopes: map[string][]string{
"tasks.TaskService.Create": {"tasks:write"},
"tasks.TaskService.Delete": {"tasks:admin"},
"billing.Billing.Charge": {"billing:admin"},
},
})
```
### Scope Enforcement
When a tool is called:
1. Gateway checks if the tool has required scopes
2. If scopes are defined, the caller's token must include at least one matching scope
3. A token with scope `*` has unrestricted access (admin)
4. If no scopes are defined for a tool, any authenticated token can call it
5. Denied calls return `403 Forbidden`
### Common Scope Patterns
| Pattern | Use Case |
|---------|----------|
| `service:read` | Read-only access to a service |
| `service:write` | Create and update operations |
| `service:admin` | Delete and destructive operations |
| `*` | Full admin access (use sparingly) |
| `internal` | Internal-only tools not exposed to external agents |
### Token Examples
```
Token A: scopes=["tasks:read"]
✅ Can call TaskService.Get, TaskService.List
❌ Cannot call TaskService.Create, TaskService.Delete
Token B: scopes=["tasks:read", "tasks:write"]
✅ Can call Get, List, Create, Update
❌ Cannot call TaskService.Delete (needs tasks:admin)
Token C: scopes=["*"]
✅ Can call everything (admin)
```
## Rate Limiting
Prevent abuse with per-tool rate limiting using a token bucket algorithm:
```go
mcp.ListenAndServe(":3000", mcp.Options{
Registry: reg,
RateLimit: &mcp.RateLimitConfig{
RequestsPerSecond: 10, // Sustained rate
Burst: 20, // Allow bursts up to 20
},
})
```
When the rate limit is exceeded, calls return `429 Too Many Requests`.
### Choosing Rate Limits
| Service Type | Requests/sec | Burst | Rationale |
|-------------|-------------|-------|-----------|
| Read-heavy API | 50 | 100 | High throughput, low cost |
| Write API | 10 | 20 | Moderate, prevents spam |
| Expensive operation | 2 | 5 | Protect downstream resources |
| Internal tool | 100 | 200 | Trusted callers, higher limits |
## Audit Logging
Record every tool call for compliance, debugging, and analytics:
```go
mcp.ListenAndServe(":3000", mcp.Options{
Registry: reg,
Auth: authProvider,
AuditFunc: func(record mcp.AuditRecord) {
log.Printf("[AUDIT] tool=%s account=%s allowed=%v duration=%v err=%v",
record.Tool,
record.AccountID,
record.Allowed,
record.Duration,
record.Error,
)
},
})
```
### AuditRecord Fields
| Field | Type | Description |
|-------|------|-------------|
| `Tool` | `string` | Full tool name (e.g., `tasks.TaskService.Create`) |
| `AccountID` | `string` | Caller's account ID from the auth token |
| `Scopes` | `[]string` | Scopes on the caller's token |
| `Allowed` | `bool` | Whether the call was permitted |
| `Duration` | `time.Duration` | How long the call took |
| `Error` | `error` | Error if the call failed |
| `TraceID` | `string` | UUID trace ID for correlation |
| `DeniedReason` | `string` | Why the call was denied (empty if allowed) |
### Production Audit Logging
For production, send audit records to a structured logging system:
```go
AuditFunc: func(r mcp.AuditRecord) {
// Structured JSON logging
logger.Info("mcp_tool_call",
"tool", r.Tool,
"account", r.AccountID,
"allowed", r.Allowed,
"duration_ms", r.Duration.Milliseconds(),
"trace_id", r.TraceID,
)
// Alert on denied calls
if !r.Allowed {
alerting.Notify("MCP access denied",
"tool", r.Tool,
"account", r.AccountID,
)
}
},
```
## Tracing
Every MCP tool call gets a UUID trace ID, propagated via metadata headers:
| Header | Description |
|--------|-------------|
| `Mcp-Trace-Id` | UUID for the tool call |
| `Mcp-Tool-Name` | Name of the tool called |
| `Mcp-Account-Id` | Caller's account ID |
These are available in your handler via context metadata:
```go
func (t *TaskService) Create(ctx context.Context, req *CreateRequest, rsp *CreateResponse) error {
md, _ := metadata.FromContext(ctx)
traceID := md["Mcp-Trace-Id"]
log.Printf("Creating task, trace: %s", traceID)
// ...
}
```
### OpenTelemetry Integration
For full distributed tracing, plug in an OpenTelemetry trace provider:
```go
import (
"go.opentelemetry.io/otel"
"go-micro.dev/v6/gateway/mcp"
)
mcp.ListenAndServe(":3000", mcp.Options{
Registry: reg,
TraceProvider: otel.GetTracerProvider(),
})
```
Each tool call creates a span (`mcp.tool.call`) with these attributes:
| Attribute | Example |
|-----------|---------|
| `mcp.tool.name` | `tasks.TaskService.Create` |
| `mcp.transport` | `http`, `websocket`, `stdio` |
| `mcp.account.id` | `user-123` |
| `mcp.trace.id` | `a1b2c3d4-...` |
| `mcp.auth.allowed` | `true` |
| `mcp.auth.denied_reason` | `insufficient_scope` |
| `mcp.scopes.required` | `tasks:write` |
| `mcp.rate_limited` | `false` |
The gateway propagates W3C trace context downstream, so you get end-to-end traces from agent → gateway → service in Jaeger, Zipkin, or any OTel-compatible backend.
## WebSocket Authentication
The WebSocket transport supports two authentication methods:
### Connection-Level Auth (Recommended)
Pass the token in the WebSocket upgrade request:
```javascript
const ws = new WebSocket("ws://localhost:3000/mcp/ws", {
headers: { "Authorization": "Bearer <token>" }
});
```
The token is validated once on connection and applies to all messages on that connection.
### Per-Message Auth
For stateless connections, pass a `_token` parameter with each tool call:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "tasks.TaskService.Create",
"arguments": {"title": "New task"},
"_token": "Bearer <token>"
}
}
```
Connection-level auth takes precedence over per-message auth.
## Production Checklist
Before deploying MCP to production:
- [ ] **Auth enabled** - Configure an `auth.Auth` provider
- [ ] **Scopes defined** - Every write/delete endpoint has required scopes
- [ ] **Rate limits set** - Appropriate limits for each service type
- [ ] **Audit logging active** - All calls logged to a persistent store
- [ ] **HTTPS/TLS** - MCP gateway behind TLS termination
- [ ] **Token rotation** - Process for rotating compromised tokens
- [ ] **Monitoring** - Alerts on high error rates or denied calls
- [ ] **Testing** - Verified scope enforcement with `micro mcp test`
## Full Example
```go
package main
import (
"log"
"go-micro.dev/v6"
"go-micro.dev/v6/auth"
"go-micro.dev/v6/gateway/mcp"
"go-micro.dev/v6/server"
)
func main() {
service := micro.NewService("tasks",
micro.Address(":8081"),
)
service.Init()
// Register handler with scopes
handler := service.Server().NewHandler(
&TaskService{tasks: make(map[string]*Task)},
server.WithEndpointScopes("TaskService.Get", "tasks:read"),
server.WithEndpointScopes("TaskService.Create", "tasks:write"),
server.WithEndpointScopes("TaskService.Delete", "tasks:admin"),
)
service.Server().Handle(handler)
// Start MCP gateway with full security
go mcp.ListenAndServe(":3000", mcp.Options{
Registry: service.Options().Registry,
Auth: service.Options().Auth,
Scopes: map[string][]string{
// Gateway-level overrides
"billing.Billing.Charge": {"billing:admin"},
},
RateLimit: &mcp.RateLimitConfig{
RequestsPerSecond: 10,
Burst: 20,
},
AuditFunc: func(r mcp.AuditRecord) {
log.Printf("[AUDIT] tool=%s account=%s allowed=%v duration=%v",
r.Tool, r.AccountID, r.Allowed, r.Duration)
},
})
service.Run()
}
```
## Next Steps
- [Building AI-Native Services](ai-native-services.md) - End-to-end tutorial
- [Tool Description Best Practices](tool-descriptions.md) - Write effective documentation
- [Agent Integration Patterns](agent-patterns.md) - Multi-agent architectures