go-mcp-server-example

BearHuddleston/go-mcp-server-example

3.4

If you are the rightful owner of go-mcp-server-example and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.

The MCP Coffee Shop server is a Go-based implementation of the Model Context Protocol (MCP) that provides coffee shop information through various tools, resources, and prompts.

MCP Server Example (MCP Coffee Shop)

A Model Context Protocol (MCP) server implementation in Go that provides coffee shop information through tools, resources, and prompts, following Go project layout best practices.

Features

  • MCP 2025-03-26 Specification Compliant
  • Multiple Transport Support: stdio (default, compatible with MCP Inspector), http (with SSE)
  • Coffee Shop Domain: Tools, resources, and prompts for coffee shop operations
  • Graceful Shutdown & Configurable Timeouts
  • Production Ready: Structured logging, error handling, validation

Project Structure

simple-mcp-server-refactored/
├── cmd/mcpserver/           # Application entrypoint
├── pkg/                     # Public library code
│   ├── mcp/                 # Core MCP protocol types
│   ├── config/              # Configuration management
│   ├── transport/           # Transport implementations
│   └── handlers/            # Domain-specific handlers
├── internal/server/         # Server implementation
└── go.mod

Usage

# Build the application
go build -o mcpserver ./cmd/mcpserver

# Run with stdio transport (default)
./mcpserver

# Run with HTTP transport
./mcpserver -transport http -port 8080
  • stdio: Standard input/output (default, compatible with MCP Inspector)
  • http: HTTP with Server-Sent Events (SSE) support
  • Coffee Shop Domain: Tools, resources, and prompts for coffee shop operations
  • Graceful Shutdown: Proper signal handling and resource cleanup
  • Configurable Timeouts: Request, shutdown, and HTTP timeouts
  • Production Ready: Structured logging, error handling, and validation

Quick Start

Docker

You can run the MCP server using Docker:

  1. Build the Docker image:

    docker build -t mcp-server .
    
  2. Run the container:

    # For HTTP transport (exposes port 8080)
    docker run -p 8080:8080 mcp-server --transport http --port 8080
    
    # For stdio transport (useful with MCP Inspector)
    docker run -it mcp-server --transport stdio
    
  3. Using environment variables:

    docker run -p 8080:8080 -e TRANSPORT=http -e PORT=8080 mcp-server
    

Prerequisites

Installation

git clone <repository-url>
cd simple-mcp-server
go build

Basic Usage

# Start with stdio transport (default)
go run ./...

# Start with HTTP transport
go run ./... --transport http --port 8080

# Custom configuration
go run ./... --transport http --port 9000 --request-timeout 45s

Configuration

Command Line Flags

FlagDescriptionDefaultExample
--transportTransport type (stdio or http)stdio--transport http
--portHTTP port (ignored for stdio)8080--port 9000
--request-timeoutRequest timeout duration30s--request-timeout 45s

Environment Variables

The server uses Go's built-in flag parsing. Configuration is primarily through command-line flags.

Transports

Stdio Transport

Perfect for command-line tools and MCP Inspector integration:

go run ./... --transport stdio

Use Cases:

  • MCP Inspector debugging
  • CLI integrations
  • Development and testing

HTTP Transport

RESTful HTTP API with optional Server-Sent Events:

go run ./... --transport http --port 8080

Endpoints:

  • POST /mcp - Send JSON-RPC requests
  • GET /mcp - Open SSE stream
  • GET /health - Health check

Examples:

# Regular JSON response
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","id":1}'

# SSE stream response
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":"test"}'

Implementing a New Handler

To add a new handler to the MCP server, follow these steps using the getWeather handler as an example:

  1. Create a new handler file in pkg/handlers/ (e.g., weather.go):
package handlers

import (
	"context"
	"encoding/json"

	"github.com/your-org/simple-mcp-server-refactored/pkg/mcp"
)

type WeatherHandler struct {
	// Add any dependencies here (e.g., API clients, config)
}

// WeatherRequest represents the expected request parameters
type WeatherRequest struct {
	Location string `json:"location"`
}

// WeatherResponse represents the response structure
type WeatherResponse struct {
	Location    string  `json:"location"`
	Temperature float64 `json:"temperature"`
	Condition   string  `json:"condition"`
	Humidity    int     `json:"humidity"`
	WindSpeed   float64 `json:"wind_speed"`
	Unit        string  `json:"unit"`
}

// Handle processes the weather request
func (h *WeatherHandler) Handle(ctx context.Context, request json.RawMessage) (interface{}, error) {
	var req WeatherRequest
	if err := json.Unmarshal(request, &req); err != nil {
		return nil, mcp.NewInvalidParamsError("invalid request parameters")
	}

	// TODO: Implement actual weather data retrieval
	// This is a mock implementation
	return WeatherResponse{
		Location:    req.Location,
		Temperature: 72.5,
		Condition:   "Sunny",
		Humidity:    45,
		WindSpeed:   8.2,
		Unit:        "fahrenheit",
	}, nil
}

