Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"updateInternalDependencies": "patch",
"ignore": [
"@modelcontextprotocol/examples-client",
"@modelcontextprotocol/examples-client-multi-server",
"@modelcontextprotocol/examples-client-quickstart",
"@modelcontextprotocol/examples-server",
"@modelcontextprotocol/examples-server-quickstart",
Expand Down
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pnpm-lock.yaml
# Ignore generated files
src/spec.types.ts

# Quickstart examples uses 2-space indent to match ecosystem conventions
# Standalone examples use 2-space indent to match ecosystem conventions
examples/client-multi-server/
examples/client-quickstart/
examples/server-quickstart/
1 change: 1 addition & 0 deletions examples/client-multi-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
33 changes: 33 additions & 0 deletions examples/client-multi-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Multi-Server Routing Example

Minimal example showing how to connect to multiple MCP servers and route tool calls to the correct one.

Spawns two in-repo servers (`server-quickstart` and `mcpServerOutputSchema`), discovers their tools, builds a prefixed routing table, and calls one tool on each server to demonstrate the routing.

No API key required. No external config file needed.

## Quick Start

```bash
# Install dependencies (from repo root)
pnpm install

# Run the example
npx tsx examples/client-multi-server/src/index.ts
```

## How It Works

1. Spawns each server as a child process via `StdioClientTransport`
2. Connects a `Client` to each and calls `listTools()` to discover available tools
3. Builds a routing map that prefixes each tool name with its server name (e.g. `weather-nws__get-alerts`) to avoid collisions
4. Calls one tool on each server to prove routing works
5. Cleans up all connections

## Adapting This Pattern

To route tool calls in your own multi-server setup:

- Prefix tool names with the server name when presenting them to an LLM
- When the LLM calls a prefixed tool, strip the prefix and forward to the correct server
- Check for collisions if multiple servers expose tools with the same name
17 changes: 17 additions & 0 deletions examples/client-multi-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@modelcontextprotocol/examples-client-multi-server",
"private": true,
"version": "2.0.0-alpha.0",
"type": "module",
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@modelcontextprotocol/client": "workspace:^"
},
"devDependencies": {
"@types/node": "^24.10.1",
"typescript": "catalog:devTools"
}
}
112 changes: 112 additions & 0 deletions examples/client-multi-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Minimal multi-server routing example.
*
* Spawns two in-repo MCP servers (server-quickstart and mcpServerOutputSchema),
* connects a Client to each, discovers their tools, and routes tool calls to
* the correct server based on which one registered the tool.
*
* Run: npx tsx examples/client-multi-server/src/index.ts
*/

import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

import { Client, StdioClientTransport } from '@modelcontextprotocol/client';

const __dirname = dirname(fileURLToPath(import.meta.url));

// Each server entry: a name and the path to its stdio entrypoint.
const servers = [
{
name: 'weather-nws',
script: resolve(__dirname, '../../server-quickstart/src/index.ts'),
},
{
name: 'weather-structured',
script: resolve(__dirname, '../../server/src/mcpServerOutputSchema.ts'),
},
];

// Maps prefixed tool name -> { client, originalName, serverName }
const toolRouter = new Map<
string,
{ client: Client; originalName: string; serverName: string }
>();

async function main() {
const clients: Client[] = [];

// 1. Connect to each server and discover tools
for (const { name, script } of servers) {
const transport = new StdioClientTransport({
command: process.execPath,
args: ['--import', 'tsx', script],
});
const client = new Client({ name: `router-${name}`, version: '1.0.0' });
await client.connect(transport);
clients.push(client);

const { tools } = await client.listTools();
for (const tool of tools) {
const prefixed = `${name}__${tool.name}`;
toolRouter.set(prefixed, {
client,
originalName: tool.name,
serverName: name,
});
}

console.log(`[${name}] connected, tools: ${tools.map((t) => t.name).join(', ')}`);
}

console.log(`\nRouting table (${toolRouter.size} tools):`);
for (const [prefixed, { serverName, originalName }] of toolRouter) {
console.log(` ${prefixed} -> ${serverName} (${originalName})`);
}

// 2. Demonstrate routing: call one tool from each server
console.log('\n--- Routing demo ---\n');

// Call get-alerts from weather-nws (server-quickstart)
const alertsKey = 'weather-nws__get-alerts';
const alertsRoute = toolRouter.get(alertsKey);
if (alertsRoute) {
console.log(`Calling ${alertsKey} ...`);
const result = await alertsRoute.client.callTool({
name: alertsRoute.originalName,
arguments: { state: 'CA' },
});
const text = result.content
.filter((c) => c.type === 'text')
.map((c) => c.text)
.join('\n');
console.log(`Result: ${text.slice(0, 200)}...\n`);
}

// Call get_weather from weather-structured (mcpServerOutputSchema)
const weatherKey = 'weather-structured__get_weather';
const weatherRoute = toolRouter.get(weatherKey);
if (weatherRoute) {
console.log(`Calling ${weatherKey} ...`);
const result = await weatherRoute.client.callTool({
name: weatherRoute.originalName,
arguments: { city: 'Seattle', country: 'US' },
});
const text = result.content
.filter((c) => c.type === 'text')
.map((c) => c.text)
.join('\n');
console.log(`Result: ${text.slice(0, 200)}...\n`);
}

// 3. Cleanup
for (const client of clients) {
await client.close();
}
console.log('All servers disconnected.');
}

main().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});
23 changes: 23 additions & 0 deletions examples/client-multi-server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2023",
"lib": ["ES2023"],
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"],
"@modelcontextprotocol/client/_shims": ["./node_modules/@modelcontextprotocol/client/src/shimsNode.ts"],
"@modelcontextprotocol/core": [
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/index.ts"
]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading