The MCPClientManager is your gateway to MCP servers. It handles connections, manages multiple servers, and provides a unified interface for calling tools, reading resources, and accessing prompts.
When to Use MCPClientManager
Use MCPClientManager when you need to:
- Connect to one or more MCP servers
- Execute tools programmatically (without an LLM)
- Build applications that aggregate tools from multiple servers
- Set up test environments for your MCP server
Two Types of Servers
MCP servers come in two transport types:
STDIO Servers (Local)
STDIO servers run as subprocesses on your machine. You provide a command and arguments, and the SDK spawns the process.
import { MCPClientManager } from "@mcpjam/sdk";
const manager = new MCPClientManager({
myServer: {
command: "node",
args: ["./my-server.js"],
env: {
API_KEY: process.env.MY_API_KEY,
},
},
});
Best for:
- Local development
- Servers you’re building
- CLI-based MCP servers
- Servers requiring local resources
HTTP Servers (Remote)
HTTP servers connect via SSE (Server-Sent Events) or Streamable HTTP over the network.
const manager = new MCPClientManager({
asana: {
url: "https://mcp.asana.com/sse",
requestInit: {
headers: {
Authorization: `Bearer ${process.env.ASANA_TOKEN}`,
},
},
},
});
Best for:
- Production MCP servers
- Third-party integrations (Asana, Slack, etc.)
- Shared team servers
- Serverless deployments
HTTP Authentication Options
For HTTP servers, MCPClientManager supports three auth patterns:
accessToken for a static bearer token
refreshToken + clientId for non-interactive OAuth token exchange with automatic refresh
authProvider when you need to provide a custom MCP SDK OAuth provider
If you already have a refresh token, you can let the SDK handle access token exchange and refresh for you:
const manager = new MCPClientManager({
asana: {
url: "https://mcp.asana.com/mcp",
refreshToken: process.env.ASANA_REFRESH_TOKEN!,
clientId: process.env.ASANA_CLIENT_ID!,
clientSecret: process.env.ASANA_CLIENT_SECRET,
},
});
With this configuration, the SDK:
- exchanges the refresh token for an access token during connection
- automatically retries with a fresh access token when the server returns an auth challenge
- stores rotated refresh tokens returned by the authorization server
refreshToken is only supported for HTTP servers. When you use it, do not also set accessToken, authProvider, or an Authorization header in requestInit.headers.
Connecting and Disconnecting
After configuring servers, you must explicitly connect:
// Connect to a specific server
await manager.connectToServer("myServer");
// Now you can use it
const tools = await manager.listTools("myServer");
console.log("Available tools:", tools.tools.map((tool) => tool.name));
// Always disconnect when done
await manager.disconnectServer("myServer");
Always disconnect servers when you’re done, especially in tests. This cleans up subprocess handles and network connections.
Working with Multiple Servers
A key strength of MCPClientManager is managing multiple servers simultaneously:
const manager = new MCPClientManager({
math: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-everything"],
},
asana: {
url: "https://mcp.asana.com/sse",
requestInit: {
headers: { Authorization: `Bearer ${token}` },
},
},
github: {
url: "https://mcp.github.com/sse",
requestInit: {
headers: { Authorization: `Bearer ${ghToken}` },
},
},
});
// Connect to all
await Promise.all([
manager.connectToServer("math"),
manager.connectToServer("asana"),
manager.connectToServer("github"),
]);
// Aggregate tools from all servers
const allTools = await manager.getTools();
// Now allTools contains tools from math, asana, and github
You can call tools without involving an LLM—useful for unit tests:
// Direct tool execution
const result = await manager.executeTool("math", "add", { a: 5, b: 3 });
console.log(result); // 8
// Test your server's tools deterministically
expect(result).toBe(8);
Integration with TestAgent
The most common pattern is connecting servers, then creating a TestAgent with their tools:
const manager = new MCPClientManager({
myServer: { command: "node", args: ["./server.js"] },
});
await manager.connectToServer("myServer");
// Get tools for TestAgent
const tools = await manager.getTools();
// Create agent with those tools
const agent = new TestAgent({
tools,
model: "anthropic/claude-sonnet-4-20250514",
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Now prompt the agent
const result = await agent.prompt("Add 2 and 3");
Error Handling
Connection failures throw errors. Wrap in try/catch for production code:
try {
await manager.connectToServer("myServer");
} catch (error) {
console.error("Failed to connect:", error.message);
// Handle gracefully - maybe fall back to another server
}
Health Checks
Verify a server is responsive:
try {
manager.pingServer("myServer");
} catch {
console.warn("Server not responding, reconnecting...");
await manager.disconnectServer("myServer");
await manager.connectToServer("myServer");
}
Next Steps