How MetaMCP Aggregates Multiple MCP Servers into a Single Unified Instance
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. The getMcpServers() function (lines 18‑30) queries the database to retrieve all active MCP servers belonging to a specific namespace.
// 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. The McpServerPool.getSession() method (lines 70‑94) implements a three-tier resolution strategy:
- Return an already-active client for the current session if it exists.
- Convert an idle (pre-warmed) client to active status.
- Create a fresh connection only when necessary, respecting maximum connection limits.
// 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. 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.
// 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 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):
// 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 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:
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:
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:
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.tsretrieves 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__toolformat. - 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 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 automatically excludes servers with an error_status of "ERROR" from the aggregation. Additionally, the connection pool in 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →