mcpio

package module
v0.0.0-...-f971134 Latest Latest
Warning

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

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 14 Imported by: 0

README

mcp-io

Go Reference Go Report Card License

A library that wraps the Model Context Protocol (MCP) SDK to provide a functional options constructor API with error handling instead of panics.

Overview

This library wraps the official MCP SDK to provide a functional options API with improved error handling. The official SDK provides flexibility through bare structs and direct initialization, which can result in runtime panics when required fields are missing. This wrapper adds validation at configuration time, returning errors instead of panicking, and uses functional options for composable configuration.

Features

  • Graceful Error Handling: Configuration errors return meaningful error messages instead of panicking
  • Functional Options Constructors: Composable API using the functional options pattern
  • Type-Safe Tools: Define MCP resources with Go generics to specify the in/out schema shapes
  • Interface-Based Parameters: Functions accept RequestContext interface for dependency injection and testing
  • Multiple Transports: Streamable HTTP and stdio support through a single handler
  • Sentinel Error Types: Errors return specific types that can be checked with errors.Is
  • Extensive Examples: Includes examples demonstrating MCP features and usage patterns

Installation

go get github.com/robbyt/mcp-io

Quick Start

Here's a basic example of creating an MCP server that exposes a tool that can convert text to uppercase:

package main

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

	mcpio "github.com/robbyt/mcp-io"
)

// Define your input and output types
type TextInput struct {
	Text string `json:"text" jsonschema:"Text to transform"`
}

type TextOutput struct {
	Result string `json:"result" jsonschema:"Transformed text"`
}

// Tool function
func toUpper(ctx context.Context, toolCtx mcpio.RequestContext, input TextInput) (TextOutput, error) {
	return TextOutput{Result: strings.ToUpper(input.Text)}, nil
}

func main() {
	// Create an MCP handler with functional options
	handler, err := mcpio.NewHandler(
		mcpio.WithName("example-server"),
		mcpio.WithVersion("1.0.0"),
		mcpio.WithTool("to_upper", "Convert text to uppercase", toUpper),
	)
	if err != nil {
		log.Fatalf("Failed to create handler: %v", err)
	}

	// Start HTTP server using the handler function returned by mcpio.New
	http.Handle("/mcp", handler)
	log.Printf("MCP server listening on :8080/mcp")
	log.Fatal(http.ListenAndServe(":8080", nil))
}
Running and Testing This Example

A slightly more complex version of this example is available in examples/cli_simple/main.go. Run the example with an HTTP transport:

go run ./examples/cli_simple/ --listen :8080

The server will listen on http://localhost:8080/mcp and can be tested with the MCP Inspector CLI:

# List available tools
npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --method tools/list

# Call the to_upper tool
npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --method tools/call --tool-name to_upper --tool-arg text="hello world"

Example MCP protocol output from tools/list:

The inputSchema and outputSchema fields contain JSON Schemas definitions that are populated by the tool's parameters, and are typically handled by a MCP client library.

{
  "tools": [
    {
      "name": "to_upper",
      "description": "Convert text to uppercase",
      "inputSchema": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "Text to transform"
          }
        },
        "required": ["text"],
        "additionalProperties": false
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "description": "Transformed text"
          }
        },
        "required": ["result"],
        "additionalProperties": false
      }
    }
  ]
}

Example output from tools/call:

When a MCP client calls the to_upper tool with the argument text="hello world", the server responds with:

{
  "content": [
    {
      "type": "text",
      "text": "{\"result\":\"HELLO WORLD\"}"
    }
  ],
  "structuredContent": {
    "result": "HELLO WORLD"
  }
}

Development Concepts In Depth

Handler Instantiation

The library uses a functional options pattern allowing for discoverable configuration, and future extensibility. Create a new MCP handler using mcpio.NewHandler, passing in options like server name, version, and tools:

handler, err := mcpio.NewHandler(
    mcpio.WithName("my-server"),
    mcpio.WithVersion("1.0.0"),
    mcpio.WithTool("tool1", "Description", toolFunc1),
    mcpio.WithTool("tool2", "Description", toolFunc2),
)
Input/Output Schema Definition

The easiest way to define JSON Schemas for your tools is to use annotated Go structs. The library automatically generates schemas from struct types using github.com/google/jsonschema-go/jsonschema. The jsonschema struct tag provides descriptions and constraints that guide the LLM in understanding and populating fields.

For simple descriptions, use the tag value directly. For advanced constraints like enums, ranges, or patterns, use comma-separated key=value pairs. See the jsonschema package documentation for all supported annotations.

Example: Division Calculator with Schema Constraints

package main

import (
    "context"
    "log"
    "math"

    mcpio "github.com/robbyt/mcp-io"

    // More guidance on using this in the next section:
    toolOption "github.com/robbyt/mcp-io/primitives/tool"
)

type DivideInput struct {
    Numerator   float64 `json:"numerator" jsonschema:"Number to be divided"`
    Denominator float64 `json:"denominator" jsonschema:"Number to divide by (cannot be zero)"`
    Precision   int     `json:"precision" jsonschema:"Decimal places for rounding,minimum=0,maximum=10,default=2"`
}

type DivideOutput struct {
    Result float64 `json:"result" jsonschema:"Division result rounded to specified precision"`
}

func divide(ctx context.Context, toolCtx mcpio.RequestContext, input DivideInput) (DivideOutput, error) {
    if input.Denominator == 0 {
        return DivideOutput{}, mcpio.NewToolError("division by zero")
    }

    result := input.Numerator / input.Denominator

    // Round to specified precision
    multiplier := math.Pow(10, float64(input.Precision))
    rounded := math.Round(result*multiplier) / multiplier

    return DivideOutput{Result: rounded}, nil
}

func main() {
    handler, err := mcpio.NewHandler(
        mcpio.WithName("calculator"),
        mcpio.WithTool("divide", "Divide two numbers with configurable precision", divide,
            toolOption.WithReadOnly(),
            toolOption.WithIdempotent(),
        ),
    )
    if err != nil {
        log.Fatalf("Failed to create handler: %v", err)
    }

    // The previous example used a HTTP transport, but you can also use stdio pipes for MCP communication:
    if err := handler.Run(context.Background()); err != nil {
        log.Fatalf("Server error: %v", err)
    }
}
Request Context Interface

Handler functions receive a RequestContext interface parameter for accessing request metadata and session capabilities:

type RequestContext interface {
    GetSession() *capabilities.Session  // Access session capabilities
    GetIdentifier() string              // Tool name, prompt name, or resource URI
    GetTokenInfo() *auth.TokenInfo      // OAuth token information
    GetHeaders() http.Header            // HTTP headers from request
    GetMeta() map[string]any            // Request metadata
}

The MCP SDK passes concrete request types to handlers, requiring functions to depend on SDK struct layout. This library uses an interface instead, decoupling handler signatures from SDK implementation details. The interface approach enables dependency injection for testing, where mock implementations satisfy the interface contract without SDK types.

Tool Metadata and Multiple Tools Per Server

MCP servers typically expose multiple related tools via a single handler. Tool metadata provides behavioral hints per the MCP specification that help LLMs and MCP clients make safer decisions. These annotations allow clients to understand tool behavior WITHOUT executing them. For example, an LLM can see that a tool is marked WithDestructive() and should (hopefully) act carefully when calling it. MCP Elicitation is another feature that adds a user confirmation step, and will be reviewed in a later section.

Important: Per the MCP spec, tool annotations are hints only and NOT guaranteed to provide faithful description of tool behavior. Clients MUST consider annotations untrusted unless from trusted servers, and should never make security decisions based solely on these hints.

The following example demonstrates a database management server with four related tools, each using appropriate metadata annotations:

import (
	"github.com/robbyt/mcp-io"
	toolOption "github.com/robbyt/mcp-io/primitives/tool"
)

// Database management server exposing multiple related tools
handler, err := mcpio.NewHandler(
	mcpio.WithName("database-manager"),
	mcpio.WithVersion("1.0.0"),

	// Safe read operation - can be called repeatedly without side effects
	mcpio.WithTool("get_records", "Retrieve records from database", getRecords,
		toolOption.WithReadOnly(),
		toolOption.WithIdempotent(),
	),

	// Update operation - modifies state but safe to retry
	mcpio.WithTool("update_record", "Update existing database record", updateRecord,
		toolOption.WithIdempotent(),  // Same inputs produce same result
	),

	// Destructive operation - irreversible deletion
	mcpio.WithTool("delete_records", "Permanently delete records", deleteRecords,
		toolOption.WithDestructive(),  // Signals LLM to use caution
	),

	// Backup operation - interacts with external storage
	mcpio.WithTool("backup_database", "Backup to cloud storage", backupDatabase,
		toolOption.WithReadOnly(),     // Doesn't modify local database
		toolOption.WithOpenWorld(),    // Communicates with external services
	),
)

Tool annotations guide LLM decision-making per the MCP specification, helping clients understand tool behavior and make safer choices. For details on each annotation's meaning and usage, see the documented functions in primitives/tool/options.go.

Transport Options

A single handler instance supports multiple transport types. Choose one transport at runtime based on your deployment needs:

  • stdio - Process communication via standard input/output (recommended default)
  • Streamable HTTP - HTTP transport supporting both request/response and streaming (MCP 2025-03-26 spec)

For details on MCP transport protocols, see the official specification.

package main

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

    mcpio "github.com/robbyt/mcp-io"
)

func main() {
    transport := flag.String("transport", "stdio", "Transport type: http or stdio")
    flag.Parse()

    handler, err := mcpio.NewHandler(
        mcpio.WithName("my-server"),
        mcpio.WithVersion("1.0.0"),
        mcpio.WithTool("to_upper", "Convert text", toUpper),
    )
    if err != nil {
        log.Fatal(err)
    }

    switch *transport {
    case "http":
        // Streamable HTTP - for HTTP clients (supports both request/response and streaming)
        http.Handle("/mcp", handler)
        log.Printf("Starting Streamable HTTP server on :8080/mcp")
        log.Fatal(http.ListenAndServe(":8080", nil))

    case "stdio":
        // Stdio - for CLI tools and process communication
        log.Printf("Starting stdio transport")
        if err := handler.Run(context.Background()); err != nil {
            log.Fatal(err)
        }

    default:
        log.Fatalf("Unknown transport: %s", *transport)
    }
}

Session Capabilities

Session capabilities enable interactive MCP tools with user confirmation, AI delegation, progress reporting, and detailed logging. These capabilities are automatically available in your tool handlers via context without additional setup. Not all MCP clients support all session capabilities, and your tools should gracefully handle unsupported features. The capabilities helpers return sentinel errors (like mcpio.ErrElicitationNotSupported) when the feature is available on the client, which can be checked and handled with errors.Is.

See examples/ directory for complete working examples of this library.

Elicitation (Interactive User Input)

Request additional information from users at runtime for configuration, preferences, or confirmation. Elicitation enables interactive workflows where tools can gather structured data dynamically. Never use elicitation for passwords, API keys, or secrets. Use only for configuration data, preferences, and non-sensitive user input.

This example shows using elicitation as a confirmation when performing destructive operations:

type DeleteRecordsInput struct {
    UserID int `json:"user_id" jsonschema:"User ID whose records will be deleted"`
}

type ConfirmDeletion struct {
    Confirm string `json:"confirm" jsonschema:"Type DELETE to confirm deletion"`
}

func deleteRecords(ctx context.Context, toolCtx mcpio.RequestContext, input DeleteRecordsInput) (map[string]any, error) {
    // Preview what will be deleted
    records := getRecords(input.UserID) // Returns []Record from database

    if len(records) == 0 {
        return map[string]any{"deleted": 0, "message": "No records found for user"}, nil
    }

    // Build confirmation message showing what will be deleted
    message := fmt.Sprintf(
        "You are about to delete %d records for user ID %d:\n\n%s\n\nType DELETE to confirm.",
        len(records), input.UserID, formatRecordList(records),
    )

    // Create elicitor and ask user to confirm
    elicitor := mcpio.NewElicitor(toolCtx)
    result, err := mcpio.ElicitTyped[ConfirmDeletion](ctx, elicitor, message)
    if err != nil {
        // Handle clients that don't support elicitation
        if errors.Is(err, mcpio.ErrElicitationNotSupported) {
            return nil, fmt.Errorf("cannot delete records: client does not support confirmation")
        }
        return nil, err
    }

    if result.IsAccepted() {
        var conf ConfirmDeletion
        if err := result.DecodeContent(&conf); err != nil {
            return nil, err
        }

        if conf.Confirm == "DELETE" {
            // Perform the actual deletion
            deleted := performDeletion(records)
            return map[string]any{
                "deleted": deleted,
                "message": fmt.Sprintf("Deleted %d records", deleted),
            }, nil
        }
    }

    return map[string]any{"status": "cancelled", "deleted": 0}, nil
}

// Register tool - session is automatically injected
handler, err := mcpio.NewHandler(
    mcpio.WithName("database-manager"),
    mcpio.WithTool("delete_records", "Delete database records with confirmation", deleteRecords,
        toolOption.WithDestructive(),
    ),
)
Sampling (LLM Integration)

Sampling allows your MCP server to request LLM generations from the client without needing API keys. The client maintains control over which models to use and whether to allow the request. This enables agentic behaviors where your server can make LLM calls nested inside tool execution to build more intelligent responses.

Sampling flow:

  1. MCP tool receives input
  2. Tool builds sampling messages
  3. Server sends request to client using the context
  4. Client gets user approval (optional)
  5. Client forwards the completion request to the LLM
  6. LLM generates response
  7. Tool uses response in its output
Example: AI Dungeon Master
import (
    "github.com/robbyt/mcp-io/capabilities"
    "github.com/robbyt/mcp-io/capabilities/sampling"
)

type AdventureInput struct {
    Action string `json:"action" jsonschema:"What the player does"`
}

func dungeonMaster(ctx context.Context, toolCtx mcpio.RequestContext, input AdventureInput) (map[string]any, error) {
    session := toolCtx.GetSession()
    if session == nil {
        return nil, fmt.Errorf("no session available")
    }

    // Delegate storytelling to the client's LLM
    prompt := "You are a dungeon master. The player: \"" + input.Action +
              "\". Narrate what happens next in 2 sentences. Be dramatic!"

    result, err := session.CreateMessage(ctx, []*sampling.Message{{
        Role:    "user",
        Content: prompt,
    }}, sampling.WithMaxTokens(300))
    if err != nil {
        return nil, err
    }

    // Use the LLM's narrative in our response
    return map[string]any{"narrative": result.Content.Text}, nil
}

Running the example:

The examples/simple_dungeon_master directory contains a full implementation using stdio transport. This example requires a client with sampling support to delegate narrative generation to the client's LLM.

# List available tools
npx @modelcontextprotocol/inspector --cli go run -C examples/simple_dungeon_master . --method tools/list

# Test the debug tool (works without sampling)
npx @modelcontextprotocol/inspector --cli go run -C examples/simple_dungeon_master . --method tools/call --tool-name debug_gameState

Output from debug_gameState:

{
  "content": [
    {
      "type": "text",
      "text": "{\"currentTurnNumber\":0,\"skillCheckRequired\":0,\"summary\":\"\",\"turnHistory\":[]}"
    }
  ],
  "structuredContent": {
    "currentTurnNumber": 0,
    "skillCheckRequired": 0,
    "summary": "",
    "turnHistory": []
  }
}

Note: The dungeon_master tool requires a client with sampling capability. The CLI inspector will return "client does not support sampling". To fully test this example, configure it in Claude Desktop or another MCP client that supports sampling.

Progress Notifications

Send progress updates to the MCP client during long-running operations using functional options to control notification behavior.

Basic Progress (no options):

func backgroundTask(ctx context.Context, toolCtx mcpio.RequestContext, input TaskInput) (TaskOutput, error) {
    session := toolCtx.GetSession()
    total := len(input.Items)

    for i, item := range input.Items {
        session.NotifyProgress(ctx, float64(i+1), float64(total))
        // Process item...
    }
    return output, nil
}

Request-Specific Progress (with token and message):

func processBatch(ctx context.Context, toolCtx mcpio.RequestContext, input struct{ Files []string }) (map[string]any, error) {
    session := toolCtx.GetSession()
    token := toolCtx.GetProgressToken()
    total := len(input.Files)

    for i, file := range input.Files {
        session.NotifyProgress(ctx, float64(i+1), float64(total),
            capabilities.WithProgressToken(token),
            capabilities.WithProgressMessage(fmt.Sprintf("Processing %s (%d/%d)", file, i+1, total)))
        // Process file...
    }

    session.NotifyProgress(ctx, float64(total), float64(total), capabilities.WithProgressToken(token))
    return map[string]any{"status": "done", "processed": total}, nil
}

Available Options:

  • capabilities.WithProgressToken(token) - Associate notification with specific request for concurrent tracking (token is int or string)
  • capabilities.WithProgressMessage(message) - Add descriptive message
  • capabilities.WithProgressMeta(meta) - Add custom metadata
Logging

⚠️ Work in Progress: The current logging API is incomplete and missing optional MCP protocol fields (Logger, Meta). Single-logger tools work fine, but multi-subsystem tools cannot tag logs by source (e.g., "database", "auth", "cache").

Send structured log messages to the client for debugging and monitoring.

func myTool(ctx context.Context, toolCtx mcpio.RequestContext, input MyInput) (MyOutput, error) {
    session := toolCtx.GetSession()

    session.LogInfo(ctx, "Processing started", map[string]any{
        "itemCount": len(input.Items),
    })

    // Do work...

    session.LogDebug(ctx, "Detailed state", map[string]any{
        "processed": processed,
        "remaining": remaining,
    })

    return output, nil
}

Note: Future releases will add functional options to support logger names and metadata for complex multi-subsystem tools.

Request Metadata Access

Access request-specific metadata including OAuth tokens, HTTP headers, and resource identifiers through the ToolContext parameter. This metadata is automatically injected when handlers are invoked by MCP clients.

Security Note: MCP logs (via session.LogInfo(), session.LogDebug(), etc.) are sent to the LLM as context. Never log sensitive data like OAuth tokens, session IDs, or internal identifiers to MCP logs. Use a separate logging backend (slog, standard logger, metrics system) for audit trails and analytics.

Available methods on toolCtx:

  • toolCtx.GetIdentifier() - Returns the tool name, prompt name, or resource URI for the current request
  • toolCtx.GetTokenInfo() - Returns OAuth token information if the client provided authentication
  • toolCtx.GetHeaders().Get(key) - Returns a specific HTTP header value from the request
  • toolCtx.GetHeaders() - Returns all HTTP headers from the request

Example: Accessing request context

import "log/slog"

func myTool(ctx context.Context, toolCtx mcpio.RequestContext, input MyInput) (MyOutput, error) {
    // Get the tool/prompt/resource identifier
    identifier := toolCtx.GetIdentifier()

    // Read custom HTTP headers from the client
    clientVersion := toolCtx.GetHeaders().Get("X-Client-Version")
    deploymentEnv := toolCtx.GetHeaders().Get("X-Deployment-Env")

    // Use backend logger for analytics/audit (NOT MCP logs which go to LLM)
    slog.Info("Request received",
        "identifier", identifier,
        "clientVersion", clientVersion,
        "env", deploymentEnv,
    )

    // Process request...
    return output, nil
}
Session Interface Access

Access the Session interface directly via toolCtx to check capabilities, call session methods, or access features not yet available through convenience helpers.

Note: This returns mcp-io's Session interface, NOT the underlying MCP SDK's *mcp.ServerSession. You're still using mcp-io's abstraction layer, which means you're subject to the same incomplete implementations (missing Logger, ProgressToken, Meta fields, etc.). For direct MCP SDK access, use handler.GetServer() to access the raw *mcp.Server.

func advancedTool(ctx context.Context, toolCtx mcpio.RequestContext, _ struct{}) (map[string]any, error) {
    session := toolCtx.GetSession()
    if session == nil {
        return nil, errors.New("no session available")
    }

    // Check capabilities before using features
    if session.SupportsElicitation() {
        // Use elicitation...
    }
    if session.SupportsSampling() {
        // Use sampling...
    }

    // Access client capabilities for detailed information
    caps := session.ClientCapabilities()
    if caps.Roots != nil && caps.Roots.ListChanged {
        // Client supports notifications for roots list changes
        roots, err := session.ListRoots(ctx)
        if err == nil {
            // Use roots...
            _ = roots
        }
    }

    // Access session methods directly
    sessionID := session.SessionID()

    return map[string]any{"sessionID": sessionID}, nil
}
Completion (Autocomplete)

Provide autocomplete suggestions for prompt arguments and resource URIs. Completion handlers can use context from previously-resolved arguments to provide smarter, dependency-aware suggestions. See examples/cli_completion/ for usage.

Advanced Features

Raw JSON Tools

Raw JSON tools work with []byte directly instead of typed Go structs, providing flexibility for dynamic data structures without automatic validation. Specifying the input schema is required; output schema is optional.

When to use raw tools (WithRawTool):

  • Accept arbitrary JSON structures that can't be predefined as Go structs
  • Process JSON-to-JSON transformations where the structure varies
  • Work with dynamic schemas determined at runtime
  • Interface with external APIs that return varying JSON formats
  • Need maximum flexibility and willing to handle validation yourself

When NOT to use raw tools (use WithTool[In, Out] instead):

  • Data structures can be defined as Go structs
  • Want compile-time type safety
  • Want automatic input/output validation against schemas
  • Prefer catching schema mismatches early rather than at runtime
import (
    mcpio "github.com/robbyt/mcp-io"
    "github.com/robbyt/mcp-io/primitives/tool"
)

// Example: A tool that validates and reformats any JSON input
validateJSON := func(ctx context.Context, toolCtx mcpio.RequestContext, input []byte) ([]byte, error) {
    // Unmarshal to confirm it's valid JSON
    var jsonData any
    if err := json.Unmarshal(input, &jsonData); err != nil {
        return nil, mcpio.ValidationError("Invalid JSON: " + err.Error())
    }

    // Re-marshal back to JSON with indentation for pretty formatting
    formatted, err := json.MarshalIndent(jsonData, "", "  ")
    if err != nil {
        return nil, mcpio.ProcessingError("Failed to format JSON: " + err.Error())
    }

    // Return the formatted JSON wrapped in a result object
    result := map[string]any{
        "formatted_json": string(formatted),
        "valid": true,
        "size_bytes": len(input),
    }

    return json.Marshal(result)
}

// Define input schema (required) - tells LLM what to send
inputSchema := `{
    "type": "object",
    "properties": {
        "json_data": {
            "type": "object",
            "description": "Any JSON object or array to validate and format"
        }
    },
    "required": ["json_data"]
}`

// Define output schema (optional) - tells LLM what to expect back
outputSchema := `{
    "type": "object",
    "properties": {
        "formatted_json": {"type": "string", "description": "Pretty-printed JSON"},
        "valid": {"type": "boolean", "description": "Whether input was valid JSON"},
        "size_bytes": {"type": "integer", "description": "Size of input in bytes"}
    }
}`

handler, err := mcpio.NewHandler(
    mcpio.WithName("json-processor"),
    mcpio.WithRawTool("validate_json", "Validate and format any JSON input", inputSchema, validateJSON,
        tool.WithOutputSchema(outputSchema),  // Optional but recommended for LLM understanding
    ),
)
if err != nil {
    log.Fatalf("Failed to create raw tool: %v", err)
}
Schema Type Options

The WithRawTool option (introduced in the Raw JSON Tools section above) accepts schemas in multiple formats. Choose the format that best fits your use case:

// Option 1: JSON string (for readability, shown in previous section)
schemaJSON := `{
    "type": "object",
    "properties": {
        "operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
        "a": {"type": "number"},
        "b": {"type": "number"}
    },
    "required": ["operation", "a", "b"]
}`
mcpio.WithRawTool("calculator", "Arithmetic calculator", schemaJSON, calcRawFunc)

// Option 2: map[string]any (for programmatic/dynamic schema construction)
dynamicSchema := map[string]any{
    "type": "object",
    "properties": map[string]any{
        "message": map[string]any{"type": "string"},
        "repeat": map[string]any{"type": "integer", "minimum": 1, "maximum": 10},
    },
    "required": []string{"message"},
}
mcpio.WithRawTool("echo", "Echo with repetition", dynamicSchema, echoRawFunc)

// Option 3: json.RawMessage (best performance)
jRaw := json.RawMessage(`{
    "type": "object",
    "properties": {
        "data": {"type": "string"},
        "count": {"type": "integer", "minimum": 1}
    },
    "required": ["data"]
}`)
mcpio.WithRawTool("fast_processor", "High-performance processing", jRaw, processorRawFunc)

Comparison with Direct MCP SDK

This library wraps the official MCP SDK to provide additional safety and convenience. Here are the key differences:

Configuration and Error Handling

MCP SDK: Panics on invalid configuration

// Panics at runtime with: panic("nil Implementation")
server := mcpSDK.NewServer(nil, nil)

mcp-io: Returns errors on invalid configuration

// Returns error at configuration time
handler, err := mcpio.NewHandler(
    mcpio.WithServer(nil), // error: server cannot be nil
)
if err != nil {
    log.Printf("Configuration error: %v", err)
}
Simplified Handler Signatures

Both libraries support type-safe tools with automatic schema generation. The key difference is handler signature complexity.

MCP SDK: Explicit access to request metadata

type GreetInput struct {
    Name string `json:"name" jsonschema:"User's name"`
}

type GreetOutput struct {
    Greeting string `json:"greeting" jsonschema:"The greeting message"`
}

// Full access to request context, OAuth tokens, HTTP headers
func greet(ctx context.Context, req *mcpSDK.CallToolRequest, input GreetInput) (
    *mcpSDK.CallToolResult, GreetOutput, error,
) {
    // Access OAuth token for authorization
    token := req.Extra.TokenInfo
    // Access HTTP headers
    headers := req.Extra.Header
    // Access tool name
    toolName := req.Params.Name

    return nil, GreetOutput{Greeting: "Hello " + input.Name}, nil
}

mcpSDK.AddTool(server, tool, greet) // Schema auto-generated from types

mcp-io: Simplified signature with RequestContext parameter

// Clean signature with toolCtx parameter for metadata access
func greet(ctx context.Context, toolCtx mcpio.RequestContext, input GreetInput) (GreetOutput, error) {
    // Session available via toolCtx, if needed
    session := toolCtx.GetSession()

    return GreetOutput{Greeting: "Hello " + input.Name}, nil
}

handler, err := mcpio.NewHandler(
    mcpio.WithName("greeter"),
    mcpio.WithTool("greet", "Greet user", greet), // Schema auto-generated
)
Request Context Pattern

MCP SDK: Functions receive concrete request types

func handler(ctx context.Context, req *mcpSDK.CallToolRequest, input T) (*mcpSDK.CallToolResult, T, error) {
    token := req.Extra.TokenInfo
    session := req.Session
    toolName := req.Params.Name
    // ...
}

mcp-io: Functions receive an interface

func handler(ctx context.Context, toolCtx mcpio.RequestContext, input T) (T, error) {
    token := toolCtx.GetTokenInfo()
    session := toolCtx.GetSession()
    toolName := toolCtx.GetIdentifier()
    // ...
}

The interface decouples handlers from SDK struct layout, enabling dependency injection and simpler testing with mock implementations.

Comparison:

Feature MCP SDK mcp-io (current) mcp-io (planned)
Auto-generate schemas ✅ Yes ✅ Yes ✅ Yes
Simple handler signature ⚠️ 5 params ✅ 3 params ✅ 3 params
Access session capabilities ✅ Via request param ✅ Via toolCtx ✅ Via toolCtx
Access OAuth tokens ✅ Via req.Extra.TokenInfo ✅ Via toolCtx.GetTokenInfo() N/A
Access HTTP headers ✅ Via req.Extra.Header ✅ Via toolCtx.GetHeaders() N/A
Access tool name ✅ Via req.Params.Name ✅ Via toolCtx.GetIdentifier() N/A
Control output format ✅ Via CallToolResult ❌ Auto-wrapped ⚠️ Raw handler option
Escape hatch to MCP SDK N/A ⚠️ Via GetServer() WithRawToolHandler

Request metadata (OAuth tokens, HTTP headers, tool/prompt/resource identifiers) is accessible via the toolCtx parameter: toolCtx.GetTokenInfo(), toolCtx.GetHeaders().Get(key), toolCtx.GetHeaders(), and toolCtx.GetIdentifier(). See Request Metadata Access for details.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	RefTypePrompt   = "ref/prompt"
	RefTypeResource = "ref/resource"
)

MCP completion reference types

View Source
const (
	ElicitActionAccept  = "accept"
	ElicitActionDecline = "decline"
	ElicitActionCancel  = "cancel"
)

Elicitation action constants defined by the MCP specification

View Source
const (
	ErrorCodeValidation       = "VALIDATION_ERROR"     // Input validation failures
	ErrorCodeProcessing       = "PROCESSING_ERROR"     // Data processing issues
	ErrorCodeRateLimit        = "RATE_LIMITED"         // API rate limiting
	ErrorCodeInsufficientData = "INSUFFICIENT_DATA"    // Missing required information
	ErrorCodeAuthentication   = "AUTHENTICATION_ERROR" // Authentication failures
	ErrorCodeAuthorization    = "AUTHORIZATION_ERROR"  // Authorization/permission issues
	ErrorCodeNotFound         = "NOT_FOUND"            // Resource or entity not found
	ErrorCodeTimeout          = "TIMEOUT"              // Operation timeout
)

Common tool error codes for categorizing user-facing tool errors

Variables

View Source
var (
	ErrEmptyValue = errors.New("value cannot be empty")
	ErrNilValue   = errors.New("value cannot be nil")
)

Configuration validation errors

View Source
var (
	ErrInvalidJSONSchema  = errors.New("invalid JSON schema")
	ErrNoSchemaProperties = errors.New("schema has no properties")
)

Schema validation errors

View Source
var (
	ErrInvalidJSON      = errors.New("tool returned invalid JSON")
	ErrSchemaGeneration = errors.New("failed to generate schema")
	ErrNoContent        = errors.New("no content to decode")
	ErrUnmarshalContent = errors.New("failed to unmarshal content")
)

Runtime and operational errors

View Source
var ErrElicitationNotSupported = errors.New("client does not support elicitation")

ErrElicitationNotSupported is returned when attempting to elicit user input from a client that doesn't support the elicitation capability. Check capabilities.Session.SupportsElicitation() before calling elicitation functions.

View Source
var ErrInvalidTotal = errors.New("total must be 0 (unknown) or >= len(values)")

ErrInvalidTotal is returned when Total is set but is less than the number of Values

View Source
var ErrNoCompletions = errors.New("completion handler must return at least one value")

ErrNoCompletions is returned when a completion handler provides no suggestions

View Source
var ErrNoSession = errors.New("no session available in context")

ErrNoSession is returned when a session-dependent operation is attempted without an active MCP session in the context. This typically occurs when calling sampling, elicitation, or logging functions outside of a proper tool, prompt, or resource handler execution.

View Source
var ErrSamplingNotSupported = errors.New("client does not support sampling")

ErrSamplingNotSupported is returned when attempting to use CreateMessage (sampling) with a client that doesn't support the sampling capability. Check capabilities.Session.SupportsSampling() before calling CreateMessage.

View Source
var ErrUnsupportedRefType = errors.New("unsupported completion reference type")

ErrUnsupportedRefType is returned when the reference type is not supported

Functions

func ResourceNotFoundError

func ResourceNotFoundError(uri string) error

ResourceNotFoundError returns a protocol-level error indicating that a resource could not be found. This provides the proper MCP error code (-32002) for resource not found conditions.

Types

type CompletionError

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

CompletionError represents a user-facing completion error. Use this for errors that should be returned to the MCP client (e.g., unsupported ref types).

func NewCompletionError

func NewCompletionError(msg string) *CompletionError

NewCompletionError creates a new completion error with the given message

func (*CompletionError) Error

func (e *CompletionError) Error() string

type CompletionFunc

CompletionFunc is the function signature for user-defined completion handlers. The function receives context, request metadata, completion reference details, and returns completion suggestions with optional metadata.

Example:

func myCompletionHandler(ctx context.Context, reqCtx mcpio.RequestContext, ref CompletionRef) (*CompletionResult, error) {
    if ref.Type == mcpio.RefTypePrompt && ref.Name == "greet" {
        return &CompletionResult{
            Values: []string{"Hello", "Hi", "Greetings"},
        }, nil
    }
    return nil, mcpio.NewCompletionError("unsupported reference type")
}

type CompletionRef

type CompletionRef struct {
	// Type indicates what kind of reference this is (e.g., "ref/prompt", "ref/resource")
	Type string

	// Name is the name of the prompt being completed (used with "ref/prompt")
	Name string

	// URI is the resource URI being completed (used with "ref/resource")
	URI string

	// Argument is the specific argument name being completed within a prompt
	Argument string

	// Value is the current input value for the argument being completed
	Value string

	// Context contains previously-resolved arguments for dependency-aware completions.
	// For example, if completing a "style" argument and the user has already selected
	// language="Spanish", Context would contain {"language": "Spanish"}.
	Context map[string]string
}

CompletionRef contains information about what is being completed. This maps to mcp.CompleteRequest.Params.Ref with user-friendly field names.

type CompletionResult

type CompletionResult struct {
	// Values contains the completion suggestions (required, max recommended: 100)
	Values []string

	// HasMore indicates if additional completions are available beyond this set (optional)
	// Default: false (all completions provided)
	HasMore bool

	// Total indicates the total number of completions available (optional)
	// Use 0 or omit if unknown. When HasMore is true, Total > len(Values)
	Total int

	// Meta is protocol-reserved metadata for extensibility (optional)
	Meta map[string]any
}

CompletionResult represents completion suggestions returned by a completion handler. This provides a simplified, opinionated interface compared to the raw SDK types.

func (*CompletionResult) Validate

func (r *CompletionResult) Validate() error

Validate ensures the CompletionResult is well-formed before returning to the SDK

type ElicitationResult

type ElicitationResult struct {
	*mcp.ElicitResult
}

ElicitationResult provides typed access to elicitation results. This wraps the raw MCP ElicitResult with convenience methods.

func ElicitTyped

func ElicitTyped[T any](ctx context.Context, elicitor *Elicitor, message string) (*ElicitationResult, error)

ElicitTyped sends an elicitation request with automatic schema generation from a Go struct. The schema is generated from struct tags, allowing you to specify validation rules, descriptions, and constraints.

Example:

type UserConfig struct {
    Name     string `json:"name" jsonschema:"Your full name"`
    Email    string `json:"email" jsonschema:"format=email"`
    EnableUI bool   `json:"enableUI" jsonschema:"Enable graphical interface"`
}

func myTool(ctx context.Context, toolCtx mcpio.RequestContext, input MyInput) (MyOutput, error) {
    elicitor := mcpio.NewElicitor(toolCtx)
    result, err := mcpio.ElicitTyped[UserConfig](ctx, elicitor, "Please provide your configuration:")
    if err != nil {
        return MyOutput{}, err
    }

    if result.IsAccepted() {
        var config UserConfig
        if err := result.DecodeContent(&config); err != nil {
            return MyOutput{}, err
        }
        // Use config...
    }
    return MyOutput{}, nil
}

Schema Tags: Use jsonschema struct tags for validation:

  • description: Human-readable field description
  • minimum/maximum: Numeric constraints
  • enum: Allowed values (enum=option1,enum=option2)
  • format: String format (email, uri, etc.)

