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:

  1. get_pokemon: Retrieves information about a single Pokémon
  2. compare_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:

  1. Extracts the Pokémon name from the arguments
  2. Fetches the Pokémon data from the API
  3. 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:

  1. Takes two Pokémon names
  2. Fetches data for both
  3. Creates a side-by-side comparison of their stats
  4. Returns a formatted comparison with emoji (⚔️)

Data Fetching

func fetchPokemon(name string) (*PokemonResponse, error) {

The fetchPokemon function:

  1. Makes an HTTP GET request to the PokéAPI
  2. Handles potential errors (network issues, invalid responses)
  3. Decodes the JSON response into our PokemonResponse structure
  4. 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.