Build an MCP Server in Go: Tools, Connections, and Tests
Go is a practical choice for an MCP server when you need a small binary, high concurrency, or integration with an existing Go codebase. This guide walks through building a focused Go MCP server, testing it, and recording the tool contracts your team depends on.
Updated 5 min read
Go is a reasonable choice for an MCP server when you want a small, self-contained binary, when you need to handle many concurrent connections efficiently, or when the server integrates with an existing Go codebase. The MCP specification does not require a specific language; a Go server that implements the protocol correctly connects to any compliant client.
Building in Go means using community libraries rather than an official SDK — the Model Context Protocol team publishes official SDKs for TypeScript and Python, but not yet for Go. Before starting, check the Go libraries available at modelcontextprotocol.io/sdks (opens in a new tab) and in the wider community for the current maintenance state. Tools like fenbs store the decision about which library to use and why, so the next person working on the server starts from the agreed choice.
Why Go for an MCP server
Go produces small, statically linked binaries with no runtime dependency. An MCP server that compiles to a single executable is easy to distribute, deploy into containers, and run on machines without a Node.js or Python installation.
Go's concurrency model handles many simultaneous tool calls efficiently. For a remote Streamable HTTP server that multiple team members connect to, Go's goroutines and standard library HTTP server are a natural fit.
The trade-off is maturity. The TypeScript and Python SDKs are maintained by the same team that writes the specification and track protocol changes closely. A Go community library may lag behind on protocol versions or optional capabilities. Verify the library's spec coverage before building production features on it. See mcp server frameworks for a broader comparison.
Choose a Go MCP library
Search for Go MCP libraries on GitHub and compare them by: the MCP protocol version they target, the transports they support (stdio and/or Streamable HTTP), how recently the repository was updated, and whether the maintainers respond to issues.
A library that supports both stdio and HTTP transports is more flexible. One that generates JSON Schema from Go types automatically saves you from writing schemas by hand. Check the README for examples of both tool registration and transport setup before committing to a library.
Once you have chosen a library, record the choice as a Decision in fenbs: the library name and version, the protocol version it targets, the date you evaluated it, and the alternatives you considered. That record saves the next person from repeating the evaluation.
Project setup and structure
Start with a standard Go module. Keep the MCP server in its own package or in main for a simple server. Separate the transport setup, the tool registration, and the business logic (the actual lookup or action the tool performs) into distinct parts, so each is testable independently.
package main
import (
"context"
"fmt"
// import your chosen MCP library here
)
func main() {
// 1. Create a server instance
// 2. Register tools
// 3. Start the transport (stdio or HTTP)
}
// Tool handler: separate from transport setup
func lookupItem(ctx context.Context, id string) (string, error) {
// replace with real lookup logic
return fmt.Sprintf("Item %s: example data", id), nil
}The exact API depends on the library you choose. Follow its documentation for creating the server instance and registering tools. The pattern above — separate handler functions, minimal main — applies to any library.
Define input schemas clearly
MCP tools describe their inputs as JSON Schema. Some Go libraries generate the schema from struct tags or function signatures automatically. Others expect you to provide the schema as a Go map or a raw JSON string.
Whichever approach the library uses, make sure each input parameter has a clear description. The AI client reads those descriptions when it decides what values to pass. A parameter labeled id with no description is much less useful than one labeled id: the unique identifier from the item lookup, e.g. "ORD-12345".
Also provide a description on the tool itself: what it does, when to call it, and what the output looks like. An AI that understands when to call your tool will call it correctly; one that is guessing may call it with wrong inputs or when a different tool would be more appropriate.
Record tool contracts where your team shares them
Add each tool's name, input parameters, expected output, and known error cases as a Decisions and rules entry in fenbs. When the tool contract changes — a new parameter, a renamed field, a changed error format — update the decision. Every AI assistant working on the server reads those decisions first and builds on the agreed contract.
Create a free accountFree for one person · No credit card · Works with Claude, ChatGPT, Cursor and more
Set up the transport
For a local server, use stdio transport. The client starts your compiled binary when it needs the server and communicates through the process's stdin and stdout. In your client's configuration, set command to the absolute path to the compiled binary.
For a remote server, use Streamable HTTP. The Go standard library's net/http package handles the HTTP side. Your MCP library wraps it. Start the HTTP listener on a specific port and configure the client to connect by URL.
For a stdio server, read environment variables from os.Getenv — do not hardcode credentials. For an HTTP server, credentials and configuration belong in environment variables or a config file that is not committed to version control.
Test with the MCP Inspector
The MCP Inspector, available from the modelcontextprotocol.io repository, is the fastest way to test a server without setting up a full AI client. It connects over stdio, lists the tools your server declares, lets you call them with specific inputs, and shows the raw JSON responses.
Run the Inspector before connecting to Claude or another client. Confirm that: your tool appears in the tool list with the correct name and description, calling it with valid inputs returns the expected result, and calling it with invalid inputs returns a clear isError: true response.
After the Inspector, test in the actual client. Ask the AI to use the tool by name with a specific known input. If it calls the tool with wrong parameters or not at all, revise the tool and parameter descriptions until it behaves correctly.
How to do this with fenbs
- Create a free account and set up a project for the Go MCP server work.
- Record the Go library choice and version as a Decisions and rules entry before writing any code. Include the alternatives you evaluated.
- Add each planned tool as a task with a plan: its name, inputs, expected output, and refusal cases.
- After the first working tool, add a lesson with what the library got right and what was harder than expected. Link it to the task.
- Connect your AI to fenbs over MCP from Integrations so it reads the current tool contracts and library decisions before working on the server code.
fenbs connects to AI clients over Streamable HTTP at https://fenbs.ai/api/mcp and is a working example of a remote MCP server. If the context you need to share is project tasks, decisions, and lessons rather than a custom internal tool, fenbs may be the right service to connect rather than one to build.