How to Architect MCP Server Management and Governance at Scale: Connecting 100+ Agents to Tools Without Creating Infrastructure Chaos

The Model Context Protocol promised a clean, universal way to give AI agents access to tools. And it delivered. Now comes the harder part: running it in production across a hundred servers, dozens of teams, and hundreds of autonomous agents without everything catching fire. The teams that get this right treat MCP the same way mature engineering organizations treat APIs and databases. They build a control plane first, then plug tools into it. The teams that skip that step end up with scattered credentials, no audit trail, runaway costs, and agents calling things they never should have touched.

This article walks through the architectural patterns that separate disciplined MCP deployments from sprawl, drawing on the 2026 MCP specification, enterprise deployment patterns, and production experience.


What Skipping Architecture Looks Like

Imagine your organization has moved fast with MCP. A dozen teams have wired up their agents to tools: a Slack MCP server here, a database query server there, a GitHub integration, a Jira connector, a handful of internal APIs. Each team controls its own server configuration, issues its own credentials, and monitors its own logs (or doesn't).

Six months in, you have 80 MCP servers in production. Nobody has a complete list. Three separate teams built their own Slack integrations because they didn't know the others existed. Credentials are stored as plaintext in environment variables, rotated manually when someone remembers, and never revoked when engineers leave. An agent in the marketing automation stack has broad read access to the customer database because "it needed it for one thing." There is no audit trail, so when something goes wrong you have no way to know which agent called what.

This is not a hypothetical. It is the natural endpoint of "just add it" MCP adoption, and organizations are hitting it right now. The fix is not a better spreadsheet of servers. It is infrastructure.


The Gateway: Your Single Point of Control

The foundational shift is moving from direct agent-to-MCP connections to a gateway architecture. Instead of each agent knowing the addresses of individual MCP servers, every request routes through a central control layer before reaching any tool.

Agent A ─┐
Agent B ─┤──▶ MCP Gateway ──▶ Tool Server A (GitHub)
Agent C ─┘          │
                     ├──▶ Tool Server B (Postgres)
                     └──▶ Tool Server C (Slack)

The gateway is not just a proxy. It is the enforcement point for your entire governance model: authentication, authorization, rate limiting, cost tracking, and audit logging all happen here, once, consistently. Without a gateway, you have to implement all of that in every MCP server independently, which means you will not implement most of it.

A minimal gateway in FastAPI shows how this centralizes enforcement:

from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.responses import Response
import httpx
import time
import json

app = FastAPI()

async def validate_virtual_key(request: Request) -> dict:
    key = request.headers.get("X-MCP-Key")
    if not key:
        raise HTTPException(status_code=401, detail="Missing MCP key")

    # Resolve the key from your key store (Redis, DB, etc.)
    key_data = await key_store.resolve(key)
    if not key_data or key_data["expires_at"] < time.time():
        raise HTTPException(status_code=401, detail="Invalid or expired key")

    return key_data

@app.api_route("/{server_id}/{path:path}", methods=["GET", "POST"])
async def gateway(server_id: str, path: str, request: Request,
                  key_data: dict = Depends(validate_virtual_key)):

    # Enforce tool allowlist
    if request.method == "POST":
        body = await request.json()
        tool = body.get("params", {}).get("name")
        if tool and tool not in key_data["allowed_tools"]:
            await audit_log(key_data, server_id, tool, "DENIED")
            raise HTTPException(status_code=403, detail=f"Tool '{tool}' not allowed")

    # Enforce spend cap
    if key_data["monthly_spend"] >= key_data["spend_cap"]:
        raise HTTPException(status_code=429, detail="Monthly spend cap reached")

    # Forward to the upstream MCP server
    upstream = await server_registry.get(server_id)
    start = time.time()
    async with httpx.AsyncClient() as client:
        resp = await client.request(
            method=request.method,
            url=f"{upstream['url']}/{path}",
            headers={"Authorization": f"Bearer {upstream['internal_token']}"},
            content=await request.body(),
        )

    await audit_log(key_data, server_id, tool, "ALLOWED",
                    duration_ms=(time.time() - start) * 1000,
                    status=resp.status_code)

    return Response(content=resp.content, status_code=resp.status_code,
                    media_type=resp.headers.get("content-type"))

This is simplified, but the shape is real. A single endpoint handler checks credentials, enforces allowlists, applies spend caps, proxies the request, and logs the result. Scale this to 100 servers and you have implemented governance once.


Solving the Context Window Tax

Tool schema overhead is larger than most teams expect. When an agent connects directly to multiple MCP servers, all available tool schemas are loaded upfront, regardless of whether those tools are ever called. Connect to 20 servers with 5 tools each and you are spending roughly 9,400 tokens before the agent processes a single line of task context. For agents running complex, multi-step work, that fixed overhead competes with task context for available space and drives up cost on every call.

The solution is a "portal" or "meta-tool" pattern: instead of exposing all tool schemas directly, you expose just two meta-tools through the gateway.

# Instead of exposing 52 tools consuming ~9,400 tokens...

# Expose just these two portal tools (~600 tokens total):
PORTAL_TOOLS = [
    {
        "name": "list_available_tools",
        "description": "Returns tools the current agent is authorized to call, with their schemas.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "category": {"type": "string", "description": "Filter by tool category (optional)"}
            }
        }
    },
    {
        "name": "invoke_tool",
        "description": "Call any authorized tool by name with the provided arguments.",
        "inputSchema": {
            "type": "object",
            "required": ["tool_name", "arguments"],
            "properties": {
                "tool_name": {"type": "string"},
                "arguments": {"type": "object"}
            }
        }
    }
]

