Skip to main content
The mcp-use TypeScript SDK allows you to easily proxy and compose multiple MCP servers into a single unified “Aggregator” server. This is extremely useful when you have multiple microservices or specialized MCP servers (like a database server, a weather server, and an internal API server) and you want to expose all of their tools, resources, and prompts through a single unified endpoint.

High-Level API (Config Object)

The most elegant way to proxy servers is to pass a configuration object directly to server.proxy(). The keys of the object act as the namespaces for the child servers, preventing any naming collisions.
import { MCPServer } from "mcp-use/server";
import path from "node:path";

const server = new MCPServer({ name: "UnifiedServer", version: "1.0.0" });

// The SDK handles all connections, sessions, and synchronization automatically
await server.proxy({
  // Proxy a local TypeScript server (using the 'tsx' runner)
  database: {
    command: "tsx",
    args: [path.resolve(__dirname, "./db-server.ts")]
  },
  
  // Proxy a local Python FastMCP server
  weather: {
    command: "uv",
    args: ["run", "weather_server.py"],
    env: { ...process.env, FASTMCP_LOG_LEVEL: "ERROR" }
  },
  
  // Proxy a remote server over HTTP
  manufact: {
    url: "https://manufact.com/docs/mcp"
  }
});

// Expose our own tools alongside the proxied ones
server.tool({
  name: "aggregator_status",
  description: "Check aggregator status",
}, async () => ({ content: [{ type: "text", text: "All systems operational" }] }));

// Start the unified server
await server.listen(3000);
In the example above, the database tools will be prefixed with database_ (e.g. database_query), the weather tools will be prefixed with weather_ (e.g. weather_get_forecast), and so on.

Low-Level API (Explicit Session)

For advanced use cases—such as dynamically determining auth headers, managing complex connection lifecycles, or using custom connectors—you can inject an explicit MCPSession directly into the proxy method using the mcp-use Client SDK.
import { MCPServer } from "mcp-use/server";
import { MCPClient } from "mcp-use/client";

const server = new MCPServer({ name: "UnifiedServer", version: "1.0.0" });

// Create a custom client orchestration
const customClient = new MCPClient({
  mcpServers: {
    secure_db: {
      url: "https://secure-db.example.com/mcp"
    }
  }
});

// Manage the session manually
const dbSession = await customClient.createSession("secure_db");

// Proxy the active session, manually specifying the namespace
await server.proxy(dbSession, { namespace: "secure_db" });

await server.listen(3000);

Example Usage

Proxy Server Example

See a fully working example of server composition in the mcp-use repository.

How It Works

When you proxy a server, the mcp-use SDK automatically:
  1. Introspects the child server by calling listTools(), listResources(), and listPrompts().
  2. Translates the raw JSON Schemas returned by the child server into runtime Zod schemas for perfect compatibility with the mcp-use validation pipeline.
  3. Namespaces the component names to prevent collisions.
  4. Relays execution. When a client calls a proxied tool, the Aggregator intercepts it and forwards the execution to the child server.
  5. Synchronizes state. The Aggregator listens to notifications/tools/list_changed (and other list_changed events) emitted by the child server and instantly forwards them to all clients connected to the Aggregator.

Advanced Features

The mcp-use proxying system goes far beyond simple tool forwarding. It transparently supports the most advanced features of the Model Context Protocol:

LLM Sampling & Elicitation

If a child server requests LLM Sampling (sampling/createMessage) or Elicitation (elicitation/create), the Aggregator automatically intercepts the out-of-band JSONRPC request, dynamically resolves the HTTP context of the specific user who triggered the tool, and routes the request back to that specific user’s client. If a request context is somehow lost (e.g., executing a tool via a headless script), the Aggregator provides graceful fallbacks so the child server doesn’t hang.

Progress Tracking

If a child tool emits notifications/progress/report events, the Aggregator catches those events and pipes them directly through the unified ToolContext back to the parent client.

Resource URIs

To prevent URI collisions across different servers, the proxy automatically prepends the namespace to the resource URI protocol. For example, if the weather server exposes a resource at app://settings, the Aggregator will expose it as weather://app://settings. When a client reads that resource, the Aggregator strips the prefix and requests the original URI from the child server.