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 errors
  • RequestID() — Inject unique request IDs for tracing
  • Timeout(d) — Enforce request deadlines
  • Logging(logger) — Structured request logging
  • RateLimit(rps) — Request throttling
  • SizeLimit(bytes) — Request size limits
  • OTel() — OpenTelemetry tracing and metrics
  • Audit() — Request/response audit logging
  • Tracing() — 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 requests
  • GET /sse — Server-Sent Events stream
  • GET /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)