Skip to main content
The MCPClientManager class manages connections to one or more MCP servers and provides methods to interact with their tools, resources, and prompts.

Import

import { MCPClientManager } from "@mcpjam/sdk";

Constructor

new MCPClientManager(servers: ServerConfig)

Parameters

servers
Record<string, StdioServerConfig | HttpServerConfig>
required
A map of server IDs to their configurations.

Server Configuration Types

StdioServerConfig

For subprocess-based MCP servers.
PropertyTypeRequiredDescription
commandstringYesThe command to run (e.g., "node", "python", "npx")
argsstring[]NoCommand arguments
envRecord<string, string>NoEnvironment variables for the subprocess
clientInfo{ name?: string; version?: string } & Record<string, unknown>NoOverride the MCP initialize.params.clientInfo sent to this server. Useful for testing how servers respond to different client identities.
supportedProtocolVersionsstring[]NoOrdered list of MCP protocol versions to advertise. The first entry is proposed in initialize.params.protocolVersion; the full list is the accept-set.
{
  myServer: {
    command: "node",
    args: ["./server.js", "--port", "3000"],
    env: {
      API_KEY: process.env.API_KEY,
      DEBUG: "true",
    },
  },
}

HttpServerConfig

For remote MCP servers via SSE or Streamable HTTP.
PropertyTypeRequiredDescription
urlstringYesThe server URL (e.g., "https://mcp.example.com/sse")
accessTokenstringNoStatic bearer token added as Authorization: Bearer <token>
requestInitRequestInitNoFetch options including headers for authentication
eventSourceInitEventSourceInitNoSSE-specific options
authProviderOAuthClientProviderNoCustom MCP SDK OAuth provider
refreshTokenstringNoRefresh token used for non-interactive OAuth token exchange and automatic token refresh
clientIdstringNoOAuth client ID. Required when refreshToken is set
clientSecretstringNoOAuth client secret for confidential clients
reconnectionOptionsStreamableHTTPClientTransportOptions["reconnectionOptions"]NoStreamable HTTP reconnection behavior
sessionIdstringNoExisting Streamable HTTP session ID
preferSSEbooleanNoForces SSE instead of Streamable HTTP
clientInfo{ name?: string; version?: string } & Record<string, unknown>NoOverride the MCP initialize.params.clientInfo sent to this server. Useful for testing how servers respond to different client identities.
supportedProtocolVersionsstring[]NoOrdered list of MCP protocol versions to advertise. The first entry is proposed in initialize.params.protocolVersion; the full list is the accept-set.
onUnauthorizedUnauthorizedRefreshHandlerNo401 recovery hook. Called once when an operation fails with HTTP 401; return a new accessToken and the SDK rebuilds the transport and retries. Not invoked for 403, or when refreshToken/authProvider is set.
{
  remoteServer: {
    url: "https://mcp.asana.com/sse",
    requestInit: {
      headers: {
        Authorization: "Bearer YOUR_TOKEN",
        "X-Custom-Header": "value",
      },
    },
  },
}
Refresh Token Example
{
  remoteServer: {
    url: "https://mcp.example.com/mcp",
    refreshToken: process.env.MCP_REFRESH_TOKEN!,
    clientId: process.env.MCP_CLIENT_ID!,
    clientSecret: process.env.MCP_CLIENT_SECRET,
  },
}
When refreshToken is provided, the SDK will exchange it for an access token during connection and automatically refresh tokens if the server challenges the current token.
refreshToken is mutually exclusive with accessToken, authProvider, and requestInit.headers.Authorization.
Client Identity Example
Use clientInfo to pin the identity sent in MCP initialize. This is useful when testing how a server behaves for different clients (e.g., Claude, ChatGPT):
{
  remoteServer: {
    url: "https://mcp.example.com/mcp",
    clientInfo: { name: "claude-ai", version: "0.1.0" },
    supportedProtocolVersions: ["2025-11-25", "2025-06-18"],
  },
}
The first entry in supportedProtocolVersions is proposed to the server; the full list is the accept-set. A server that negotiates any listed version is accepted.
onUnauthorized Example
Use onUnauthorized when you manage access tokens externally (e.g., from a secrets vault) and want the SDK to automatically recover from a 401 without a full reconnect flow:
import { MCPClientManager, UnauthorizedRefreshHandler } from "@mcpjam/sdk";

