or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client.mdcontent-types.mdindex.mdserver.mdtransport-http.mdtransport-stdio.mdtransport.md
tile.json

server.mddocs/

MCP Server API

The Server API enables building MCP servers that expose tools, prompts, and resources to clients. Servers handle incoming requests and execute registered handlers to provide functionality.

Package

import mcp "github.com/metoro-io/mcp-golang"

Server

The Server type represents an MCP server that handles client requests and manages tools, prompts, and resources.

type Server struct {
    // Contains unexported fields
}

Creating a Server

func NewServer(transport transport.Transport, options ...ServerOptions) *Server

Creates a new MCP server with the specified transport and optional configuration.

Parameters:

  • transport: A transport implementation (stdio, HTTP, Gin) for communication
  • options: Variadic ServerOptions functions for configuration

Returns: A new Server instance

Example:

import (
    mcp "github.com/metoro-io/mcp-golang"
    "github.com/metoro-io/mcp-golang/transport/stdio"
)

server := mcp.NewServer(
    stdio.NewStdioServerTransport(),
    mcp.WithName("my-mcp-server"),
    mcp.WithVersion("1.0.0"),
    mcp.WithInstructions("Use this server to perform calculations and data processing"),
)

ServerOptions

type ServerOptions func(*Server)

Function type for configuring server options.

WithName

func WithName(name string) ServerOptions

Sets the server name returned to clients during initialization.

Parameters:

  • name: Server name

Example:

server := mcp.NewServer(
    transport,
    mcp.WithName("calculator-server"),
)

WithVersion

func WithVersion(version string) ServerOptions

Sets the server version returned to clients during initialization.

Parameters:

  • version: Server version string

Example:

server := mcp.NewServer(
    transport,
    mcp.WithVersion("2.1.0"),
)

WithInstructions

func WithInstructions(instructions string) ServerOptions

Sets usage instructions returned to clients during initialization.

Parameters:

  • instructions: Human-readable instructions for using the server

Example:

server := mcp.NewServer(
    transport,
    mcp.WithInstructions("This server provides mathematical operations. Use the 'calculate' tool with operation and numbers."),
)

WithProtocol

func WithProtocol(protocol *protocol.Protocol) ServerOptions

Sets a custom protocol instance for advanced configuration.

Parameters:

  • protocol: Custom protocol implementation

Note: This is an advanced option; most users should not need to set a custom protocol.

WithPaginationLimit

func WithPaginationLimit(limit int) ServerOptions

Sets the pagination limit for list operations (tools, prompts, resources).

Parameters:

  • limit: Maximum number of items per page

Example:

server := mcp.NewServer(
    transport,
    mcp.WithPaginationLimit(50), // Return up to 50 items per page
)

Server Lifecycle

Serve

func (s *Server) Serve() error

Starts the server and begins handling requests. This method blocks until the server is stopped or encounters an error.

Returns: Error if the server fails to start or encounters a fatal error

Example:

server := mcp.NewServer(stdio.NewStdioServerTransport())

// Register tools, prompts, and resources
server.RegisterTool("greet", "Greets a person", greetHandler)

// Start server (blocks)
if err := server.Serve(); err != nil {
    log.Fatalf("Server error: %v", err)
}

Tool Management

RegisterTool

func (s *Server) RegisterTool(name string, description string, handler any) error

Registers a new tool with the server. The handler function receives typed arguments and returns a ToolResponse.

Parameters:

  • name: Unique tool name
  • description: Human-readable tool description
  • handler: Function that handles tool invocations. Must have signature: func(args T) (*ToolResponse, error) where T is a struct with json and jsonschema tags

Returns: Error if registration fails (e.g., duplicate name, invalid handler)

Example with typed arguments:

// Define strongly-typed arguments with jsonschema tags
type CalculateArgs struct {
    Operation string  `json:"operation" jsonschema:"required,enum=add|subtract|multiply|divide,description=The arithmetic operation to perform"`
    A         float64 `json:"a" jsonschema:"required,description=First number"`
    B         float64 `json:"b" jsonschema:"required,description=Second number"`
}

// Handler function with typed arguments
func calculateHandler(args CalculateArgs) (*mcp.ToolResponse, error) {
    var result float64
    switch args.Operation {
    case "add":
        result = args.A + args.B
    case "subtract":
        result = args.A - args.B
    case "multiply":
        result = args.A * args.B
    case "divide":
        if args.B == 0 {
            return nil, fmt.Errorf("division by zero")
        }
        result = args.A / args.B
    default:
        return nil, fmt.Errorf("unknown operation: %s", args.Operation)
    }

    return mcp.NewToolResponse(
        mcp.NewTextContent(fmt.Sprintf("Result: %.2f", result)),
    ), nil
}

// Register the tool
err := server.RegisterTool(
    "calculate",
    "Performs basic arithmetic operations on two numbers",
    calculateHandler,
)
if err != nil {
    log.Fatalf("Failed to register tool: %v", err)
}

Example with complex types:

type Address struct {
    Street  string `json:"street" jsonschema:"required"`
    City    string `json:"city" jsonschema:"required"`
    Country string `json:"country" jsonschema:"required"`
}

type CreateUserArgs struct {
    Name     string   `json:"name" jsonschema:"required,minLength=1,maxLength=100,description=User's full name"`
    Email    string   `json:"email" jsonschema:"required,format=email,description=User's email address"`
    Age      int      `json:"age" jsonschema:"minimum=0,maximum=150,description=User's age"`
    Address  Address  `json:"address" jsonschema:"required,description=User's address"`
    Tags     []string `json:"tags,omitempty" jsonschema:"description=Optional user tags"`
}

func createUserHandler(args CreateUserArgs) (*mcp.ToolResponse, error) {
    // Validate and create user
    user := fmt.Sprintf("Created user: %s <%s>, Age: %d, Address: %s, %s, %s",
        args.Name, args.Email, args.Age,
        args.Address.Street, args.Address.City, args.Address.Country)

    if len(args.Tags) > 0 {
        user += fmt.Sprintf(", Tags: %v", args.Tags)
    }

    return mcp.NewToolResponse(
        mcp.NewTextContent(user),
    ), nil
}

server.RegisterTool(
    "create_user",
    "Creates a new user with the provided information",
    createUserHandler,
)

Supported jsonschema tags:

  • required - Field is required
  • description=... - Field description
  • minimum=N - Minimum numeric value
  • maximum=N - Maximum numeric value
  • minLength=N - Minimum string length
  • maxLength=N - Maximum string length
  • pattern=... - Regex pattern for strings
  • format=... - Format hint (e.g., email, uri, date-time)
  • enum=val1|val2|val3 - Enumerated values

Handler Context Support

All handler types (tools, prompts, resources) support an optional context.Context parameter as the first argument. This enables:

  • Request cancellation
  • Timeout handling
  • Tracing and logging
  • Request-scoped values

Tool Handler Signatures:

// Without context
func(args T) (*ToolResponse, error)

// With context (recommended)
func(ctx context.Context, args T) (*ToolResponse, error)

Example with timeout:

import (
    "context"
    "time"
    mcp "github.com/metoro-io/mcp-golang"
)

type ProcessArgs struct {
    Data string `json:"data" jsonschema:"required,description=Data to process"`
}

func processHandler(ctx context.Context, args ProcessArgs) (*mcp.ToolResponse, error) {
    // Check for cancellation
    select {
    case <-ctx.Done():
        return nil, ctx.Err()
    default:
    }

    // Create timeout for long operation
    timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    result, err := performLongOperation(timeoutCtx, args.Data)
    if err != nil {
        return nil, err
    }

    return mcp.NewToolResponse(
        mcp.NewTextContent(result),
    ), nil
}

// Register with context support
server.RegisterTool("process", "Process data with timeout", processHandler)

DeregisterTool

func (s *Server) DeregisterTool(name string) error

Removes a registered tool from the server.

Parameters:

  • name: Name of the tool to remove

Returns: Error if tool doesn't exist

Example:

err := server.DeregisterTool("calculate")
if err != nil {
    log.Printf("Failed to deregister tool: %v", err)
}

CheckToolRegistered

func (s *Server) CheckToolRegistered(name string) bool

Checks if a tool is currently registered.

Parameters:

  • name: Tool name to check

Returns: true if tool is registered, false otherwise

Example:

if server.CheckToolRegistered("calculate") {
    log.Println("Calculate tool is available")
}

Prompt Management

RegisterPrompt

func (s *Server) RegisterPrompt(name string, description string, handler any) error

Registers a new prompt with the server. The handler function receives typed arguments and returns a PromptResponse.

Parameters:

  • name: Unique prompt name
  • description: Human-readable prompt description
  • handler: Function that handles prompt requests. Must have signature: func(args T) (*PromptResponse, error) where T is a struct with json and jsonschema tags

Returns: Error if registration fails

Example:

type ExplainArgs struct {
    Topic      string `json:"topic" jsonschema:"required,description=The topic to explain"`
    Difficulty string `json:"difficulty" jsonschema:"enum=beginner|intermediate|advanced,description=Target difficulty level"`
    MaxWords   int    `json:"max_words,omitempty" jsonschema:"minimum=50,maximum=1000,description=Maximum words in explanation"`
}

func explainHandler(args ExplainArgs) (*mcp.PromptResponse, error) {
    difficulty := args.Difficulty
    if difficulty == "" {
        difficulty = "beginner"
    }

    systemPrompt := fmt.Sprintf(
        "You are an expert educator. Explain topics clearly at a %s level.",
        difficulty,
    )

    userPrompt := fmt.Sprintf("Explain the following topic: %s", args.Topic)
    if args.MaxWords > 0 {
        userPrompt += fmt.Sprintf(" (keep under %d words)", args.MaxWords)
    }

    return mcp.NewPromptResponse(
        "Educational explanation prompt",
        mcp.NewPromptMessage(
            mcp.NewTextContent(systemPrompt),
            mcp.RoleAssistant,
        ),
        mcp.NewPromptMessage(
            mcp.NewTextContent(userPrompt),
            mcp.RoleUser,
        ),
    ), nil
}

err := server.RegisterPrompt(
    "explain-topic",
    "Generates an educational explanation for any topic",
    explainHandler,
)
if err != nil {
    log.Fatalf("Failed to register prompt: %v", err)
}

Prompt Handler Signatures

Like tool handlers, prompt handlers also support an optional context.Context parameter:

// Without context
func(args T) (*PromptResponse, error)

// With context (recommended)
func(ctx context.Context, args T) (*PromptResponse, error)

Example with context:

func explainHandlerWithContext(ctx context.Context, args ExplainArgs) (*mcp.PromptResponse, error) {
    // Check for cancellation
    if ctx.Err() != nil {
        return nil, ctx.Err()
    }

    // Generate prompt...
    return mcp.NewPromptResponse(
        "Educational explanation prompt",
        mcp.NewPromptMessage(
            mcp.NewTextContent("System prompt here"),
            mcp.RoleAssistant,
        ),
    ), nil
}

Important: Prompt Argument Type Restrictions

Unlike tool handlers which support complex types, prompt handler arguments are restricted to structs with only string or *string fields. This is because prompts use a simpler schema system designed for template rendering.

Valid prompt argument struct:

type PromptArgs struct {
    Topic     string  `json:"topic" jsonschema:"required,description=The topic"`
    Style     string  `json:"style,omitempty" jsonschema:"description=Style preference"`
    MaxLength *string `json:"max_length,omitempty" jsonschema:"description=Maximum length"`
}

Invalid prompt argument struct (will cause registration error):

type InvalidPromptArgs struct {
    Count int       `json:"count"` // ERROR: int not allowed
    Items []string  `json:"items"` // ERROR: slices not allowed
    Flag  bool      `json:"flag"`  // ERROR: bool not allowed
}

If you need complex parameter types (integers, booleans, arrays, nested objects), use tool handlers instead of prompt handlers.

DeregisterPrompt

func (s *Server) DeregisterPrompt(name string) error

Removes a registered prompt from the server.

Parameters:

  • name: Name of the prompt to remove

Returns: Error if prompt doesn't exist

Example:

err := server.DeregisterPrompt("explain-topic")
if err != nil {
    log.Printf("Failed to deregister prompt: %v", err)
}

CheckPromptRegistered

func (s *Server) CheckPromptRegistered(name string) bool

Checks if a prompt is currently registered.

Parameters:

  • name: Prompt name to check

Returns: true if prompt is registered, false otherwise

Example:

if server.CheckPromptRegistered("explain-topic") {
    log.Println("Explain topic prompt is available")
}

Resource Management

RegisterResource

func (s *Server) RegisterResource(uri string, name string, description string, mimeType string, handler any) error

