You started with one MCP server. It handled your CRM lookups and your knowledge base searches. Everything was fine.
Then the billing team needed their own server. Then scheduling. Then the returns workflow. Then compliance wanted an audit log server. Now you have eight MCP servers spread across four codebases, each agent team has its own hardcoded server addresses, three of those addresses are pointing at staging environments in production, and onboarding a new agent requires a week of Slack archaeology.
This is the MCP fleet problem, and it's where most growing agent teams are right now.
The answer is a central registry: a single source of truth for what servers exist, who owns them, how to reach them, and who's allowed to use them. Pinterest built one and scaled to 66,000 tool invocations per month across 30+ domain teams. Cloudflare published a reference architecture for it. Here's how the pattern works and how to build one.
Why the fleet problem appears at three servers
Three servers is where most teams hit their first MCP management wall. The reason is simple: hardcoded addresses don't survive updates, there's no way to ask "which agents use this server?" without a codebase search, and access control becomes coarse when every agent either has full access or none.
A single MCP server is easy to manage. Its address is in your config. Every agent that needs it connects directly. When it breaks, you fix it.
At three servers, the cracks appear. Different agents need different subsets. Some teams don't know about servers other teams built. Addresses change when servers are updated or redeployed, and every agent that hardcoded the old address breaks silently. There's no way to ask "which agents are currently connected to the billing server?" without reading every agent's config.
At eight servers, you have a dependency graph problem. You can't safely update or deprecate a server without knowing who depends on it. You can't give a new agent team a list of available tools without walking them through each team's documentation. You can't audit what tools a given agent can reach without reading its configuration files.
The registry pattern solves this by inverting the dependency direction. Instead of agents knowing about servers, the registry knows about servers and agents ask it. When the billing server's address changes, you update the registry once. Every agent that queries the registry for billing tools gets the new address automatically.
What a central registry actually stores
A minimal registry stores five things per server: the server ID, its endpoint address, its owner (team and contact), the list of tools it exposes, and the authorization rules that control access.
The authorization rules are the part most teams get wrong when building a first registry. It's tempting to make authorization binary: a server is either public (all agents can connect) or private (only specific agents). Pinterest's architecture uses a more useful model: authorization is per-tool, per-agent-identity, and includes business-group gating so tools can be restricted to agents running in a specific team's context.
For a CX fleet, a practical authorization model looks like this:
// server-manifest.json schema
interface MCPServerEntry {
id: string; // 'crm-server-v2'
endpoint: string; // 'https://mcp.internal/crm'
owner: {
team: string; // 'cx-platform'
contact: string; // 'cx-platform@company.com'
};
tools: MCPToolEntry[];
auth: {
mode: 'jwt' | 'mesh' | 'both';
requiredScopes?: string[]; // ['crm:read'] for read-only agents
businessGroups?: string[]; // Restrict to specific teams
};
healthCheck: string; // endpoint to ping for live status
version: string; // semver for change tracking
}The registry doesn't need to be complex to be valuable. Even a JSON file in version control, served over HTTP, with a discovery endpoint that filters by agent identity eliminates the hardcoded-address problem across your entire fleet.
The Pinterest architecture: domain servers and a central registry
Pinterest's MCP ecosystem is the most detailed public case study of this pattern at scale. The key architectural decisions they made are worth examining directly.
Domain-specific servers, not one large server. Pinterest runs a separate MCP server per engineering domain, one for Presto (their SQL engine), one for Spark, one for Airflow (workflow orchestration), and others for internal tools. Each server exposes 8-15 tools, all within a single domain. This limits context bloat, isolates failures, and allows fine-grained access control per domain.
Context bloat is worth quantifying. Each tool definition in an MCP server consumes roughly 200-400 tokens in the agent's context window. A monolithic server with 40 tools consumes 12,000-16,000 tokens before the agent processes its first message. Three domain-specific servers with 15 tools each, where the agent connects only to the servers it needs, costs 3,000-6,000 tokens. For a voice agent where every token adds latency, that's a significant difference.
A registry as the source of truth. The registry serves two interfaces: a web UI for human developers to browse available servers and check their status, and a discovery API for agent clients to programmatically query authorized servers. The API returns endpoint addresses, connection parameters, and live health status. Agents call the discovery API at initialization instead of using hardcoded addresses.
A unified deployment pipeline. Domain teams define their tools and the platform handles deployment, scaling, and infrastructure. What had been a multi-day process (spinning up a new MCP server, configuring network access, registering it, setting up health checks) became something a domain engineer can complete in a day. This is why the ecosystem grew: lower publishing cost means more teams contribute.
Two-layer authorization. Interactive sessions use end-user JWTs so tools can enforce user-level permissions. Automated service-to-service flows use mesh identities that authenticate the agent service itself. Individual tools implement fine-grained authorization decorators, and business-group gating restricts tool access based on team membership. No tool is "public" by default.
The result: 66,000 invocations per month, an estimated 7,000 developer hours saved monthly, and a security model that can answer the audit question "which agents have access to which tools" in seconds.
Building a discovery API
The discovery API is the core of the registry. Here's a minimal implementation that covers the essential behavior.
import express from 'express';
import { readFileSync } from 'fs';
interface AgentIdentity {
agentId: string;
team: string;
scopes: string[];
}
function loadManifest(): MCPServerEntry[] {
return JSON.parse(readFileSync('./server-manifest.json', 'utf-8'));
}
function isAuthorized(server: MCPServerEntry, agent: AgentIdentity): boolean {
const auth = server.auth;
// Check required scopes
if (auth.requiredScopes?.length) {
const hasScopes = auth.requiredScopes.every(s => agent.scopes.includes(s));
if (!hasScopes) return false;
}
// Check business group gating
if (auth.businessGroups?.length) {
if (!auth.businessGroups.includes(agent.team)) return false;
}
return true;
}
async function checkHealth(server: MCPServerEntry): Promise<boolean> {
try {
const res = await fetch(server.healthCheck, {
signal: AbortSignal.timeout(2000)
});
return res.ok;
} catch {
return false;
}
}
const app = express();
app.get('/discover', async (req, res) => {
const agentIdentity = await verifyAgentToken(req.headers.authorization);
if (!agentIdentity) return res.status(401).json({ error: 'Unauthorized' });
const manifest = loadManifest();
const authorized = manifest.filter(server =>
isAuthorized(server, agentIdentity)
);
// Check live health for authorized servers
const withHealth = await Promise.all(
authorized.map(async (server) => ({
id: server.id,
endpoint: server.endpoint,
tools: server.tools.map(t => t.name), // Just names, not full schemas
healthy: await checkHealth(server),
version: server.version,
}))
);
res.json({ servers: withHealth });
});
app.listen(3100);This is intentionally simple. The discovery endpoint returns only the server IDs, endpoints, tool names, health status, and versions for servers the agent is authorized to use. It doesn't return full tool schemas; agents fetch those by connecting to the individual servers. This keeps the discovery response lean, usually under 1,000 tokens even for large fleets.
The deployment pipeline: making it easy to publish servers
The registry is only valuable if it stays current. That requires making it easy for teams to register new servers and update existing ones.
A practical pipeline has three stages:
-
Tool definition. Teams write their MCP server code and define the
server-manifest.jsonentry for their server (ID, endpoint, tools, auth requirements, health check endpoint). This is a pull request to the manifest repo. -
Automated validation. On PR creation, a CI check verifies the manifest entry: does the health check endpoint exist? Do the declared tools match the server's actual tool list? Are the authorization rules consistent with your organization's policy (no "public" tools without review)?
-
Registry update. On merge, the manifest is redeployed. The discovery API picks up the new entry immediately. No agent code changes are required.
name: Validate MCP Registry Entry
on:
pull_request:
paths:
- 'server-manifest.json'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate manifest schema
run: npx tsx scripts/validate-manifest.ts
- name: Check health endpoints reachable
run: npx tsx scripts/check-health-endpoints.ts
- name: Verify tool list consistency
run: npx tsx scripts/verify-tool-declarations.ts
- name: Auth policy lint
run: npx tsx scripts/lint-auth-policies.tsThe validation step for tool list consistency is worth implementing even if the others seem optional. If a team declares that their server exposes a getCustomerProfile tool but the actual MCP server code only implements getAccountInfo, agents that query the registry for the declared tool will fail silently when they try to use it.
The authorization layer: human vs service identity
Interactive and automated agent flows need different authentication credentials, and a registry that treats them the same creates either over-privileged service accounts or blocked human sessions. The two-layer model is the fix.
Interactive flows happen when a human developer is actively working with an agent. The agent might be a coding assistant querying an internal database, or an analyst using a conversational interface to generate reports. In these cases, end-user JWTs carry the human's organizational identity. Tools can make per-user authorization decisions: "this user is on the billing team, so they can access billing tools."
Automated flows happen when agents run without human presence. Nightly reconciliation agents, background monitoring agents, and scheduled data pipeline agents all operate without a human in the session. These agents authenticate using mesh identities (service accounts with verifiable identities in your service mesh). Tools see the service identity, not a user identity, and apply service-level authorization.
// Tool-level authorization decorator
function requiresAuth(options: {
userScopes?: string[];
serviceScopes?: string[];
businessGroups?: string[];
}) {
return function(target: MCPTool, context: ClassMethodDecoratorContext) {
const original = target.execute;
target.execute = async function(params, authContext) {
if (authContext.type === 'user') {
const missingScopes = options.userScopes?.filter(
s => !authContext.userScopes.includes(s)
);
if (missingScopes?.length) {
throw new MCPAuthError(`Missing user scopes: ${missingScopes.join(', ')}`);
}
if (options.businessGroups?.length) {
if (!options.businessGroups.includes(authContext.userTeam)) {
throw new MCPAuthError('Tool restricted to specific business groups');
}
}
} else if (authContext.type === 'service') {
const missingScopes = options.serviceScopes?.filter(
s => !authContext.serviceScopes.includes(s)
);
if (missingScopes?.length) {
throw new MCPAuthError(`Missing service scopes: ${missingScopes.join(', ')}`);
}
}
return original.call(this, params, authContext);
};
};
}
// Usage
class BillingMCPServer extends MCPServer {
@requiresAuth({
userScopes: ['billing:read'],
serviceScopes: ['billing-service:read'],
businessGroups: ['billing', 'cx-platform'],
})
async getRefundHistory(params: GetRefundParams) {
// Tool implementation
}
}This pattern keeps authorization logic co-located with the tool definition, rather than in a central policy file that drifts out of sync. When a tool's authorization requirements change, the change is in the same file as the tool code.
See MCP auth and multi-tenant production agents for a deeper treatment of the authentication flow at the protocol level, including token relay patterns for Streamable HTTP transport.
The operational view: what your registry should expose
Your registry needs three operational views beyond discovery: which servers are healthy right now, which tools agents are actually calling, and which agents haven't made a tool call recently. Without these, the registry is a static catalog that drifts from reality.
A registry that only handles discovery is half the picture.
The metrics endpoint (the last two steps in the diagram) is the part teams build last but wish they'd built first. Once you have invocation logs flowing into the registry, you can answer questions that are otherwise unanswerable:
- Which tools are your agents actually using vs which tools are declared in the manifest?
- Which servers are called most frequently (capacity planning)?
- Which tool calls are failing, and at what rate?
- Which agents haven't made a tool call in the last 30 days (stale agents that may still be in production)?
For MCP tool monitoring in production, the registry's invocation log becomes the foundation of your observability stack. You can also surface per-tool call metrics directly through Chanl's tools dashboard, which aggregates invocation data across your MCP fleet alongside non-MCP tool integrations.
How the registry and gateway work together
The registry and gateway solve different problems and both belong in a mature fleet.
The registry answers: what servers exist, who can use them, and are they healthy?
The gateway answers: is this specific tool call authorized for this specific user at this moment, should I rate-limit it, and what should the audit log entry look like?
In practice, the flow is: agent queries registry for authorized servers, agent connects to the target server through the gateway, gateway verifies the agent's identity against the registry's authorization model and enforces per-request policies, tool call executes, gateway writes the audit log entry.
Neither component is redundant. The registry handles fleet-level metadata and discovery. The gateway handles request-level enforcement and logging. Running one without the other leaves gaps.
Start small, expand incrementally
The registry pattern scales from a JSON file to a full fleet management system. You don't need Pinterest's architecture on day one.
Start with a manifest file in version control, a simple discovery endpoint, and the habit of never hardcoding an MCP server address in agent code. That alone eliminates the most painful failure modes: stale addresses, unknown dependencies, and the "which agents use this server?" question that currently requires a full codebase search.
Add health checks when you've been burned by a server being silently unhealthy. Add authorization layers when different agents need different access levels. Add the deployment pipeline when adding a new server takes longer than a day.
The teams that go further, like Pinterest did, report that the registry's main payoff isn't the discovery mechanism itself. It's that a shared registry changes the team dynamic: domain teams start publishing tools proactively because they know agents will find them, and agent teams build faster because they can browse available tools instead of asking around.
Onboarding a new agent to your tool fleet goes from a week of Slack archaeology to an afternoon of registry queries. That's the second-order effect worth building toward.
Connect and monitor your MCP tools in one place
Chanl's MCP integration gives your CX agents access to tools across any MCP server, with per-tool call logging and connection health monitoring built in.
Explore MCP integrationsCo-founder
Building the platform for AI agents at Chanl — tools, testing, and observability for customer experience.
Learn Agentic AI
Weekly. Patterns for shipping agents that work. MCP, scorecards, regression tests, prompts, model comparisons.



