After successfully building a weather MCP server using Python with stdio transport, I decided to explore the Go ecosystem for MCP development. This journey led me to discover the power of go-zero framework and Server-Sent Events (SSE) for building robust MCP servers. Today, I’ll share my experience building an intelligent calculator server that showcases a different approach to MCP implementation.

Why Go for MCP Development?

While Python provides excellent MCP libraries and is perfect for rapid prototyping, Go offers several advantages for production MCP servers:

  • Performance: Go’s compiled nature and excellent concurrency support
  • Deployment: Single binary deployment without dependency management
  • Scalability: Built-in goroutines for handling multiple concurrent connections
  • Type Safety: Strong typing system reduces runtime errors

The choice of go-zero framework was particularly compelling because it handles all the underlying complexities, allowing me to focus on business logic and creating intelligent experiences.

The Project: Building an Intelligent Calculator

Unlike my previous weather server that integrated with external APIs, this calculator server demonstrates pure computational capabilities. The server exposes a single powerful tool:

  • calculator: Performs basic mathematical operations (add, subtract, multiply, divide) with intelligent error handling and formatted responses

My Go-Zero Journey

Project Setup and Configuration

I started by setting up the project structure. Go-zero emphasizes configuration-driven development, so I began with a clean configuration file:

# config.yaml
name: calculator
port: 8080

This simple configuration tells go-zero everything it needs to know about our service - the name and the port to run on.

Core Server Implementation

The beauty of go-zero lies in its simplicity. Here’s how I implemented the main server logic:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/zeromicro/go-zero/core/conf"
    "github.com/zeromicro/go-zero/mcp"
)

func main() {
    // Load configuration
    var c mcp.McpConf
    conf.MustLoad("config.yaml", &c)

    // Create MCP server
    server := mcp.NewMcpServer(c)
    defer server.Stop()

    // Register calculator tool
    calculatorTool := mcp.Tool{
        Name:        "calculator",
        Description: "Perform basic mathematical operations",
        InputSchema: createCalculatorSchema(),
        Handler:     handleCalculatorOperation,
    }

    // Register tool with server
    if err := server.RegisterTool(calculatorTool); err != nil {
        log.Fatalf("Failed to register calculator tool: %v", err)
    }

    fmt.Printf("Starting MCP server on port: %d\n", c.Port)
    server.Start()
}

Building the Calculator Tool Schema

One of the most important aspects of MCP tool development is creating a comprehensive input schema. This schema helps AI models understand exactly what parameters are expected:

func createCalculatorSchema() mcp.InputSchema {
    return mcp.InputSchema{
        Properties: map[string]any{
            "operation": map[string]any{
                "type":        "string",
                "description": "The operation to perform (add, subtract, multiply, divide)",
                "enum":        []string{"add", "subtract", "multiply", "divide"},
            },
            "a": map[string]any{
                "type":        "number",
                "description": "The first operand",
            },
            "b": map[string]any{
                "type":        "number",
                "description": "The second operand",
            },
        },
        Required: []string{"operation", "a", "b"},
    }
}

Implementing the Calculator Handler

The handler function is where the actual business logic lives. I focused on creating a robust implementation with proper error handling:

func handleCalculatorOperation(ctx context.Context, params map[string]any) (any, error) {
    var req struct {
        Operation string  `json:"operation"`
        A         float64 `json:"a"`
        B         float64 `json:"b"`
    }

    if err := mcp.ParseArguments(params, &req); err != nil {
        return nil, fmt.Errorf("failed to parse arguments: %v", err)
    }

    // Execute operation
    var result float64
    switch req.Operation {
    case "add":
        result = req.A + req.B
    case "subtract":
        result = req.A - req.B
    case "multiply":
        result = req.A * req.B
    case "divide":
        if req.B == 0 {
            return nil, fmt.Errorf("division by zero is not allowed")
        }
        result = req.A / req.B
    default:
        return nil, fmt.Errorf("unknown operation: %s", req.Operation)
    }

    // Return formatted result
    return map[string]any{
        "expression": fmt.Sprintf("%g %s %g", req.A, getOperationSymbol(req.Operation), req.B),
        "result":     result,
    }, nil
}

func getOperationSymbol(op string) string {
    switch op {
    case "add":
        return "+"
    case "subtract":
        return "-"
    case "multiply":
        return "×"
    case "divide":
        return "÷"
    default:
        return op
    }
}

Running and Testing the Server

Local Development

Starting the server is straightforward:

go run main.go

If everything is configured correctly, you’ll see:

Starting MCP server on port: 8080

The server now runs on port 8080, ready to accept MCP connections via Server-Sent Events.

Claude Desktop Integration

The integration with Claude Desktop required a different configuration approach compared to my Python weather server. Since this server uses HTTP/SSE instead of stdio, I needed to use the mcp-remote package:

{
  "mcpServers": {
    "calculator": {
      "command": "npx",
      "args": ["mcp-remote", "http://localhost:8080/sse"]
    }
  }
}

Key difference: Instead of directly running the Go binary, we use mcp-remote to bridge between Claude Desktop and our HTTP-based MCP server.

Real-World Testing Results

After restarting Claude Desktop, I tested the calculator with various queries:

Successful Operations

  • “Calculate 15 + 27” ✅
  • “What’s 144 divided by 12?” ✅
  • “Multiply 8.5 by 3.2” ✅
  • “Subtract 45 from 100” ✅

Error Handling

  • “Divide 10 by 0” → Proper error message about division by zero ✅
  • Invalid operations → Clear error messages ✅

Response Quality

The structured response format proved particularly effective:

{
  "expression": "15 + 27",
  "result": 42
}

Claude interprets this beautifully, presenting results in a conversational format like: “15 + 27 equals 42.”

Lessons Learned and Best Practices

1. SSE vs Stdio Transport

The transition from stdio (Python) to SSE (Go) revealed important differences:

SSE Advantages:

  • HTTP-based communication is more familiar to web developers
  • Easier to debug with standard HTTP tools
  • Better suited for distributed deployments
  • Natural fit for web-based MCP clients

Stdio Advantages:

  • Direct process communication
  • Lower overhead
  • Simpler configuration in Claude Desktop

2. Go-Zero Framework Benefits

Using go-zero provided several advantages:

  • Configuration Management: Centralized configuration handling
  • Graceful Shutdown: Built-in server lifecycle management
  • Tool Registration: Clean API for registering MCP tools
  • Error Handling: Consistent error handling patterns

3. Type Safety Matters

Go’s strong typing system caught several issues during development:

  • Parameter validation happens at compile time
  • JSON marshaling/unmarshaling is type-safe
  • Interface definitions prevent runtime errors

4. Debugging Strategies

For Go-based MCP servers:

  • Use fmt.Printf for debugging during development
  • Check network connectivity with curl http://localhost:8080/sse
  • Validate JSON responses with standard HTTP tools
  • Monitor Claude Desktop logs for connection issues

Performance Insights

The Go implementation showed significant performance improvements:

  • Startup Time: Nearly instantaneous compared to Python
  • Memory Usage: Significantly lower baseline memory consumption
  • Response Time: Faster mathematical operations and JSON serialization
  • Concurrent Connections: Better handling of multiple simultaneous requests

Comparison: Python vs Go for MCP Development

Having built MCP servers in both languages, here’s my assessment:

Python Strengths

  • Rapid prototyping
  • Rich ecosystem of libraries
  • Excellent for data processing and API integration
  • Built-in MCP libraries

Go Strengths

  • Superior performance and memory efficiency
  • Single binary deployment
  • Excellent concurrency support
  • Strong typing system
  • Better suited for production deployments

When to Choose Each

  • Python: Rapid prototyping, complex data processing, extensive third-party integrations
  • Go: Production deployments, performance-critical applications, microservices architectures