
This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.
You build an MCP server in Go with the official SDK at github.com/modelcontextprotocol/go-sdk. You create an mcp.Server, register tools with the generic mcp.AddTool function, and run the server over a transport such as stdio or Streamable HTTP. A typed Go struct becomes the tool's input schema, so you write very little JSON by hand.
This guide is for developers who already know Go and want a working server fast. It covers install, a minimal server, a second tool, testing with MCP Inspector, HTTP deployment and the traps that catch people using older tutorials.
Everything here was checked against the go-sdk v1.8.0 README and docs on 2 November 2026. eSEOspace did not compile or run these snippets, because no Go toolchain was available for this article. Treat them as documented patterns and run go build yourself.
Key Takeaways
- The official Go SDK lives at
github.com/modelcontextprotocol/go-sdk; install themcppackage withgo get. - As of November 2026 the latest release is v1.8.0, and the SDK targets Go 1.25 or later.
mcp.AddToolinfers the input and output JSON Schema from your Go structs, andjsonschemastruct tags become argument descriptions.- Returning an ordinary Go error from a tool handler produces a tool result with
IsErrorset totrue, which the model can read. - For HTTP, the 2026-07-28 protocol is accepted only when
StreamableHTTPOptions.Statelessistrue. - Never print to stdout in a stdio server; use the
logpackage, which writes to stderr by default.
Prerequisites
You need three things before you start:
- Go 1.25 or newer. The SDK's go.mod file declares
go 1.25.0, and the official build-a-server tutorial lists Go 1.25 as the minimum. - Node.js if you want to use MCP Inspector, which runs through
npx. - An MCP host such as Claude Desktop, Claude Code, Cursor or VS Code to try the finished server.
If you are new to MCP terms like host, client, server and tool, read how to build your first MCP server first. It explains the moving parts with Python and TypeScript. This page focuses only on what is different in Go.
Which Go SDK version to use
The Go SDK is one of the ten official SDKs. The official SDK page lists it as Tier 1, the highest support level. Our official MCP SDKs overview explains what the tiers promise.
The Go SDK README publishes a compatibility table. Versions v1.7.0 and later target the 2026-07-28 spec and still support every earlier revision back to 2024-11-05. On 2 November 2026 the latest GitHub release was v1.8.0, published 14 September 2026.
The SDK is split into packages. You will mostly use mcp. The README says jsonrpc is for custom transports, auth holds OAuth primitives and oauthex adds OAuth extensions such as protected resource metadata.
Install the SDK
Create a module and fetch the mcp package, exactly as the quick start doc shows:
mkdir inventory-mcp && cd inventory-mcp
go mod init inventory-mcp
go get github.com/modelcontextprotocol/go-sdk/mcp
A minimal working server
The README's first example registers one tool and serves it over stdin and stdout. Here it is, unchanged except for comments:
package main
import (
"context"
"log"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type Input struct {
Name string `json:"name" jsonschema:"the name of the person to greet"`
}
type Output struct {
Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
}
func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (
*mcp.CallToolResult,
Output,
error,
) {
return nil, Output{Greeting: "Hi " + input.Name}, nil
}
func main() {
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
}
What each part does:
mcp.NewServertakes anImplementationwith a name and version. The second argument is*ServerOptions;nilmeans defaults.mcp.AddToolis a generic function. It reads theInputandOutputtypes from your handler's signature.- The handler returns three values: an optional
*CallToolResult, your typed output and an error. Returningnilfor the result lets the SDK fill it in. server.Runwith&mcp.StdioTransport{}serves the process's stdin and stdout until the client disconnects.
Build it with go build -o inventory-mcp . so you have one binary to point a host at.
How the schema is generated
The server feature doc explains the rules for AddTool:
- If you leave
Tool.InputSchemaunset, the SDK infers it from the input type. - If you leave
Tool.OutputSchemaunset and the output type is notany, it infers an output schema too. - Optional
jsonschemastruct tags become the description of each argument or output field. - If the handler returns an ordinary error, the SDK stores it in the
CallToolResultand setsIsErrortotrue.
That last rule matters. A tool error is a normal result the model can see and react to, not a crash.
When you need tighter rules than struct tags allow, such as a number between 0 and 1, the doc shows building a schema with jsonschema.For and setting Minimum and Maximum before passing it as InputSchema.
Add a second tool
Now add something closer to real work. This tool looks up stock for a product SKU in an in-memory map. It follows the same AddTool pattern, and it returns an error for unknown SKUs so the model gets a readable failure:
type StockInput struct {
SKU string `json:"sku" jsonschema:"the product SKU, for example TSHIRT-M"`
}
type StockOutput struct {
SKU string `json:"sku" jsonschema:"the SKU that was checked"`
Quantity int `json:"quantity" jsonschema:"units currently in stock"`
}
var stock = map[string]int{"TSHIRT-M": 42, "TSHIRT-L": 0, "MUG": 17}
func CheckStock(ctx context.Context, req *mcp.CallToolRequest, in StockInput) (
*mcp.CallToolResult,
StockOutput,
error,
) {
qty, ok := stock[in.SKU]
if !ok {
return nil, StockOutput{}, fmt.Errorf("unknown SKU %q", in.SKU)
}
return nil, StockOutput{SKU: in.SKU, Quantity: qty}, nil
}
Register it in main next to the first tool, and add "fmt" to your imports:
mcp.AddTool(server, &mcp.Tool{
Name: "check_stock",
Description: "Return the number of units in stock for one product SKU.",
}, CheckStock)
Two habits help models pick the right tool. Write a description that says what the tool returns, not just what it is. And keep names short and specific, like check_stock. The SDK's rough edges doc notes that v1 only logs an error for invalid tool names instead of refusing them, so check your names yourself.
Test with MCP Inspector
MCP Inspector is the official debugging tool. As of November 2026 the npm latest tag is the v2 line (2.13.0), with web, CLI and terminal interfaces in one package, per the Inspector README.
Open the web UI against your binary:
npx @modelcontextprotocol/inspector ./inventory-mcp
Or script it with the CLI. These flags come from the Inspector CLI README:
npx @modelcontextprotocol/inspector --cli ./inventory-mcp --method tools/list
npx @modelcontextprotocol/inspector --cli ./inventory-mcp \
--method tools/call --tool-name check_stock --tool-arg sku=MUG
The CLI also has --protocol-era legacy|auto|modern. Run tools/list once with legacy and once with modern to confirm your server answers both the old initialize handshake and the 2026-07-28 stateless model. The Go SDK's protocol doc says it supports both lifecycles "transparently based on the negotiated protocol version." Our Inspector lesson walks through the web UI.
Connect it to a host
For Claude Desktop, the official tutorial's Go tab points the config at the compiled binary with an absolute path:
{
"mcpServers": {
"inventory": {
"command": "/ABSOLUTE/PATH/TO/inventory-mcp/inventory-mcp"
}
}
}
The tutorial gives the config locations as ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and $env:AppData\Claude\claude_desktop_config.json on Windows. On Windows the command ends in .exe. Fully quit and reopen Claude Desktop after editing. Our config generator writes this block for other hosts too.
Serve over Streamable HTTP
For a remote server, wrap the same mcp.Server in mcp.NewStreamableHTTPHandler. This sketch follows the handler example in the protocol doc:
server := mcp.NewServer(&mcp.Implementation{Name: "inventory", Version: "v1.0.0"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "check_stock", Description: "Return units in stock for one SKU."}, CheckStock)
handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
return server
}, &mcp.StreamableHTTPOptions{Stateless: true})
log.Fatal(http.ListenAndServe("127.0.0.1:8080", handler))
The Stateless: true option is not optional if you want current clients. The protocol doc says: "the streamable HTTP transport accepts requests at protocol version 2026-07-28 only when Stateless = true." In stateless mode the server cannot send requests back to the client, so plan features like elicitation with that in mind.
Before you put an HTTP server on the internet, add authorization. The SDK's auth package provides OAuth building blocks, and the protocol doc has sections on confused deputy, token passthrough and session hijacking risks. Bind to 127.0.0.1 while testing locally.
Common pitfalls
- Printing to stdout. The official tutorial says never use
fmt.Println()orfmt.Printf()in a stdio server, because stdout carries the JSON-RPC messages. Uselog.Println(), which defaults to stderr, orfmt.Fprintln(os.Stderr, ...). - Using a third-party SDK's examples. The README credits community SDKs such as
mark3labs/mcp-goas inspirations and "viable alternatives." Their APIs differ, so do not mix their snippets with the officialmcppackage. - Old spec versions. SDK versions before v1.7.0 do not support 2026-07-28. Check
go.modbefore you debug strange client behavior. - Stateful HTTP by accident. Without
Stateless: true, 2026-07-28 requests are rejected. - Legacy SSE. The SDK still ships the 2024-11-05 SSE transport for old peers, but the docs say new deployments should use Streamable HTTP.
- Deprecated features. The README says roots, sampling and logging are deprecated as of 2026-07-28 (SEP-2577), with support kept for at least twelve months.
Next step
Want the same server in another language? Compare it with our guides for TypeScript tool registration and Rust, or work through the free MCP course. If you would rather have a production server built, secured and documented for you, see eSEOspace's MCP development service.
More in the MCP Guide
Free tools: server.json Validator · MCP Error Code Lookup
Latest research: State of MCP: October 2026
Frequently asked questions
Is the Go MCP SDK official?
Yes. It is maintained in the modelcontextprotocol GitHub organization and listed as a Tier 1 SDK on the official SDK page as of November 2026. The README notes that its design drew on community SDKs such as mcp-go.
Which Go version do I need?
Go 1.25 or later. The SDK's go.mod declares go 1.25.0, and the README says new releases target only supported Go versions.
Does the Go SDK support the 2026-07-28 spec?
Yes, from v1.7.0 onward, according to the README's compatibility table. Those versions also keep supporting 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
How do I return an error from a Go MCP tool?
Return a normal Go error from the handler. The SDK puts the message into the tool result and sets IsError to true, so the model sees the failure instead of the call crashing.
Can I run a Go MCP server on serverless platforms?
Stateless Streamable HTTP fits request-per-call platforms well, and the repo includes a distributed stateless example. Check your platform's HTTP and timeout limits; the SDK docs do not cover specific hosts.