func WrapElicitResult

func WrapElicitResult(result *mcp.ElicitResult) *ElicitationResult

WrapElicitResult wraps an MCP ElicitResult with convenience methods.

func (*ElicitationResult) DecodeContent

func (r *ElicitationResult) DecodeContent(target any) error

DecodeContent decodes the elicitation content into the target struct. This eliminates the need for manual JSON marshaling/unmarshaling when working with typed elicitation results.

Example usage:

type UserConfig struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

result, err := ElicitTypedResult[UserConfig](ctx, capability, "Enter config:")
if err != nil {
    return err
}

if result.IsAccepted() {
    var config UserConfig
    if err := result.DecodeContent(&config); err != nil {
        return fmt.Errorf("failed to decode config: %w", err)
    }
    // Use config...
}

The method returns an error if:

  • The elicitation was not accepted (content is nil)
  • JSON marshaling/unmarshaling fails
  • The target is not a valid pointer

func (*ElicitationResult) GetContent

func (r *ElicitationResult) GetContent() map[string]any

GetContent returns the submitted form data when the action is "accept". Returns nil if the user declined or cancelled.

Example:

result, err := ElicitSimple(ctx, capability, "Name:", "name", "Your name")
if err != nil {
    return nil, err
}

if content := result.GetContent(); content != nil {
    name := content["name"].(string)
    // Use name...
} else {
    // User declined or cancelled
}

func (*ElicitationResult) IsAccepted

func (r *ElicitationResult) IsAccepted() bool

IsAccepted returns true if the user accepted the elicitation.

Example:

result, err := ElicitTyped[Config](ctx, capability, "Enter configuration:")
if err != nil {
    return nil, err
}

if result.IsAccepted() {
    // User provided data - process result.Content
    data := result.GetContent()
    // Convert to your struct...
}

func (*ElicitationResult) IsCancelled

func (r *ElicitationResult) IsCancelled() bool

IsCancelled returns true if the user cancelled/dismissed the elicitation.

Example:

result, err := ElicitTyped[Config](ctx, capability, "Enter config:")
if err != nil {
    return nil, err
}

if result.IsCancelled() {
    return map[string]any{"status": "cancelled"}, nil
}

func (*ElicitationResult) IsDeclined

func (r *ElicitationResult) IsDeclined() bool

IsDeclined returns true if the user explicitly declined the elicitation.

Example:

result, err := ElicitSimple(ctx, capability, "Continue?", "confirm", "Type yes/no")
if err != nil {
    return nil, err
}

if result.IsDeclined() {
    return map[string]any{"status": "user_declined"}, nil
}

type Elicitor

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

Elicitor provides elicitation functionality for interactive user input. Create via mcpio.NewElicitor(toolCtx) in tool functions.

func NewElicitor

func NewElicitor(toolCtx RequestContext) *Elicitor

NewElicitor creates an Elicitor with the given RequestContext. This allows elicitation to access the session and validate elicitation support.

func (*Elicitor) ElicitSimple

func (e *Elicitor) ElicitSimple(ctx context.Context, message, fieldName, description string) (*ElicitationResult, error)

ElicitSimple is a convenience function for simple string input elicitation. The session is automatically extracted from the context. This creates a schema for a single string field with the specified name and description. It's ideal for quick confirmations, simple text inputs, or collecting single values.

Example:

func myTool(ctx context.Context, input MyInput) (MyOutput, error) {
    result, err := mcpio.ElicitSimple(ctx, "Enter your username:", "username", "Your account username")
    if err != nil {
        return MyOutput{}, err
    }

    if result.IsAccepted() {
        username := result.GetContent()["username"].(string)
        // Use username...
    }
    return MyOutput{}, nil
}

Confirmation Example:

result, err := mcpio.ElicitSimple(ctx,
    "Delete all files? This cannot be undone.", "confirm", "Type 'DELETE' to confirm")
if err != nil {
    return MyOutput{}, err
}

if result.IsAccepted() {
    if confirmation := result.GetContent()["confirm"].(string); confirmation == "DELETE" {
        // Proceed with deletion
    }
}

The fieldName parameter becomes the key in the returned Content map. The description parameter guides the user on what to enter.

type Handler

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

Handler is the main MCP handler struct

func NewHandler

func NewHandler(opts ...Option) (*Handler, error)

NewHandler creates a new MCP handler that supports any combination of MCP resources. This is the unified constructor that can handle tools, prompts, resources, and resource templates.

func NewInMemoryPair

func NewInMemoryPair(ctx context.Context, opts ...Option) (*Handler, mcp.Transport, error)

NewInMemoryPair creates a Handler and client Transport for in-memory communication. This is useful for testing and integrating mcp-io servers with MCP clients like Google ADK.

The returned handler is configured with an in-memory server transport, and the returned client transport can be used to connect to the server. The handler must be started with Run() before the client transport is used.

Example with Google ADK:

handler, clientTransport, err := mcpio.NewInMemoryPair(ctx,
    mcpio.WithTool("weather", "Get weather", getWeather),
)
if err != nil {
    log.Fatal(err)
}

// Start server in background
var wg sync.WaitGroup
wg.Go(func() {
    if err := handler.Run(ctx); err != nil {
        log.Printf("Server error: %v", err)
    }
})
defer wg.Wait()

// Use with ADK (requires google.golang.org/adk)
mcpToolSet, err := mcptoolset.New(mcptoolset.Config{
    Transport: clientTransport,
})
if err != nil {
    log.Fatal(err)
}

agent, err := llmagent.New(llmagent.Config{
    Toolsets: []tool.Toolset{mcpToolSet},
})

func (*Handler) GetServer

func (h *Handler) GetServer() MCPServer

GetServer returns the underlying MCP server for advanced usage

func (*Handler) GetTransport

func (h *Handler) GetTransport() mcp.Transport

GetTransport returns the currently configured transport for this handler. Returns nil if no transport has been set.

This is useful for advanced use cases where access to the underlying transport is needed, such as when integrating with external MCP client libraries like Google ADK.

For common use cases with in-memory transports, consider using NewInMemoryPair() instead, which returns both the handler and client transport in one call.

Example:

handler, _ := mcpio.NewHandler(mcpio.WithTool(...))
transport := handler.GetTransport()
// transport can now be used with external MCP clients

func (*Handler) Run

func (h *Handler) Run(ctx context.Context) error

Run starts the MCP server with the configured transport. This is the main entry point for starting the server.

For stdio transport (default), this reads from os.Stdin and writes to os.Stdout. For in-memory transport (from NewInMemoryPair), this connects to the paired client transport. For HTTP transport, use ServeHTTP instead.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler for Streamable HTTP transport. Delegates to the MCPServer's ServeHTTP method.

func (*Handler) ServeStdio deprecated

func (h *Handler) ServeStdio(ctx context.Context) error

ServeStdio runs the server using stdio transport for command-line tools. Uses os.Stdin and os.Stdout as configured by the MCP SDK.

Deprecated: Use Run() instead. ServeStdio is kept for backward compatibility but Run() is the preferred method as it works with any transport type.

type MCPRequestContext

