Enterprise MCP Aggregation: Postgres, Figma & Playwright

9 min readYaseen Khatib · MERN + AI Architect

Your production agent needs to query Postgres, read a Figma design, and drive a Playwright browser — so you wired in three MCP servers (Lesson 5) and shipped it. Now every request drags three servers' worth of tool schemas through the context window, three sets of credentials live in three configs, and a teammate's agent re-does the same wiring with subtly different auth. Connecting MCP servers is easy; governing a fleet of them is the enterprise problem.

Core Architectural Concepts & Trade-offs

The pattern is an MCP aggregation gateway: a single MCP server that the agent connects to, which itself fans out to many backend MCP servers. The agent sees one governed surface; the gateway owns the connections to Postgres, Figma, Playwright, and the rest. This is the same N+M collapse MCP gave you for integrations (Lesson 5), now applied to the servers themselves — one connection point, one auth boundary, one place to enforce policy, instead of every agent managing the full mesh.

Aggregation forces namespacing, because tool names collide. Postgres and Playwright might both expose query; the gateway prefixes them — postgres__query, browser__query — so the agent can address the right one unambiguously. The gateway also becomes the natural auth broker: it holds the scoped credentials for each backend, and the agent never sees a database password or a Figma token. Credentials live in one audited place with least privilege per backend, not scattered across every agent's environment.

The dominant cost is context, and the gateway is where you control it. Every backend tool schema the gateway exposes is re-sent on every turn (Lesson 1), so naively aggregating ten servers can put a hundred tool definitions in the window — token bloat and worse tool selection, because the model is choosing from noise. The gateway should filter and lazily expose: present only the tools an agent's role needs, and cache the aggregated schema as a stable prefix (Lesson 9) so the whole toolset bills at cache rates. Governance and cost control are the same knob.

The trade-off is a new hop and a single point of failure. The gateway adds latency and an availability dependency — if it's down, every agent loses every tool — so it needs health checks, per-backend timeouts, and graceful degradation when one backend is unreachable (a failing Figma server shouldn't take down database access). You're trading the sprawl of an unmanaged mesh for the discipline of a managed chokepoint; that's the right trade at scale, but only if the chokepoint is built to stay up.

An Aggregation Gateway

One gateway server, three namespaced backends, role-filtered tools, and per-backend scoped credentials the agent never sees.

mcp-gateway.ts
// One surface to the agent; the gateway owns the fan-out, auth, and policy.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { connectBackend } from "./backends"

// Each backend: its own scoped credentials, held only here.
const BACKENDS = {
  postgres: { url: "stdio://pg-mcp",   env: { DATABASE_URL: secrets.PG_RO } },
  figma:    { url: "https://figma-mcp", auth: secrets.FIGMA_TOKEN },
  browser:  { url: "stdio://playwright-mcp" },
} as const

// Role → which namespaced tools are exposed. Least privilege per agent.
const ROLE_TOOLS: Record<string, string[]> = {
  analyst:  ["postgres__query", "figma__get_file"],
  qa:       ["browser__navigate", "browser__click", "postgres__query"],
}

export async function buildGateway(role: string) {
  const gateway = new McpServer({ name: "gateway", version: "1.0.0" })
  const allowed = new Set(ROLE_TOOLS[role] ?? [])

  for (const [ns, cfg] of Object.entries(BACKENDS)) {
    const backend = await connectBackend(cfg)        // auth brokered here
    for (const tool of await backend.listTools()) {
      const name = `${ns}__${tool.name}`            // namespace to avoid collisions
      if (!allowed.has(name)) continue                // filter → small context
      gateway.tool(name, tool.description, tool.schema, (args) =>
        backend.callTool(tool.name, args),            // proxy through
      )
    }
  }
  return gateway   // exposes only this role's tools, as a cacheable prefix
}

The agent connects to one server and sees a handful of role-scoped, namespaced tools. The database password, the Figma token, and the full backend mesh stay behind the gateway.

One Governed Surface

agentrole: analystMCP gatewaynamespace · filter by roleauth broker · cache schemahealth · timeouts · degradepostgres__*scoped: PG_ROfigma__*scoped: FIGMA_TOKENbrowser__*Playwright
The agent sees one role-scoped, namespaced surface. The gateway owns auth, filtering, and the backend mesh.

A governed tool surface is only trustworthy if you can prove the agent behaves. The next lesson makes that measurable: eval-driven prompt engineering with golden datasets. Or browse the full roadmap.

Looking to architect a similar system?

Let's ship it at AI-speed.

Start a conversation →