Introduction
First off I guess we should start off with what is MCP and why do we need it. The Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to Large Language Models (LLMs). MCP provides a standardized way to connect AI models to different data sources and tools, making integration seamless and consistent
Prior to MCP, there were many different ways to provide context to LLMs. This led to a lot of fragmentation and inconsistency in the way that different applications and tools integrated with LLMs. MCP aims to solve this problem by providing a standardized way to provide context to LLMs.
From a developer standpoint, this means that you can now use the same tools and techniques to integrate with LLMs regardless of the application or tool you are using. This is a game changer for developers and will allow for a lot more innovation and creativity in the way that we use LLMs.
MCP Documentation
Golang Example
I hope to put some more examples up here in the future but we will start with a simple example using the Pokemon API. The pokemon API is a free to use API that provides information about all the Pokemon characters. We can pretend that this API is a private API that we are trying to integrate with an LLM. This could be network metadata, an API to communicate with network devices, a third party API, etc.
We will start with the code for the server. This is the code that will be running on the server that is hosting the LLM and integrate with our MCP Client and in this example we can connect it to Claude Desktop.
package main
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// PokemonResponse represents the Pokemon data we want from the API
type PokemonResponse struct {
Name string `json:"name"`
Height int `json:"height"`
Weight int `json:"weight"`
Types []struct {
Type struct {
Name string `json:"name"`
} `json:"type"`
} `json:"types"`
Stats []struct {
BaseStat int `json:"base_stat"`
Stat struct {
Name string `json:"name"`
} `json:"stat"`
} `json:"stats"`
}
func main() {
// Create MCP server
s := server.NewMCPServer(
"Pokemon Server 🎮",
"1.0.0",
server.WithToolCapabilities(true),
)
// Add Pokemon info tool
pokemonTool := mcp.NewTool("get_pokemon",
mcp.WithDescription("Get information about a Pokemon"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("Name of the Pokemon (lowercase)"),
),
)
// Add Pokemon compare tool
compareTool := mcp.NewTool("compare_pokemon",
mcp.WithDescription("Compare two Pokemon"),
mcp.WithString("pokemon1",
mcp.Required(),
mcp.Description("First Pokemon to compare (lowercase)"),
),
mcp.WithString("pokemon2",
mcp.Required(),
mcp.Description("Second Pokemon to compare (lowercase)"),
),
)
// Register tools
s.AddTool(pokemonTool, getPokemonHandler)
s.AddTool(compareTool, comparePokemonHandler)
// Start the stdio server
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
func getPokemonHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
name, ok := arguments["name"].(string)
if !ok {
return mcp.NewToolResultError("name must be a string"), nil
}
pokemon, err := fetchPokemon(name)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Error fetching Pokemon: %v", err)), nil
}
// Format the response
types := make([]string, len(pokemon.Types))
for i, t := range pokemon.Types {
types[i] = t.Type.Name
}
response := fmt.Sprintf("🔍 Pokemon Information for %s:\n\n"+
"Height: %d decimeters\n"+
"Weight: %d hectograms\n"+
"Types: %s\n\n"+
"Base Stats:\n",
strings.Title(pokemon.Name),
pokemon.Height,
pokemon.Weight,
strings.Join(types, ", "))
for _, stat := range pokemon.Stats {
response += fmt.Sprintf("%s: %d\n", stat.Stat.Name, stat.BaseStat)
}
return mcp.NewToolResultText(response), nil
}
func comparePokemonHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
pokemon1, ok := arguments["pokemon1"].(string)
if !ok {
return mcp.NewToolResultError("pokemon1 must be a string"), nil
}
pokemon2, ok := arguments["pokemon2"].(string)
if !ok {
return mcp.NewToolResultError("pokemon2 must be a string"), nil
}
p1, err := fetchPokemon(pokemon1)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Error fetching %s: %v", pokemon1, err)), nil
}
p2, err := fetchPokemon(pokemon2)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Error fetching %s: %v", pokemon2, err)), nil
}
// Create comparison text
comparison := fmt.Sprintf("⚔️ Pokemon Comparison: %s vs %s\n\n",
strings.Title(p1.Name),
strings.Title(p2.Name))
// Compare stats
comparison += "Base Stats Comparison:\n"
for i := 0; i < len(p1.Stats); i++ {
stat1 := p1.Stats[i]
stat2 := p2.Stats[i]
comparison += fmt.Sprintf("%s: %d vs %d\n",
stat1.Stat.Name,
stat1.BaseStat,
stat2.BaseStat)
}
return mcp.NewToolResultText(comparison), nil
}
func fetchPokemon(name string) (*PokemonResponse, error) {
resp, err := http.Get(fmt.Sprintf("https://pokeapi.co/api/v2/pokemon/%s", strings.ToLower(name)))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API returned status code %d", resp.StatusCode)
}
var pokemon PokemonResponse
if err := json.NewDecoder(resp.Body).Decode(&pokemon); err != nil {
return nil, err
}
return &pokemon, nil
}
Let’s examine each part in detail.
Data Structures
type PokemonResponse struct {
Name string `json:"name"`
Height int `json:"height"`
Weight int `json:"weight"`
Types []struct {
Type struct {
Name string `json:"name"`
} `json:"type"`
} `json:"types"`
Stats []struct {
BaseStat int `json:"base_stat"`
Stat struct {
Name string `json:"name"`
} `json:"stat"`
} `json:"stats"`
}
This structure defines how we want to parse the JSON response from the PokéAPI. It includes:
- Basic information (name, height, weight)
- Types (fire, water, etc.)
- Base stats (HP, attack, defense, etc.)
The struct tags (json:"name"
) tell Go’s JSON decoder how to map the API response to our structure.
Server Setup
func main() {
s := server.NewMCPServer(
"Pokemon Server 🎮",
"1.0.0",
server.WithToolCapabilities(true),
)
The server is created using the MCP (Message Control Protocol) framework. It’s initialized with:
- A friendly name (“Pokemon Server 🎮”)
- Version number
- Tool capabilities enabled
The server defines two tools:
get_pokemon
: Retrieves information about a single Pokémoncompare_pokemon
: Compares statistics between two Pokémon
Each tool is configured with required parameters:
pokemonTool := mcp.NewTool("get_pokemon",
mcp.WithDescription("Get information about a Pokemon"),
mcp.WithString("name",
mcp.Required(),
mcp.Description("Name of the Pokemon (lowercase)"),
),
)
Tool Handlers
Single Pokémon Information
func getPokemonHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
This handler:
- Extracts the Pokémon name from the arguments
- Fetches the Pokémon data from the API
- Formats a response with:
- Height and weight
- Types
- Base stats
The response includes emoji (🔍) for better readability and properly formats lists using string joining.
Pokémon Comparison
func comparePokemonHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
This handler:
- Takes two Pokémon names
- Fetches data for both
- Creates a side-by-side comparison of their stats
- Returns a formatted comparison with emoji (⚔️)
Data Fetching
func fetchPokemon(name string) (*PokemonResponse, error) {
The fetchPokemon
function:
- Makes an HTTP GET request to the PokéAPI
- Handles potential errors (network issues, invalid responses)
- Decodes the JSON response into our
PokemonResponse
structure - Returns the parsed data
Key features:
- Uses
strings.ToLower()
to ensure consistent API calls - Properly closes the response body using
defer
- Handles non-200 status codes appropriately
Error Handling
The code includes robust error handling throughout:
- Parameter validation
- API request errors
- JSON parsing errors
- Invalid response status codes
Each error is properly wrapped with context and returned to the client.
Usage Example
When running the server, you can make requests like:
// Get information about Pikachu
result := getPokemonHandler({"name": "pikachu"})
// Compare Charizard and Blastoise
result := comparePokemonHandler({
"pokemon1": "charizard",
"pokemon2": "blastoise"
})
Integrating this with Claude Desktop is simple as building the server and settting the binary to be a MCP server within the Claude Desktop settings.
Add the MCP server configuration to your Claude desktop config file (claude_desktop_config.json):
{
"mcpServers": {
"pokemon-mcp": {
"command": "/tmp/pokemon-mcp"
}
}
}
This configuration tells Claude Desktop to use the pokemon-mcp
server with the command /tmp/pokemon-mcp
.
After adding this configuration, you can use the pokemon-mcp
tool in your Claude sessions.
This example demonstrates how to integrate a simple server with MCP, providing a basic framework for building more complex applications.