MCP Servers in Production: Architecture, Auth and Scaling
MCP Servers in Production: Architecture, Auth and Scaling
The honeymoon phase of the Model Context Protocol (MCP) is officially over. We’ve all seen the demos: a local Claude Desktop instance connecting to a SQLite database or a filesystem via a local stdio pipe. It’s magical, it’s fast, and it’s completely insufficient for production-grade agentic systems.
As we move into 2026, the challenge has shifted from "How do I give my agent a tool?" to "How do I manage 500 specialized tools across 10,000 concurrent agent sessions without blowing up my security budget or latency SLAs?"
At agentic.dev, we’ve spent the last year breaking and fixing MCP deployments. If you are building autonomous systems that need to interact with internal APIs, legacy databases, or real-time web services, you need to treat your MCP servers as first-class microservices. Here is how we architect, secure, and scale them for the real world.
1. Beyond STDIO: The Shift to Remote MCP Architecture
In a local environment, MCP relies on stdio-the agent starts a child process and communicates via standard input/output. In production, your agent (the client) and your tool (the MCP server) are rarely on the same machine.
The 2026 standard for production MCP is SSE (Server-Sent Events) over HTTPS. While WebSockets are an option, SSE is generally preferred for MCP because it handles the typical request-response pattern of tool calling while allowing the server to push context updates or progress notifications back to the agent without the overhead of a full duplex connection.
The MCP Gateway Pattern
Don't expose your MCP servers directly to the internet. Instead, implement an MCP Gateway. This acts as a reverse proxy that handles:// Example: A simple MCP Server using the @modelcontextprotocol/sdk
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
const app = express(); const mcpServer = new Server({ name: "Production-Inventory-Tool", version: "2.4.1", }, { capabilities: { tools: {} } });
// Register a tool mcpServer.tool("get_stock_level", { sku: z.string() }, async ({ sku }) => { const stock = await db.inventory.findUnique({ where: { sku } }); return { content: [{ type: "text", text: Current stock for ${sku}: ${stock.count} }] }; });
// Production-grade SSE endpoint app.get("/mcp/connect", async (req, res) => { const transport = new SSEServerTransport("/mcp/message", res); await mcpServer.connect(transport); });
app.post("/mcp/message", async (req, res) => { // Handle incoming JSON-RPC calls from the Agent Gateway });
app.listen(3000, () => console.log("MCP Inventory Server online"));
2. Authentication and The "On-Behalf-Of" Problem
The biggest security hole in early agentic implementations was "God-mode" tools. You give an agent an MCP server for GitHub, and it uses a single administrative PAT (Personal Access Token) for every user. In production, this is a non-starter.
You must implement Identity Propagation. The MCP server shouldn't just know that an agent is calling; it needs to know which user the agent is representing.
Token Exchange Flow
Authorization header to the MCP Gateway.Pro-tip: Use mcp_extensions in your JSON-RPC calls to pass audit metadata. We’ve found that logging the intent_id alongside the tool call is vital for reconstructing why an agent performed an action during a security audit.
3. Scaling: Tool Registry and Resource Isolation
Scaling MCP isn't just about adding more pods in Kubernetes; it’s about managing the "Tool Surface Area."
If you provide an LLM with 200 tools in a single MCP manifest, you will suffer from Context Dilution. The model will get confused, hallucinate parameters, or fail to choose the right tool.
Dynamic Tool Discovery
Instead of a static list, implement a Tool Registry. When an agent starts a task, the orchestrator queries the Registry for the most relevant MCP servers based on the user's prompt.Rate Limiting the "Agent Loop"
Agents are recursive by nature. A bug in your logic can lead to an infinite loop where an agent calls an MCP tool 100 times a second.Inventory-Tool has triggered $50 of LLM reasoning in 5 minutes, kill the session.4. Observability: The Agentic Trace
Traditional APM (Application Performance Monitoring) fails to capture the "Why" of an agentic failure. If an MCP tool returns an error, was it because the database was down, or because the LLM provided a malformed UUID?
- In 2026, we use OpenTelemetry (OTel) with Agentic Context. Every MCP request should be wrapped in a trace that includes:
llm.model_name: Which model made the call?agent.session_id: Which long-running conversation does this belong to?tool.definition: The prompt/description provided to the model for this tool.tool.input_json: The raw arguments generated by the LLM.