// Register registers the handler with the MCP server
func (h *WeatherHandler) Register(router *mcp.Router) {
	router.RegisterHandler("getWeather", h.Handle)
}
  1. Register the handler in internal/server/server.go:
// In NewServer function
weatherHandler := &handlers.WeatherHandler{}
weatherHandler.Register(router)
  1. Add tests in pkg/handlers/weather_test.go:
package handlers_test

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/your-org/simple-mcp-server-refactored/pkg/handlers"
	"github.com/stretchr/testify/assert"
)

func TestWeatherHandler(t *testing.T) {
	h := &handlers.WeatherHandler{}
	
	t.Run("successful request", func(t *testing.T) {
		req := map[string]interface{}{
			"location": "New York, NY",
		}
		reqBytes, _ := json.Marshal(req)

		result, err := h.Handle(context.Background(), reqBytes)
		assert.NoError(t, err)
		assert.NotNil(t, result)
		
		resp, ok := result.(handlers.WeatherResponse)
		assert.True(t, ok)
		assert.Equal(t, "New York, NY", resp.Location)
	})

	t.Run("invalid request", func(t *testing.T) {
		req := map[string]interface{}{
			"invalid": "data",
		}
		reqBytes, _ := json.Marshal(req)

		_, err := h.Handle(context.Background(), reqBytes)
		assert.Error(t, err)
	})
}
  1. Update documentation in the README.md to document the new handler.

MCP Capabilities

Tools

Interactive functions that can be called by the LLM:

ToolDescriptionParameters
getDrinkNamesGet list of available drinksNone
getDrinkInfoGet detailed drink informationname: string (required)

Example:

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "id": "1",
  "params": {
    "name": "getDrinkInfo",
    "arguments": {"name": "Latte"}
  }
}

Resources

Contextual data managed by the application:

ResourceURIDescription
menumenu://appComplete coffee shop menu

Example:

{
  "jsonrpc": "2.0",
  "method": "resources/read",
  "id": "1",
  "params": {"uri": "menu://app"}
}

Prompts

Template-driven interactions for the LLM:

PromptDescriptionParameters
drinkRecommendationGet personalized drink recommendationsbudget: number (optional)
preference: string (optional)
drinkDescriptionGet detailed drink descriptionsdrink_name: string (required)

Example:

{
  "jsonrpc": "2.0",
  "method": "prompts/get",
  "id": "1",
  "params": {
    "name": "drinkRecommendation",
    "arguments": {"budget": 6, "preference": "sweet"}
  }
}

Testing with MCP Inspector

  1. Install MCP Inspector:

    npm install -g @modelcontextprotocol/inspector
    
  2. Start the inspector:

    npx @modelcontextprotocol/inspector
    
  3. Connect to the server:

    • Transport: stdio
    • Command: go
    • Args: run ./...

Manual Testing

# Test stdio transport
echo '{"jsonrpc":"2.0","method":"initialize","id":1}' | go run ./... --transport stdio

# Test HTTP transport
go run ./... --transport http &
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","id":1}'

API Reference

JSON-RPC Methods

MethodDescriptionParameters
initializeInitialize MCP sessionClient info (optional)
tools/listList available toolsNone
tools/callExecute a toolname, arguments
resources/listList available resourcesNone
resources/readRead resource contenturi
prompts/listList available promptsNone
prompts/getGet prompt templatename, arguments (optional)
pingHealth checkNone

Error Codes

CodeMeaningDescription
-32700Parse ErrorInvalid JSON was received
-32600Invalid RequestInvalid JSON-RPC request
-32601Method Not FoundMethod does not exist
-32602Invalid ParamsInvalid method parameters
-32603Internal ErrorInternal JSON-RPC error

Systemd Service

[Unit]
Description=MCP Coffee Server
After=network.target

[Service]
Type=simple
User=mcp
ExecStart=/usr/local/bin/mcp-server --transport http --port 8080
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Troubleshooting

Common Issues

Connection Refused (HTTP)

# Check if server is running
curl http://localhost:8080/health

# Verify port is not in use
lsof -i :8080

Stdio Transport Not Responding

# Check JSON format
echo '{"jsonrpc":"2.0","method":"ping","id":1}' | go run ./...

Request Timeout

# Increase timeout
go run ./... --request-timeout 60s

Parse Errors

  • Ensure JSON is valid and properly formatted
  • Check that all required fields are present
  • Verify JSON-RPC 2.0 compliance

Resources

Support

For issues and questions:

License

MIT