Building Your First MCP Server in Go
MCP (Model Context Protocol) is the standard that lets LLMs connect to the outside world. By default, your AI assistant is isolated — it can't read your database, call an API, or touch your filesystem. When you build an MCP server, you give it that access. Add a Postgres MCP server to your agent, and it can query your PostgreSQL database by making a tool call.
In this guide we'll build a hello world MCP server in Go from scratch, to understand the concept.
Project Setup
mkdir hello-mcp-server && cd hello-mcp-server
go mod init github.com/yourname/hello-mcp-server
go get github.com/mark3labs/mcp-go
We use mcp-go — the most popular Go library for MCP servers.
Create the Server
s := server.NewMCPServer(
"Demo 🚀",
"1.0.0",
server.WithToolCapabilities(false),
)
Creates the MCP server with a name and version. WithToolCapabilities(false) tells the client the tool list is fixed at startup — no dynamic registration. No HTTP, no ports.
Define a Tool
helloTool := mcp.NewTool("hello_world",
mcp.WithDescription("Say hello to someone"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("Name of the person to greet"),
),
)
The description is what the AI reads to decide when to call this tool — write it clearly. The parameter is typed and required, with its own description so the AI knows what to pass.
Write the Handler
func helloHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, err := req.RequireString("name")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil
}
RequireString reads the parameter from the request. NewToolResultError returns a soft error the AI can read and react to — the server keeps running. NewToolResultText returns the result back to the AI.
Register and Serve
s.AddTool(helloTool, helloHandler)
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
AddTool wires the tool to its handler. ServeStdio starts the server and blocks — Claude Code and Cursor spawn your binary as a subprocess and talk to it over stdin/stdout.
Full Code
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
s := server.NewMCPServer(
"Demo 🚀",
"1.0.0",
server.WithToolCapabilities(false),
)
helloTool := mcp.NewTool("hello_world",
mcp.WithDescription("Say hello to someone"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("Name of the person to greet"),
),
)
s.AddTool(helloTool, helloHandler)
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
func helloHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, err := req.RequireString("name")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil
}
Build
go build -o hello-mcp-server .
A single self-contained binary. No runtime, no dependencies — just a path to hand to the AI client.
Connect to Claude Code
claude mcp add --scope user hello-mcp-server -- /Users/absolute/path/to/hello-mcp-server
--scope user makes it available across all your projects. Use --scope project to share with your team via .mcp.json in git.
claude mcp list
This command should show the hello-mcp-server in the list of mcp server of Claude code
Try it
Once connected, just type in Claude Code:
use hello_world with name John
Connect to Cursor
Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json in your project root:
{"mcpServers":{"hello-mcp-server":{"command":"/absolute/path/to/hello-mcp-server","args":[]}}}
Restart Cursor. Your tool will appear in the MCP tools list.
That's It
| Step | What |
|---|---|
NewMCPServer |
Create the server |
NewTool |
Define a tool — name, description, params |
AddTool |
Wire the tool to its handler |
ServeStdio |
Start serving over stdin/stdout |
go build |
Compile to a single binary |
Add more tools by repeating the define → register → handle pattern for each one.