The agent calls list_available_tools when it needs to know what it can do, and invoke_tool when it wants to act. Tool schemas are fetched on demand and cached, not loaded upfront. The result is a context footprint that stays nearly constant regardless of how many MCP servers you run behind the gateway. Teams that have adopted this pattern report a 94% reduction in schema overhead per request, from roughly 9,400 tokens to about 600.


Stateless Servers: Unlocking Horizontal Scaling

Early MCP server designs required sticky sessions: each agent had to reconnect to the same server instance because state lived in memory. This made horizontal scaling painful, requiring shared session stores and complex load balancer configuration.

The 2026 MCP specification changes this. Servers can now operate as fully stateless HTTP workloads. Session state, if needed, lives in an external store (Redis is the common choice). Each request carries everything the server needs to process it. This means you can scale MCP servers the same way you scale any stateless web service: add instances behind a load balancer, and route on an Mcp-Method header to distinguish protocol messages from tool calls.

A simplified nginx routing configuration illustrates the pattern:

upstream mcp_servers {
    least_conn;
    server mcp-1.internal:8080;
    server mcp-2.internal:8080;
    server mcp-3.internal:8080;
}

server {
    location /mcp/ {
        proxy_pass http://mcp_servers;
        # Route initialize/list calls to any instance;
        # tool calls include a session token for state lookup
        proxy_set_header X-Forwarded-For $remote_addr;

        # Cache tool list responses per server version
        # (TTL driven by the ttlMs field in tool list responses;
        #  production implementations typically use a cache sidecar
        #  or reverse-proxy module for header-driven expiry)
        proxy_cache mcp_tool_cache;
        proxy_cache_valid 200 5m;
        proxy_cache_key "$host$uri$http_mcp_server_version";
    }
}

The ttlMs field in tool list responses tells the gateway how long to cache schemas. A server that rarely changes its tool definitions can serve thousands of agents without re-sending schemas on every connection.


Virtual Keys: Fine-Grained Permissions Without the Overhead

The permission model that works for humans ("does this user have access?") is too coarse for AI agents. You need to express not just "can this agent use this server" but "can this agent call this specific tool, with what parameter constraints, up to what cost limit, in what environment."

Virtual keys encode all of that in a single scoped credential. Teams issue keys to their AI workloads the same way they issue API keys today, except each key carries a complete policy:

{
  "key_id": "vk_prod_marketing_summarizer_v2",
  "team": "marketing-automation",
  "environment": "production",
  "expires_at": "2026-08-01T00:00:00Z",
  "allowed_servers": ["crm-mcp", "analytics-mcp"],
  "allowed_tools": ["get_customer_segment", "query_campaign_metrics"],
  "denied_tools": ["delete_customer", "export_bulk_data"],
  "spend_cap_monthly_usd": 50.00,
  "requests_per_minute": 20,
  "parameter_constraints": {
    "query_campaign_metrics": {
      "date_range_days": {"max": 90},
      "include_pii": {"allowed_values": [false]}
    }
  }
}

This key authorizes the marketing summarizer agent to query CRM segments and campaign metrics, but it cannot delete customer records, export bulk data, look back more than 90 days, or request PII. It is capped at $50/month and 20 requests per minute. If the agent tries to exceed any of these constraints, the gateway denies the request and logs it.

When a key is rotated or revoked, it happens in one place. Every agent using that key is immediately cut off, with no server restarts required.


Identity, SSO, and Preventing Supply Chain Risk

Virtual keys handle authorization within your system. The harder problem is authentication at the boundary: how do remote MCP servers know they are talking to a legitimate agent, not a compromised workload or a prompt-injected request impersonating one?

The 2026 MCP specification formalizes OAuth 2.1 as the standard for remote server authentication. This maps cleanly to what enterprise identity platforms already provide. Cloudflare Access, Okta, and similar platforms can serve as the OAuth provider, issuing tokens that carry contextual attributes: device certificate status, network location, geographic region, and agent identity claims.

In practice, the intent is this: any request to an MCP server must present a valid service token issued to a named AI workload, with the workload's identity tied to a service account in your identity provider. Human operators who need to test MCP servers authenticate through SSO with MFA, receiving short-lived session tokens.

