mcp

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2026 License: MIT Imports: 19 Imported by: 7

README

MCP Server Library for Go

A Go library for building Model Context Protocol (MCP) servers with a clean, fluent API.

Features

  • Simple API: Fluent interface for defining tools and parameters
  • Type Safety: Strongly typed parameter access with automatic conversion
  • Rich Responses: Support for text, image, audio, resource, and structured content
  • TOON Support: Compact, human-readable JSON encoding for LLM prompts with NewToolResponseTOON()
  • Thread Safe: Concurrent request handling with mutex protection
  • Remote Servers: Connect to and proxy remote MCP servers with authentication
  • Unified Interface: Combine local and remote tools in a single server
  • Searchable Tools: Reduce context window usage with on-demand tool discovery
  • Dynamic Tool Providers: Load tools from external sources (scripts, databases, APIs)
  • MCP 2025-11-25 Compliant: Full support for latest protocol version including:
    • Protocol version validation (MCP-Protocol-Version header)
    • Optional session management (MCP-Session-Id header)
    • Multi-version support (2024-11-05 through 2025-11-25)

Installation

go get github.com/paularlott/mcp

Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"

    "github.com/paularlott/mcp"
)

func main() {
    // Create server
    server := mcp.NewServer("my-server", "1.0.0")

    // Register a tool
    server.RegisterTool(
        mcp.NewTool("greet", "Greet someone",
            mcp.String("name", "Name to greet", mcp.Required()),
            mcp.String("greeting", "Custom greeting"),
        ),
        func(ctx context.Context, req *mcp.ToolRequest) (*mcp.ToolResponse, error) {
            name, _ := req.String("name")
            greeting := req.StringOr("greeting", "Hello")
            return mcp.NewToolResponseText(fmt.Sprintf("%s, %s!", greeting, name)), nil
        },
    )

    // Start server
    http.HandleFunc("/mcp", server.HandleRequest)
    log.Fatal(http.ListenAndServe(":8000", nil))
}

TOON Support

The library includes built-in support for TOON (Token-Oriented Object Notation), a compact, human-readable encoding of the JSON data model for LLM prompts.

Using TOON Responses

Instead of JSON responses, you can return TOON-formatted data:

return mcp.NewToolResponseTOON(map[string]interface{}{
    "users": []interface{}{
        map[string]interface{}{"id": 1, "name": "Alice", "role": "admin"},
        map[string]interface{}{"id": 2, "name": "Bob", "role": "user"},
    },
    "total": 2,
}), nil

This produces output like:

total: 2
users[2]{id,name,role}:
  1,Alice,admin
  2,Bob,user
TOON vs JSON
  • TOON: Compact, human-readable encoding optimized for LLM prompts
  • JSON: Standard machine-readable format

Use NewToolResponseTOON() when you want structured data that's both compact and easily readable by LLMs.

Tool Definition

Parameter Types

The library provides a clean, declarative API for defining tools:

// Basic types
mcp.String(name, description, options...)
mcp.Number(name, description, options...)
mcp.Boolean(name, description, options...)

// Array types
mcp.StringArray(name, description, options...)
mcp.NumberArray(name, description, options...)

// Object types
mcp.Object(name, description, properties...)
mcp.ObjectArray(name, description, properties...)

// Options
mcp.Required()  // Makes parameter required
mcp.Output(...) // Defines structured output
Object Support

The library provides comprehensive support for objects and arrays of objects with a clean, declarative syntax:

Basic Object Parameters

Define structured object parameters with typed properties:

server.RegisterTool(
    mcp.NewTool("create_user", "Create a new user",
        mcp.Object("user", "User information",
            mcp.String("name", "User's full name", mcp.Required()),
            mcp.String("email", "User's email address", mcp.Required()),
            mcp.Number("age", "User's age"),
            mcp.Boolean("active", "Whether user is active"),
            mcp.StringArray("tags", "User tags"),
            mcp.Required(),
        ),
        mcp.Output(
            mcp.Object("result", "Creation result",
                mcp.String("id", "User ID"),
                mcp.StringArray("permissions", "User permissions"),
            ),
        ),
    ),
    handleCreateUser,
)

Extract object parameters in your handler:

func handleCreateUser(ctx context.Context, req *mcp.ToolRequest) (*mcp.ToolResponse, error) {
    // Get the entire object
    user, err := req.Object("user")
    if err != nil {
        return nil, err
    }

    // Extract specific properties with type safety
    name, err := req.GetObjectStringProperty("user", "name")
    if err != nil {
        return nil, err
    }

    email, err := req.GetObjectStringProperty("user", "email")
    if err != nil {
        return nil, err
    }

    // Optional properties with manual checking
    age := 0
    if ageVal, exists := user["age"]; exists {
        if ageFloat, ok := ageVal.(float64); ok {
            age = int(ageFloat)
        }
    }

    return mcp.NewToolResponseText(fmt.Sprintf("Created user: %s (%s)", name, email)), nil
}
Array of Objects

Define and handle arrays of objects:

server.RegisterTool(
    mcp.NewTool("process_orders", "Process multiple orders",
        mcp.ObjectArray("orders", "List of orders to process",
            mcp.String("id", "Order ID", mcp.Required()),
            mcp.Number("amount", "Order amount", mcp.Required()),
            mcp.String("currency", "Currency code"),
            mcp.Required(),
        ),
        mcp.Output(
            mcp.StringArray("processed_ids", "List of processed order IDs"),
            mcp.NumberArray("totals", "List of order totals"),
        ),
    ),
    handleProcessOrders,
)

func handleProcessOrders(ctx context.Context, req *mcp.ToolRequest) (*mcp.ToolResponse, error) {
    orders, err := req.ObjectSlice("orders")
    if err != nil {
        return nil, err
    }

    for i, order := range orders {
        id, ok := order["id"].(string)
        if !ok {
            return nil, fmt.Errorf("order %d missing or invalid id", i)
        }

        amount, ok := order["amount"].(float64)
        if !ok {
            return nil, fmt.Errorf("order %d missing or invalid amount", i)
        }

        // Process order...
    }

    return mcp.NewToolResponseText("Orders processed"), nil
}
Nested Objects

Objects can contain other objects and arrays:

server.RegisterTool(
    mcp.NewTool("create_order", "Create an order with customer info",
        mcp.Object("order", "Order information",
            mcp.String("id", "Order ID", mcp.Required()),
            mcp.Number("total", "Order total", mcp.Required()),
            mcp.Object("customer", "Customer information",
                mcp.String("name", "Customer name", mcp.Required()),
                mcp.String("email", "Customer email"),
                mcp.Required(),
            ),
            mcp.ObjectArray("items", "Order items",
                mcp.String("sku", "Item SKU", mcp.Required()),
                mcp.Number("quantity", "Item quantity", mcp.Required()),
            ),
            mcp.Required(),
        ),
    ),
    handleCreateOrder,
)
Generic Objects

For cases where you need to accept arbitrary object structures:

server.RegisterTool(
    mcp.NewTool("configure", "Configure with arbitrary settings",
        mcp.Object("config", "Configuration object", mcp.Required()),
    ),
    handleConfigure,
)

Generic objects allow any properties and generate a schema with "additionalProperties": true.

API Reference

Parameter Functions:

  • String(name, description, options...) - String parameter
  • Number(name, description, options...) - Number parameter
  • Boolean(name, description, options...) - Boolean parameter
  • StringArray(name, description, options...) - Array of strings
  • NumberArray(name, description, options...) - Array of numbers
  • Object(name, description, properties...) - Object with properties (use mcp.Required() to make required)
  • ObjectArray(name, description, properties...) - Array of objects (use mcp.Required() to make required)

Options:

  • Required() - Makes any parameter required
  • Output(parameters...) - Defines structured output schema

ToolRequest Methods:

  • Object(name) - Extract an object parameter as map[string]interface{}
  • ObjectOr(name, default) - Extract an object parameter with default
  • ObjectSlice(name) - Extract an array of objects as []map[string]interface{}
  • ObjectSliceOr(name, default) - Extract an array of objects with default
  • GetObjectProperty(objectName, propertyName) - Get a property from an object
  • GetObjectStringProperty(objectName, propertyName) - Get a string property with type safety
  • GetObjectIntProperty(objectName, propertyName) - Get an int property with type safety
  • GetObjectBoolProperty(objectName, propertyName) - Get a bool property with type safety
Generated JSON Schema

The library generates proper JSON Schema for object parameters:

{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "description": "User information",
      "properties": {
        "name": {
          "type": "string",
          "description": "User's full name"
        },
        "email": {
          "type": "string",
          "description": "User's email address"
        },
        "age": {
          "type": "number",
          "description": "User's age"
        }
      },
      "required": ["name", "email"],
      "additionalProperties": false
    }
  },
  "required": ["user"],
  "additionalProperties": false
}
Complete Example
tool := mcp.NewTool("example", "Comprehensive example tool",
    // Input parameters
    mcp.String("name", "User name", mcp.Required()),
    mcp.Number("age", "User age"),
    mcp.StringArray("tags", "User tags"),
    mcp.Object("address", "User address",
        mcp.String("street", "Street address", mcp.Required()),
        mcp.String("city", "City", mcp.Required()),
    ),

    // Structured output
    mcp.Output(
        mcp.String("user_id", "Created user ID"),
        mcp.StringArray("permissions", "User permissions"),
        mcp.Object("profile", "User profile",
            mcp.String("display_name", "Display name"),
            mcp.Boolean("verified", "Is verified"),
        ),
    ),
)```

## Request Handling

### Type-Safe Parameter Access

```go
func handler(ctx context.Context, req *mcp.ToolRequest) (*mcp.ToolResponse, error) {
    // Required parameters (returns error if missing/wrong type)
    name, err := req.String("name")
    count, err := req.Int("count")
    enabled, err := req.Bool("enabled")
    price, err := req.Float("price")

    // Optional parameters with defaults
    greeting := req.StringOr("greeting", "Hello")
    limit := req.IntOr("limit", 10)
    debug := req.BoolOr("debug", false)
    rate := req.FloatOr("rate", 1.0)

    // Array parameters
    tags, err := req.StringSlice("tags")
    numbers, err := req.IntSlice("numbers")

    // Object parameters
    user, err := req.Object("user")
    orders, err := req.ObjectSlice("orders")

    // Extract object properties with type safety
    userName, err := req.GetObjectStringProperty("user", "name")
    userAge, err := req.GetObjectIntProperty("user", "age")

    return mcp.NewToolResponseText("Success"), nil
}

Response Types

Text Response
return mcp.NewToolResponseText("Hello, world!")
Image Response (auto base64 encoded)
imageBytes, _ := os.ReadFile("image.png")
return mcp.NewToolResponseImage(imageBytes, "image/png")
Audio Response (auto base64 encoded)
audioBytes, _ := os.ReadFile("audio.wav")
return mcp.NewToolResponseAudio(audioBytes, "audio/wav")
Resource Response
return mcp.NewToolResponseResource("file://path", "content", "text/plain")
return mcp.NewToolResponseResourceLink("https://example.com", "View details")
Structured Response
data := map[string]interface{}{
    "status": "success",
    "count": 42,
}
return mcp.NewToolResponseStructured(data)
Multi-Content Response
response1 := mcp.NewToolResponseText("Results:")
response2 := mcp.NewToolResponseImage(imageBytes, "image/png")
return mcp.NewToolResponseMulti(response1, response2)
Error Responses
// Invalid parameter error
if name == "" {
    return nil, mcp.NewToolErrorInvalidParams("name parameter is required")
}

// Internal server error
if err := someOperation(); err != nil {
    return nil, mcp.NewToolErrorInternal("failed to process request")
}

// Custom error with specific code
return nil, mcp.NewToolError(-32000, "Custom server error", map[string]interface{}{
    "details": "Additional error information",
})

Server Configuration

server := mcp.NewServer("server-name", "1.0.0")

// Register multiple tools
server.RegisterTool(tool1, handler1)
server.RegisterTool(tool2, handler2)

// Handle MCP requests
http.HandleFunc("/mcp", server.HandleRequest)

Protocol Support

The library supports MCP protocol versions:

  • 2024-11-05 (minimum)
  • 2025-03-26
  • 2025-06-18 (latest)

Thread Safety

The server is thread-safe and can handle concurrent requests. Tool registration and execution are protected by mutexes.

Session Management (MCP 2025-11-25)

Enable optional session tracking for stateful interactions. Sessions are disabled by default and remain optional per the MCP spec.

JWT-based sessions provide stateless, scalable session management with zero infrastructure dependencies:

server := mcp.NewServer("my-server", "1.0.0")

// Enable JWT session management (stateless, production-ready)
if err := server.EnableSessionManagement(); err != nil {
    log.Fatal(err)
}

// No cleanup needed - JWT sessions self-expire
// Sessions are validated on every request, no storage required

Why JWT Sessions?

  • Zero Dependencies: No Redis, Database, or external storage needed
  • Horizontal Scaling: Works perfectly across all server instances
  • Stateless: Each server validates sessions independently
  • Simple: Just enable and it works in any deployment
  • Secure: Cryptographically signed with HMAC-SHA256

Trade-off: Sessions cannot be revoked before expiry (acceptable for most use cases)

All Deployments

JWT sessions work perfectly for all deployment scenarios:

  • ✅ Single instance (development)
  • ✅ Multiple instances (production clusters)
  • ✅ Serverless/edge deployments
  • ✅ Multi-region architectures

No need for different session managers based on deployment type.

Advanced: Custom Session Storage

If you need session revocation or custom storage:

// Redis (requires external dependency)
rdb := redis.NewClient(&redis.Options{Addr: "redis:6379"})
server.SetSessionManager(mcp.NewRedisSessionManager(rdb, 30*time.Minute))

// Custom signing key (persist JWT key across restarts)
signingKey := loadKeyFromSecureStorage()
server.EnableSessionManagementWithKey(signingKey, 30*time.Minute)

// Implement your own SessionManager interface
server.SetSessionManager(myCustomManager)
Session Behavior

When session management is enabled:

  • Server generates a secure session ID on initialization
  • Returns MCP-Session-Id header in the initialize response
  • Clients must include MCP-Session-Id on all subsequent requests
  • Server validates session existence and returns 404 if not found
  • Clients can terminate sessions via DELETE requests

Session management is optional and backwards compatible. Servers without session management enabled work exactly as before.

Remote Servers and Clients

MCP Client

Connect to remote MCP servers:

// Bearer token authentication
auth := mcp.NewBearerTokenAuth("your-token")
client := mcp.NewClient("https://api.example.com/mcp", auth)

// OAuth2 authentication
oauth := mcp.NewOAuth2Auth("client-id", "client-secret", "https://auth.example.com/token", []string{"mcp:read"})
client := mcp.NewClient("https://api.example.com/mcp", oauth)

// Use client
tools, err := client.ListTools(ctx)
result, err := client.CallTool(ctx, "tool-name", args)
Unified Server with Remote Tools

Register remote servers directly with your local server for a unified tool interface:

// Create server
server := mcp.NewServer("my-server", "1.0.0")

// Register local tools (as usual)
server.RegisterTool(
    mcp.NewTool("local-greet", "Local greeting").
        AddParam("name", mcp.String, "Name to greet", true),
    func(ctx context.Context, req *mcp.ToolRequest) (*mcp.ToolResponse, error) {
        name, _ := req.String("name")
        return mcp.NewToolResponseText(fmt.Sprintf("Hello, %s!", name)), nil
    },
)

// Register remote servers with namespaces
bearerAuth := mcp.NewBearerTokenAuth("ai-tools-token")
server.RegisterRemoteServer("https://ai.example.com/mcp", "ai", bearerAuth)

oauth2Auth := mcp.NewOAuth2Auth("client-id", "client-secret", "https://auth.example.com/token", []string{"mcp:read"})
server.RegisterRemoteServer("https://data.example.com/mcp", "data", oauth2Auth)

// ListTools returns all tools (local + remote with namespaces)
tools := server.ListTools() // Returns: ["local-greet", "ai/generate-text", "data/query", ...]

// CallTool with intelligent routing
result, err := server.CallTool(ctx, "local-greet", args)       // Calls local tool
result, err := server.CallTool(ctx, "ai/generate-text", args)  // Calls remote AI tool
result, err := server.CallTool(ctx, "unknown-tool", args)      // Returns ErrUnknownTool

// Serve unified interface as HTTP endpoint
http.HandleFunc("/mcp", server.HandleRequest)
Tool Resolution
  • Namespaced calls (namespace/tool-name): Route directly to the specified remote server
  • Non-namespaced calls: Try local tools first, then fast lookup for remote tools
  • Caching: Remote tool lists are cached and can be refreshed with RefreshTools()
  • Error handling: Failed remote servers are skipped gracefully during registration
Authentication

Bearer Token:

auth := mcp.NewBearerTokenAuth("your-token")

OAuth2 with Client Credentials:

auth := mcp.NewOAuth2Auth(
    "client-id",
    "client-secret",
    "https://auth.example.com/token",
    []string{"mcp:read", "mcp:execute"},
)

Error Handling

The library provides structured error handling:

// Tool-specific errors
if name == "" {
    return nil, mcp.NewToolErrorInvalidParams("name parameter is required")
}

// Internal errors
if err := someOperation(); err != nil {
    return nil, mcp.NewToolErrorInternal("operation failed")
}

// Custom errors
return nil, mcp.NewToolError(-32000, "Custom error", map[string]interface{}{
    "details": "Additional information",
})

API Reference

