agentic.dev

MCP Servers in Production: Architecture, Auth and Scaling

Published 2026-02-21

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:
  • Protocol Translation: Converting various internal formats to MCP-compliant JSON-RPC.
  • Aggregation: Presenting multiple downstream MCP servers as a single unified catalog to the LLM.
  • Context Injection: Automatically appending relevant metadata (user IDs, session state) to every tool call.
  • // 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

  • The Agent Client (e.g., your frontend or backend orchestrator) receives a user’s JWT.
  • When the Agent decides to call a tool, it passes this JWT (or a scoped down version) in the Authorization header to the MCP Gateway.
  • The MCP Gateway validates the JWT and performs a Token Exchange. It trades the user's identity for a short-lived, scoped token for the specific downstream service (e.g., a GitHub App User-to-Server token).
  • The MCP Server receives the call with the specific user’s credentials, ensuring that the agent can only access data the user actually owns.
  • 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.
  • Horizontal Scaling: MCP servers are stateless by design. We deploy them as serverless functions (AWS Lambda or Google Cloud Run) or as auto-scaling containers.
  • Sandboxing: For tools that execute code (like a Python REPL MCP), we use gVisor or Firecracker microVMs. You cannot run untrusted code generated by an LLM in a shared container environment. Every MCP call that involves code execution should happen in a disposable, network-isolated sandbox that lives for exactly the duration of the request.
  • 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.
  • Global Rate Limits: Set per-user and per-agent limits at the Gateway level.
  • Cost Quotas: Track the token cost associated with each MCP server. If the 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?

    The "Shadow Tool" Technique

    Before rolling out a new version of an MCP server, we run it in Shadow Mode. The agent calls the production tool, but the Gateway simultaneously sends the same request to the "Candidate" MCP server. We compare the JSON outputs. If the candidate tool's output would have caused a different LLM response (simulated via a cheap