# How MetaMCP Aggregates Multiple MCP Servers into a Single Unified Instance

> Discover how MetaMCP unifies multiple MCP servers into one instance by pooling and namespacing tools through a single endpoint for efficient access and persistent connections.

- Repository: [metatool-ai/metamcp](https://github.com/metatool-ai/metamcp)
- Tags: how-to-guide
- Published: 2026-03-07

---

**MetaMCP implements a virtual MCP server that discovers, pools, and namespaces tools from every server in a namespace, exposing them through a single unified endpoint while maintaining persistent connections for optimal performance.**

MetaMCP is an open-source aggregation layer for the Model Context Protocol (MCP) that transforms collections of disparate MCP servers into one cohesive interface. By implementing a three-layer architecture spanning discovery, connection pooling, and tool namespacing, the `metatool-ai/metamcp` repository enables clients to aggregate multiple MCP servers into a single unified instance without managing individual server connections manually.

## The Three-Layer Aggregation Architecture

MetaMCP aggregates multiple MCP servers through three distinct operational layers: namespace cataloguing, connection pooling, and tool discovery with namespacing. Each layer corresponds to specific source files in the backend implementation.

### Namespace Discovery and Server Cataloguing

The aggregation process begins in [`apps/backend/src/lib/metamcp/fetch-metamcp.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/lib/metamcp/fetch-metamcp.ts). The `getMcpServers()` function (lines 18‑30) queries the database to retrieve all active MCP servers belonging to a specific namespace.

```typescript
// apps/backend/src/lib/metamcp/fetch-metamcp.ts
export async function getMcpServers(
  namespaceUuid: string,
  includeInactiveServers = false,
): Promise<Record<string, ServerParameters>> {
  // SQL join returns only ACTIVE servers unless includeInactiveServers is true
  // Always excludes servers with error_status == "ERROR"
}

```

This function returns a dictionary mapping server UUIDs to their connection parameters, filtering out errored servers and optionally including inactive ones based on the `includeInactiveServers` flag.

### Connection Pooling with Session Reuse

Rather than spawning new processes for every request, MetaMCP maintains an intelligent connection pool in [`apps/backend/src/lib/metamcp/mcp-server-pool.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/lib/metamcp/mcp-server-pool.ts). The `McpServerPool.getSession()` method (lines 70‑94) implements a three-tier resolution strategy:

1. Return an already-active client for the current session if it exists.
2. Convert an idle (pre-warmed) client to active status.
3. Create a fresh connection only when necessary, respecting maximum connection limits.

```typescript
// apps/backend/src/lib/metamcp/mcp-server-pool.ts
async getSession(
  sessionId: string,
  serverUuid: string,
  params: ServerParameters,
  namespaceUuid?: string,
): Promise<ConnectedClient | undefined> {
  // 1. Check for existing active session
  // 2. Promote idle session to active
  // 3. Establish new connection if needed
}

```

The pool maintains **idle** instances created at application startup, ensuring subsequent MetaMCP sessions connect instantly without the overhead of process spawning.

### Tool Namespacing and Conflict Resolution

The core aggregation logic resides in [`apps/backend/src/lib/metamcp/metamcp-proxy.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/lib/metamcp/metamcp-proxy.ts). The `originalListToolsHandler` function (lines 44‑122) iterates over the server catalogue, paginates through each backend's `tools/list` endpoint, and applies a sanitization prefix to prevent naming collisions.

```typescript
// apps/backend/src/lib/metamcp/metamcp-proxy.ts
const toolsWithSource = serverTools.map(tool => ({
  ...tool,
  name: `${sanitizeName(actualServerName)}__${tool.name}`,
}));

```

This produces tool names like `my-stdio-server__list_files` and `my-sse-server__search`, making the origin server immediately identifiable. The handler also implements **self-referencing protection** by checking if `actualServerName === 'metamcp-unified-${namespaceUuid}'` to prevent infinite recursion, and applies `filterOutOverrideTools` (lines 140‑170) to remove duplicate entries.

## Unified Server Creation and Request Handling

The factory function `createServer()` in [`apps/backend/src/lib/metamcp/metamcp-proxy.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/lib/metamcp/metamcp-proxy.ts) instantiates the unified MCP server with custom middleware handlers.

### The createServer Factory

When a client connects to the MetaMCP endpoint, the system generates a unique session ID and invokes `createServer(namespaceUuid, sessionId, includeInactiveServers)`:

```typescript
// apps/backend/src/lib/metamcp/metamcp-proxy.ts
export const createServer = async (
  namespaceUuid: string,
  sessionId: string,
  includeInactiveServers = false,
) => {
  const server = new Server(
    { name: `metamcp-unified-${namespaceUuid}`, version: "1.0.0" },
    { capabilities: { tools: {}, prompts: {}, resources: {} } },
  );

  server.addHandler("tools/list", compose(
    createFilterListToolsMiddleware(...),
    createToolOverridesListToolsMiddleware(...),
    originalListToolsHandler,
  ));
  // ...
};

```

The Express router in [`apps/backend/src/routers/mcp-proxy/metamcp.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/routers/mcp-proxy/metamcp.ts) exposes this unified server via HTTP and SSE transports at the `/mcp` and `/sse` endpoints.

### Handling Tool Calls

For `tools/call` requests, MetaMCP parses the prefixed tool name to identify the target backend server. The system extracts the server identifier from the `server__tool` format, locates the appropriate client connection from the pool, and forwards the request to the underlying MCP server transparently.

## Practical Implementation Examples

### Creating a MetaMCP Instance

To initialize a unified server for a specific namespace:

```typescript
import { createServer } from "@/lib/metamcp";

const { server, cleanup } = await createServer(
  namespaceUuid,    // Target namespace UUID
  sessionId,        // Unique per client connection
  true,             // Include inactive servers
);

// Wire to transport (Streamable-HTTP or SSE)
await server.connect(transport);

```

### Listing Aggregated Tools

Clients receive the merged tool catalogue with prefixed identifiers:

```typescript
const result = await client.request(
  { method: "tools/list" },
  ListToolsResultSchema,
);

console.log(result.tools?.map(t => t.name));
// Output: ["my-stdio-server__list_files", "my-sse-server__search"]

```

### Calling Namespaced Tools

Invoke a specific tool using its prefixed name:

```typescript
const callResult = await client.request(
  {
    method: "tools/call",
    params: {
      name: "my-sse-server__search",
      arguments: { query: "open-source AI" }
    },
  },
  CallToolResultSchema,
);

```

## Summary

- **MetaMCP** acts as a virtual MCP server that transparently aggregates multiple backend MCP servers into a single unified instance.
- The **namespace-based discovery** layer in [`fetch-metamcp.ts`](https://github.com/metatool-ai/metamcp/blob/main/fetch-metamcp.ts) retrieves active server parameters from the database while filtering out errored connections.
- **Connection pooling** via `McpServerPool.getSession()` reuses idle processes and maintains active sessions to eliminate connection overhead.
- **Tool namespacing** prevents collisions by prefixing each tool name with its sanitized source server identifier using the `server__tool` format.
- **Self-referencing protection** blocks recursive inclusion of MetaMCP's own tools, while override filtering eliminates duplicate entries.
- The **unified server factory** `createServer()` registers custom handlers that orchestrate discovery, pagination, and request forwarding across the entire server catalogue.

## Frequently Asked Questions

### How does MetaMCP prevent tool name collisions between different servers?

MetaMCP sanitizes each source server's name and prepends it to the tool name using a double-underscore delimiter (`server__tool`). This namespacing occurs in [`apps/backend/src/lib/metamcp/metamcp-proxy.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/lib/metamcp/metamcp-proxy.ts) within the `originalListToolsHandler` function (lines 140‑170), ensuring every tool has a unique identifier across the unified instance.

### What happens if an underlying MCP server disconnects or errors?

The `getMcpServers()` function in [`apps/backend/src/lib/metamcp/fetch-metamcp.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/lib/metamcp/fetch-metamcp.ts) automatically excludes servers with an `error_status` of "ERROR" from the aggregation. Additionally, the connection pool in [`mcp-server-pool.ts`](https://github.com/metatool-ai/metamcp/blob/main/mcp-server-pool.ts) handles reconnection logic, promoting idle sessions or creating fresh connections as needed to maintain service availability.

### Can MetaMCP aggregate servers using different transport protocols?

Yes. MetaMCP supports aggregating STDIO, SSE, and Streamable-HTTP MCP servers simultaneously. The `ServerParameters` interface abstracts transport-specific configuration, and the connection pool manages each type appropriately, whether spawning local processes for STDIO servers or maintaining HTTP connections for remote SSE endpoints.

### How does MetaMCP avoid infinite recursion when aggregating?

The system implements self-referencing protection in the tool discovery handler. Before adding tools from a backend server, MetaMCP checks if the server's reported name matches `metamcp-unified-${namespaceUuid}`. If detected, the server skips that backend to prevent a MetaMCP instance from aggregating itself, which would create an infinite loop.