> ## Documentation Index
> Fetch the complete documentation index at: https://mcpjam-mintlify-docs-update-pr-3444-1785050029015.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# MCPClientManager

> API reference for MCPClientManager

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

## Import

```typescript theme={"theme":"css-variables"}
import { MCPClientManager } from "@mcpjam/sdk";
```

## Constructor

```typescript theme={"theme":"css-variables"}
new MCPClientManager(servers: ServerConfig, options?: MCPClientManagerOptions)
```

### Parameters

<ParamField path="servers" type="Record<string, StdioServerConfig | HttpServerConfig>" required>
  A map of server IDs to their configurations.
</ParamField>

<ParamField path="options" type="MCPClientManagerOptions">
  Optional manager-level settings. See [Manager options](#manager-options) below.
</ParamField>

### Server Configuration Types

#### StdioServerConfig

For subprocess-based MCP servers.

| Property | Type | Required | Description |
| - | - | - | - |
| `command` | `string` | Yes | The command to run (e.g., `"node"`, `"python"`, `"npx"`) |
| `args` | `string[]` | No | Command arguments |
| `env` | `Record<string, string>` | No | Environment variables for the subprocess |
| `clientInfo` | `{ name?: string; version?: string } & Record<string, unknown>` | No | Override the MCP `initialize.params.clientInfo` sent to this server. Useful for testing how servers respond to different client identities. |
| `supportedProtocolVersions` | `string[]` | No | Ordered list of MCP protocol versions to advertise. The first entry is proposed in `initialize.params.protocolVersion`; the full list is the accept-set. |
| `firstPageOnly` | `boolean` | No | When `true`, simulates a client that reads only the first page of paginated list results (`tools/list`, `resources/list`, `resources/templates/list`, `prompts/list`) and stops. Tools beyond page one are invisible to the client, and `Mcp-Param-*` header mirroring is limited to page-one tools. Applies on every transport (stdio, Streamable HTTP, SSE). Explicit-cursor requests (manual paging in the inspector) are left untouched. |

```typescript theme={"theme":"css-variables"}
{
  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.

| Property | Type | Required | Description |
| - | - | - | - |
| `url` | `string` | Yes | The server URL (e.g., `"https://mcp.example.com/sse"`) |
| `accessToken` | `string` | No | Static bearer token added as `Authorization: Bearer <token>` |
| `requestInit` | `RequestInit` | No | Fetch options including headers for authentication |
| `eventSourceInit` | `EventSourceInit` | No | SSE-specific options |
| `authProvider` | `OAuthClientProvider` | No | Custom MCP SDK OAuth provider |
| `refreshToken` | `string` | No | Refresh token used for non-interactive OAuth token exchange and automatic token refresh |
| `clientId` | `string` | No | OAuth client ID. Required when `refreshToken` is set |
| `clientSecret` | `string` | No | OAuth client secret for confidential clients |
| `reconnectionOptions` | `StreamableHTTPClientTransportOptions["reconnectionOptions"]` | No | Streamable HTTP reconnection behavior |
| `sessionId` | `string` | No | Existing Streamable HTTP session ID |
| `preferSSE` | `boolean` | No | Forces SSE instead of Streamable HTTP |
| `clientInfo` | `{ name?: string; version?: string } & Record<string, unknown>` | No | Override the MCP `initialize.params.clientInfo` sent to this server. Useful for testing how servers respond to different client identities. |
| `supportedProtocolVersions` | `string[]` | No | Ordered list of MCP protocol versions to advertise. The first entry is proposed in `initialize.params.protocolVersion`; the full list is the accept-set. |
| `onUnauthorized` | `UnauthorizedRefreshHandler` | No | 401 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. |
| `firstPageOnly` | `boolean` | No | When `true`, simulates a client that reads only the first page of paginated list results (`tools/list`, `resources/list`, `resources/templates/list`, `prompts/list`) and stops. Tools beyond page one are invisible to the client, and `Mcp-Param-*` header mirroring is limited to page-one tools. Applies on every transport (stdio, Streamable HTTP, SSE). Explicit-cursor requests (manual paging in the inspector) are left untouched. |

```typescript theme={"theme":"css-variables"}
{
  remoteServer: {
    url: "https://mcp.asana.com/sse",
    requestInit: {
      headers: {
        Authorization: "Bearer YOUR_TOKEN",
        "X-Custom-Header": "value",
      },
    },
  },
}
```

##### Refresh Token Example

```typescript theme={"theme":"css-variables"}
{
  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.

<Note>
  `refreshToken` is mutually exclusive with `accessToken`, `authProvider`, and `requestInit.headers.Authorization`.
</Note>

##### 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):

```typescript theme={"theme":"css-variables"}
{
  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:

```typescript theme={"theme":"css-variables"}
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.

<Note>
  `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.
</Note>

### Example

```typescript theme={"theme":"css-variables"}
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" },
    },
  },
});
```

***

## Manager options

The second argument to the constructor accepts an `MCPClientManagerOptions` object.

| Option | Type | Description |
| - | - | - |
| `negotiationOutcomeLogger` | `NegotiationOutcomeLogger` | Optional callback invoked once per connection attempt with the negotiation outcome. Use it to forward telemetry to your own analytics pipeline. The manager guards it — a throw inside the callback never disturbs the connection. |
| `cacheEventLogger` | `CacheEventLogger` | Optional callback for response-cache hit events. |
| `rpcLogger` | `RpcLogger` | Optional callback for raw RPC request/response pairs. |
| `retryPolicy` | `RetryPolicy` | Default retry policy for retryable manager operations. |
| `lazyConnect` | `boolean` | When `true`, defers the actual transport connection until the first RPC call. |

### negotiationOutcomeLogger

`negotiationOutcomeLogger` receives a `NegotiationOutcomeEvent` for every connection attempt:

| Field | Type | Description |
| - | - | - |
| `serverId` | `string` | The server whose connection was attempted. |
| `transport` | `"http" \| "stdio"` | Transport the attempt used. |
| `configuredMode` | `"auto" \| "modern-pin" \| "legacy"` | The negotiation mode the client was asked to use. `"auto"` for unconfigured connections (always probed); `"modern-pin"` for an explicit 2026-07-28 pin; `"legacy"` for an explicit legacy pin. |
| `outcome` | `"connected" \| "failed"` | Whether the connection established or failed. |
| `negotiatedEra` | `"legacy" \| "modern"` | *(connected only)* The era that was negotiated. |
| `negotiatedProtocolVersion` | `string` | *(connected only)* The wire protocol version that was negotiated. |
| `failureClass` | `string` | *(failed only)* Coarse, non-PII failure class — the error's `name` or `code`, e.g. `"UnauthorizedError"`, `"TypeError"`. |

```typescript theme={"theme":"css-variables"}
import { MCPClientManager, NegotiationOutcomeEvent } from "@mcpjam/sdk";