const refreshToken: UnauthorizedRefreshHandler = async ({ serverId }) => {
  // Fetch a fresh token from your own token store or backend
  const response = await fetch("https://auth.example.com/token/refresh", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SERVICE_TOKEN}` },
    body: JSON.stringify({ serverId }),
  });
  const { accessToken } = await response.json();
  return { accessToken };
};

const manager = new MCPClientManager({
  myServer: {
    url: "https://mcp.example.com/mcp",
    accessToken: await getInitialToken(),
    onUnauthorized: refreshToken,
  },
});
When the server returns HTTP 401, the SDK calls onUnauthorized once, updates the access token, rebuilds the transport, and retries the operation. A second 401 after the retry surfaces the error to the caller.
onUnauthorized is only active for accessToken-based HTTP configs. It is ignored when refreshToken or authProvider is set, and it is never triggered by 403 responses.

Example

const manager = new MCPClientManager({
  // STDIO server
  local: {
    command: "npx",
    args: ["-y", "@modelcontextprotocol/server-everything"],
  },
  // HTTP server
  remote: {
    url: "https://mcp.example.com/sse",
    requestInit: {
      headers: { Authorization: "Bearer token" },
    },
  },
});

Methods

connectToServer()

Establishes a connection to a configured server.
connectToServer(serverId: string): Promise<Client>

Parameters

ParameterTypeDescription
serverIdstringThe server ID from the constructor config

Returns

Promise<Client> - Resolves with the connected MCP client, rejects on failure.

Example

await manager.connectToServer("myServer");

Throws

  • Error if serverId is not in the configuration
  • Error if connection fails (network, subprocess spawn, etc.)

disconnectServer()

Closes the connection to a server and cleans up resources.
disconnectServer(serverId: string): Promise<void>

Parameters

ParameterTypeDescription
serverIdstringThe server ID to disconnect

Returns

Promise<void>

Example

await manager.disconnectServer("myServer");

pingServer()

Sends a ping to check if a server is responsive.
pingServer(serverId: string): void

Parameters

ParameterTypeDescription
serverIdstringThe server ID to ping

Returns

void - This method is synchronous and does not return a value.

Throws

  • Error if the server is not connected
  • Error if the ping fails

Example

try {
  manager.pingServer("myServer");
  console.log("Server is responsive");
} catch (error) {
  console.warn("Server not responding:", error.message);
}

listTools()

Returns all tools available on a server.
listTools(serverId: string): Promise<{ tools: Tool[] }>

Parameters

ParameterTypeDescription
serverIdstringThe server ID

Returns

Promise<{ tools: Tool[] }> - Tool list response from the server.

Tool Object

PropertyTypeDescription
namestringTool identifier
descriptionstringHuman-readable description
inputSchemaobjectJSON Schema for tool arguments

Example

const tools = await manager.listTools("myServer");

for (const tool of tools.tools) {
  console.log(`${tool.name}: ${tool.description}`);
}

executeTool()

Executes a tool and returns the result.
executeTool(
  serverId: string,
  toolName: string,
  args: Record<string, unknown>
): Promise<unknown>

Parameters

ParameterTypeDescription
serverIdstringThe server ID
toolNamestringName of the tool to execute
argsRecord<string, unknown>Arguments to pass to the tool

Returns

Promise<unknown> - The tool’s return value (type depends on the tool).

Example

const result = await manager.executeTool("myServer", "add", {
  a: 5,
  b: 3,
});
console.log(result); // 8

Throws

  • Error if tool doesn’t exist
  • Error if arguments are invalid
  • Error if tool execution fails

listResources()

Returns all resources available on a server.
listResources(serverId: string): Promise<Resource[]>

Parameters

ParameterTypeDescription
serverIdstringThe server ID

Returns

Promise<Resource[]> - Array of resource definitions.

Resource Object

PropertyTypeDescription
namestringResource identifier
uristringResource URI
descriptionstringHuman-readable description
mimeTypestringContent type

Example

const resources = await manager.listResources("myServer");

for (const resource of resources) {
  console.log(`${resource.name}: ${resource.uri}`);
}

readResource()

Reads the content of a resource.
readResource(
  serverId: string,
  params: { uri: string }
): Promise<ResourceContent>

Parameters

ParameterTypeDescription
serverIdstringThe server ID
params.uristringThe resource URI to read

Returns

Promise<ResourceContent> - The resource content.

Example

const content = await manager.readResource("myServer", {
  uri: "file://config.json",
});
console.log(content);

listPrompts()

Returns all prompts available on a server.
listPrompts(serverId: string): Promise<Prompt[]>

Parameters

ParameterTypeDescription
serverIdstringThe server ID

Returns

Promise<Prompt[]> - Array of prompt definitions.

Prompt Object

PropertyTypeDescription
namestringPrompt identifier
descriptionstringHuman-readable description
argumentsPromptArgument[]Expected arguments

Example

const prompts = await manager.listPrompts("myServer");

for (const prompt of prompts) {
  console.log(`${prompt.name}: ${prompt.description}`);
}

getPrompt()

Gets a prompt with optional arguments.
getPrompt(
  serverId: string,
  params: { name: string; arguments?: Record<string, string> }
): Promise<PromptResult>

Parameters

ParameterTypeDescription
serverIdstringThe server ID
params.namestringThe prompt name
params.argumentsRecord<string, string>Optional prompt arguments

Returns

Promise<PromptResult> - The prompt content with messages.

Example

const prompt = await manager.getPrompt("myServer", {
  name: "summarize",
  arguments: { length: "short" },
});
console.log(prompt.messages);

getTools()

Returns all tools from specified servers or all connected servers.
getTools(serverIds?: string[]): Promise<Tool[]>

Parameters

ParameterTypeDescription
serverIdsstring[]Optional. Array of server IDs to fetch tools from. If omitted, returns tools from all connected servers.

Returns

Promise<Tool[]> - Array of tool definitions from all specified servers.

Tool Object

Each tool in the returned array includes:
PropertyTypeDescription
namestringTool identifier
descriptionstringHuman-readable description
inputSchemaobjectJSON Schema for tool arguments
executefunctionFunction to execute the tool with arguments

Example

// Tools from specific servers
const tools = await manager.getTools(["myServer", "anotherServer"]);

// Tools from all connected servers
const allTools = await manager.getTools();

for (const tool of allTools) {
  console.log(`${tool.name}: ${tool.description}`);
}

// Use with HostRunner
const agent = new HostRunner({
  tools: await manager.getTools(),
  model: "anthropic/claude-sonnet-4-20250514",
  apiKey: process.env.ANTHROPIC_API_KEY,
});

const result = await agent.run("Add 2 and 3");
console.log(result.getText());
getTools() returns all tools including app-only ones. Tools with _meta.ui.visibility = ["app"] are filtered out by getToolsForAiSdk() per SEP-1865 before being passed to the model. These tools remain callable by apps via the MCP bridge.

getToolsForAiSdk()

Returns tools in Vercel AI SDK format, ready to pass directly to generateText() or streamText(). App-only tools are filtered out by default per SEP-1865.
getToolsForAiSdk(
  serverIds?: string[] | string,
  options?: {
    schemas?: ToolSchemaOverrides | "automatic";
    needsApproval?: boolean;
    includeAppOnly?: boolean;
  }
): Promise<ToolSet>

Parameters

ParameterTypeDescription
serverIdsstring[] | stringOptional. Server IDs to include. If omitted, uses all connected servers.
options.schemasToolSchemaOverrides | "automatic"Optional. Control JSON schema conversion.
options.needsApprovalbooleanOptional. When true, each tool call requires user approval before execution.
options.includeAppOnlybooleanOptional. When true, includes tools with _meta.ui.visibility = ["app"] in the returned set. Defaults to false (spec-compliant). Use only when intentionally mirroring a host that does not implement SEP-1865 visibility filtering.

Returns

Promise<ToolSet> - Tools in Vercel AI SDK format with execution wired up.

Example

// Default: app-only tools are excluded (spec-compliant)
const tools = await manager.getToolsForAiSdk(["myServer"]);

// Include app-only tools (e.g. to mirror a non-filtering host)
const allTools = await manager.getToolsForAiSdk(["myServer"], {
  includeAppOnly: true,
});

// Use directly with Vercel AI SDK
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

const response = await generateText({
  model: openai("gpt-4o"),
  tools: await manager.getToolsForAiSdk(),
  messages: [{ role: "user", content: "Add 5 and 7" }],
});

Complete Example

import { MCPClientManager, HostRunner } from "@mcpjam/sdk";

async function main() {
  const manager = new MCPClientManager({
    math: {
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-everything"],
    },
    asana: {
      url: "https://mcp.asana.com/sse",
      requestInit: {
        headers: { Authorization: `Bearer ${process.env.ASANA_TOKEN}` },
      },
    },
  });

  try {
    // Connect
    await manager.connectToServer("math");
    await manager.connectToServer("asana");

    // Direct tool execution
    const sum = await manager.executeTool("math", "add", { a: 10, b: 5 });
    console.log("Sum:", sum);

    // List capabilities
    const mathTools = await manager.listTools("math");
    const asanaTools = await manager.listTools("asana");
    console.log("Math tools:", mathTools.tools.length);
    console.log("Asana tools:", asanaTools.tools.length);

    // Create agent with all tools
    const agent = new HostRunner({
      tools: await manager.getTools(),
      model: "anthropic/claude-sonnet-4-20250514",
      apiKey: process.env.ANTHROPIC_API_KEY,
    });

    const result = await agent.run("Add 2 and 3");
    console.log(result.getText());
  } finally {
    await manager.disconnectServer("math");
    await manager.disconnectServer("asana");
  }
}