Getting Started
What is mcp-go?
mcp-go is a Go framework for building Model Context Protocol (MCP) servers. It is not an SDK—it's a higher-level framework that provides typed handlers, automatic JSON Schema generation, input validation, middleware, and multiple transports.
Think of it as "Gin for MCP": just as Gin builds on top of net/http to provide structure and convenience,
mcp-go builds on top of the MCP protocol to give you a production-ready application framework.
Prerequisites
- Go 1.25 or later
- Basic Go knowledge (packages, structs, functions)
- No prior MCP knowledge required
Installation
go get go.klarlabs.de/mcp Your first MCP server
Create a file called main.go:
package main
import (
"context"
"log"
"go.klarlabs.de/mcp"
)
// GreetInput defines the typed input for our tool.
// Struct tags control JSON serialization and schema generation.
type GreetInput struct {
Name string `json:"name" jsonschema:"required,description=Name to greet"`
}
func main() {
// Create a new MCP server with metadata.
srv := mcp.NewServer(mcp.ServerInfo{
Name: "hello-server",
Version: "0.1.0",
})
// Register a tool with a typed handler.
// The input struct is automatically converted to JSON Schema
// and validated before your handler is called.
srv.Tool("greet").
Description("Generate a greeting message").
Handler(func(ctx context.Context, input GreetInput) (string, error) {
return "Hello, " + input.Name + "!", nil
})
// Start the server using stdio transport.
if err := mcp.ServeStdio(context.Background(), srv); err != nil {
log.Fatal(err)
}
} Running the server
Build and run:
go build -o hello-server
./hello-server The server communicates over stdio transport—it reads JSON-RPC messages from stdin and writes responses to stdout. This is the standard transport for MCP servers used by CLI tools and AI agents like Claude Desktop.
In practice, an MCP client (like an AI agent) handles this communication for you.
What you get for free
By defining a typed handler, mcp-go automatically provides:
- Typed input — Your handler receives a strongly-typed Go struct, not
map[string]any - Automatic JSON Schema — Generated from your struct and exposed via MCP introspection
- Input validation — Invalid input is rejected before your handler runs
- MCP-compliant errors — Errors are formatted according to the MCP specification
- Context propagation — Cancellation and deadlines work out of the box
Adding middleware
Use the built-in middleware stack for production defaults:
package main
import (
"context"
"fmt"
"log"
"time"
"go.klarlabs.de/mcp"
)
type GreetInput struct {
Name string `json:"name" jsonschema:"required"`
}
// simpleLogger implements mcp.Logger for demonstration.
type simpleLogger struct{}
func (l *simpleLogger) Info(msg string, fields ...mcp.LogField) {
fmt.Printf("[INFO] %s %v\n", msg, formatFields(fields))
}
func (l *simpleLogger) Error(msg string, fields ...mcp.LogField) {
fmt.Printf("[ERROR] %s %v\n", msg, formatFields(fields))
}
func (l *simpleLogger) Debug(msg string, fields ...mcp.LogField) {
fmt.Printf("[DEBUG] %s %v\n", msg, formatFields(fields))
}
func (l *simpleLogger) Warn(msg string, fields ...mcp.LogField) {
fmt.Printf("[WARN] %s %v\n", msg, formatFields(fields))
}
func formatFields(fields []mcp.LogField) string {
if len(fields) == 0 {
return ""
}
result := "{"
for i, f := range fields {
if i > 0 {
result += ", "
}
result += fmt.Sprintf("%s=%v", f.Key, f.Value)
}
return result + "}"
}
func main() {
srv := mcp.NewServer(mcp.ServerInfo{
Name: "hello-server",
Version: "0.1.0",
})
srv.Tool("greet").
Description("Generate a greeting message").
Handler(func(ctx context.Context, input GreetInput) (string, error) {
return "Hello, " + input.Name + "!", nil
})
// DefaultMiddlewareWithTimeout provides:
// - Panic recovery
// - Request ID injection
// - Timeout enforcement
// - Structured logging
logger := &simpleLogger{}
middleware := mcp.DefaultMiddlewareWithTimeout(logger, 30*time.Second)
if err := mcp.ServeStdio(context.Background(), srv, mcp.WithMiddleware(middleware...)); err != nil {
log.Fatal(err)
}
} Running over HTTP
The same server can run over HTTP with Server-Sent Events. Just change the transport:
// Instead of:
mcp.ServeStdio(ctx, srv)
// Use:
mcp.ServeHTTP(ctx, srv, ":8080",
mcp.WithReadTimeout(30*time.Second),
mcp.WithWriteTimeout(30*time.Second),
) This exposes:
POST /mcp— JSON-RPC endpointGET /sse— Server-Sent Events for streamingGET /health— Health check endpoint
The key point: same server, different transport. Your tools, resources, and prompts work identically regardless of how clients connect.
Next steps
- Concepts — Learn about tools, resources, prompts, and middleware
- Comparison — See how mcp-go differs from other MCP libraries
- Examples — Browse complete example servers
- API Reference — Full Go documentation