const manager = new MCPClientManager(
  {
    myServer: { command: "node", args: ["./server.js"] },
  },
  {
    negotiationOutcomeLogger: (event: NegotiationOutcomeEvent) => {
      console.log(
        `[${event.serverId}] ${event.transport} ${event.configuredMode} → ${event.outcome}`,
        event.negotiatedEra ?? event.failureClass,
      );
    },
  },
);
```

***

## Methods

### connectToServer()

Establishes a connection to a configured server.

```typescript theme={"theme":"css-variables"}
connectToServer(serverId: string): Promise<Client>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID from the constructor config |

#### Returns

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

#### Example

```typescript theme={"theme":"css-variables"}
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.

```typescript theme={"theme":"css-variables"}
disconnectServer(serverId: string): Promise<void>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID to disconnect |

#### Returns

`Promise<void>`

#### Example

```typescript theme={"theme":"css-variables"}
await manager.disconnectServer("myServer");
```

***

### pingServer()

Sends a liveness probe to check if a server is responsive. The probe is era-aware: on a modern (2026-07-28) connection it uses `server/discover`; on a legacy connection it uses `ping`.

```typescript theme={"theme":"css-variables"}
pingServer(serverId: string): Promise<EmptyResult>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID to ping |

#### Returns

`Promise<EmptyResult>` - Resolves with an empty result if the server is responsive.

#### Throws

* Error if the server is not connected
* Error if the probe fails

#### Example

```typescript theme={"theme":"css-variables"}
try {
  await 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.

```typescript theme={"theme":"css-variables"}
listTools(serverId: string): Promise<{ tools: Tool[] }>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID |

#### Returns

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

#### Tool Object

| Property | Type | Description |
| - | - | - |
| `name` | `string` | Tool identifier |
| `description` | `string` | Human-readable description |
| `inputSchema` | `object` | JSON Schema for tool arguments |

#### Example

```typescript theme={"theme":"css-variables"}
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.

```typescript theme={"theme":"css-variables"}
executeTool(
  serverId: string,
  toolName: string,
  args: Record<string, unknown>
): Promise<CallToolResult | Record<string, unknown>>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID |
| `toolName` | `string` | Name of the tool to execute |
| `args` | `Record<string, unknown>` | Arguments to pass to the tool |

#### Returns

`Promise<CallToolResult | Record<string, unknown>>` — Resolves to a `CallToolResult` for standard tool calls, or a task envelope (`Record<string, unknown>`) when the server returns a task instead of an inline result.

Because the return type is a union, TypeScript will not let you read `.content` directly. Use `assertCallToolResult` (throws on a task envelope) or `isCallToolResult` (type-guard) to narrow the value first:

```typescript theme={"theme":"css-variables"}
import { MCPClientManager, assertCallToolResult, isCallToolResult } from "@mcpjam/sdk";

// assertCallToolResult — throws if the result is a task envelope
const result = assertCallToolResult(
  await manager.executeTool("myServer", "get-sum", { a: 2, b: 3 }),
);
const [block] = result.content;
if (block.type !== "text") throw new Error("Expected a text block");
console.log(block.text); // "The sum of 2 and 3 is 5."

// isCallToolResult — type-guard for conditional handling
const raw = await manager.executeTool("myServer", "add", { a: 5, b: 3 });
if (isCallToolResult(raw)) {
  console.log(raw.content);
} else {
  // task envelope — handle async task flow
}
```

#### 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.

```typescript theme={"theme":"css-variables"}
listResources(serverId: string): Promise<Resource[]>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID |

#### Returns

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

#### Resource Object

| Property | Type | Description |
| - | - | - |
| `name` | `string` | Resource identifier |
| `uri` | `string` | Resource URI |
| `description` | `string` | Human-readable description |
| `mimeType` | `string` | Content type |

#### Example

```typescript theme={"theme":"css-variables"}
const resources = await manager.listResources("myServer");

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

***

### readResource()

Reads the content of a resource.

```typescript theme={"theme":"css-variables"}
readResource(
  serverId: string,
  params: { uri: string }
): Promise<ResourceContent>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID |
| `params.uri` | `string` | The resource URI to read |

#### Returns

`Promise<ResourceContent>` - The resource content.

#### Example

```typescript theme={"theme":"css-variables"}
const content = await manager.readResource("myServer", {
  uri: "file://config.json",
});
console.log(content);
```

***

### listPrompts()

Returns all prompts available on a server.

```typescript theme={"theme":"css-variables"}
listPrompts(serverId: string): Promise<Prompt[]>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID |

#### Returns

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

#### Prompt Object

| Property | Type | Description |
| - | - | - |
| `name` | `string` | Prompt identifier |
| `description` | `string` | Human-readable description |
| `arguments` | `PromptArgument[]` | Expected arguments |

#### Example

```typescript theme={"theme":"css-variables"}
const prompts = await manager.listPrompts("myServer");

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

***

### getPrompt()

Gets a prompt with optional arguments.

```typescript theme={"theme":"css-variables"}
getPrompt(
  serverId: string,
  params: { name: string; arguments?: Record<string, string> }
): Promise<PromptResult>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverId` | `string` | The server ID |
| `params.name` | `string` | The prompt name |
| `params.arguments` | `Record<string, string>` | Optional prompt arguments |

#### Returns

`Promise<PromptResult>` - The prompt content with messages.

#### Example

```typescript theme={"theme":"css-variables"}
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.

```typescript theme={"theme":"css-variables"}
getTools(serverIds?: string[]): Promise<Tool[]>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverIds` | `string[]` | 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:

| Property | Type | Description |
| - | - | - |
| `name` | `string` | Tool identifier |
| `description` | `string` | Human-readable description |
| `inputSchema` | `object` | JSON Schema for tool arguments |
| `execute` | `function` | Function to execute the tool with arguments |

#### Example

```typescript theme={"theme":"css-variables"}
// 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.text);
```

<Note>
  `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.
</Note>

***

### 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.

```typescript theme={"theme":"css-variables"}
getToolsForAiSdk(
  serverIds?: string[] | string,
  options?: {
    schemas?: ToolSchemaOverrides | "automatic";
    needsApproval?: boolean;
    includeAppOnly?: boolean;
  }
): Promise<ToolSet>
```

#### Parameters

| Parameter | Type | Description |
| - | - | - |
| `serverIds` | `string[] \| string` | Optional. Server IDs to include. If omitted, uses all connected servers. |
| `options.schemas` | `ToolSchemaOverrides \| "automatic"` | Optional. Control JSON schema conversion. |
| `options.needsApproval` | `boolean` | Optional. When true, each tool call requires user approval before execution. |
| `options.includeAppOnly` | `boolean` | Optional. 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

```typescript theme={"theme":"css-variables"}
// 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

```typescript theme={"theme":"css-variables"}
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.text);
  } finally {
    await manager.disconnectServer("math");
    await manager.disconnectServer("asana");
  }
}
```

## Related

* [Connecting to Servers](/sdk/concepts/connecting-servers) - Conceptual guide
* [HostRunner Reference](/sdk/reference/host-runner) - Use tools with LLMs


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.