Server Methods
  • NewServer(name, version string) *Server - Create a new server
  • RegisterTool(tool *ToolBuilder, handler ToolHandler) - Register a local tool
  • RegisterRemoteServer(url, namespace string, auth AuthProvider) error - Register a remote server
  • ListTools() []MCPTool - Get all tools (local + remote)
  • CallTool(ctx context.Context, name string, args map[string]interface{}) (*ToolResponse, error) - Execute a tool
  • RefreshTools() error - Refresh remote tool cache
  • HandleRequest(w http.ResponseWriter, r *http.Request) - HTTP handler for MCP requests
Client Methods
  • NewClient(baseURL string, auth AuthProvider) *Client - Create a new client
  • Initialize(ctx context.Context) error - Initialize connection (called automatically)
  • ListTools(ctx context.Context) ([]MCPTool, error) - List remote tools
  • CallTool(ctx context.Context, name string, args map[string]interface{}) (*ToolResponse, error) - Call remote tool
  • RefreshToolCache(ctx context.Context) error - Refresh tool cache

Examples

See the examples/ directory for complete working examples:

  • examples/server/ - Basic MCP server
  • examples/client/ - MCP client connecting to remote server
  • examples/unified-server/ - Server with both local and remote tools
  • examples/object-example/ - Comprehensive object and array handling examples
  • examples/tool-discovery/ - Searchable tools and dynamic providers for reduced context usage

Searchable Tools and Tool Discovery

When working with many tools, the context window can become bloated with tool definitions. The discovery package provides searchable tools - tools that are hidden from tools/list but can be discovered via search and executed on-demand.

This approach is inspired by Anthropic's Tool Search Tool pattern, which can reduce token usage by up to 85% while maintaining access to your full tool library.

For full documentation, see the discovery package README.

Quick Example
import (
    "github.com/paularlott/mcp"
    "github.com/paularlott/mcp/discovery"
)

func main() {
    server := mcp.NewServer("my-server", "1.0.0")

    // Create a tool registry for searchable tools
    registry := discovery.NewToolRegistry()

    // Register searchable tools (hidden from tools/list, but discoverable)
    registry.RegisterTool(
        mcp.NewTool("send_email", "Send an email",
            mcp.String("to", "Recipient", mcp.Required()),
            mcp.String("subject", "Subject", mcp.Required()),
        ),
        handleSendEmail,
        "email", "notification", "smtp", // keywords for search
    )

    // Attach to server - registers tool_search, execute_tool
    registry.Attach(server)

    // Start server - only shows tool_search, execute_tool
    // but send_email is discoverable and callable!
    http.HandleFunc("/mcp", server.HandleRequest)
    log.Fatal(http.ListenAndServe(":8000", nil))
}

The workflow for LLMs becomes:

  1. tool_search(query="email") → finds "send_email" with full schema
  2. execute_tool(name="send_email", arguments={...}) → executes the tool

OpenAI Compatibility

The openai subpackage provides types and utilities for building OpenAI-compatible APIs that use MCP tools:

import "github.com/paularlott/mcp/openai"

// Convert MCP tools to OpenAI function format
mcpTools := server.ListTools()
openAITools := openai.MCPToolsToOpenAI(mcpTools)

// Extract results from MCP tool responses for OpenAI
result, err := openai.ExtractToolResult(mcpResponse)

// Stream handling utilities
stream := openai.NewChatStream(ctx, responseChan, errorChan)
acc := &openai.CompletionAccumulator{}

See openai/README.md for complete documentation.

License

MIT License

Documentation

Index

Constants

View Source
const (
	MCPProtocolVersionLatest = "2025-11-25"
	MCPProtocolVersionMin    = "2024-11-05"
)
View Source
const (
	// ErrorCodeParseError indicates invalid JSON was received by the server.
	// Use when the request body cannot be parsed as JSON.
	ErrorCodeParseError = -32700

	// ErrorCodeInvalidRequest indicates the JSON sent is not a valid Request object.
	// Use when required fields are missing or have wrong types.
	ErrorCodeInvalidRequest = -32600

	// ErrorCodeMethodNotFound indicates the method does not exist or is not available.
	// Used internally when an unknown MCP method is called.
	ErrorCodeMethodNotFound = -32601

	// ErrorCodeInvalidParams indicates invalid method parameters.
	// Use this in tool handlers when required parameters are missing or invalid.
	// Prefer using NewToolErrorInvalidParams() helper.
	ErrorCodeInvalidParams = -32602

	// ErrorCodeInternalError indicates an internal JSON-RPC error.
	// Use this in tool handlers for unexpected server-side errors.
	// Prefer using NewToolErrorInternal() helper.
	ErrorCodeInternalError = -32603

	// ErrorCodeImplementationErrorStart is the start of the implementation-defined
	// server error range (-32000 to -32099). Use codes in this range for
	// application-specific errors. Create with NewToolError().
	ErrorCodeImplementationErrorStart = -32000

	// ErrorCodeImplementationErrorEnd is the end of the implementation-defined
	// server error range.
	ErrorCodeImplementationErrorEnd = -32099
)

MCP JSON-RPC Error Codes These are standard JSON-RPC 2.0 error codes used by the MCP protocol. See: https://www.jsonrpc.org/specification#error_object

Variables

View Source
var (
	ErrUnknownTool      = errors.New("unknown tool")
	ErrUnknownParameter = errors.New("parameter not found")
)
View Source
var DefaultNamespaceSeparator = "/"

DefaultNamespaceSeparator is the default separator used for namespacing tool names

Functions

func GenerateSigningKey added in v0.6.6

func GenerateSigningKey() ([]byte, error)

GenerateSigningKey creates a cryptographically secure random signing key

func NewToolError

func NewToolError(code int, message string, data interface{}) error

NewToolError creates a custom MCP error with a specific code. Use codes in the range -32000 to -32099 for application-specific errors. The data parameter can include additional error details and will be serialized to JSON.

Example:

return nil, mcp.NewToolError(-32001, "Rate limit exceeded", map[string]interface{}{
    "retry_after": 60,
    "limit": 100,
})

func NewToolErrorInternal

func NewToolErrorInternal(message string) error

NewToolErrorInternal creates an error for internal server errors. Use this for unexpected failures like database errors, network issues, etc. This returns ErrorCodeInternalError (-32603).

func NewToolErrorInvalidParams

func NewToolErrorInvalidParams(message string) error

NewToolErrorInvalidParams creates an error for invalid or missing parameters. Use this when a required parameter is missing, has the wrong type, or fails validation. This returns ErrorCodeInvalidParams (-32602).

Types

type AuthProvider

type AuthProvider interface {
	GetAuthHeader() (string, error)
	Refresh() error
}

AuthProvider interface for different authentication methods

type BearerTokenAuth

type BearerTokenAuth struct {
	// contains filtered or unexported fields
}

BearerTokenAuth implements simple bearer token authentication

func NewBearerTokenAuth

func NewBearerTokenAuth(token string) *BearerTokenAuth

NewBearerTokenAuth creates a new bearer token auth provider

func (*BearerTokenAuth) GetAuthHeader

func (b *BearerTokenAuth) GetAuthHeader() (string, error)

func (*BearerTokenAuth) Refresh

func (b *BearerTokenAuth) Refresh() error

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client represents an MCP client for connecting to remote servers

func NewClient

func NewClient(baseURL string, auth AuthProvider, namespace string) *Client

NewClient creates a new MCP client using the shared HTTP pool. The namespace will be added to all tool names (e.g., namespace "scriptling" makes tool "search" available as "scriptling/search"). Use an empty namespace for no namespacing.

The namespace should be a simple identifier (letters, numbers, hyphens, underscores). Whitespace is trimmed automatically.

func (*Client) CallTool

func (c *Client) CallTool(ctx context.Context, name string, args map[string]interface{}) (*ToolResponse, error)

CallTool executes a tool on the remote server. If the client has a namespace, the tool name should include it (e.g., "scriptling/search"). The namespace will be stripped before calling the underlying tool.

func (*Client) ExecuteDiscoveredTool added in v0.8.0

func (c *Client) ExecuteDiscoveredTool(ctx context.Context, name string, arguments map[string]interface{}) (*ToolResponse, error)

ExecuteDiscoveredTool executes a tool by name using the execute_tool MCP tool. This is the only way to call tools that were discovered via ToolSearch. Discovered tools cannot be called directly via CallTool.

func (*Client) Initialize

func (c *Client) Initialize(ctx context.Context) error

Initialize performs the MCP handshake with the remote server

func (*Client) ListTools

func (c *Client) ListTools(ctx context.Context) ([]MCPTool, error)

ListTools retrieves tools from the remote server

func (*Client) Namespace added in v0.8.0

func (c *Client) Namespace() string

Namespace returns the namespace for this client's tools.

func (*Client) RefreshToolCache

func (c *Client) RefreshToolCache(ctx context.Context) error

RefreshToolCache explicitly refreshes the tool cache

func (*Client) ToolSearch added in v0.8.0

func (c *Client) ToolSearch(ctx context.Context, query string, maxResults int) ([]map[string]interface{}, error)

ToolSearch performs a tool search using the tool_search MCP tool. This is useful when the server has many tools registered via a discovery registry. The query searches tool names, descriptions, and keywords.

type JWTSessionManager added in v0.6.6

type JWTSessionManager struct {
	// contains filtered or unexported fields
}

JWTSessionManager provides stateless session management using JWT tokens This is the RECOMMENDED approach for production clusters as it: - Requires no external storage (Redis, Database) - Scales horizontally without coordination - Works across all server instances - Has zero infrastructure dependencies

Trade-off: Sessions cannot be revoked before expiry (acceptable for most use cases)

func NewJWTSessionManager added in v0.6.6

func NewJWTSessionManager(signingKey []byte, ttl time.Duration) *JWTSessionManager

NewJWTSessionManager creates a new JWT-based session manager signingKey should be a cryptographically secure random key (at least 32 bytes recommended) ttl is the session lifetime (e.g., 30 * time.Minute)

func (*JWTSessionManager) CleanupExpiredSessions added in v0.6.6

func (m *JWTSessionManager) CleanupExpiredSessions(ctx context.Context, maxIdleTime time.Duration) error

CleanupExpiredSessions is a no-op for JWT sessions (tokens expire automatically)

func (*JWTSessionManager) CreateSession added in v0.6.6

func (m *JWTSessionManager) CreateSession(ctx context.Context, protocolVersion string) (string, error)

CreateSession generates a new JWT session token

func (*JWTSessionManager) DeleteSession added in v0.6.6

func (m *JWTSessionManager) DeleteSession(ctx context.Context, sessionID string) error

DeleteSession is a no-op for JWT sessions (cannot revoke before expiry)

func (*JWTSessionManager) GetProtocolVersion added in v0.6.6

func (m *JWTSessionManager) GetProtocolVersion(ctx context.Context, sessionID string) (string, error)

GetProtocolVersion extracts the protocol version from a JWT session token

func (*JWTSessionManager) ValidateSession added in v0.6.6

func (m *JWTSessionManager) ValidateSession(ctx context.Context, sessionID string) (bool, error)

ValidateSession validates a JWT session token

type MCPError

type MCPError struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

type MCPRequest

type MCPRequest struct {
	JSONRPC string      `json:"jsonrpc"`
	ID      interface{} `json:"id"`
	Method  string      `json:"method"`
	Params  interface{} `json:"params,omitempty"`
}

MCP Protocol types

type MCPResponse

type MCPResponse struct {
	JSONRPC string      `json:"jsonrpc"`
	ID      interface{} `json:"id,omitempty"`
	Result  interface{} `json:"result,omitempty"`
	Error   *MCPError   `json:"error,omitempty"`
}

type MCPTool