type MCPRequestContext struct {
	// MCPParams contains raw MCP request parameters.
	Params mcp.Params

	// Session provides access to MCP session capabilities (sampling, elicitation, logging, etc).
	// Never nil - always contains a valid session instance.
	Session *capabilities.Session

	// Identifier is the name or URI for the current request:
	//   - Tool requests: tool name
	//   - Prompt requests: prompt name
	//   - Resource requests: resource URI
	Identifier string

	// TokenInfo contains OAuth token information when the request includes authentication.
	// Nil if no token is present in the request.
	TokenInfo *auth.TokenInfo

	// Headers contains HTTP headers when the request was made via HTTP transport.
	// Never nil - always contains a valid (possibly empty) http.Header map.
	Headers http.Header
}

MCPRequestContext holds all MCP request metadata and implements the RequestContext interface. This struct is created once per request and passed directly to the primitives (tool, prompt, resource).

Exported to enable test mocking. Tests construct MCPRequestContext instances with mock sessions when testing elicitation, logging, and other session-dependent functionality.

func (*MCPRequestContext) GetHeaders

func (r *MCPRequestContext) GetHeaders() http.Header

GetHeaders returns all HTTP headers from the request. Never returns nil - returns empty http.Header{} if no headers present.

func (*MCPRequestContext) GetIdentifier

func (r *MCPRequestContext) GetIdentifier() string

GetIdentifier returns the identifier for the current request. For tools, this is the tool name. For prompts, the prompt name. For resources, the URI.

func (*MCPRequestContext) GetMeta

func (r *MCPRequestContext) GetMeta() map[string]any

GetMeta returns MCP request metadata as a map[string]any.

func (*MCPRequestContext) GetProgressToken

func (r *MCPRequestContext) GetProgressToken() any

GetProgressToken returns the progress token from the request params if available. Returns nil if the params don't support progress tokens or no token is present.

func (*MCPRequestContext) GetSession

func (r *MCPRequestContext) GetSession() *capabilities.Session

GetSession returns the MCP session for accessing session capabilities like logging, elicitation, and sampling.

func (*MCPRequestContext) GetTokenInfo

func (r *MCPRequestContext) GetTokenInfo() *auth.TokenInfo

GetTokenInfo returns OAuth token information from the request if available. Returns nil if no token was provided.

type MCPServer

type MCPServer interface {
	// Tool registration
	AddTool(tool *mcp.Tool, handler mcp.ToolHandler)

	// Prompt registration
	AddPrompt(prompt *mcp.Prompt, handler mcp.PromptHandler)

	// Resource registration
	AddResource(resource *mcp.Resource, handler mcp.ResourceHandler)
	AddResourceTemplate(template *mcp.ResourceTemplate, handler mcp.ResourceHandler)

	// Transport - run the server with configured transport
	Run(ctx context.Context) error

	// GetTransport returns the currently configured transport
	GetTransport() mcp.Transport

	// ServeHTTP implements http.Handler for Streamable HTTP transport
	ServeHTTP(w http.ResponseWriter, r *http.Request)

	// Unwrap returns the underlying SDK server for advanced usage.
	// Returns *mcp.Server from github.com/modelcontextprotocol/go-sdk/mcp
	// Returns nil if this is a mock implementation.
	Unwrap() any
}

MCPServer provides an interface to the concrete MCP SDK server instance.

The Unwrap method provides an escape hatch for power users who need access to features not yet wrapped by mcp-io.

type Option

type Option func(*handlerConfig) error

Option is a functional option for configuring handlers

func WithCompletion

func WithCompletion(fn CompletionFunc) Option

WithCompletion adds a completion handler to the server. The completion handler provides autocomplete suggestions for prompts, resources, or custom reference types.

Only ONE completion handler can be registered per server. Multiple calls to WithCompletion will result in only the last handler being used (SDK limitation).

Example:

mcpio.WithCompletion(func(ctx context.Context, reqCtx mcpio.RequestContext, ref mcpio.CompletionRef) (*mcpio.CompletionResult, error) {
    switch ref.Type {
    case "ref/prompt":
        if ref.Name == "greet" && ref.Argument == "language" {
            return &mcpio.CompletionResult{
                Values: []string{"English", "Spanish", "French", "German"},
            }, nil
        }
    case "ref/resource":
        return &mcpio.CompletionResult{
            Values: []string{"file:///data1.txt", "file:///data2.txt"},
        }, nil
    }
    return nil, mcpio.NewCompletionError("unsupported reference type")
})

func WithHTTPTransport

func WithHTTPTransport(opts ...mcpwrapper.TransportOptionStreamableHTTP) Option

WithHTTPTransport configures the Streamable HTTP transport. Accepts variadic TransportOption functions for configuration (e.g., mcp.WithStateless()).

Example:

mcpio.WithHTTPTransport(
    mcp.WithStateless(),
    mcp.WithJSONResponse(),
)

func WithName

func WithName(name string) Option

WithName sets the server name

func WithPrompt

func WithPrompt(name, description string, fn PromptFunc) Option

WithPrompt adds a prompt to the handler

func WithPromptWithArgs

func WithPromptWithArgs(name, description string, args []*mcp.PromptArgument, fn PromptFunc) Option

WithPromptWithArgs adds a prompt with argument definitions

func WithRawTool

func WithRawTool(name, description string, inputSchema any, fn RawToolFunc, opts ...tool.Option) Option

WithRawTool adds a tool with manual JSON handling and explicit schema. Use this when you need direct control over JSON processing or dynamic schemas. Optional metadata can be provided via functional options from primitives/tool package.

Examples:

rawFunc := func(ctx context.Context, input []byte) ([]byte, error) { return input, nil }
WithRawTool("process", "Process JSON", `{"type":"object"}`, rawFunc)

// With metadata (recommended import alias: import toolOption "github.com/robbyt/mcp-io/primitives/tool")
WithRawTool("process", "Process JSON", inputSchema, rawFunc,
    toolOption.WithTitle("JSON Processor"),
    toolOption.WithReadOnly(),
)

func WithResource

func WithResource(uri, description string, fn ResourceFunc) Option

WithResource adds a resource to the handler

func WithResourceTemplate

func WithResourceTemplate(uriTemplate, description string, fn ResourceFunc) Option

WithResourceTemplate adds a resource template to the handler

func WithServer

func WithServer(server *mcp.Server) Option

WithServer allows injecting a custom server for testing

func WithServerConfig

func WithServerConfig(opts *mcp.ServerOptions) Option

WithServerConfig sets the complete MCP server options struct. Use this for advanced configurations requiring handler functions. For simple configurations, prefer WithServerOptions with functional options.

func WithServerOptions

func WithServerOptions(opts ...mcpwrapper.ServerOption) Option

WithServerOptions configures MCP server options using functional options. Accepts variadic ServerOption functions for configuration.

Example:

mcpio.WithServerOptions(
    mcp.WithInstructions("Use this server for data processing"),
    mcp.WithPageSize(50),
    mcp.WithCapabilityPrompts(),
)

func WithTool

func WithTool[TIn, TOut any](name, description string, fn ToolFunc[TIn, TOut], opts ...tool.Option) Option

WithTool adds a type-safe tool with automatic schema generation from Go types.

The InputSchema is automatically generated from the TIn type parameter. Optional metadata can be provided using functional options from the primitives/tool package.

Examples:

// Simple case: Auto-generated schemas only
WithTool("to_upper", "Convert text to uppercase", toUpperFunc)