Shadow MCP detection adds an enforcement layer at the network boundary. By monitoring egress for connections to known MCP server ports and comparing them against your server registry, you can detect unauthorized servers before they become a security incident. This is the same pattern mature organizations use to catch shadow IT in SaaS and API contexts.


Observability Is Not Optional

You cannot govern what you cannot see. Every agent request, every tool call, every denial, and every error must flow into centralized logging. This is non-negotiable at scale, and it is also the piece most teams skip until they have already had a problem.

A structured audit log schema for MCP events should capture everything you will need for both operational debugging and compliance reporting:

{
  "event_id": "evt_01j2k3m4n5p6",
  "timestamp": "2026-07-10T14:32:01.443Z",
  "event_type": "tool_call",
  "outcome": "allowed",
  "agent": {
    "id": "marketing-summarizer-v2",
    "team": "marketing-automation",
    "key_id": "vk_prod_marketing_summarizer_v2"
  },
  "server": {
    "id": "analytics-mcp",
    "version": "2.4.1"
  },
  "tool": {
    "name": "query_campaign_metrics",
    "input_hash": "sha256:a3f9...",
    "output_tokens": 842
  },
  "cost_usd": 0.0034,
  "duration_ms": 1240,
  "context": {
    "job_id": "campaign_summary_batch_20260710",
    "trace_id": "trace_7x8y9z"
  }
}

Publishing this event stream to your existing SIEM and APM infrastructure means MCP activity appears in the same dashboards as the rest of your backend. You can answer: which teams are spending the most on tool calls, which tools fail most often, which agents are approaching their spend caps, and which requests are being denied (and why).

The 2026 MCP specification is moving toward standardized hooks for enterprise audit trail emission. Building toward that direction now means your observability layer is less likely to need a full rewrite as the spec matures.


Discovery, Portals, and the Authorized View

When teams have 100+ MCP servers running, the question "what tools can my agent use?" has no simple answer unless you build one. MCP portals solve this by giving each team, agent, or workload a filtered view of the server ecosystem based on their permissions.

When an agent (or a developer configuring one) queries the portal, they see exactly the servers and tools their key authorizes. Unauthorized servers are invisible, not merely inaccessible. This delivers two concrete benefits: agents do not waste context on tools they cannot call, and engineers building new agents get a correct starting inventory instead of hunting through internal wikis.

Portals also become the natural place to enforce data loss prevention guardrails. If a tool call's parameters or return value pattern matches a DLP rule (for example, a query that would return bulk customer records), the portal intercepts and blocks it before it reaches the upstream server.


The Extensions Framework: Future-Proofing Your Governance Layer

One concern with investing in governance infrastructure is that the underlying protocol will change and invalidate your work. The 2026 MCP specification addresses this directly with the Extensions framework.

New governance capabilities (audit event hooks, compliance webhooks, policy decision points, new authentication flows) ship as opt-in extensions. They stabilize as extensions before moving into the core spec. This means you can adopt new governance features incrementally as your organization's needs evolve, without breaking existing deployments or waiting for a major spec version to land.

For platform teams, this is the right design signal. The spec itself is treating governance as a first-class evolving concern, not an afterthought. Investments made in gateway infrastructure and virtual key systems today will extend naturally as the protocol grows.


Putting It Together: A Reference Architecture

A well-governed MCP deployment at scale has these components working together:

Control plane: A gateway service that all agent traffic flows through, backed by a key store, a server registry, and a rate limiter. No agent connects to any MCP server directly.

Identity layer: OAuth 2.1 integration with your enterprise IdP for authenticating remote servers and human operators. Service accounts for AI workloads, with contextual claims encoded in tokens.

Permission layer: Virtual keys with scoped allowlists, spend caps, rate limits, and parameter constraints. One key per workload, not per team. Keys issued and revoked through a self-service portal with approval workflows.

Context layer: Portal meta-tools exposed through the gateway that give agents on-demand access to tool schemas without burning context on schemas they never use.

Observability layer: Structured audit events from the gateway to a centralized log aggregator, with dashboards for cost by team, tool usage, denial rates, and latency per server.

Server layer: Stateless MCP servers running behind standard HTTP load balancers, horizontally scaled to demand, with ttlMs-driven tool list caching at the gateway.


Conclusion

MCP governance is not the exciting part of building AI agent systems. But it is the part that determines whether you can keep building them six months from now. The teams that invest in a gateway, virtual keys, structured observability, and stateless server design early end up with a platform that other teams want to build on. The teams that skip it end up with a mess that consumes more engineering time to manage than it saves through automation.

The patterns are well understood. The 2026 specification provides the right primitives. The only variable is whether your organization treats MCP infrastructure with the same seriousness it gives to APIs, databases, and message queues. If it does, 100 servers is not chaos. It is a managed fleet.

Subscribe to Marvin G6 | Engineering, AI & Automation

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe