tessl install tessl/golang-mcp-golang@0.16.1An unofficial implementation of the Model Context Protocol in Go
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)
}
}