type MCPTool struct {
	Name         string      `json:"name"`
	Description  string      `json:"description"`
	InputSchema  interface{} `json:"inputSchema"`
	OutputSchema interface{} `json:"outputSchema,omitempty"`
}

type OAuth2Auth

type OAuth2Auth struct {
	// contains filtered or unexported fields
}

OAuth2Auth implements OAuth2 authentication with token refresh

func NewOAuth2Auth

func NewOAuth2Auth(clientID, clientSecret, tokenURL string, scopes []string) *OAuth2Auth

NewOAuth2Auth creates a new OAuth2 auth provider

func (*OAuth2Auth) GetAuthHeader

func (o *OAuth2Auth) GetAuthHeader() (string, error)

func (*OAuth2Auth) Refresh

func (o *OAuth2Auth) Refresh() error

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option interface for parameter options

func Required

func Required() Option

type Parameter

type Parameter interface {
	// contains filtered or unexported methods
}

Parameter interface for all parameter types

func Boolean

func Boolean(name, description string, options ...Option) Parameter

Boolean creates a boolean parameter

func Number

func Number(name, description string, options ...Option) Parameter

Number creates a number parameter

func NumberArray

func NumberArray(name, description string, options ...Option) Parameter

NumberArray creates a number array parameter

func Object

func Object(name, description string, propertiesAndOptions ...interface{}) Parameter

Object creates an object parameter with properties

func ObjectArray

func ObjectArray(name, description string, propertiesAndOptions ...interface{}) Parameter

ObjectArray creates an array of objects parameter

func Output

func Output(parameters ...Parameter) Parameter

func String

func String(name, description string, options ...Option) Parameter

String creates a string parameter

func StringArray

func StringArray(name, description string, options ...Option) Parameter

StringArray creates a string array parameter

type ResourceContent

type ResourceContent struct {
	URI      string `json:"uri"`
	MimeType string `json:"mimeType,omitempty"`
	Text     string `json:"text,omitempty"`
	Blob     string `json:"blob,omitempty"` // base64 encoded
}

type ResourceResponse

type ResourceResponse struct {
	Contents []ResourceContent `json:"contents"`
}

func NewResourceResponseBlob

func NewResourceResponseBlob(uri string, data []byte, mimeType string) *ResourceResponse

func NewResourceResponseText

func NewResourceResponseText(uri, text, mimeType string) *ResourceResponse

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server represents an MCP server instance.

Design Philosophy

The Server struct is the central hub for MCP protocol handling. It intentionally combines several related concerns to provide a cohesive API:

  • Core Identity: name, version, and instructions for protocol negotiation
  • Tool Management: local tools with thread-safe registration and caching
  • Federation: remote MCP server integration with namespacing
  • Sessions: pluggable session management for stateful deployments
  • Discovery: optional tool registry for large tool sets

This design prioritizes ease of use over strict separation of concerns. A typical server setup requires only a few lines:

server := mcp.NewServer("myapp", "1.0.0")
server.RegisterTool(myTool, myHandler)
http.HandleFunc("/mcp", server.HandleRequest)

For advanced use cases, the server delegates to specialized components:

  • SessionManager interface for custom session storage
  • ToolRegistry interface for on-demand tool discovery
  • Client for remote server federation

Thread Safety: All methods are safe for concurrent use. The server uses RWMutex for read-heavy operations (ListTools, CallTool) with minimal lock contention.

func NewServer

func NewServer(name, version string) *Server

NewServer creates a new MCP server instance

func (*Server) CallTool

func (s *Server) CallTool(ctx context.Context, name string, args map[string]interface{}) (*ToolResponse, error)

CallTool executes a tool directly with namespace support (direct API) It checks local tools first, then remote tools, then deferred/dynamic tools from the registry

func (*Server) CleanupExpiredSessions added in v0.6.6

func (s *Server) CleanupExpiredSessions(maxIdleTime time.Duration) error

CleanupExpiredSessions removes sessions that haven't been used in the specified duration Only works if a session manager is configured

func (*Server) EnableSessionManagement added in v0.6.6

func (s *Server) EnableSessionManagement() error

EnableSessionManagement enables JWT-based session management (stateless, production-ready) This is the recommended approach for all deployments as it: - Requires no external dependencies (Redis, Database) - Scales horizontally without coordination - Works across all server instances - Validates sessions in ~12 microseconds

Only use SetSessionManager() if you need session revocation (Redis, Database)

func (*Server) EnableSessionManagementWithKey added in v0.6.6

func (s *Server) EnableSessionManagementWithKey(signingKey []byte, ttl time.Duration)

EnableSessionManagementWithKey enables JWT session management with a specific signing key Use this to maintain sessions across server restarts (persist the key securely)

func (*Server) HandleRequest

func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request)

HandleRequest handles MCP protocol requests

func (*Server) ListTools

func (s *Server) ListTools() []MCPTool

ListTools returns all registered tools including remote ones (direct API). The returned slice is a copy, safe for concurrent use and modification. For high-frequency access where copying is a concern, consider caching the result on the caller side.

func (*Server) RefreshTools

func (s *Server) RefreshTools() error

RefreshTools manually refreshes the tool cache and lookup from all remote servers. This method is safe for concurrent use - it releases the lock during network calls to avoid blocking other operations, then atomically swaps in the new data.

func (*Server) RegisterRemoteServer

func (s *Server) RegisterRemoteServer(client *Client) error

RegisterRemoteServer registers a remote MCP server

func (*Server) RegisterRemoteServerHidden added in v0.6.5

func (s *Server) RegisterRemoteServerHidden(client *Client) error

RegisterRemoteServerHidden registers a remote MCP server with hidden tools

func (*Server) RegisterRemoteServerWithVisibility added in v0.6.12

func (s *Server) RegisterRemoteServerWithVisibility(client *Client, visibility ToolVisibility) error

RegisterRemoteServerWithVisibility registers a remote MCP server with the specified visibility. - ToolVisibilityVisible: Tools appear in ListTools() and tool_search - ToolVisibilityHidden: Tools don't appear in ListTools() or tool_search (but can be called directly) - ToolVisibilityOnDemand: Tools don't appear in ListTools() but are in tool_search

