MCP finally reached Go: how to build a production-ready agent server without drowning in custom glue code
I’m Denis Shokhirev, an Agentic AI Systems Architect based in Freiburg im Breisgau, Germany, and at DennisCraft AI Studio I ship production AI systems on Claude, Supabase, n8n, Doppler, and self-hosted Postgres for DACH B2B clients. The moment agent projects usually get messy is not model selection; it’s week 3, when five tools, three auth patterns, and two partial retries have created more custom glue code than business logic. That’s why MCP reaching Go matters. Not because every agent stack s
I’m Denis Shokhirev, an Agentic AI Systems Architect based in Freiburg im Breisgau, Germany, and at DennisCraft AI Studio I ship production AI systems on Claude, Supabase, n8n, Doppler, and self-hosted Postgres for DACH B2B clients. The moment agent projects usually get messy is not model selection; it’s week 3, when five tools, three auth patterns, and two partial retries have created more custom glue code than business logic.
That’s why MCP reaching Go matters. Not because every agent stack suddenly becomes simple, but because a standardized server boundary reduces the number of homegrown adapters you need to maintain. If you treat MCP as an engineering surface instead of a demo feature, you can build a stable tool layer with explicit contracts, auditable behavior, and fewer hidden assumptions between the model and your infrastructure.
Why agent integrations fail before the model does
Most teams start with prompts, evaluation, and orchestration. In production, failures usually come from the integration layer: tools return inconsistent shapes, retries duplicate side effects, database permissions are too broad, and logs don’t let you reconstruct what happened. Once an agent can call SQL, internal APIs, and automation flows, you are running a distributed system with real policy and observability requirements.
The common anti-pattern
I keep seeing the same path. A team writes two quick helper functions, wraps them in a custom JSON schema, adds manual parameter validation, then builds another wrapper so the same tool can be consumed by Claude and by an internal runner. It works, but now the team owns a private protocol. Every new tool adds one more place where behavior can drift.
MCP helps because it creates a standard interface for tools and resources. Anthropic’s documentation describes Model Context Protocol as a way to connect models to tools and data sources through a consistent contract: https://docs.anthropic.com/. The immediate benefit is not novelty. It is lower protocol entropy inside your stack.
What a production-ready MCP server actually needs
A server that answers list_tools is not production-ready. A production-grade MCP service should be testable, observable, permissioned, and boring to operate. My minimum checklist looks like this:
| Layer | What to implement | Why it matters |
|---|---|---|
| Contract | Strict input and output schemas | Prevent silent type drift and hidden fields |
| Authorization | Tool-level and environment-level access control | Keep the agent inside least-privilege boundaries |
| Observability | Request IDs, audit logs, traceable tool calls | Support incident review and reproducibility |
| Safety | Secret isolation, SQL allowlists, static analysis | Reduce data exposure and unsafe execution paths |
| Operations | Health checks, timeouts, idempotent retries | Handle partial failures without creating chaos |
If two or three of those are missing, you do not have an agent platform yet. You have an early integration layer. That distinction matters because the operating assumptions are very different.
Why Go is a practical fit
Go is useful here for plain operational reasons: a small binary, predictable dependency behavior, and a concurrency model that works well for serving multiple tool calls with bounded runtime costs. In client environments where I already have n8n, Postgres, internal APIs, and self-hosted infrastructure, adding a compact Go service is often easier than introducing another heavy runtime just to expose six or seven tools.
A minimal server shape with fewer surprises
The design principle is simple: one tool, one explicit handler, validated input, and a narrow side effect. Even if your development loop uses Claude Code or the Anthropic SDK, the server boundary should stay strict and boring.
export DATABASE_URL="postgres://app:***@db.internal:5432/app"
export MCP_ALLOWED_TABLES="customers,orders"
export TOOL_TIMEOUT_SECONDS="8"
gitleaks detect --source . --no-banner
semgrep --config p/owasp-top-ten .
bandit -r scripts/
go test ./...
go build -o mcp-server ./cmd/server
./mcp-serverI still run gitleaks and semgrep around the repository because the server is only part of the risk surface. CI files, shell scripts, migration helpers, and automation code are often where secrets and unsafe execution paths leak in. If the repo includes Python utility scripts, bandit is worth adding as well.
version: "3.9"
services:
mcp-server:
image: ghcr.io/example/mcp-server:latest
environment:
DATABASE_URL: ${DATABASE_URL}
MCP_ALLOWED_TABLES: customers,orders
TOOL_TIMEOUT_SECONDS: "8"
read_only: true
restart: unless-stopped
healthcheck:
test: ["CMD", "/app/mcp-server", "healthcheck"]
interval: 30s
timeout: 3s
retries: 3The important part is not Docker itself. It is the policy around the server. Your MCP service should not run with unrestricted database credentials. If a tool only needs to read customer and order data, give it a read-only database role and restrict the tables it can touch at the application layer too. On three recent agent deployments I reviewed, over-permissioned database access was the single most common issue before launch.
Where teams get the tool layer wrong
1. They build a universal tool executor
The “one executor for everything” pattern sounds efficient but usually creates vague contracts. Six narrow tools with clean schemas are easier to test and safer to operate than one giant tool with a dozen modes.
2. They mix orchestration with policy
n8n is useful for orchestration and integration flows. Policy decisions such as which tables may be queried or which domains can be called should live in the MCP server and its configuration. Otherwise your control surface becomes fragmented.
3. They log too little
It is not enough to know that a tool was called. You need caller identity, validated inputs, result metadata, duration, error code, and a trace ID that lets you connect the tool call to the larger agent run.
4. They only test happy paths
Test timeouts, cancellations, malformed arguments, empty result sets, and duplicate calls after partial failure. Those cases affect production behavior far more than the successful IDE demo.
External guidance worth using
OWASP Top 10 2021 highlights Broken Access Control and Insecure Design as major application risks: https://owasp.org/www-project-top-ten/. That maps directly onto MCP server design. The server becomes the choke point where permissions, validation, and error handling are either disciplined or dangerously loose.
OpenAI’s cookbook also consistently pushes structured tool usage and schema-defined interfaces in function-calling patterns: https://cookbook.openai.com/. Even if your primary runtime is Claude, the broader lesson is the same: standardized tool contracts reduce operator pain and make multi-model architectures easier to sustain.
FAQ
Is MCP only useful with Claude?
No. The main value is the contract between models and tools. Even in a mixed-model environment, a standard server boundary reduces adapter sprawl.
Should I expose all internal capabilities as MCP tools?
No. Start with a small set of high-value tools with narrow permissions. Wide tool catalogs create larger failure surfaces and usually degrade governance.
Can I place Supabase behind an MCP server?
Yes, as long as you separate service roles from agent-facing access and keep strict read or write boundaries. MCP does not replace data governance; it gives you a cleaner execution boundary.
Where should secrets live?
Not in prompts and not hardcoded in tool wrappers. I usually keep them in Doppler or another secret manager and inject only the environment-specific values the server needs.
When is MCP overkill?
If you have one local script and one external API, a dedicated MCP server may be unnecessary. Once you have multiple tools, permission tiers, and audit requirements, the standard boundary becomes much more valuable.
Which part of your current LLM stack creates the most production pain: tool contracts, access control, auditability, or incident debugging after release? I’d genuinely like to know, because that bottleneck usually says more about system maturity than the model choice does.
I run a free 30-min stack audit for DACH founders building AI in regulated markets. DM me on LinkedIn or write to @ger_dennis_ai.
Turn your process into an AI system
Production quality. DACH B2B focus.