
A Go implementation of the Model Context Protocol (MCP), enabling seamless integration between LLM applications and external data sources and tools.
Discuss the SDK on Discord
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// Create a new MCP server
s := server.NewMCPServer(
"Demo 🚀",
"1.0.0",
server.WithToolCapabilities(false),
)
// Add tool
tool := mcp.NewTool("hello_world",
mcp.WithDescription("Say hello to someone"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("Name of the person to greet"),
),
)
// Add tool handler
s.AddTool(tool, helloHandler)
// Start the stdio server
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, err := request.RequireString("name")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil
}
That's it!
MCP Go handles all the complex protocol details and server management, so you can focus on building great tools. It aims to be high-level and easy to use.
(*emphasis on aims)
🚨 🚧 🏗️ MCP Go is under active development, as is the MCP specification itself. Core features are working but some advanced capabilities are still in progress.
go get github.com/mark3labs/mcp-go
Let's create a simple MCP server that exposes a calculator tool and some data:
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// Create a new MCP server
s := server.NewMCPServer(
"Calculator Demo",
"1.0.0",
server.WithToolCapabilities(false),
server.WithRecovery(),
)
// Add a calculator tool
calculatorTool := mcp.NewTool("calculate",
mcp.WithDescription("Perform basic arithmetic operations"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("The operation to perform (add, subtract, multiply, divide)"),
mcp.Enum("add", "subtract", "multiply", "divide"),
),
mcp.WithNumber("x",
mcp.Required(),
mcp.Description("First number"),
),
mcp.WithNumber("y",
mcp.Required(),
mcp.Description("Second number"),
),
)
// Add the calculator handler
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Using helper functions for type-safe argument access
op, err := request.RequireString("operation")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
x, err := request.RequireFloat("x")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
y, err := request.RequireFloat("y")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
var result float64
switch op {
case "add":
result = x + y
case "subtract":
result = x - y
case "multiply":
result = x * y
case "divide":
if y == 0 {
return mcp.NewToolResultError("cannot divide by zero"), nil
}
result = x / y
}
return mcp.NewToolResultText(fmt.Sprintf("%.2f", result)), nil
})
// Start the server
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
The Model Context Protocol (MCP) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions.
MCP servers can: - Expose data through Resources (think of these sort of like GET endpoints; they are used to load information into the LLM's context) - Provide functionality through Tools (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) - Define interaction patterns through Prompts (reusable templates for LLM interactions) - And more!
mcp-go implements the Model Context Protocol specification version 2025-11-25, with backward compatibility for versions 2025-06-18, 2025-03-26, and 2024-11-05.
Show Server Examples
The server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
// Create a basic server
s := server.NewMCPServer(
"My Server", // Server name
"1.0.0", // Version
)
// Start the server using stdio
if err := server.ServeStdio(s); err != nil {
log.Fatalf("Server error: %v", err)
}
Show Resource Examples
Resources are how you expose data to LLMs. They can be anything - files, API responses, database queries, system information, etc. Resources can be:
Here's a simple example of a static resource:
// Static resource example - exposing a README file
resource := mcp.NewResource(
"docs://readme",
"Project README",
mcp.WithResourceDescription("The project's README file"),
mcp.WithMIMEType("text/markdown"),
)
// Add resource with its handler
s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
content, err := os.ReadFile("README.md")
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: "docs://readme",
MIMEType: "text/markdown",
Text: string(content),
},
}, nil
})
And here's an example of a dynamic resource using a template:
// Dynamic resource example - user profiles by ID
template := mcp.NewResourceTemplate(
"users://{id}/profile",
"User Profile",
mcp.WithTemplateDescription("Returns user profile information"),
mcp.WithTemplateMIMEType("application/json"),
)
// Add template with its handler
s.AddResourceTemplate(template, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
// Extract ID from the URI using regex matching
// The server automatically matches URIs to templates
userID := extractIDFromURI(request.Params.URI)
profile, err := getUserProfile(userID) // Your DB/API call here
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: request.Params.URI,
MIMEType: "application/json",
Text: profile,
},
}, nil
})
The examples are simple but demonstrate the core concepts. Resources can be much more sophisticated - serving multiple contents, integrating with databases or external APIs, etc.
Show Tool Examples
Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects. They're similar to POST endpoints in a REST API.
Task-augmented tools execute asynchronously and return results via polling. This is useful for long-running operations that would otherwise block or time out. Task tools support three modes:
// Example: A tool that requires task execution
processBatchTool := mcp.NewTool("process_batch",
mcp.WithDescription("Process a batch of items asynchronously"),
mcp.WithTaskSupport(mcp.TaskSupportRequired),
mcp.WithArray("items",
mcp.Description("Array of items to process"),
mcp.WithStringItems(),
mcp.Required(),
),
)
// Task tool handler returns CreateTaskResult instead of CallToolResult
s.AddTaskTool(processBatchTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CreateTaskResult, error) {
items := request.GetStringSlice("items", []string{})
// Long-running work here
for i, item := range items {
select {
case <-ctx.Done():
// Task was cancelled
return nil, ctx.Err()
default:
// Process item...
processItem(item)
}
}
// Return result - task ID and metadata are managed by the server
return &mcp.CreateTaskResult{
Task: mcp.Task{
// Task fields (ID, status, etc.) are populated by the server
},
}, nil
})
// Enable task capabilities when creating the server
s := server.NewMCPServer(
"Task Server",
"1.0.0",
server.WithTaskCapabilities(
true, // listTasks: allows clients to list all tasks
true, // cancel: allows clients to cancel running tasks
true, // toolCallTasks: enables task augmentation for tools
),
server.WithMaxConcurrentTasks(10), // Optional: limit concurrent running tasks
)
Task execution flow:
1. Client calls tool with task parameter
2. Server immediately returns task ID
3. Tool executes asynchronously in the background
4. Client polls tasks/result to retrieve the result
5. Server sends task status notifications on completion
For optional task tools, the same tool can be called synchronously (without task parameter) or asynchronously (with task parameter):
// Tool with optional task support
analyzeTool := mcp.NewTool("analyze_data",
mcp.WithDescription("Analyze data - can run sync or async"),
mcp.WithTaskSupport(mcp.TaskSupportOptional),
mcp.WithString("data", mcp.Required()),
)
// Use AddTaskTool for hybrid tools that support both modes
s.AddTaskTool(analyzeTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CreateTaskResult, error) {
// This handler runs when called as a task
data := request.GetString("data", "")
result := analyzeData(data)
return &mcp.CreateTaskResult{
Task: mcp.Task{},
}, nil
})
// The server automatically handles both sync and async invocations
// When called without task param: executes handler and returns immediately
// When called with task param: executes handler asynchronously
To prevent resource exhaustion, you can limit the number of concurrent running tasks:
s := server.NewMCPServer(
"Task Server",
"1.0.0",
server.WithTaskCapabilities(true, true, true),
server.WithMaxConcurrentTasks(10), // Allow up to 10 concurrent running tasks
)
When the limit is reached, new task creation requests will fail with an error. Completed, failed, or cancelled tasks don't count toward the limit - only tasks in "working" status. If WithMaxConcurrentTasks is not specified or set to 0, there is no limit on concurrent tasks.
For traditional synchronous tools that execute and return results immediately:
Simple calculation example: ```go calculatorTool := mcp.NewTool("calculate", mcp.WithDescription("Perform basic arithmetic calculations"), mcp.WithString("operation", mcp.Required(), mcp.Description("The arithmetic operation to perform"), mcp.Enum("add", "subtract", "multiply", "divide"), ), mcp.WithNumber("x", mcp.Required(), mcp.Description("First number"), ), mcp.WithNumber("y", mcp.Required(), mcp.Description("Second number"), ), )
s.AddTool(calculatorTool, func(ctx context.Context,
$ claude mcp add mcp-go \
-- python -m otcore.mcp_server <graph>