Model Context Protocol (MCP) servers allow LLMs (MCP hosts/clients) to access prompts, resources, and tools in a standard way, allowing you to build agents and complex workflows on top of LLMs.

SDKs make building and integrating MCP clients and servers easy. While there isn’t an official SDK for Go yet, the community-built mark3labs/mcp-go has been gaining a lot of popularity among Go developers—including myself.

I used this SDK today to make a real-world MCP for a real project, and it has been pretty neat so far. This article is a quick walkthrough of how I set up an MCP server using the MCP Go SDK for DiceDB, an in-memory key-value store like Redis.

Install the mcp-go Module

To use the mcp-go module, run:

go get github.com/mark3labs/mcp-go

We’ll also use the dicedb-go module to talk to the database:

go get github.com/dicedb/dicedb-go

Create a New MCP Server

The entire server fits into a single main.go file, thanks to the abstractions provided by the SDK. Let’s break down the important bits, starting with setting up the server:

main.go
1package main 2 3// Import ALL required modules 4import ( 5 "context" 6 "fmt" 7 "net" 8 "strconv" 9 "strings" 10 11 "github.com/dicedb/dicedb-go" 12 "github.com/dicedb/dicedb-go/wire" 13 "github.com/mark3labs/mcp-go/mcp" 14 "github.com/mark3labs/mcp-go/server" 15) 16 17func main() { 18 // Create a new MCP server 19 s := server.NewMCPServer( 20 "DiceDB MCP", // Name of the server 21 "0.1.0", // Version 22 // Set listChanged to false as this example 23 // server does not emit notifications 24 // when the list of available tool changes 25 // https://modelcontextprotocol.io/specification/2024-11-05/server/tools#capabilities 26 server.WithToolCapabilities(false), 27 )

Here’s what’s happening here:

  • 4-15: We import the MCP and DiceDB modules and some helpers.
  • 19-27: We create a new MCP server instance:
    • 19: "DiceDB MCP" is just a human-readable name.
    • 20: "0.1.0" is the version.
    • 26: server.WithToolCapabilities(false) indicates that the server won’t notify clients when the list of available tools changes, which is the case for our simple server.

Define a New Tool

Now, let’s add a tool. This one just pings the DiceDB server to check if it’s reachable:

main.go
29 pingTool := mcp.NewTool("ping", // Tool name 30 // Short description of what the tool does 31 mcp.WithDescription("Ping the DiceDB server to check connectivity"), 32 // Add a string property to the tool schema 33 // to accept the URL of the DiceDB server 34 mcp.WithString("url", 35 // Description of the property 36 mcp.Description("The URL of the DiceDB server in format 'host:port'"), 37 // Default value that compliant clients 38 // can use if the value isn't explicitly set 39 mcp.DefaultString("localhost:7379"), 40 ), 41 )

Under the hood, these definitions get translated to a JSON schema following the JSON-RPC 2.0 specification. MCP clients use this schema to discover and call the tool.

The "ping" tool has the following properties:

  • 29: mcp.NewTool("ping", ... defines a tool named "ping" that can be invoked by MCP clients.
  • 31: mcp.WithDescription adds a description property to the tool schema that helps both humans and AI (MCP host/client) decide which tool to use.
  • 34: mcp.WithString adds a string property to the tool schema to obtain the URL of the DiceDB server (from the MCP host/client).
  • 39: mcp.DefaultString isn’t defined in the MCP specification, but compliant clients can use this to set a default value if none is provided.

Next, we wire in the actual logic.

Create a Handler Function

Now that we’ve defined the tool let’s add what happens when it’s invoked. In our case, we want to ping the DiceDB server to check if it’s reachable:

main.go
43 s.AddTool(pingTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 44 // Extract the URL argument from the client request 45 url := request.Params.Arguments["url"].(string) 46 47 // Parse host and port from URL 48 parts := strings.Split(url, ":") 49 host := parts[0] 50 port := 7379 // Default to 7379 if no port is provided 51 52 if len(parts) > 1 { 53 if p, err := strconv.Atoi(parts[1]); err == nil { 54 port = p 55 } 56 } 57 58 // Create a new DiceDB client 59 client, err := dicedb.NewClient(host, port) 60 if err != nil { 61 return mcp.NewToolResultText(fmt.Sprintf("Error connecting to DiceDB: %v", err)), nil 62 } 63 64 // Send the PING command to DiceDB 65 resp := client.Fire(&wire.Command{Cmd: "PING"}) 66 67 // Return the result to the MCP client 68 return mcp.NewToolResultText(fmt.Sprintf("Response from DiceDB: %v", resp)), nil 69 })

Here, we define a handler function that runs when the MCP client calls the tool. This is what it does:

  • 45: Extracts the URL from the client request.
  • 48-56: Splits the string to separate host and port. If a port isn’t specified, we fall back to DiceDB’s default (7379).
  • 59-62: Initializes a new DiceDB client using the dicedb-go SDK.
  • 65: Sends a PING command to DiceDB.
  • 68: Returns the formatted response back to the MCP client.

See how the SDK provides neat wrappers like NewToolResultText to structure responses as required by the MCP specification.

Start the Server

All that’s left is to start the server:

main.go
71 if err := server.ServeStdio(s); err != nil { 72 fmt.Printf("Error starting server: %v\n", err) 73 } 74}

This uses the stdio transport, meaning our MCP server communicates using standard input and output streams. This is the simplest way to run an MCP server locally.

The MCP specification also allows SSE or Server-Sent Events transport, which enables server-to-client streaming and uses HTTP POST for client-to-server messages. You can also create custom transports for specific needs.

Build/Install the Server

Before using the server, build the binary:

go build -o ./dist/dicedb-mcp

Or install it globally (adds it to $GOBIN):

go install

This gives you either a local or a global runnable binary that can be executed by MCP clients and hosts.

Use with MCP Hosts/Clients

To use this MCP server with host applications like Claude Desktop or Cursor, add it to their configuration file (claude_desktop_config.json or mcp.json):

mcp.json
{ "mcpServers": { "dicedb-mcp": { "command": "path/to/dicedb-mcp" } } }

Now you can prompt the LLM like:

Can you check if DiceDB is running at localhost:7379?

The LLM will detect the ping tool we registered, ask to run it, and show the result returned by the MCP server.

Note: Make sure you have DiceDB running. You can also apply this pattern to wrap other tools into MCP servers instead of DiceDB, as used in this example.

Learn More

We’ve built a minimal example to demonstrate how the MCP Go SDK works. It is functional but intentionally simple.

You can check out a more robust DiceDB MCP implementation (with additional tools and features) on GitHub.

To go further, I recommend these resources: