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.
import mcp "github.com/metoro-io/mcp-golang"The Server type represents an MCP server that handles client requests and manages tools, prompts, and resources.
type Server struct {
// Contains unexported fields
}func NewServer(transport transport.Transport, options ...ServerOptions) *ServerCreates a new MCP server with the specified transport and optional configuration.
Parameters:
transport: A transport implementation (stdio, HTTP, Gin) for communicationoptions: Variadic ServerOptions functions for configurationReturns: 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"),
)type ServerOptions func(*Server)Function type for configuring server options.
func WithName(name string) ServerOptionsSets the server name returned to clients during initialization.
Parameters:
name: Server nameExample:
server := mcp.NewServer(
transport,
mcp.WithName("calculator-server"),
)func WithVersion(version string) ServerOptionsSets the server version returned to clients during initialization.
Parameters:
version: Server version stringExample:
server := mcp.NewServer(
transport,
mcp.WithVersion("2.1.0"),
)func WithInstructions(instructions string) ServerOptionsSets usage instructions returned to clients during initialization.
Parameters:
instructions: Human-readable instructions for using the serverExample:
server := mcp.NewServer(
transport,
mcp.WithInstructions("This server provides mathematical operations. Use the 'calculate' tool with operation and numbers."),
)func WithProtocol(protocol *protocol.Protocol) ServerOptionsSets a custom protocol instance for advanced configuration.
Parameters:
protocol: Custom protocol implementationNote: This is an advanced option; most users should not need to set a custom protocol.
func WithPaginationLimit(limit int) ServerOptionsSets the pagination limit for list operations (tools, prompts, resources).
Parameters:
limit: Maximum number of items per pageExample:
server := mcp.NewServer(
transport,
mcp.WithPaginationLimit(50), // Return up to 50 items per page
)func (s *Server) Serve() errorStarts 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)
}func (s *Server) RegisterTool(name string, description string, handler any) errorRegisters a new tool with the server. The handler function receives typed arguments and returns a ToolResponse.
Parameters:
name: Unique tool namedescription: Human-readable tool descriptionhandler: Function that handles tool invocations. Must have signature: func(args T) (*ToolResponse, error) where T is a struct with json and jsonschema tagsReturns: 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 requireddescription=... - Field descriptionminimum=N - Minimum numeric valuemaximum=N - Maximum numeric valueminLength=N - Minimum string lengthmaxLength=N - Maximum string lengthpattern=... - Regex pattern for stringsformat=... - Format hint (e.g., email, uri, date-time)enum=val1|val2|val3 - Enumerated valuesAll handler types (tools, prompts, resources) support an optional context.Context parameter as the first argument. This enables:
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)func (s *Server) DeregisterTool(name string) errorRemoves a registered tool from the server.
Parameters:
name: Name of the tool to removeReturns: Error if tool doesn't exist
Example:
err := server.DeregisterTool("calculate")
if err != nil {
log.Printf("Failed to deregister tool: %v", err)
}func (s *Server) CheckToolRegistered(name string) boolChecks if a tool is currently registered.
Parameters:
name: Tool name to checkReturns: true if tool is registered, false otherwise
Example:
if server.CheckToolRegistered("calculate") {
log.Println("Calculate tool is available")
}func (s *Server) RegisterPrompt(name string, description string, handler any) errorRegisters a new prompt with the server. The handler function receives typed arguments and returns a PromptResponse.
Parameters:
name: Unique prompt namedescription: Human-readable prompt descriptionhandler: Function that handles prompt requests. Must have signature: func(args T) (*PromptResponse, error) where T is a struct with json and jsonschema tagsReturns: 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)
}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
}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.
func (s *Server) DeregisterPrompt(name string) errorRemoves a registered prompt from the server.
Parameters:
name: Name of the prompt to removeReturns: Error if prompt doesn't exist
Example:
err := server.DeregisterPrompt("explain-topic")
if err != nil {
log.Printf("Failed to deregister prompt: %v", err)
}func (s *Server) CheckPromptRegistered(name string) boolChecks if a prompt is currently registered.
Parameters:
name: Prompt name to checkReturns: true if prompt is registered, false otherwise
Example:
if server.CheckPromptRegistered("explain-topic") {
log.Println("Explain topic prompt is available")
}func (s *Server) RegisterResource(uri string, name string, description string, mimeType string, handler any) errorRegisters 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 namedescription: Resource descriptionmimeType: 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,
)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
}func (s *Server) DeregisterResource(uri string) errorRemoves a registered resource from the server.
Parameters:
uri: URI of the resource to removeReturns: 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)
}func (s *Server) CheckResourceRegistered(uri string) boolChecks if a resource is currently registered.
Parameters:
uri: Resource URI to checkReturns: true if resource is registered, false otherwise
Example:
if server.CheckResourceRegistered("file:///config/app.json") {
log.Println("Config resource is available")
}func (s *Server) RegisterResourceTemplate(uriTemplate string, name string, description string, mimeType string) errorRegisters 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 namedescription: Template descriptionmimeType: MIME type of resources matching this templateReturns: 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.jsonURI Template syntax:
{varname} - Simple variable substitutionfile:///data/{category}/{id}.json - Multiple variableshttps://api.example.com/v1/{resource} - Any valid URI schemefunc (s *Server) DeregisterResourceTemplate(uriTemplate string) errorRemoves a registered resource template from the server.
Parameters:
uriTemplate: URI template to removeReturns: 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)
}func (s *Server) CheckResourceTemplateRegistered(uriTemplate string) boolChecks if a resource template is currently registered.
Parameters:
uriTemplate: Resource template to checkReturns: true if template is registered, false otherwise
Example:
if server.CheckResourceTemplateRegistered("file:///users/{userId}/profile.json") {
log.Println("User profile template is available")
}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)
}
}