,

Building an MCP Server in Go

Building and MCP Server in Go

In my last post, Connecting Open WebUI to MCP Servers, we connected our local AI to existing MCP servers and saw the power of the protocol. But the real competitive advantage lies in building your own MCP servers — custom tools that wire your AI directly to your systems, your data, your workflows.

This is the first in a four-part series. We’re going to build the same MCP server in four different languages: Go, JavaScript, C#, and Python. Each post covers its language in isolation — you only need to read the one matching your stack.

The tool we’re building? A Google Reviews scraper. Your AI agent asks “What are our latest Google reviews?” or “Show me reviews with ratings below 3 stars” — and the MCP server fetches and returns structured data. One server. Every AI client. Forever reusable.

What Is an MCP Server?

MCP (Model Context Protocol) is an open specification that defines how AI clients communicate with external tools and data sources. Think of it as a universal USB-C port for AI. One standard. Any tool. Any client.

The protocol exposes three primitives:

  • Tools — actions the AI can invoke (scrape reviews, query a database, create a file)
  • Resources — readable data sources (a live dashboard, an API response, a document)
  • Prompts — reusable message templates for common workflows

The official MCP SDK is available in six languages, all Tier 1 for feature completeness and protocol support: TypeScript, Python, C#, and Go (maintained in collaboration with Google), plus Java and Rust at Tier 2. The SDK handles the JSON-RPC plumbing. You focus on the business logic.

Setting Up the Go SDK

The official Go SDK lives at github.com/modelcontextprotocol/go-sdk and is maintained in collaboration with Google. It’s the most battle-tested SDK for HTTP-based MCP servers — the Go team itself uses it to build their AI integrations.

Create a new Go module and install the SDK:

mkdir mcp-google-reviews && cd mcp-google-reviews
go mod init mcp-google-reviews
go get github.com/modelcontextprotocol/go-sdk/mcp

What this command is doing:

  • go mod init mcp-google-reviews – Initializes a new Go module. Every Go project needs a go.mod file to track dependencies.
  • go get github.com/modelcontextprotocol/go-sdk/mcp – Downloads the official MCP Go SDK. This package provides the mcp.NewServer(), mcp.AddTool(), and transport APIs.

The Complete Google Reviews MCP Server in Go

Here is the full server. It exposes a single tool — fetch_reviews — that takes a Google Maps place ID and returns the latest reviews as structured JSON.

Save this as main.go:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/modelcontextprotocol/go-sdk/mcp"
)

// Review represents a single Google review.
type Review struct {
    AuthorName  string `json:"author_name"`
    Rating      float64 `json:"rating"`
    Text        string  `json:"text"`
    TimeAgo     string  `json:"time_ago"`
}

// FetchReviewsInput defines the parameters for the tool.
type FetchReviewsInput struct {
    PlaceID string `json:"place_id" jsonschema:"The Google Maps place ID of the business"`
    Limit   int    `json:"limit" jsonschema:"Maximum number of reviews to return, defaults to 10"`
}

func fetchReviews(
    ctx context.Context,
    req *mcp.CallToolRequest,
    input *FetchReviewsInput,
) (*mcp.CallToolResult, any, error) {
    placeID := input.PlaceID
    limit := input.Limit
    if limit <= 0 {
        limit = 10
    }

    // In a real implementation, you would:
    // 1. Call the Google Places API or scrape the Google Maps page
    // 2. Parse the response into Review structs
    // 3. Return structured data
    reviews := []Review{
        {AuthorName: "Alice Johnson", Rating: 5, Text: "Excellent service! The team went above and beyond.", TimeAgo: "2 days ago"},
        {AuthorName: "Bob Smith", Rating: 4, Text: "Great experience overall, minor wait time.", TimeAgo: "1 week ago"},
        {AuthorName: "Carol Davis", Rating: 1, Text: "Disappointing. Did not meet expectations.", TimeAgo: "3 weeks ago"},
    }

    result := FetchReviewsOutput{
        PlaceID: placeID,
        Reviews: reviews,
        Count:   len(reviews),
    }

    jsonBytes, err := json.Marshal(result)
    if err != nil {
        return &mcp.CallToolResult{
            Content: []mcp.Content{&mcp.TextContent{Text: fmt.Sprintf("Error: %v", err)}},
            IsError: true,
        }, nil, nil
    }

    return &mcp.CallToolResult{
        Content: []mcp.Content{&mcp.TextContent{Text: string(jsonBytes)}},
    }, nil, nil
}

// FetchReviewsOutput is the structured response from the tool.
type FetchReviewsOutput struct {
    PlaceID string   `json:"place_id"`
    Reviews []Review `json:"reviews"`
    Count   int      `json:"count"`
}

func main() {
    // Create the MCP server with identification metadata.
    server := mcp.NewServer(&mcp.Implementation{
        Name:    "google-reviews-scraper",
        Version: "1.0.0",
    }, nil)

    // Register the fetch_reviews tool with its schema and handler.
    mcp.AddTool(server, &mcp.Tool{
        Name:        "fetch_reviews",
        Description: "Fetch Google reviews for a business using its Place ID. Returns structured review data including ratings, text, and author names.",
    }, fetchReviews)

    // Create the Streamable HTTP handler.
    handler := mcp.NewStreamableHTTPHandler(func(req *http.Request) *mcp.Server {
        return server
    }, nil)

    port := os.Getenv("PORT")
    if port == "" {
        port = "3001"
    }

    log.Printf("Google Reviews MCP server starting on :%s", port)
    log.Printf("Open WebUI connection URL: http://localhost:%s/mcp", port)

    if err := http.ListenAndServe(":"+port, handler); err != nil {
        log.Fatalf("Server failed: %v", err)
    }
}

What this code is doing:

  • mcp.NewServer() – Creates the MCP server instance with a name and version. This metadata is sent to every connecting client so they know what they’re talking to.
  • mcp.AddTool() – Registers a single tool named fetch_reviews. The first argument is the server, the second defines the tool’s name and description (which becomes the AI’s instruction for when to call this tool), and the third is the handler function.
  • mcp.NewStreamableHTTPHandler() – Wraps the server in a Streamable HTTP endpoint. This is the transport layer that Open WebUI (and other MCP clients) use to communicate with your server. The factory function returns the server instance for each incoming request.
  • http.ListenAndServe() – Starts the HTTP server. The handler receives requests at /mcp by default and speaks the MCP protocol over HTTP.

Running the Server

Compile and run the server:

go run main.go

Expected output:

2026/05/08 10:00:00 Google Reviews MCP server starting on :3001
2026/05/08 10:00:00 Open WebUI connection URL: http://localhost:3001/mcp

The server is now listening on port 3001. The fetch_reviews tool is registered and ready for AI clients to discover and invoke.

Connecting to Open WebUI

Follow the exact process from my Connecting Open WebUI to MCP Servers post:

  1. Navigate to Admin Settings → External Tools in your Open WebUI admin panel
  2. Click + (Add Server)
  3. Set Type to MCP (Streamable HTTP)
  4. Set Server URL to http://localhost:3001
  5. Set Auth to None (our server doesn’t require authentication)
  6. Click Save

Once connected, open a chat with your model and enable the fetch_reviews tool via the + button → Integrations → Tools menu. Your LLM can now ask “What are our latest reviews for our business?” and the MCP server will return structured review data in real time.

From Example to Production

The code above uses hardcoded sample data. In production, you’d replace the sample reviews with a real implementation:

  • Google Places API – Use the official Google Maps Places API with a valid API key. Add the key as an environment variable and pass it to your HTTP client.
  • Caching – Google review data doesn’t change every minute. Cache responses with a TTL (TTL: 300)
  • Error handling – Add proper error responses with retry logic for rate limits and network failures.

The MCP structure stays exactly the same. You’re only replacing the business logic inside the fetchReviews handler function. The SDK handles everything else: protocol compliance, JSON-RPC, session management, tool discovery.

What’s Next

This was the Go version. The same Google Reviews scraper exists in three other languages. Here is how they compare:

  • JavaScript version – Built with @modelcontextprotocol/server and Express. Great for Node.js environments and NPM-based deployments.
  • C# version – Built with the official Microsoft-maintained C# SDK and ASP.NET Core. Ideal for enterprise .NET stacks.
  • Python version – Built with the FastMCP API using decorators. The quickest to write and prototype.

For the full SDK reference, see the official MCP SDK documentation.

Key Takeaway

  • Performance: Go compiles to a single binary with zero runtime dependencies. Your MCP server runs as a lightweight, standalone executable — ideal for Docker containers, edge deployments, or bare-metal servers where every megabyte matters.
  • Developer experience: The official Go SDK provides strong typing, compile-time tool registration, and a clean API that maps directly to the MCP specification. You get type safety without boilerplate.
  • Strategic: Building your own MCP server in Go means your AI has direct, low-latency access to proprietary data sources. You trade volatile API fees for a single, owned integration.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *