Skip to main content

MCP server sprawl: consolidate your team-built MCP servers behind one governed gateway

MCP server sprawl leaves every team's AI agent with its own credentials, scope and logs. Here's how to fold them into one governed Gateway MCP endpoint.

  • mcp
  • ai-agents
  • governance
  • api-gateway
  • audit
Zerq team

MCP server sprawl rarely starts as a decision. The support team wraps the ticketing API in a small MCP server so their Claude workspace can look up cases. The payments team writes another one for refund status. Someone in data engineering publishes a third that queries the customer warehouse, and a platform engineer adds a fourth for internal service health. Six months later a security review asks a simple question, "which AI agents can reach which internal APIs, and where is the record of what they did?", and nobody can answer it from one place.

Every one of those servers is a small API gateway that nobody calls a gateway. Each holds a backend credential, decides which operations it exposes, has its own logging (or none), and runs on whatever host its author chose. We see this pattern in almost every enterprise that took MCP seriously in the last year. The protocol worked exactly as intended: it made connecting an agent to an API easy. It was never meant to decide who may connect, at what rate, or with what audit trail. MCP leaves those questions to the layers around it.

This post covers why MCP server sprawl happens, why the usual fixes don't hold, and how to consolidate team-built servers into a single governed Gateway MCP endpoint in Zerq without asking any team to give up the agent workflows they already depend on.

Why MCP server sprawl is an API governance problem, not a tooling problem

The instinct is to treat sprawl as an inventory problem: build a spreadsheet, or deploy an MCP registry that lists approved servers. A registry helps people find servers. It does not change what each server does at runtime. If the refund-status server holds a payments API key with write scope, listing it in a registry does nothing to stop an agent from calling a write operation through it.

Three things break architecturally when every team runs its own MCP server.

First, credentials multiply and drift. Each server needs a backend credential, so the payments API now has one key for the mobile app, one for the partner channel, and one sitting in a config file on the MCP host. Rotation schedules, IP allowlists and scope reviews that apply to the first two rarely reach the third.

Second, policy is duplicated in code. Rate limits, method restrictions and "read-only for this agent" rules get written inside each server, in whatever language its author preferred. When the payments team tightens limits at the gateway, the MCP server keeps calling the backend at its own pace.

Third, the audit trail splits. REST traffic lands in your gateway logs. MCP traffic lands in each server's stdout, if anywhere. An investigator who needs to reconstruct what an agent did on a given afternoon has to collect logs from several hosts with different formats, clocks and retention.

Why existing approaches don't close the gap

Most teams try one of three fixes. The first is a central "approved MCP servers" list with a review process. It slows new sprawl but leaves the existing servers, and their credentials, exactly where they were. The second is putting a generic reverse proxy in front of the MCP servers. That gives you a single hostname, but the proxy only sees opaque JSON-RPC posts, so it can't tell a read from a write, can't attribute a call to a client identity your API program recognises, and logs one line per request with no tool or endpoint context.

The third is adopting an MCP feature from an existing API platform. Kong, Apigee, Azure API Management and AWS have each shipped ways to put MCP in front of APIs, and some of them work well. The question to ask of any of them is narrow and architectural: does the MCP path use the same client identity, the same rate limit counters, and the same request log as the REST path to the same API? If the MCP surface is a separate product, a separate plugin chain or a separate deployment, you have moved the sprawl into your gateway vendor rather than removed it. You still have two credential paths and two places to look during an incident.

What actually removes sprawl is collapsing the per-team servers into the API layer you already govern, so that an MCP tool call is just another request from a known client, under a known profile, against a published endpoint.

How Zerq consolidates MCP servers into one gateway endpoint

Zerq exposes a single Gateway MCP endpoint on the same gateway that serves your REST traffic. The default path is /mcp (configurable with MCP_PATH), it speaks JSON-RPC over streamable HTTP, and it requires the same Authorization, X-Client-ID and X-Profile-ID headers as any other gateway request. There is no separate deployment and no second credential path. You can read more about the runtime model on the architecture page.

Instead of one MCP server per backend, every agent sees the same four tools:

  • list_collections returns the active collections the calling client is assigned, with a published_endpoints count for each.
  • list_endpoints returns every published endpoint across those collections (endpoint_id, collection_name, name, description).
  • endpoint_details returns the endpoint path, methods, path and query parameters, request and response examples, and JSON schemas.
  • execute_endpoint makes a real gateway request with the caller's own identity, so authentication, profile restrictions, rate limits, workflows and logging all apply.

The important property is that the tool list is fixed but the catalog behind it is scoped per client. Two agents calling list_endpoints against the same /mcp path get different answers, because the gateway resolves the client's assigned collections and returns only endpoints whose status is published. A draft proxy never appears to an agent, and a collection that isn't assigned to the client doesn't exist from the agent's point of view.

The request path after consolidation

Here is the data flow an engineer could draw on a whiteboard. An MCP client (Claude Desktop, Cursor, a custom agent, or Zerq Copilot in the developer portal) sends initialize to https://gateway.example.com/mcp with its bearer token, client ID and profile ID, and receives an Mcp-Session-Id header. Discovery calls (tools/list, then list_collections, list_endpoints, endpoint_details) are answered from the client's collection assignments. When the agent calls execute_endpoint, Zerq issues a normal gateway request to the collection's base path plus the endpoint path, carrying the caller's X-Client-ID, X-Profile-ID and Authorization header, plus X-Gateway-Source: mcp. That request passes through the same auth middleware, profile checks, policy rate limit and quota, optional workflow, and request logger as REST traffic, then reaches the backend with the gateway's stored backend credential. The backend sees one caller: the gateway.

Step by step: folding team-built MCP servers into Gateway MCP

The migration works one server at a time. Each team keeps working while you move them.

  1. Inventory what each server actually calls. For every team-built MCP server, list its tools and map each one to the backend operation it wraps (method and path). Note which tools write. This becomes the scope for that team's agent.

  2. Import the backend as a collection. In the management UI, open Collections and use Import from OpenAPI with the backend's OpenAPI 3.0 or 3.1 spec. Zerq creates one proxy per operation, carrying the request schema from the spec. Every imported proxy is created as draft, so nothing becomes visible to agents yet.

  3. Publish only the operations the old server exposed. Open each proxy that maps to a tool from step 1 and switch its status from Draft to Published. Operations the old server never exposed stay in draft. This is where you usually find that a server wrapped five read operations but held a credential that allowed thirty.

  4. Create a policy for agent traffic. Under Policies, create a policy with a rate limit and a quota sized for an agent loop rather than a human. The same policy object is what your REST clients use.

  5. Create one client per agent. Under Clients, choose Create Client, name it after the agent (for example support-claude), select only the collections it needs in API Collections, and attach the policy from step 4.

  6. Create the access profile. On the client's Access Profiles page, choose New Profile, then choose the authentication type (token, jwt, oidc or mtls). Under Allowed HTTP Methods, the form starts with every method selected; clear them and keep POST and GET. The profile check runs on the MCP transport request itself as well as on each execute_endpoint call, and MCP needs POST for JSON-RPC messages and GET for the event stream. A read-only agent stays read-only because its collections only publish read operations, which step 3 already decided.

  7. Repoint the MCP client. Replace the old server entry in the agent's MCP config with the Zerq Gateway MCP endpoint and the new client and profile IDs.

  8. Verify, then decommission. Run one allowed call, one call to an endpoint in an unassigned collection, and one write method against a read endpoint, confirm the results in the request log, then shut down the old MCP server and revoke its backend credential.

The policy from step 4, as the management API stores it:

{
  "name": "ai-agent-standard",                // shown when attaching the policy to a client
  "description": "Default limits for internal AI agents",
  "active": true,
  "rate_limit_interval": "1m",                 // window for the burst limit
  "rate_limit_requests": 120,                  // max requests per window for the client
  "quota_interval": "1d",                      // window for the sustained quota
  "quota_requests": 20000                      // ceiling that stops a runaway agent loop
}

The Claude Desktop entry from step 7 replaces the team's old local server:

{
  "mcpServers": {
    "zerq-gateway": {
      "url": "https://gateway.example.com/mcp",      // one endpoint for every team
      "transport": "streamableHttp",
      "headers": {
        "Authorization": "Bearer <GATEWAY_TOKEN>",   // profile credential, rotatable in Zerq
        "X-Client-ID": "<SUPPORT_CLAUDE_CLIENT_ID>", // decides which collections exist
        "X-Profile-ID": "<PROFILE_ID>"              // decides auth type and allowed methods
      }
    }
  }
}

Keep tokens out of git-tracked files and use separate client and profile identities for staging and production. The MCP docs cover Cursor and ChatGPT configs with the same three headers.

What the agent sends and receives

After consolidation the agent's discovery is uniform. A list_endpoints call from support-claude returns only what the support team published:

[
  {
    "endpoint_id": "6718f0c2a91e4b0012ab34c1",
    "collection_name": "Support Cases",
    "name": "Get case",
    "description": "Fetch a support case by ID"
  },
  {
    "endpoint_id": "6718f0c2a91e4b0012ab34c4",
    "collection_name": "Support Cases",
    "name": "List case events",
    "description": "Timeline of updates on a case"
  }
]

The agent then calls execute_endpoint with a method and the full gateway path:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "execute_endpoint",
    "arguments": {
      "method": "GET",
      "path": "/support/v1/cases/CS-48213",
      "query": { "include": "summary" }
    }
  }
}

The tool result wraps the gateway response as status_code, body and headers. Any status of 400 or above comes back with isError: true. An agent that tries POST against an endpoint published only for GET gets a 405 from the proxy method check, a clear refusal it can stop on rather than a silent failure.

One request log for agent and app traffic

Because execute_endpoint goes through the normal gateway path, every agent call creates the same request log entry as a REST call, stored in your own MongoDB. An abridged entry:

{
  "request_id": "b6c1e7f2-3d4a-4f6e-9a21-77c0d5e1a9b3", // joins the agent call to backend traces
  "method": "GET",
  "path": "/support/v1/cases/CS-48213",
  "status": 200,
  "latency": 84,                                        // milliseconds, end to end
  "client_id": "6718ee01a91e4b0012ab3301",              // which agent
  "profile_id": "6718ee01a91e4b0012ab3302",             // under which restrictions
  "collection": "6718ef77a91e4b0012ab3310",             // which API product
  "target_url": "https://cases.internal/v1/cases/CS-48213",
  "request_headers": {
    "X-Gateway-Source": "mcp"                           // marks the call as MCP-originated
  },
  "created_at": "2026-10-22T09:14:03Z"
}

For a compliance team this answers the questions the sprawl made unanswerable: which agent, under which profile, called which API product, with what outcome, and whether it came through MCP or a direct REST integration. The request log supports filtering by client_id, profile_id, collection, status, method, path and time range, so "everything support-claude did yesterday that returned 403" is one query. Configuration changes, such as publishing a proxy or widening a client's collections, land separately in the audit log with actor_id, resource_type and action, readable by users holding the audit role. The observability and security pages cover retention and SIEM export.

A decision checklist for consolidating MCP servers

Use these questions on each team-built server before you move it:

  • Does the server call only APIs that the gateway already fronts? If not, onboard that backend as a collection first.
  • Does any tool wrap more than one backend call? If yes, rebuild it as a workflow on a single proxy so the agent still sees one endpoint.
  • Does the server's backend credential allow more than its tools use? If yes, the gap is your immediate risk reduction.
  • Do write tools need a human in the loop? Put them in a separate collection assigned only to a dedicated client, or leave them unpublished.
  • Is anyone calling the server who isn't the team that built it? Give each caller its own client so the request log can tell them apart.
  • Will the platform team manage these collections through automation? Management MCP uses the same OIDC session and RBAC as the admin UI, so config changes made by an assistant are audited like any admin's.

What this looks like in practice

Consider a retail bank's operations group with six internal MCP servers built over two quarters: case lookup, card status, refund status, branch directory, service health and a warehouse query tool. Each held its own backend key. Two of the keys had write scope that no tool used. The warehouse tool was being used by three teams, all appearing in its logs as the same service account.

The platform team imported the four backend specs as collections, published nineteen of the roughly ninety imported operations, and rebuilt the warehouse tool as a workflow that ran a fixed set of read queries. They created eight clients, one per agent and per consuming team, and attached a single agent policy. Every agent-facing collection published read operations only, except a small refund-actions collection assigned to one refund-ops client. Each team changed one block in its MCP config. The six servers were shut down over three weeks and their keys revoked.

What changed was mostly invisible to the agent users and very visible to risk. There is now one place to answer the access question, one rate limit applied to agent loops, one credential path per backend, and one request log in which MCP calls are tagged and attributable to a specific client. When the next team asks for an agent, the answer is a client and a profile, not another server.

Summary

Zerq turns MCP server sprawl back into ordinary API governance. One Gateway MCP endpoint replaces per-team servers without changing how agents discover and call tools. Each agent gets its own client, collections and profile, so scope is enforced at the gateway rather than in each server's code. Backend credentials live in one place instead of on every MCP host. Agent calls share rate limits, quotas and workflows with REST traffic. Every call lands in the same request log, in your own infrastructure, marked as MCP-originated.


Zerq is an enterprise API gateway built for regulated industries — one platform for API management, AI agent access, compliance audit, and developer portal, running entirely in your own infrastructure. See how it works or request a demo to walk through your specific requirements.