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 endpoint
  • GET /sse — Server-Sent Events for streaming
  • GET /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