Concepts
MCP servers expose three types of capabilities: tools, resources, and prompts. mcp-go provides a consistent API for all three, with typed handlers and automatic schema generation.
Tools
Tools are functions that can be called by an AI model to perform actions. They accept typed input, execute logic, and return results.
Use tools for: API calls, calculations, file operations, database queries—anything the model needs to do.
// import "context"
// import "fmt"
type CalculateInput struct {
Operation string `json:"operation" jsonschema:"required,enum=add|subtract|multiply|divide"`
A float64 `json:"a" jsonschema:"required"`
B float64 `json:"b" jsonschema:"required"`
}
srv.Tool("calculate").
Description("Perform arithmetic operations").
Handler(func(ctx context.Context, in CalculateInput) (float64, error) {
switch in.Operation {
case "add":
return in.A + in.B, nil
case "subtract":
return in.A - in.B, nil
case "multiply":
return in.A * in.B, nil
case "divide":
if in.B == 0 {
return 0, fmt.Errorf("division by zero")
}
return in.A / in.B, nil
default:
return 0, fmt.Errorf("unknown operation: %s", in.Operation)
}
}) The struct tags define the JSON Schema that MCP clients use for validation and documentation. Invalid input is rejected before your handler runs.
Resources
Resources expose data via URI templates. They're read-only and represent something the model can read—files, database records, API responses.
Use resources for: Configuration, user data, documents, status information.
// import "context"
// import "encoding/json"
srv.Resource("users://{id}").
Name("User").
Description("Get user by ID").
MimeType("application/json").
Handler(func(ctx context.Context, uri string, params map[string]string) (*mcp.ResourceContent, error) {
id := params["id"] // extracted from URI template
user, err := db.GetUser(ctx, id)
if err != nil {
return nil, err
}
data, err := json.Marshal(user)
if err != nil {
return nil, err
}
return &mcp.ResourceContent{
URI: uri,
MimeType: "application/json",
Text: string(data),
}, nil
})
URI templates like users://{id} define path parameters.
Parameters are extracted automatically and passed to your handler.
Prompts
Prompts are parameterized message templates. They help structure conversations and provide reusable patterns for common tasks.
Use prompts for: Code review templates, summarization tasks, structured queries.
// import "context"
// import "fmt"
srv.Prompt("code-review").
Description("Generate a code review prompt").
Argument("language", "Programming language", true).
Argument("focus", "Review focus: security, performance, or style", false).
Handler(func(ctx context.Context, args map[string]string) (*mcp.PromptResult, error) {
language := args["language"]
focus := args["focus"]
if focus == "" {
focus = "general"
}
return &mcp.PromptResult{
Description: fmt.Sprintf("Code review for %s", language),
Messages: []mcp.PromptMessage{
{
Role: "user",
Content: mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Review this %s code with focus on %s.", language, focus),
},
},
},
}, nil
})
Arguments are declared with .Argument(name, description, required).
Required arguments are validated before your handler runs.
Structured Content
Tools can return typed structured data alongside text content blocks. Declare an output schema
and return a StructuredResult — clients that understand structured content can render
it natively (tables, JSON trees, images).
Use structured content for: Tables, complex data, typed API responses that clients can render natively.
type TableOutput struct {
Headers []string `json:"headers"`
Rows [][]string `json:"rows"`
}
srv.Tool("extract_table").
OutputSchema(TableOutput{}).
Handler(func(ctx context.Context, in Input) (mcp.StructuredResult, error) {
return mcp.StructuredResult{
Content: []mcp.Content{mcp.NewTextContent("Found 3 rows")},
StructuredContent: map[string]any{"headers": []string{"name", "age"}, "rows": rows},
}, nil
})
Legacy handlers returning strings or structs continue to work unchanged.
StructuredResult is opt-in.
Dynamic Registration
Add and remove tools, resources, and prompts at runtime. Notify connected clients when the available set changes.
Use dynamic registration for: Context-dependent tools, feature flags, progressive disclosure of capabilities.
// Remove a tool that's no longer relevant
srv.RemoveTool("fill_form")
session.NotifyToolListChanged()
// Same for resources and prompts
srv.RemoveResource("config://temp")
srv.RemovePrompt("onboarding") Elicitation
Servers can request structured input from users mid-task. Use elicitation when a tool encounters ambiguity, needs confirmation, or requires user selection.
Use elicitation for: Disambiguation, confirmation dialogs, multi-step workflows, user selection from options.
elicitor := mcp.ElicitFromContext(ctx)
if elicitor != nil {
result, err := elicitor.Elicit(ctx, &mcp.ElicitRequest{
Message: "Multiple fields match 'Name'. Which one?",
RequestedSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"field": map[string]any{
"type": "string",
"enum": []string{"First Name", "Last Name", "Full Name"},
},
},
},
})
// result.Action is "accept", "decline", or "cancel"
// result.Content has the user's selection when accepted
}
Elicitation requires client support. ElicitFromContext returns nil
when the client doesn't support it.
Channels
Servers can push messages proactively into AI sessions without waiting to be polled. Channel messages are notifications — fire-and-forget with no response expected.
Use channels for: DOM mutation alerts, network request completion, page navigation events, element appearance notifications.
channel := mcp.ChannelFromContext(ctx)
if channel != nil {
// Simple text message
channel.SendText("navigation", "SPA route changed to /dashboard")
// Rich message with structured data
channel.Send(&mcp.ChannelMessage{
Channel: "network",
Content: mcp.NewTextContent("API response received"),
Data: map[string]any{"status": 200, "url": "/api/users"},
Priority: "high",
})
}
Channels require client support. ChannelFromContext returns nil
when the client doesn't support it.
Middleware
Middleware wraps request handlers to add cross-cutting concerns. mcp-go uses a Gin-style middleware chain.
Use middleware for: Logging, authentication, rate limiting, timeouts, panic recovery.
// Apply middleware globally
srv.Use(
middleware.Recover(), // Catch panics
middleware.RequestID(), // Inject request IDs
middleware.Timeout(30*time.Second),
middleware.Logging(logger),
)
// Or use the default production stack
stack := mcp.DefaultMiddlewareWithTimeout(logger, 30*time.Second)
mcp.ServeStdio(ctx, srv, mcp.WithMiddleware(stack...)) Built-in middleware
Recover()— Catch panics and convert to errorsRequestID()— Inject unique request IDs for tracingTimeout(d)— Enforce request deadlinesLogging(logger)— Structured request loggingRateLimit(rps)— Request throttlingSizeLimit(bytes)— Request size limitsOTel()— OpenTelemetry tracing and metricsAudit()— Request/response audit loggingTracing()— Correlation and trace ID propagation
Authentication is out of scope. mcp-go never handles
tokens, OAuth flows, or credentials. On the client, inject auth via the
supplied http.Client transport
(mcp.WithHTTPClient); on the server, terminate auth at the
transport/proxy layer or in your own middleware.
Transports
Transports define how clients connect to your server. mcp-go supports multiple transports with the same server instance.
stdio
The standard transport for CLI tools and AI agents. Reads JSON-RPC from stdin, writes to stdout.
mcp.ServeStdio(ctx, srv) Use stdio for: CLI tools, Claude Desktop, local development.
HTTP + SSE
Web-friendly transport with Server-Sent Events for streaming. Exposes REST-like endpoints.
mcp.ServeHTTP(ctx, srv, ":8080",
mcp.WithReadTimeout(30*time.Second),
mcp.WithWriteTimeout(30*time.Second),
) Endpoints:
POST /mcp— JSON-RPC requestsGET /sse— Server-Sent Events streamGET /health— Health check
Use HTTP for: Web deployments, service-to-service, load balancing.
WebSocket
Full-duplex transport over a single TCP connection. Supports bidirectional streaming without polling.
mcp.ServeWebSocket(ctx, srv, ":8081",
mcp.WithWebSocketReadTimeout(30*time.Second),
mcp.WithWebSocketWriteTimeout(30*time.Second),
) Use WebSocket for: Real-time applications, bidirectional streaming, persistent connections.
gRPC
High-performance transport using Protocol Buffers and HTTP/2. Ideal for service-to-service communication with strong typing and streaming.
mcp.ServeGRPC(ctx, srv, ":9090") Use gRPC for: Microservices, high-throughput pipelines, polyglot environments.
Same server, different transports
Your tools, resources, and prompts work identically regardless of transport. You can even run both simultaneously:
// Run HTTP in background
go mcp.ServeHTTP(ctx, srv, ":8080")
// Run stdio in foreground
mcp.ServeStdio(ctx, srv)