// With metadata options (recommended import alias: import toolOption "github.com/robbyt/mcp-io/primitives/tool")
WithTool("to_upper", "Convert text to uppercase", toUpperFunc,
    toolOption.WithTitle("Text Uppercaser"),
    toolOption.WithReadOnly(),
    toolOption.WithIdempotent(),
)

func WithTransport

func WithTransport(transport mcp.Transport) Option

WithTransport sets a custom transport for the handler. By default, handlers use StdioTransport. This option allows using in-memory or HTTP transports for testing or integration scenarios.

This is primarily useful for:

  • Testing with in-memory transports
  • Integration with external libraries like Google ADK
  • Custom transport implementations

For most use cases, consider using NewInMemoryPair() instead, which handles transport setup automatically.

Example with custom transport:

serverTransport, clientTransport := mcp.NewInMemoryTransports()
handler, _ := mcpio.NewHandler(
    mcpio.WithTransport(serverTransport),
    mcpio.WithTool("echo", "Echo tool", echoFunc),
)
// Start server with handler.Run(ctx)
// Connect client using clientTransport

func WithTypedPrompt

func WithTypedPrompt[TArgs any](name, description string, fn TypedPromptFunc[TArgs]) Option

WithTypedPrompt adds a type-safe prompt with automatic schema generation

func WithVersion

func WithVersion(version string) Option

WithVersion sets the server version

type PromptFunc

type PromptFunc func(context.Context, RequestContext, map[string]any) (*PromptResult, error)

PromptFunc is the function signature for user-defined prompt handlers. The function receives a context, request context with metadata, and a map of arguments, and returns a PromptResult with an optional error.

type PromptMessage

type PromptMessage struct {
	Role    string `json:"role"` // "user", "assistant", "system"
	Content string `json:"content"`
}

PromptMessage represents a single message in a prompt

type PromptResult

type PromptResult struct {
	Messages    []PromptMessage `json:"messages"`
	Description string          `json:"description,omitempty"`
}

PromptResult represents the result of prompt generation

type RawToolFunc

type RawToolFunc func(context.Context, RequestContext, []byte) ([]byte, error)

RawToolFunc is the function signature for raw JSON tools. The function receives a context, RequestContext for accessing session/metadata, and raw JSON bytes as input, and returns JSON bytes as output. Schema must be provided explicitly when using WithRawTool.

type RequestContext

type RequestContext interface {

	// GetSession returns the MCP session for accessing session capabilities like
	// logging, elicitation, and sampling.
	GetSession() *capabilities.Session

	// GetIdentifier returns the identifier for the current request.
	// For tools: tool name, for prompts: prompt name, for resources: URI.
	GetIdentifier() string

	// GetTokenInfo returns OAuth token information if present, nil otherwise.
	GetTokenInfo() *auth.TokenInfo

	// GetHeaders returns HTTP headers from the request.
	GetHeaders() http.Header

	// GetProgressToken returns the progress token from the request params if present.
	// Progress tokens are opaque identifiers (int or string per MCP specification) that
	// associate progress notifications with their originating request, enabling proper
	// concurrent request tracking. The token must be echoed back exactly as received
	// for client-side matching to work correctly. Returns nil if no token is present.
	GetProgressToken() any
	// contains filtered or unexported methods
}

RequestContext provides access to MCP request metadata and session capabilities. RequestContext implements this interface and is passed directly to tool functions, eliminating the need for context storage and retrieval.

type ResourceContent

type ResourceContent struct {
	Content  []byte `json:"content"`
	MIMEType string `json:"mimeType,omitempty"`
}

ResourceContent represents the content of a resource

type ResourceFunc

type ResourceFunc func(context.Context, RequestContext) (*ResourceContent, error)

ResourceFunc is the function signature for user-defined resource handlers. The function receives a context, request context with metadata, and returns ResourceContent with an optional error. The URI can be accessed via reqCtx.GetIdentifier().

type ToolError

type ToolError struct {
	Message string
	Code    string // Optional error code for categorization
}

ToolError represents a user-facing tool execution error that becomes visible to LLMs. When returned from tool functions, the MCP SDK automatically wraps these in CallToolResult with IsError=true, allowing LLMs to see the error and potentially retry or self-correct.

func NewToolError

func NewToolError(message string) *ToolError

NewToolError creates a new tool error with the given message.

Tool errors are user-facing errors that become visible to LLMs in the CallToolResult. When returned from tool functions, the MCP SDK automatically:

  • Sets CallToolResult.IsError = true
  • Includes the error message in CallToolResult.Content
  • Allows the LLM to see the error and potentially retry or self-correct

Use tool errors for validation failures, input problems, or other issues that the LLM should be aware of and can potentially fix.

For system-level errors (authentication, network, etc.) that should be hidden from the LLM, return regular Go errors instead.

func NewToolErrorWithCode

func NewToolErrorWithCode(message, code string) *ToolError

NewToolErrorWithCode creates a new tool error with message and error code.

The error code provides categorization for different types of tool errors, which can be useful for client-side error handling, analytics, and structured error responses. Use the ErrorCode* constants for common error types.

Like NewToolError, these become visible to LLMs through CallToolResult.

func ProcessingError

func ProcessingError(message string) *ToolError

ProcessingError is a convenience function for creating processing tool errors

func ValidationError

func ValidationError(message string) *ToolError

ValidationError is a convenience function for creating validation tool errors

func (*ToolError) Error

func (e *ToolError) Error() string

type ToolFunc

type ToolFunc[TIn, TOut any] func(context.Context, RequestContext, TIn) (TOut, error)

ToolFunc is the function signature for typed tools with automatic schema generation. The function receives a context, RequestContext for accessing session/metadata, typed input, and returns typed output with an optional error. Schema generation is handled automatically based on the TIn and TOut types.

type TypedPromptFunc

type TypedPromptFunc[TArgs any] func(context.Context, RequestContext, TArgs) (*PromptResult, error)

TypedPromptFunc is the function signature for typed prompts with automatic schema generation. The function receives a context, request context with metadata, and typed arguments, and returns a PromptResult with an optional error. Schema generation is handled automatically based on the TArgs type.

Example:

type DocumentArgs struct {
	DocumentType string `json:"document_type" jsonschema:"description:Type of document"`
	Topic        string `json:"topic" jsonschema:"description:Main topic"`
}
func myPrompt(ctx context.Context, reqCtx mcpio.RequestContext, args DocumentArgs) (*PromptResult, error) { ... }

Directories

Path Synopsis
sampling
Package sampling provides options for configuring LLM sampling requests.
Package sampling provides options for configuring LLM sampling requests.
examples
cli_agent command
cli_completion command
cli_elicitation command
cli_prompt command
cli_resource command
cli_simple command
http_multistep command
mixed_resources command
internal
Package mcpwrapper provides a wrapper around the MCP SDK server to simplify transport configuration and provide a consistent interface for both stdio and HTTP transports.
Package mcpwrapper provides a wrapper around the MCP SDK server to simplify transport configuration and provide a consistent interface for both stdio and HTTP transports.
primitives
tool
Package tool provides constructors for MCP Tool primitives using functional options.
Package tool provides constructors for MCP Tool primitives using functional options.

Jump to

Keyboard shortcuts

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