@webmcp-auto-ui/core 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,10 @@ W3C WebMCP Draft 2026-03-27 polyfill and MCP Streamable HTTP client. Pure TypeSc
8
8
 
9
9
  **McpClient** — connects to MCP servers over Streamable HTTP (SSE). Handles `initialize`, `tools/list`, and `tools/call`.
10
10
 
11
+ **McpMultiClient** — manages simultaneous connections to multiple MCP servers. Aggregates tool lists and routes `callTool` to the correct server. Useful for apps that connect to several data sources at once (e.g. flex with multi-MCP).
12
+
13
+ **Prompt caching** — the `cache_control` property is applied on the tools array (not individual tools) to work correctly with Anthropic's prompt caching. This fix ensures cache hits when the tool set is stable across requests.
14
+
11
15
  **createToolGroup** — registers a named group of tools on `navigator.modelContext`. Aborting the group unregisters all tools at once — useful for component lifecycle cleanup.
12
16
 
13
17
  **sanitizeSchema** — strips JSON Schema keywords that Anthropic's API rejects (`oneOf`, `anyOf`, `allOf`, `$ref`, `if/then/else`). Applied automatically before any LLM call.
@@ -30,6 +34,7 @@ npm install @webmcp-auto-ui/core
30
34
  import {
31
35
  initializeWebMCPPolyfill,
32
36
  McpClient,
37
+ McpMultiClient,
33
38
  createToolGroup,
34
39
  textResult, jsonResult,
35
40
  listenForAgentCalls,
@@ -52,9 +57,17 @@ const init = await client.connect();
52
57
  const tools = await client.listTools();
53
58
  const result = await client.callTool('my_tool', { arg: 'value' });
54
59
 
60
+ // Multi-server connections
61
+ const multi = new McpMultiClient();
62
+ await multi.addServer('https://mcp1.example.com/mcp');
63
+ await multi.addServer('https://mcp2.example.com/mcp');
64
+ const allTools = multi.listAllTools(); // aggregated from all servers
65
+ const result = await multi.callTool('query_sql', { sql: 'SELECT 1' }); // routes to correct server
66
+
55
67
  // Cleanup
56
68
  stop();
57
69
  group.abort();
70
+ await multi.disconnectAll();
58
71
  ```
59
72
 
60
73
  ## Types
package/dist/index.d.ts CHANGED
@@ -7,4 +7,6 @@ export { listenForAgentCalls, stopListening, callToolViaPostMessage, isWebMCPEve
7
7
  export { dispatchAndWait, signalCompletion, sanitizeSchema, createToolGroup, } from './utils.js';
8
8
  export { textResult, jsonResult, registerSkill, unregisterSkill, getSkill, listSkills, clearSkills, } from './webmcp-helpers.js';
9
9
  export type { SkillDef } from './webmcp-helpers.js';
10
+ export { McpMultiClient } from './multi-client.js';
11
+ export type { ConnectedServer } from './multi-client.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,2BAA2B,EAC3B,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGvE,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,EACf,QAAQ,EACR,UAAU,EACV,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,2BAA2B,EAC3B,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGvE,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,EACf,QAAQ,EACR,UAAU,EACV,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -12,4 +12,6 @@ export { listenForAgentCalls, stopListening, callToolViaPostMessage, isWebMCPEve
12
12
  export { dispatchAndWait, signalCompletion, sanitizeSchema, createToolGroup, } from './utils.js';
13
13
  // WebMCP helpers (skill registry + result builders)
14
14
  export { textResult, jsonResult, registerSkill, unregisterSkill, getSkill, listSkills, clearSkills, } from './webmcp-helpers.js';
15
+ // Multi-MCP client
16
+ export { McpMultiClient } from './multi-client.js';
15
17
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+DAA+D;AAiD/D,aAAa;AACb,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,WAAW;AACX,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,aAAa;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,qBAAqB;AACrB,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,YAAY;AACZ,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,oDAAoD;AACpD,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,EACf,QAAQ,EACR,UAAU,EACV,WAAW,GACZ,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+DAA+D;AAiD/D,aAAa;AACb,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,WAAW;AACX,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,aAAa;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,qBAAqB;AACrB,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,YAAY;AACZ,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,oDAAoD;AACpD,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,EACf,QAAQ,EACR,UAAU,EACV,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAG7B,mBAAmB;AACnB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { McpTool, McpToolResult } from './types.js';
2
+ export interface ConnectedServer {
3
+ url: string;
4
+ name: string;
5
+ tools: McpTool[];
6
+ }
7
+ export type AggregatedTool = McpTool & {
8
+ serverUrl: string;
9
+ serverName: string;
10
+ };
11
+ export declare class McpMultiClient {
12
+ /** Ordered map — insertion order determines first-match priority */
13
+ private servers;
14
+ /**
15
+ * Add (or reconnect) an MCP server and return its name + tools.
16
+ * If the URL is already registered, the old connection is removed first.
17
+ */
18
+ addServer(url: string, options?: {
19
+ headers?: Record<string, string>;
20
+ }): Promise<{
21
+ name: string;
22
+ tools: McpTool[];
23
+ }>;
24
+ /**
25
+ * Remove a server and disconnect its client.
26
+ */
27
+ removeServer(url: string): Promise<void>;
28
+ /**
29
+ * List all connected servers with their metadata.
30
+ */
31
+ listServers(): ConnectedServer[];
32
+ /**
33
+ * List ALL tools from ALL connected servers.
34
+ * Each tool is augmented with its origin server URL and name.
35
+ * Tools with the same name from different servers are all included.
36
+ */
37
+ listAllTools(): AggregatedTool[];
38
+ /**
39
+ * Call a tool by name. Automatically routes to the first server (insertion
40
+ * order) that exposes a tool with the given name.
41
+ */
42
+ callTool(name: string, args?: Record<string, unknown>): Promise<McpToolResult>;
43
+ /**
44
+ * Disconnect from all servers.
45
+ */
46
+ disconnectAll(): Promise<void>;
47
+ /** Number of connected servers. */
48
+ get serverCount(): number;
49
+ /** True if at least one server is connected. */
50
+ get hasConnections(): boolean;
51
+ }
52
+ //# sourceMappingURL=multi-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-client.d.ts","sourceRoot":"","sources":["../src/multi-client.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EAEd,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAMjF,qBAAa,cAAc;IACzB,oEAAoE;IACpE,OAAO,CAAC,OAAO,CAA4E;IAM3F;;;OAGG;IACG,SAAS,CACb,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC7C,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAoB9C;;OAEG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9C;;OAEG;IACH,WAAW,IAAI,eAAe,EAAE;IAQhC;;;;OAIG;IACH,YAAY,IAAI,cAAc,EAAE;IAUhC;;;OAGG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAUpF;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAapC,mCAAmC;IACnC,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,gDAAgD;IAChD,IAAI,cAAc,IAAI,OAAO,CAE5B;CACF"}
@@ -0,0 +1,105 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @webmcp-auto-ui/core — McpMultiClient
3
+ // Manages multiple simultaneous MCP server connections via McpClient instances.
4
+ // Zero dependencies, SSR-safe.
5
+ // ---------------------------------------------------------------------------
6
+ import { McpClient } from './client.js';
7
+ // ---------------------------------------------------------------------------
8
+ // McpMultiClient
9
+ // ---------------------------------------------------------------------------
10
+ export class McpMultiClient {
11
+ /** Ordered map — insertion order determines first-match priority */
12
+ servers = new Map();
13
+ // -------------------------------------------------------------------------
14
+ // Public API
15
+ // -------------------------------------------------------------------------
16
+ /**
17
+ * Add (or reconnect) an MCP server and return its name + tools.
18
+ * If the URL is already registered, the old connection is removed first.
19
+ */
20
+ async addServer(url, options) {
21
+ // Reconnect semantics: remove existing connection for this URL
22
+ if (this.servers.has(url)) {
23
+ await this.removeServer(url);
24
+ }
25
+ const clientOptions = options?.headers
26
+ ? { headers: options.headers }
27
+ : undefined;
28
+ const client = new McpClient(url, clientOptions);
29
+ const initResult = await client.connect();
30
+ const tools = await client.listTools();
31
+ const name = initResult.serverInfo.name;
32
+ this.servers.set(url, { client, name, tools });
33
+ return { name, tools };
34
+ }
35
+ /**
36
+ * Remove a server and disconnect its client.
37
+ */
38
+ async removeServer(url) {
39
+ const entry = this.servers.get(url);
40
+ if (!entry)
41
+ return;
42
+ await entry.client.disconnect();
43
+ this.servers.delete(url);
44
+ }
45
+ /**
46
+ * List all connected servers with their metadata.
47
+ */
48
+ listServers() {
49
+ const result = [];
50
+ for (const [url, entry] of this.servers) {
51
+ result.push({ url, name: entry.name, tools: entry.tools });
52
+ }
53
+ return result;
54
+ }
55
+ /**
56
+ * List ALL tools from ALL connected servers.
57
+ * Each tool is augmented with its origin server URL and name.
58
+ * Tools with the same name from different servers are all included.
59
+ */
60
+ listAllTools() {
61
+ const result = [];
62
+ for (const [url, entry] of this.servers) {
63
+ for (const tool of entry.tools) {
64
+ result.push({ ...tool, serverUrl: url, serverName: entry.name });
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+ /**
70
+ * Call a tool by name. Automatically routes to the first server (insertion
71
+ * order) that exposes a tool with the given name.
72
+ */
73
+ async callTool(name, args) {
74
+ for (const [, entry] of this.servers) {
75
+ const match = entry.tools.find((t) => t.name === name);
76
+ if (match) {
77
+ return entry.client.callTool(name, args);
78
+ }
79
+ }
80
+ throw new Error(`McpMultiClient: no server exposes tool "${name}"`);
81
+ }
82
+ /**
83
+ * Disconnect from all servers.
84
+ */
85
+ async disconnectAll() {
86
+ const promises = [];
87
+ for (const [, entry] of this.servers) {
88
+ promises.push(entry.client.disconnect());
89
+ }
90
+ await Promise.all(promises);
91
+ this.servers.clear();
92
+ }
93
+ // -------------------------------------------------------------------------
94
+ // Getters
95
+ // -------------------------------------------------------------------------
96
+ /** Number of connected servers. */
97
+ get serverCount() {
98
+ return this.servers.size;
99
+ }
100
+ /** True if at least one server is connected. */
101
+ get hasConnections() {
102
+ return this.servers.size > 0;
103
+ }
104
+ }
105
+ //# sourceMappingURL=multi-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-client.js","sourceRoot":"","sources":["../src/multi-client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/core",
3
- "version": "0.2.0",
3
+ "version": "0.5.0",
4
4
  "description": "W3C WebMCP polyfill + MCP Streamable HTTP client — zero dependencies, framework-agnostic",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "jb@media.mit.edu",
package/src/index.ts CHANGED
@@ -90,3 +90,7 @@ export {
90
90
  clearSkills,
91
91
  } from './webmcp-helpers.js';
92
92
  export type { SkillDef } from './webmcp-helpers.js';
93
+
94
+ // Multi-MCP client
95
+ export { McpMultiClient } from './multi-client.js';
96
+ export type { ConnectedServer } from './multi-client.js';
@@ -0,0 +1,140 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @webmcp-auto-ui/core — McpMultiClient
3
+ // Manages multiple simultaneous MCP server connections via McpClient instances.
4
+ // Zero dependencies, SSR-safe.
5
+ // ---------------------------------------------------------------------------
6
+
7
+ import { McpClient } from './client.js';
8
+ import type {
9
+ McpTool,
10
+ McpToolResult,
11
+ McpClientOptions,
12
+ } from './types.js';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export interface ConnectedServer {
19
+ url: string;
20
+ name: string;
21
+ tools: McpTool[];
22
+ }
23
+
24
+ export type AggregatedTool = McpTool & { serverUrl: string; serverName: string };
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // McpMultiClient
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export class McpMultiClient {
31
+ /** Ordered map — insertion order determines first-match priority */
32
+ private servers = new Map<string, { client: McpClient; name: string; tools: McpTool[] }>();
33
+
34
+ // -------------------------------------------------------------------------
35
+ // Public API
36
+ // -------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Add (or reconnect) an MCP server and return its name + tools.
40
+ * If the URL is already registered, the old connection is removed first.
41
+ */
42
+ async addServer(
43
+ url: string,
44
+ options?: { headers?: Record<string, string> },
45
+ ): Promise<{ name: string; tools: McpTool[] }> {
46
+ // Reconnect semantics: remove existing connection for this URL
47
+ if (this.servers.has(url)) {
48
+ await this.removeServer(url);
49
+ }
50
+
51
+ const clientOptions: McpClientOptions | undefined = options?.headers
52
+ ? { headers: options.headers }
53
+ : undefined;
54
+
55
+ const client = new McpClient(url, clientOptions);
56
+ const initResult = await client.connect();
57
+ const tools = await client.listTools();
58
+
59
+ const name = initResult.serverInfo.name;
60
+ this.servers.set(url, { client, name, tools });
61
+
62
+ return { name, tools };
63
+ }
64
+
65
+ /**
66
+ * Remove a server and disconnect its client.
67
+ */
68
+ async removeServer(url: string): Promise<void> {
69
+ const entry = this.servers.get(url);
70
+ if (!entry) return;
71
+ await entry.client.disconnect();
72
+ this.servers.delete(url);
73
+ }
74
+
75
+ /**
76
+ * List all connected servers with their metadata.
77
+ */
78
+ listServers(): ConnectedServer[] {
79
+ const result: ConnectedServer[] = [];
80
+ for (const [url, entry] of this.servers) {
81
+ result.push({ url, name: entry.name, tools: entry.tools });
82
+ }
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ * List ALL tools from ALL connected servers.
88
+ * Each tool is augmented with its origin server URL and name.
89
+ * Tools with the same name from different servers are all included.
90
+ */
91
+ listAllTools(): AggregatedTool[] {
92
+ const result: AggregatedTool[] = [];
93
+ for (const [url, entry] of this.servers) {
94
+ for (const tool of entry.tools) {
95
+ result.push({ ...tool, serverUrl: url, serverName: entry.name });
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Call a tool by name. Automatically routes to the first server (insertion
103
+ * order) that exposes a tool with the given name.
104
+ */
105
+ async callTool(name: string, args?: Record<string, unknown>): Promise<McpToolResult> {
106
+ for (const [, entry] of this.servers) {
107
+ const match = entry.tools.find((t) => t.name === name);
108
+ if (match) {
109
+ return entry.client.callTool(name, args);
110
+ }
111
+ }
112
+ throw new Error(`McpMultiClient: no server exposes tool "${name}"`);
113
+ }
114
+
115
+ /**
116
+ * Disconnect from all servers.
117
+ */
118
+ async disconnectAll(): Promise<void> {
119
+ const promises: Promise<void>[] = [];
120
+ for (const [, entry] of this.servers) {
121
+ promises.push(entry.client.disconnect());
122
+ }
123
+ await Promise.all(promises);
124
+ this.servers.clear();
125
+ }
126
+
127
+ // -------------------------------------------------------------------------
128
+ // Getters
129
+ // -------------------------------------------------------------------------
130
+
131
+ /** Number of connected servers. */
132
+ get serverCount(): number {
133
+ return this.servers.size;
134
+ }
135
+
136
+ /** True if at least one server is connected. */
137
+ get hasConnections(): boolean {
138
+ return this.servers.size > 0;
139
+ }
140
+ }