For OnDemand tools, a registry must be set via SetToolRegistry().

func (*Server) RegisterTool

func (s *Server) RegisterTool(tool *ToolBuilder, handler ToolHandler)

RegisterTool registers a new tool with the server. For registering multiple tools at once, consider using RegisterTools for better performance.

func (*Server) RegisterToolWithDiscovery added in v0.7.2

func (s *Server) RegisterToolWithDiscovery(tool *ToolBuilder, handler ToolHandler, registry ToolRegistry, keywords ...string)

RegisterToolWithDiscovery registers a tool with either the server (native) or a discovery registry. If registry is nil, the tool is registered with the server (visible in ListTools, callable directly). If registry is provided, the tool is registered for discovery only (hidden from ListTools, searchable via tool_search, callable via execute_tool).

func (*Server) RegisterTools added in v0.8.0

func (s *Server) RegisterTools(tools ...*ToolRegistration)

RegisterTools registers multiple tools with the server in a single batch. This is more efficient than calling RegisterTool multiple times as it only sorts the cache once at the end.

func (*Server) SetInstructions

func (s *Server) SetInstructions(instructions string)

func (*Server) SetSessionManager added in v0.6.6

func (s *Server) SetSessionManager(manager SessionManager)

SetSessionManager sets a custom session manager for the server. For most deployments, use EnableSessionManagement() for stateless JWT sessions.

Use this only when you need custom session behavior such as:

  • Session revocation (logout functionality, security incidents)
  • Session listing (admin dashboards, audit trails)
  • Custom session metadata

See session_redis.go for a reference implementation using Redis.

func (*Server) SetToolRegistry added in v0.6.12

func (s *Server) SetToolRegistry(registry ToolRegistry)

SetToolRegistry sets the tool registry for OnDemand tools. Tools with OnDemand visibility will be registered here for discovery via tool_search.

type SessionManager added in v0.6.6

type SessionManager interface {
	// CreateSession creates a new session and returns its ID
	CreateSession(ctx context.Context, protocolVersion string) (sessionID string, err error)

	// ValidateSession checks if a session exists and is valid
	// Returns true if valid, updates lastUsed timestamp if applicable
	ValidateSession(ctx context.Context, sessionID string) (valid bool, err error)

	// GetProtocolVersion returns the negotiated protocol version for a session
	GetProtocolVersion(ctx context.Context, sessionID string) (version string, err error)

	// DeleteSession removes a session
	DeleteSession(ctx context.Context, sessionID string) error

	// CleanupExpiredSessions removes sessions older than maxIdleTime
	CleanupExpiredSessions(ctx context.Context, maxIdleTime time.Duration) error
}

SessionManager defines the interface for session storage and validation Implement this interface to create custom session stores (Redis, Database, etc.)

type ToolBuilder

type ToolBuilder struct {
	// contains filtered or unexported fields
}

ToolBuilder provides fluent API for building tools

func NewTool

func NewTool(name, description string, parameters ...Parameter) *ToolBuilder

NewTool creates a new tool with the declarative API

func (*ToolBuilder) BuildOutputSchema

func (t *ToolBuilder) BuildOutputSchema() map[string]interface{}

BuildOutputSchema returns the JSON Schema for the tool's structured output. Returns nil if no output schema was defined with Output(). This is used by the discovery package and for tools that return structured content.

func (*ToolBuilder) BuildSchema

func (t *ToolBuilder) BuildSchema() map[string]interface{}

BuildSchema returns the JSON Schema for the tool's input parameters. This is primarily used by the discovery package when registering tools for search functionality, but can also be used for documentation or schema validation purposes.

func (*ToolBuilder) Description added in v0.6.0

func (t *ToolBuilder) Description() string

Description returns the tool's description

func (*ToolBuilder) Name added in v0.6.0

func (t *ToolBuilder) Name() string

Name returns the tool's name

type ToolCallParams

type ToolCallParams struct {
	Name      string                 `json:"name"`
	Arguments map[string]interface{} `json:"arguments,omitempty"`
}

type ToolContent

type ToolContent struct {
	Type     string           `json:"type"`
	Text     string           `json:"text,omitempty"`
	Data     string           `json:"data,omitempty"`
	MimeType string           `json:"mimeType,omitempty"`
	Resource *ResourceContent `json:"resource,omitempty"`
}

type ToolError

type ToolError struct {
	Code    int
	Message string
	Data    interface{}
}

ToolError represents an MCP protocol error that can be returned from tool handlers. When returned from a ToolHandler, the error code and message are sent to the client in the JSON-RPC error response.

Example usage in a tool handler:

func myHandler(ctx context.Context, req *mcp.ToolRequest) (*mcp.ToolResponse, error) {
    name, err := req.String("name")
    if err != nil {
        return nil, mcp.NewToolErrorInvalidParams("name parameter is required")
    }
    // ... process request
}

func (*ToolError) Error

func (e *ToolError) Error() string

type ToolHandler

type ToolHandler func(ctx context.Context, req *ToolRequest) (*ToolResponse, error)

ToolHandler represents a function that handles tool calls

type ToolRegistration added in v0.8.0

type ToolRegistration struct {
	Tool    *ToolBuilder
	Handler ToolHandler
}

ToolRegistration pairs a tool builder with its handler for batch registration.

func NewToolRegistration added in v0.8.0

func NewToolRegistration(tool *ToolBuilder, handler ToolHandler) *ToolRegistration

NewToolRegistration creates a tool registration for use with RegisterTools.

type ToolRegistry added in v0.6.12

type ToolRegistry interface {
	// RegisterMCPTool registers a tool for discovery/search
	RegisterMCPTool(tool *MCPTool, handler ToolHandler, keywords ...string)

	// RegisterTool registers a tool builder for discovery/search
	RegisterTool(tool *ToolBuilder, handler ToolHandler, keywords ...string)
}

ToolRegistry is an interface for registering tools that are discoverable but not in ListTools. This avoids circular imports with the discovery package.

type ToolRequest

type ToolRequest struct {
	// contains filtered or unexported fields
}

ToolRequest provides typed access to tool arguments

func NewToolRequest added in v0.6.0

func NewToolRequest(args map[string]interface{}) *ToolRequest

NewToolRequest creates a new ToolRequest with the given arguments

func (*ToolRequest) Args added in v0.6.3

func (r *ToolRequest) Args() map[string]interface{}

Args returns all arguments as a map

func (*ToolRequest) Bool

func (r *ToolRequest) Bool(name string) (bool, error)

func (*ToolRequest) BoolOr

func (r *ToolRequest) BoolOr(name string, defaultValue bool) bool

func (*ToolRequest) Float

func (r *ToolRequest) Float(name string) (float64, error)

func (*ToolRequest) FloatOr

func (r *ToolRequest) FloatOr(name string, defaultValue float64) float64

func (*ToolRequest) FloatSlice

func (r *ToolRequest) FloatSlice(name string) ([]float64, error)

func (*ToolRequest) FloatSliceOr

func (r *ToolRequest) FloatSliceOr(name string, defaultValue []float64) []float64

func (*ToolRequest) GetObjectBoolProperty

func (r *ToolRequest) GetObjectBoolProperty(objectName, propertyName string) (bool, error)

GetObjectBoolProperty extracts a bool property from an object parameter

func (*ToolRequest) GetObjectIntProperty

func (r *ToolRequest) GetObjectIntProperty(objectName, propertyName string) (int, error)

GetObjectIntProperty extracts an int property from an object parameter

func (*ToolRequest) GetObjectProperty

func (r *ToolRequest) GetObjectProperty(objectName, propertyName string) (interface{}, error)

GetObjectProperty extracts a property from an object parameter

func (*ToolRequest) GetObjectStringProperty

func (r *ToolRequest) GetObjectStringProperty(objectName, propertyName string) (string, error)

GetObjectStringProperty extracts a string property from an object parameter

func (*ToolRequest) Int

func (r *ToolRequest) Int(name string) (int, error)

func (*ToolRequest) IntOr

func (r *ToolRequest) IntOr(name string, defaultValue int) int

func (*ToolRequest) IntSlice

func (r *ToolRequest) IntSlice(name string) ([]int, error)

func (*ToolRequest) IntSliceOr

func (r *ToolRequest) IntSliceOr(name string, defaultValue []int) []int

func (*ToolRequest) Object

func (r *ToolRequest) Object(name string) (map[string]interface{}, error)

Object returns a parameter as a map[string]interface{} (generic object)

func (*ToolRequest) ObjectOr

func (r *ToolRequest) ObjectOr(name string, defaultValue map[string]interface{}) map[string]interface{}

ObjectOr returns a parameter as an object or the default value

func (*ToolRequest) ObjectSlice

func (r *ToolRequest) ObjectSlice(name string) ([]map[string]interface{}, error)

ObjectSlice returns a parameter as a slice of objects

func (*ToolRequest) ObjectSliceOr

func (r *ToolRequest) ObjectSliceOr(name string, defaultValue []map[string]interface{}) []map[string]interface{}

ObjectSliceOr returns a parameter as a slice of objects or the default value

func (*ToolRequest) String

func (r *ToolRequest) String(name string) (string, error)

func (*ToolRequest) StringOr

func (r *ToolRequest) StringOr(name, defaultValue string) string

func (*ToolRequest) StringSlice

func (r *ToolRequest) StringSlice(name string) ([]string, error)

func (*ToolRequest) StringSliceOr

func (r *ToolRequest) StringSliceOr(name string, defaultValue []string) []string

type ToolResponse

type ToolResponse struct {
	Content           []ToolContent `json:"content"`
	StructuredContent interface{}   `json:"structuredContent,omitempty"`
}

ToolResponse represents the response from a tool

func NewToolResponseAudio

func NewToolResponseAudio(data []byte, mimeType string) *ToolResponse

func NewToolResponseImage

func NewToolResponseImage(data []byte, mimeType string) *ToolResponse

func NewToolResponseJSON

func NewToolResponseJSON(data interface{}) *ToolResponse

func NewToolResponseMulti

func NewToolResponseMulti(responses ...*ToolResponse) *ToolResponse

func NewToolResponseResource

func NewToolResponseResource(uri, text, mimeType string) *ToolResponse
func NewToolResponseResourceLink(uri, text string) *ToolResponse

func NewToolResponseStructured

func NewToolResponseStructured(data interface{}) *ToolResponse

func NewToolResponseTOON added in v0.7.0

func NewToolResponseTOON(data interface{}) *ToolResponse

func NewToolResponseText

func NewToolResponseText(text string) *ToolResponse

type ToolResult

type ToolResult struct {
	Content           []ToolContent `json:"content,omitempty"`
	StructuredContent interface{}   `json:"structuredContent,omitempty"`
	IsError           bool          `json:"isError,omitempty"`
}

type ToolVisibility added in v0.6.12

type ToolVisibility int

ToolVisibility defines how tools are exposed to clients

const (
	// ToolVisibilityVisible - Tools appear in ListTools() and are searchable via tool_search
	ToolVisibilityVisible ToolVisibility = iota

	// ToolVisibilityHidden - Tools don't appear in ListTools() and are NOT searchable
	ToolVisibilityHidden

	// ToolVisibilityOnDemand - Tools don't appear in ListTools() but ARE searchable via tool_search
	ToolVisibilityOnDemand
)

Directories

Path Synopsis
Package discovery provides tool discovery functionality for MCP servers.
Package discovery provides tool discovery functionality for MCP servers.
examples
client command
object-example command
server command
session-server command
tool-discovery command
unified-server command
example command
Package toon implements the TOON (Token-Oriented Object Notation) format.
Package toon implements the TOON (Token-Oriented Object Notation) format.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL