How I rewrote Model Context Protocol in Go and achieved 100K ops/sec

Hello! Today I will talk about GoMCP - a production-grade alternative to the official MCP SDK from Anthropic. Spoiler: it turned out to be 10 times faster, with multi-tenancy and enterprise features out of the box.

Hello everyone! My name is Dima, some of you already know me here, and today I will tell you about how I created GoMCP — a production-grade alternative to the official MCP SDK from Anthropic. Spoiler: it turned out to be 10 times faster, with multi-tenancy and enterprise features out of the box.

  • 100K+ tool calls/sec (vs ~10K for the Python SDK)

  • Security hardening: input validation, audit logging, rate limiting

  • Multi-tenancy: namespace isolation + quotas

  • 3 adapters: stdio (MCP v1), gRPC, HTTP REST

  • 213 tests, 430+ Full Ralph iterations

Why not the official SDK?

At the end of 2024, Anthropic introduced the Model Context Protocol — a standard for connecting LLMs to external tools. The idea is great, but the implementation... let's just say, it's not for production:

Problems with the official SDK

# Typical MCP server in Python
@server.tool()
async def my_tool(args: dict) -> str:
    # Where's the validation? Where's the rate limiting?
    # Where's the audit logging?
    return do_dangerous_stuff(args)  # 🔥

Problem

Python SDK

GoMCP

Input validation

✅ Regex patterns, depth limits

Audit logging

✅ Structured, ring buffer

Rate limiting

✅ Per-client, configurable

Multi-tenancy

✅ Quotas, tool ACL

Hot-reload

✅ Zero-downtime

Architecture

┌─────────────────────────────────────────────────────────┐
│                    gomcp-server                         │
├─────────────┬─────────────┬─────────────┬──────────────┤
│   Stdio     │   gRPC      │   HTTP      │   Health     │
│   Adapter   │   Server    │   Mode      │   Endpoints  │
├─────────────┴─────────────┴─────────────┴──────────────┤
│                     Supervisor                          │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐  │
│  │ Security │ │ Tenant   │ │ Batching │ │ HotReload │  │
│  └──────────┘ └──────────┘ └──────────┘ └───────────┘  │
└─────────────────────────────────────────────────────────┘

Key decisions

1. Supervisor pattern — centralized management of workers:

sup := supervisor.New(supervisor.Config{
    DefaultTimeout:  30 * time.Second,
    MaxWorkers:      100,
    HeartbeatPeriod: 5 * time.Second,
})

// Graceful shutdown
sup.Shutdown()  // Waits for all calls to complete

2. Security-first — input validation:

validator := security.DefaultValidator()
// Checks:
// - Max string length: 100KB
// - Max array length: 10K items  
// - Max nesting depth: 20
// - XSS patterns: 
tm := tenant.NewManager()
t, _ := tm.CreateTenant("company-a", "Company A", tenant.Quotas{
    MaxToolCalls:      1000,
    MaxBatchSize:      50,
    MaxConcurrent:     10,
})

// Access restriction to tools
t.SetAllowedTools([]string{"read_file", "list_dir"})
// company-a will NOT be able to call "delete_file"

Performance

Tested on AMD Ryzen 9 5900X, 32GB RAM:

Operation

ops/sec

Latency p99

Supervisor.CallTool

100,000

10ms

Security.ValidateJSON

500,000

2ms

AuditLogger.Log

1,000,000

1ms

Tenant.CheckQuota

2,000,000

0.5ms

Batching

batch := batching.NewBuilder().
    Add("r1", "tool1", args1).
    Add("r2", "tool2", args2).
    Add("r3", "tool3", args3).
    Parallel(5).  // Up to 5 parallel calls
    Build()

result := processor.Process(ctx, batch)
// Instead of 3 sequential calls — 1 parallel batch

Three adapters — one interface

1. Stdio (MCP v1 compatible)

gomcp-server -mode=stdio

JSON-RPC 2.0 over stdin/stdout. Fully compatible with Claude Desktop:

{"jsonrpc":"2.0","id":"1","method":"tools/list"}

2. gRPC (native)

gomcp-server -mode=grpc -addr=:50051

For microservices architecture. Supports streaming, TLS.

3. HTTP REST (Docker-native)

gomcp-server -mode=http -addr=:8080
# List tools
curl http://localhost:8080/v1/tools

# Call tool
curl -X POST http://localhost:8080/v1/tools/call \
  -d '{"tool":"echo","arguments":{"msg":"hello"}}'

# Batch
curl -X POST http://localhost:8080/v1/tools/batch \
  -d '{"requests":[...], "parallel": true}'

Docker

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /gomcp-server ./cmd/gomcp-server

FROM alpine:3.19
RUN adduser -D gomcp
USER gomcp
COPY --from=builder /gomcp-server /app/gomcp-server
HEALTHCHECK CMD wget --spider http://localhost:8080/healthz
ENTRYPOINT ["/app/gomcp-server"]
# docker-compose.yml
services:
  gomcp-server:
    build: .
    ports: ["8080:8080"]
    deploy:
      resources:
        limits:
          memory: 512M

TypeScript SDK

import { GoMCPClient } from '@gomcp/sdk';

const client = new GoMCPClient({ 
  baseUrl: 'http://localhost:8080' 
});

// List tools
const tools = await client.listTools();

// Call tool
const result = await client.callTool('echo', { msg: 'hello' });

// Batch
const batch = await client.batchCall([
  { tool: 't1', arguments: {} },
  { tool: 't2', arguments: {} }
], { parallel: true });

Testing

I used the Full Ralph methodology — repeated test execution to identify flaky tests and race conditions:

Go packages: 12
Total tests: 174
Full Ralph iterations: 430+

TypeScript SDK: 39 tests

Each package went through at least 10 iterations of the full test suite.

Comments