Registers a new resource with the server. The handler function receives no arguments and returns a ResourceResponse.

Parameters:

  • uri: Unique resource URI (e.g., "file:///path/to/resource" or "custom://resource-id")
  • name: Human-readable resource name
  • description: Resource description
  • mimeType: MIME type of the resource content (e.g., "text/plain", "application/json")
  • handler: Function that handles resource read requests. Must have signature: func() (*ResourceResponse, error)

Returns: Error if registration fails

Example with text resource:

func configHandler() (*mcp.ResourceResponse, error) {
    config := `{
        "api_url": "https://api.example.com",
        "timeout": 30,
        "retries": 3
    }`

    return mcp.NewResourceResponse(
        mcp.NewTextEmbeddedResource(
            "file:///config/app.json",
            config,
            "application/json",
        ),
    ), nil
}

err := server.RegisterResource(
    "file:///config/app.json",
    "Application Configuration",
    "Main application configuration file",
    "application/json",
    configHandler,
)
if err != nil {
    log.Fatalf("Failed to register resource: %v", err)
}

Example with binary resource:

func logoHandler() (*mcp.ResourceResponse, error) {
    // Read logo file and base64 encode
    data, err := os.ReadFile("logo.png")
    if err != nil {
        return nil, err
    }
    base64Data := base64.StdEncoding.EncodeToString(data)

    return mcp.NewResourceResponse(
        mcp.NewBlobEmbeddedResource(
            "file:///assets/logo.png",
            base64Data,
            "image/png",
        ),
    ), nil
}

server.RegisterResource(
    "file:///assets/logo.png",
    "Company Logo",
    "Official company logo in PNG format",
    "image/png",
    logoHandler,
)

Resource Handler Signatures

Like tool and prompt handlers, resource handlers also support an optional context.Context parameter:

// Without context
func() (*ResourceResponse, error)

// With context (recommended)
func(ctx context.Context) (*ResourceResponse, error)

Example with context:

func configHandlerWithContext(ctx context.Context) (*mcp.ResourceResponse, error) {
    // Check for cancellation
    if ctx.Err() != nil {
        return nil, ctx.Err()
    }

    // Read config with context support
    config, err := loadConfigWithContext(ctx)
    if err != nil {
        return nil, err
    }

    return mcp.NewResourceResponse(
        mcp.NewTextEmbeddedResource(
            "file:///config/app.json",
            config,
            "application/json",
        ),
    ), nil
}

DeregisterResource

func (s *Server) DeregisterResource(uri string) error

Removes a registered resource from the server.

Parameters:

  • uri: URI of the resource to remove

Returns: Error if resource doesn't exist

Example:

err := server.DeregisterResource("file:///config/app.json")
if err != nil {
    log.Printf("Failed to deregister resource: %v", err)
}

CheckResourceRegistered

func (s *Server) CheckResourceRegistered(uri string) bool

Checks if a resource is currently registered.

Parameters:

  • uri: Resource URI to check

Returns: true if resource is registered, false otherwise

Example:

if server.CheckResourceRegistered("file:///config/app.json") {
    log.Println("Config resource is available")
}

Resource Template Management

RegisterResourceTemplate

func (s *Server) RegisterResourceTemplate(uriTemplate string, name string, description string, mimeType string) error

Registers a resource template for dynamic resources. Templates use RFC 6570 URI Template syntax.

Parameters:

  • uriTemplate: URI template (e.g., "file:///users/{userId}/profile.json")
  • name: Human-readable template name
  • description: Template description
  • mimeType: MIME type of resources matching this template

Returns: Error if registration fails

Example:

err := server.RegisterResourceTemplate(
    "file:///users/{userId}/profile.json",
    "User Profile",
    "User profile data in JSON format",
    "application/json",
)
if err != nil {
    log.Fatalf("Failed to register resource template: %v", err)
}

// Clients can now discover this pattern and request specific URIs like:
// file:///users/123/profile.json

URI Template syntax:

  • {varname} - Simple variable substitution
  • file:///data/{category}/{id}.json - Multiple variables
  • https://api.example.com/v1/{resource} - Any valid URI scheme

DeregisterResourceTemplate

func (s *Server) DeregisterResourceTemplate(uriTemplate string) error

Removes a registered resource template from the server.

Parameters:

  • uriTemplate: URI template to remove

Returns: Error if template doesn't exist

Example:

err := server.DeregisterResourceTemplate("file:///users/{userId}/profile.json")
if err != nil {
    log.Printf("Failed to deregister resource template: %v", err)
}

CheckResourceTemplateRegistered

func (s *Server) CheckResourceTemplateRegistered(uriTemplate string) bool

Checks if a resource template is currently registered.

Parameters:

  • uriTemplate: Resource template to check

Returns: true if template is registered, false otherwise

Example:

if server.CheckResourceTemplateRegistered("file:///users/{userId}/profile.json") {
    log.Println("User profile template is available")
}

Complete Server Example

package main

import (
    "encoding/base64"
    "fmt"
    "log"
    "os"

    mcp "github.com/metoro-io/mcp-golang"
    "github.com/metoro-io/mcp-golang/transport/stdio"
)

// Tool argument types
type GreetArgs struct {
    Name  string `json:"name" jsonschema:"required,description=Name of person to greet"`
    Style string `json:"style,omitempty" jsonschema:"enum=formal|casual,description=Greeting style"`
}

type SearchArgs struct {
    Query   string `json:"query" jsonschema:"required,minLength=1,description=Search query"`
    MaxResults int `json:"max_results,omitempty" jsonschema:"minimum=1,maximum=100,description=Maximum results to return"`
}

// Prompt argument types
type SummarizeArgs struct {
    Text      string `json:"text" jsonschema:"required,description=Text to summarize"`
    MaxLength int    `json:"max_length,omitempty" jsonschema:"minimum=10,maximum=500,description=Maximum summary length in words"`
}

func main() {
    // Create server with configuration
    server := mcp.NewServer(
        stdio.NewStdioServerTransport(),
        mcp.WithName("example-server"),
        mcp.WithVersion("1.0.0"),
        mcp.WithInstructions("Example MCP server with tools, prompts, and resources"),
        mcp.WithPaginationLimit(25),
    )

    // Register tools
    server.RegisterTool("greet", "Greets a person by name",
        func(args GreetArgs) (*mcp.ToolResponse, error) {
            style := args.Style
            if style == "" {
                style = "casual"
            }

            var greeting string
            if style == "formal" {
                greeting = fmt.Sprintf("Good day, %s.", args.Name)
            } else {
                greeting = fmt.Sprintf("Hey %s!", args.Name)
            }

            return mcp.NewToolResponse(
                mcp.NewTextContent(greeting),
            ), nil
        })

    server.RegisterTool("search", "Searches for information",
        func(args SearchArgs) (*mcp.ToolResponse, error) {
            maxResults := args.MaxResults
            if maxResults == 0 {
                maxResults = 10
            }

            result := fmt.Sprintf("Found %d results for query: %s", maxResults, args.Query)
            return mcp.NewToolResponse(
                mcp.NewTextContent(result),
            ), nil
        })

    // Register prompts
    server.RegisterPrompt("summarize", "Creates a summarization prompt",
        func(args SummarizeArgs) (*mcp.PromptResponse, error) {
            instruction := "Summarize the following text concisely"
            if args.MaxLength > 0 {
                instruction += fmt.Sprintf(" in under %d words", args.MaxLength)
            }

            return mcp.NewPromptResponse(
                "Text summarization prompt",
                mcp.NewPromptMessage(
                    mcp.NewTextContent(instruction),
                    mcp.RoleAssistant,
                ),
                mcp.NewPromptMessage(
                    mcp.NewTextContent(args.Text),
                    mcp.RoleUser,
                ),
            ), nil
        })

    // Register resources
    server.RegisterResource(
        "file:///docs/readme.txt",
        "README",
        "Server documentation",
        "text/plain",
        func() (*mcp.ResourceResponse, error) {
            readme := "# Example MCP Server\n\nThis server provides greeting and search functionality."
            return mcp.NewResourceResponse(
                mcp.NewTextEmbeddedResource(
                    "file:///docs/readme.txt",
                    readme,
                    "text/plain",
                ),
            ), nil
        })

    // Register resource templates
    server.RegisterResourceTemplate(
        "file:///docs/{filename}.txt",
        "Documentation File",
        "Server documentation files",
        "text/plain",
    )

    // Start server
    log.Println("Starting MCP server...")
    if err := server.Serve(); err != nil {
        log.Fatalf("Server error: %v", err)
    }
}