
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" ) funcmain(){// Create a new MCP servers:=server.NewMCPServer( "Demo 🚀", "1.0.0", server.WithToolCapabilities(false), ) // Add tooltool:=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 handlers.AddTool(tool, helloHandler) // Start the stdio serveriferr:=server.ServeStdio(s); err!=nil{fmt.Printf("Server error: %v\n", err) } } funchelloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error){name, err:=request.RequireString("name") iferr!=nil{returnmcp.NewToolResultError(err.Error()), nil } returnmcp.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.
- Fast: High-level interface means less code and faster development
- Simple: Build MCP servers with minimal boilerplate
- Complete*: MCP Go aims to provide a full implementation of the core MCP specification
(*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-goLet'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" ) funcmain(){// Create a new MCP servers:=server.NewMCPServer( "Calculator Demo", "1.0.0", server.WithToolCapabilities(false), server.WithRecovery(), ) // Add a calculator toolcalculatorTool:=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 handlers.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error){// Using helper functions for type-safe argument accessop, err:=request.RequireString("operation") iferr!=nil{returnmcp.NewToolResultError(err.Error()), nil } x, err:=request.RequireFloat("x") iferr!=nil{returnmcp.NewToolResultError(err.Error()), nil } y, err:=request.RequireFloat("y") iferr!=nil{returnmcp.NewToolResultError(err.Error()), nil } varresultfloat64switchop{case"add": result=x+ycase"subtract": result=x-ycase"multiply": result=x*ycase"divide": ify==0{returnmcp.NewToolResultError("cannot divide by zero"), nil } result=x/y } returnmcp.NewToolResultText(fmt.Sprintf("%.2f", result)), nil }) // Start the serveriferr:=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!
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 servers:=server.NewMCPServer( "My Server", // Server name"1.0.0", // Version ) // Start the server using stdioiferr:=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:- Static (fixed URI)
- Dynamic (using URI templates)
Here's a simple example of a static resource:
// Static resource example - exposing a README fileresource:=mcp.NewResource( "docs://readme", "Project README", mcp.WithResourceDescription("The project's README file"), mcp.WithMIMEType("text/markdown"), ) // Add resource with its handlers.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error){content, err:=os.ReadFile("README.md") iferr!=nil{returnnil, 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 IDtemplate:=mcp.NewResourceTemplate( "users://{id}/profile", "User Profile", mcp.WithTemplateDescription("Returns user profile information"), mcp.WithTemplateMIMEType("application/json"), ) // Add template with its handlers.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 templatesuserID:=extractIDFromURI(request.Params.URI) profile, err:=getUserProfile(userID) // Your DB/API call hereiferr!=nil{returnnil, 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.
Simple calculation example:
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, request mcp.CallToolRequest) (*mcp.CallToolResult, error){args:=request.GetArguments() op:=args["operation"].(string) x:=args["x"].(float64) y:=args["y"].(float64) varresultfloat64switchop{case"add": result=x+ycase"subtract": result=x-ycase"multiply": result=x*ycase"divide": ify==0{returnmcp.NewToolResultError("cannot divide by zero"), nil } result=x/y } returnmcp.FormatNumberResult(result), nil })HTTP request example:
httpTool:=mcp.NewTool("http_request", mcp.WithDescription("Make HTTP requests to external APIs"), mcp.WithString("method", mcp.Required(), mcp.Description("HTTP method to use"), mcp.Enum("GET", "POST", "PUT", "DELETE"), ), mcp.WithString("url", mcp.Required(), mcp.Description("URL to send the request to"), mcp.Pattern("^https?://.*"), ), mcp.WithString("body", mcp.Description("Request body (for POST/PUT)"), ), ) s.AddTool(httpTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error){args:=request.GetArguments() method:=args["method"].(string) url:=args["url"].(string) body:=""ifb, ok:=args["body"].(string); ok{body=b } // Create and send requestvarreq*http.Requestvarerrerrorifbody!=""{req, err=http.NewRequest(method, url, strings.NewReader(body)) } else{req, err=http.NewRequest(method, url, nil) } iferr!=nil{returnmcp.NewToolResultErrorFromErr("unable to create request", err), nil } client:=&http.Client{} resp, err:=client.Do(req) iferr!=nil{returnmcp.NewToolResultErrorFromErr("unable to execute request", err), nil } deferresp.Body.Close() // Return responserespBody, err:=io.ReadAll(resp.Body) iferr!=nil{returnmcp.NewToolResultErrorFromErr("unable to read request response", err), nil } returnmcp.NewToolResultText(fmt.Sprintf("Status: %d\nBody: %s", resp.StatusCode, string(respBody))), nil })Tools can be used for any kind of computation or side effect:
- Database queries
- File operations
- External API calls
- Calculations
- System operations
Each tool should:
- Have a clear description
- Validate inputs
- Handle errors gracefully
- Return structured responses
- Use appropriate result types
Show Prompt Examples
Prompts are reusable templates that help LLMs interact with your server effectively. They're like "best practices" encoded into your server. Here are some examples:
// Simple greeting prompts.AddPrompt(mcp.NewPrompt("greeting", mcp.WithPromptDescription("A friendly greeting prompt"), mcp.WithArgument("name", mcp.ArgumentDescription("Name of the person to greet"), ), ), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error){name:=request.Params.Arguments["name"] ifname==""{name="friend" } returnmcp.NewGetPromptResult( "A friendly greeting", []mcp.PromptMessage{mcp.NewPromptMessage( mcp.RoleAssistant, mcp.NewTextContent(fmt.Sprintf("Hello, %s! How can I help you today?", name)), ), }, ), nil }) // Code review prompt with embedded resources.AddPrompt(mcp.NewPrompt("code_review", mcp.WithPromptDescription("Code review assistance"), mcp.WithArgument("pr_number", mcp.ArgumentDescription("Pull request number to review"), mcp.RequiredArgument(), ), ), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error){prNumber:=request.Params.Arguments["pr_number"] ifprNumber==""{returnnil, fmt.Errorf("pr_number is required") } returnmcp.NewGetPromptResult( "Code review assistance", []mcp.PromptMessage{mcp.NewPromptMessage( mcp.RoleUser, mcp.NewTextContent("Review the changes and provide constructive feedback."), ), mcp.NewPromptMessage( mcp.RoleAssistant, mcp.NewEmbeddedResource(mcp.ResourceContents{URI: fmt.Sprintf("git://pulls/%s/diff", prNumber), MIMEType: "text/x-diff", }), ), }, ), nil }) // Database query builder prompts.AddPrompt(mcp.NewPrompt("query_builder", mcp.WithPromptDescription("SQL query builder assistance"), mcp.WithArgument("table", mcp.ArgumentDescription("Name of the table to query"), mcp.RequiredArgument(), ), ), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error){tableName:=request.Params.Arguments["table"] iftableName==""{returnnil, fmt.Errorf("table name is required") } returnmcp.NewGetPromptResult( "SQL query builder assistance", []mcp.PromptMessage{mcp.NewPromptMessage( mcp.RoleUser, mcp.NewTextContent("Help construct efficient and safe queries for the provided schema."), ), mcp.NewPromptMessage( mcp.RoleUser, mcp.NewEmbeddedResource(mcp.ResourceContents{URI: fmt.Sprintf("db://schema/%s", tableName), MIMEType: "application/json", }), ), }, ), nil })Prompts can include:
- System instructions
- Required arguments
- Embedded resources
- Multiple messages
- Different content types (text, images, etc.)
- Custom URI schemes
For examples, see the examples/ directory.
MCP-Go supports stdio, SSE and streamable-HTTP transport layers. For SSE transport, you can use SetConnectionLostHandler() to detect and handle HTTP/2 idle timeout disconnections (NO_ERROR) for implementing reconnection logic.
MCP-Go provides a robust session management system that allows you to:
- Maintain separate state for each connected client
- Register and track client sessions
- Send notifications to specific clients
- Provide per-session tool customization
Show Session Management Examples
// Create a server with session capabilitiess:=server.NewMCPServer( "Session Demo", "1.0.0", server.WithToolCapabilities(true), ) // Implement your own ClientSessiontypeMySessionstruct{idstringnotifChannelchan mcp.JSONRPCNotificationisInitializedbool// Add custom fields for your application } // Implement the ClientSession interfacefunc (s*MySession) SessionID() string{returns.id } func (s*MySession) NotificationChannel() chan<- mcp.JSONRPCNotification{returns.notifChannel } func (s*MySession) Initialize(){s.isInitialized=true } func (s*MySession) Initialized() bool{returns.isInitialized } // Register a sessionsession:=&MySession{id: "user-123", notifChannel: make(chan mcp.JSONRPCNotification, 10), } iferr:=s.RegisterSession(context.Background(), session); err!=nil{log.Printf("Failed to register session: %v", err) } // Send notification to a specific clienterr:=s.SendNotificationToSpecificClient( session.SessionID(), "notification/update", map[string]any{"message": "New data available!"}, ) iferr!=nil{log.Printf("Failed to send notification: %v", err) } // Unregister session when dones.UnregisterSession(context.Background(), session.SessionID())For more advanced use cases, you can implement the SessionWithTools interface to support per-session tool customization:
// Implement SessionWithTools interface for per-session toolstypeMyAdvancedSessionstruct{MySession// Embed the basic sessionsessionToolsmap[string]server.ServerTool } // Implement additional methods for SessionWithToolsfunc (s*MyAdvancedSession) GetSessionTools() map[string]server.ServerTool{returns.sessionTools } func (s*MyAdvancedSession) SetSessionTools(toolsmap[string]server.ServerTool){s.sessionTools=tools } // Create and register a session with tools supportadvSession:=&MyAdvancedSession{MySession: MySession{id: "user-456", notifChannel: make(chan mcp.JSONRPCNotification, 10), }, sessionTools: make(map[string]server.ServerTool), } iferr:=s.RegisterSession(context.Background(), advSession); err!=nil{log.Printf("Failed to register session: %v", err) } // Add session-specific toolsuserSpecificTool:=mcp.NewTool( "user_data", mcp.WithDescription("Access user-specific data"), ) // You can use AddSessionTool (similar to AddTool)err:=s.AddSessionTool( advSession.SessionID(), userSpecificTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error){// This handler is only available to this specific sessionreturnmcp.NewToolResultText("User-specific data for "+advSession.SessionID()), nil }, ) iferr!=nil{log.Printf("Failed to add session tool: %v", err) } // Or use AddSessionTools directly with ServerTool/*err := s.AddSessionTools( advSession.SessionID(), server.ServerTool{ Tool: userSpecificTool, Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error){ // This handler is only available to this specific session return mcp.NewToolResultText("User-specific data for " + advSession.SessionID()), nil }, },)if err != nil{ log.Printf("Failed to add session tool: %v", err)}*/// Delete session-specific tools when no longer needederr=s.DeleteSessionTools(advSession.SessionID(), "user_data") iferr!=nil{log.Printf("Failed to delete session tool: %v", err) }You can also apply filters to control which tools are available to certain sessions:
// Add a tool filter that only shows tools with certain prefixess:=server.NewMCPServer( "Tool Filtering Demo", "1.0.0", server.WithToolCapabilities(true), server.WithToolFilter(func(ctx context.Context, tools []mcp.Tool) []mcp.Tool{// Get session from contextsession:=server.ClientSessionFromContext(ctx) ifsession==nil{returntools// Return all tools if no session } // Example: filter tools based on session ID prefixifstrings.HasPrefix(session.SessionID(), "admin-"){// Admin users get all toolsreturntools } else{// Regular users only get tools with "public-" prefixvarfilteredTools []mcp.Toolfor_, tool:=rangetools{ifstrings.HasPrefix(tool.Name, "public-"){filteredTools=append(filteredTools, tool) } } returnfilteredTools } }), )The session context is automatically passed to tool and resource handlers:
s.AddTool(mcp.NewTool("session_aware"), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error){// Get the current session from contextsession:=server.ClientSessionFromContext(ctx) ifsession==nil{returnmcp.NewToolResultError("No active session"), nil } returnmcp.NewToolResultText("Hello, session "+session.SessionID()), nil }) // When using handlers in HTTP/SSE servers, you need to pass the context with the sessionhttpHandler:=func(w http.ResponseWriter, r*http.Request){// Get session from somewhere (like a cookie or header)session:=getSessionFromRequest(r) // Add session to contextctx:=s.WithContext(r.Context(), session) // Use this context when handling requests// ... }Hook into the request lifecycle by creating a Hooks object with your selection among the possible callbacks. This enables telemetry across all functionality, and observability of various facts, for example the ability to count improperly-formatted requests, or to log the agent identity during initialization.
Add the Hooks to the server at the time of creation using the server.WithHooks option.
Add middleware to tool call handlers using the server.WithToolHandlerMiddleware option. Middlewares can be registered on server creation and are applied on every tool call.
A recovery middleware option is available to recover from panics in a tool call and can be added to the server with the server.WithRecovery option.
Server hooks and request handlers are generated. Regenerate them by running:
go generate ./...You need go installed and the goimports tool available. The generator runs goimports automatically to format and fix imports.
