@prefecthq/fastmcp-ts 0.0.2-alpha.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/dist/client.js ADDED
@@ -0,0 +1,1758 @@
1
+ import "./chunk-PZ5AY32C.js";
2
+
3
+ // src/client/handlers.ts
4
+ function defaultLogHandler(message) {
5
+ const prefix = message.logger ? `[${message.logger}]` : "[server]";
6
+ const data = typeof message.data === "string" ? message.data : JSON.stringify(message.data);
7
+ const text = `${prefix} ${data}`;
8
+ const level = message.level;
9
+ if (level === "debug") {
10
+ console.debug(text);
11
+ } else if (level === "warning") {
12
+ console.warn(text);
13
+ } else if (level === "error" || level === "critical" || level === "alert" || level === "emergency") {
14
+ console.error(text);
15
+ } else {
16
+ console.info(text);
17
+ }
18
+ }
19
+ function defaultProgressHandler(progress, total, message) {
20
+ const pct = total != null ? ` (${Math.round(progress / total * 100)}%)` : "";
21
+ const msg = message ? ` \u2014 ${message}` : "";
22
+ console.debug(
23
+ `progress: ${progress}${total != null ? `/${total}` : ""}${pct}${msg}`
24
+ );
25
+ }
26
+
27
+ // src/client/auth.ts
28
+ import { spawn } from "child_process";
29
+ import * as fs from "fs/promises";
30
+ import * as http from "http";
31
+ import { homedir } from "os";
32
+ import { dirname, join } from "path";
33
+ var InMemoryStore = class {
34
+ _map = /* @__PURE__ */ new Map();
35
+ async get(key) {
36
+ return this._map.get(key) ?? null;
37
+ }
38
+ async set(key, value) {
39
+ this._map.set(key, value);
40
+ }
41
+ async delete(key) {
42
+ this._map.delete(key);
43
+ }
44
+ };
45
+ var FileTokenStorage = class {
46
+ _path;
47
+ constructor(path) {
48
+ this._path = path ?? join(homedir(), ".fastmcp", "tokens.json");
49
+ }
50
+ async _readAll() {
51
+ try {
52
+ const raw = await fs.readFile(this._path, "utf8");
53
+ const parsed = JSON.parse(raw);
54
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
55
+ return parsed;
56
+ }
57
+ return {};
58
+ } catch {
59
+ return {};
60
+ }
61
+ }
62
+ async _writeAll(data) {
63
+ await fs.mkdir(dirname(this._path), { recursive: true });
64
+ const tmp = `${this._path}.tmp`;
65
+ await fs.writeFile(tmp, JSON.stringify(data, null, 2), "utf8");
66
+ await fs.rename(tmp, this._path);
67
+ }
68
+ async get(key) {
69
+ const data = await this._readAll();
70
+ return data[key] ?? null;
71
+ }
72
+ async set(key, value) {
73
+ const data = await this._readAll();
74
+ data[key] = value;
75
+ await this._writeAll(data);
76
+ }
77
+ async delete(key) {
78
+ const data = await this._readAll();
79
+ if (!(key in data)) return;
80
+ delete data[key];
81
+ await this._writeAll(data);
82
+ }
83
+ };
84
+ var OAuth = class {
85
+ _serverUrl = "";
86
+ _clientId;
87
+ _clientSecret;
88
+ _scopes;
89
+ _clientName;
90
+ _store;
91
+ _callbackPort;
92
+ _onRedirect;
93
+ _codeVerifier;
94
+ _callbackPromise = null;
95
+ _callbackResolve = null;
96
+ _callbackReject = null;
97
+ _callbackServer = null;
98
+ _actualCallbackPort = null;
99
+ constructor(options = {}) {
100
+ this._clientId = options.clientId;
101
+ this._clientSecret = options.clientSecret;
102
+ this._scopes = Array.isArray(options.scopes) ? options.scopes.join(" ") : options.scopes;
103
+ this._clientName = options.clientName ?? "FastMCP Client";
104
+ this._store = options.store ?? new InMemoryStore();
105
+ this._callbackPort = options.callbackPort ?? 8765;
106
+ this._onRedirect = options.onRedirect;
107
+ }
108
+ /**
109
+ * Binds the MCP server URL so that all storage keys are namespaced by it.
110
+ * Called by Client before connecting.
111
+ */
112
+ _bind(serverUrl) {
113
+ this._serverUrl = serverUrl;
114
+ }
115
+ // ---- OAuthClientProvider interface ----
116
+ get redirectUrl() {
117
+ const port = this._actualCallbackPort ?? this._callbackPort;
118
+ return `http://localhost:${port}/callback`;
119
+ }
120
+ get clientMetadata() {
121
+ const port = this._actualCallbackPort ?? this._callbackPort;
122
+ return {
123
+ redirect_uris: [`http://localhost:${port}/callback`],
124
+ token_endpoint_auth_method: this._clientSecret ? "client_secret_post" : "none",
125
+ grant_types: ["authorization_code"],
126
+ response_types: ["code"],
127
+ client_name: this._clientName,
128
+ ...this._scopes ? { scope: this._scopes } : {}
129
+ };
130
+ }
131
+ async clientInformation() {
132
+ if (this._clientId) {
133
+ return {
134
+ client_id: this._clientId,
135
+ ...this._clientSecret ? { client_secret: this._clientSecret } : {}
136
+ };
137
+ }
138
+ const raw = await this._store.get(this._key("client_info"));
139
+ if (!raw) return void 0;
140
+ return JSON.parse(raw);
141
+ }
142
+ async saveClientInformation(info) {
143
+ if (this._clientId) return;
144
+ await this._store.set(this._key("client_info"), JSON.stringify(info));
145
+ }
146
+ async tokens() {
147
+ const raw = await this._store.get(this._key("tokens"));
148
+ if (!raw) return void 0;
149
+ return JSON.parse(raw);
150
+ }
151
+ async saveTokens(tokens) {
152
+ await this._store.set(this._key("tokens"), JSON.stringify(tokens));
153
+ }
154
+ async redirectToAuthorization(authorizationUrl) {
155
+ this._callbackPromise = new Promise((resolve2, reject) => {
156
+ this._callbackResolve = resolve2;
157
+ this._callbackReject = reject;
158
+ });
159
+ this._callbackPromise.catch(() => {
160
+ });
161
+ await this._startCallbackServer();
162
+ if (this._onRedirect) {
163
+ await this._onRedirect(authorizationUrl);
164
+ } else {
165
+ openBrowser(authorizationUrl.toString());
166
+ }
167
+ }
168
+ saveCodeVerifier(codeVerifier) {
169
+ this._codeVerifier = codeVerifier;
170
+ }
171
+ codeVerifier() {
172
+ if (this._codeVerifier === void 0) {
173
+ throw new Error("No PKCE code verifier has been saved");
174
+ }
175
+ return this._codeVerifier;
176
+ }
177
+ async invalidateCredentials(scope) {
178
+ if (scope === "all" || scope === "tokens") {
179
+ await this._store.delete(this._key("tokens"));
180
+ }
181
+ if (scope === "all" || scope === "client") {
182
+ await this._store.delete(this._key("client_info"));
183
+ }
184
+ if (scope === "all" || scope === "verifier") {
185
+ this._codeVerifier = void 0;
186
+ }
187
+ if (scope === "all" || scope === "discovery") {
188
+ await this._store.delete(this._key("discovery"));
189
+ }
190
+ }
191
+ async saveDiscoveryState(state) {
192
+ await this._store.set(this._key("discovery"), JSON.stringify(state));
193
+ }
194
+ async discoveryState() {
195
+ const raw = await this._store.get(this._key("discovery"));
196
+ if (!raw) return void 0;
197
+ return JSON.parse(raw);
198
+ }
199
+ // ---- Internal helpers ----
200
+ /**
201
+ * Returns the actual port the callback server bound to.
202
+ * Only populated after redirectToAuthorization() has been called.
203
+ */
204
+ get callbackServerPort() {
205
+ return this._actualCallbackPort;
206
+ }
207
+ /**
208
+ * Waits for the OAuth authorization code to arrive via the callback server,
209
+ * then stops the server and resolves with the code.
210
+ *
211
+ * Must be called after the UnauthorizedError thrown by connect() is caught.
212
+ */
213
+ async waitForCallback(timeoutMs = 5 * 60 * 1e3) {
214
+ if (!this._callbackPromise) {
215
+ throw new Error("No pending OAuth callback \u2014 call redirectToAuthorization() first");
216
+ }
217
+ const timeout = new Promise(
218
+ (_, reject) => setTimeout(
219
+ () => reject(new Error("OAuth callback timed out waiting for authorization")),
220
+ timeoutMs
221
+ )
222
+ );
223
+ try {
224
+ return await Promise.race([this._callbackPromise, timeout]);
225
+ } finally {
226
+ this._callbackPromise = null;
227
+ this._callbackResolve = null;
228
+ this._callbackReject = null;
229
+ this._stopCallbackServer();
230
+ }
231
+ }
232
+ _key(type) {
233
+ return `${this._serverUrl}/${type}`;
234
+ }
235
+ _startCallbackServer() {
236
+ if (this._callbackServer) return Promise.resolve();
237
+ return new Promise((resolve2, reject) => {
238
+ const server = http.createServer((req, res) => {
239
+ const url = new URL(req.url ?? "/", `http://localhost`);
240
+ const error = url.searchParams.get("error");
241
+ if (error && this._callbackReject) {
242
+ const description = url.searchParams.get("error_description") ?? error;
243
+ this._callbackReject(new Error(`OAuth authorization denied: ${description}`));
244
+ res.writeHead(200, { "content-type": "text/html" });
245
+ res.end(
246
+ "<html><body><h1>Authorization denied</h1><p>You may close this tab.</p></body></html>"
247
+ );
248
+ return;
249
+ }
250
+ const code = url.searchParams.get("code");
251
+ if (code && this._callbackResolve) {
252
+ this._callbackResolve(code);
253
+ res.writeHead(200, { "content-type": "text/html" });
254
+ res.end(
255
+ "<html><body><h1>Authorization complete</h1><p>You may close this tab.</p></body></html>"
256
+ );
257
+ } else {
258
+ res.writeHead(400);
259
+ res.end("Missing code parameter");
260
+ }
261
+ });
262
+ server.on("error", reject);
263
+ server.listen(this._callbackPort, () => {
264
+ const addr = server.address();
265
+ if (addr && typeof addr !== "string") {
266
+ this._actualCallbackPort = addr.port;
267
+ }
268
+ this._callbackServer = server;
269
+ resolve2();
270
+ });
271
+ });
272
+ }
273
+ _stopCallbackServer() {
274
+ this._callbackServer?.close();
275
+ this._callbackServer = null;
276
+ this._actualCallbackPort = null;
277
+ }
278
+ };
279
+ var BearerAuth = class {
280
+ _token;
281
+ constructor(token) {
282
+ this._token = token.startsWith("Bearer ") ? token.slice(7) : token;
283
+ }
284
+ getHeaders() {
285
+ return { Authorization: `Bearer ${this._token}` };
286
+ }
287
+ };
288
+ var DEFAULT_REFRESH_BUFFER_SECONDS = 60;
289
+ function normalizeToken(token) {
290
+ if (typeof token.expires_at === "number" && token.expires_at > 0) {
291
+ if (token.expires_at < 1e10) {
292
+ return { ...token, expires_at: token.expires_at * 1e3 };
293
+ }
294
+ return token;
295
+ }
296
+ if (typeof token.expires_in === "number" && token.expires_in > 0) {
297
+ return { ...token, expires_at: Date.now() + token.expires_in * 1e3 };
298
+ }
299
+ return token;
300
+ }
301
+ function isExpiring(token, bufferMs) {
302
+ if (token.expires_at == null) return false;
303
+ return token.expires_at - Date.now() <= bufferMs;
304
+ }
305
+ var ClientCredentials = class {
306
+ kind = "client_credentials";
307
+ _tokenEndpoint;
308
+ _clientId;
309
+ _clientSecret;
310
+ _scope;
311
+ _store;
312
+ _bufferMs;
313
+ _fetchPromise = null;
314
+ constructor(options) {
315
+ this._tokenEndpoint = options.tokenEndpoint;
316
+ this._clientId = options.clientId;
317
+ this._clientSecret = options.clientSecret;
318
+ this._scope = options.scope;
319
+ this._store = options.store ?? new InMemoryStore();
320
+ this._bufferMs = (options.refreshBufferSeconds ?? DEFAULT_REFRESH_BUFFER_SECONDS) * 1e3;
321
+ }
322
+ async getHeaders() {
323
+ const token = await this._getValidToken();
324
+ const type = token.token_type?.trim() || "Bearer";
325
+ return { Authorization: `${type} ${token.access_token}` };
326
+ }
327
+ get _tokenKey() {
328
+ return `${this._tokenEndpoint}/token`;
329
+ }
330
+ async _getValidToken() {
331
+ const raw = await this._store.get(this._tokenKey);
332
+ if (raw) {
333
+ const normalized = normalizeToken(JSON.parse(raw));
334
+ if (!isExpiring(normalized, this._bufferMs)) return normalized;
335
+ }
336
+ return await this._doFetch();
337
+ }
338
+ async _doFetch() {
339
+ this._fetchPromise ??= (async () => {
340
+ const params = new URLSearchParams({
341
+ grant_type: "client_credentials",
342
+ client_id: this._clientId,
343
+ client_secret: this._clientSecret
344
+ });
345
+ if (this._scope) params.set("scope", this._scope);
346
+ const response = await fetch(this._tokenEndpoint, {
347
+ method: "POST",
348
+ headers: { "content-type": "application/x-www-form-urlencoded" },
349
+ body: params.toString()
350
+ });
351
+ if (!response.ok) {
352
+ const detail = await response.text().catch(() => "");
353
+ throw new Error(
354
+ `ClientCredentials token fetch failed (${response.status})${detail ? `: ${detail}` : ""}`
355
+ );
356
+ }
357
+ const data = await response.json();
358
+ if (!data.access_token) {
359
+ throw new Error("ClientCredentials token response missing access_token");
360
+ }
361
+ const token = normalizeToken(data);
362
+ await this._store.set(this._tokenKey, JSON.stringify(token));
363
+ return token;
364
+ })().finally(() => {
365
+ this._fetchPromise = null;
366
+ });
367
+ return this._fetchPromise;
368
+ }
369
+ };
370
+ function openBrowser(url) {
371
+ if (process.platform === "win32") {
372
+ spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
373
+ } else if (process.platform === "darwin") {
374
+ spawn("open", [url], { detached: true, stdio: "ignore" });
375
+ } else {
376
+ spawn("xdg-open", [url], { detached: true, stdio: "ignore" });
377
+ }
378
+ }
379
+
380
+ // src/client/transports.ts
381
+ import { normalizeHeaders } from "@modelcontextprotocol/sdk/shared/transport";
382
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory";
383
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp";
384
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse";
385
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio";
386
+ function isMcpServerLike(value) {
387
+ return typeof value === "object" && value !== null && "connect" in value && typeof value.connect === "function";
388
+ }
389
+ function isMcpConfig(value) {
390
+ return typeof value === "object" && value !== null && "mcpServers" in value && typeof value.mcpServers === "object" && value.mcpServers !== null;
391
+ }
392
+ var StdioTransport = class {
393
+ command;
394
+ args;
395
+ env;
396
+ cwd;
397
+ constructor(command, args = [], options) {
398
+ this.command = command;
399
+ this.args = args;
400
+ this.env = options?.env;
401
+ this.cwd = options?.cwd;
402
+ }
403
+ };
404
+ function isAsyncAuth(auth) {
405
+ return "kind" in auth;
406
+ }
407
+ function buildHttpOptions(auth, extraHeaders = {}) {
408
+ const hasExtra = Object.keys(extraHeaders).length > 0;
409
+ if (!auth && !hasExtra) return {};
410
+ if (auth instanceof OAuth) {
411
+ const opts = { authProvider: auth };
412
+ if (hasExtra) opts.requestInit = { headers: extraHeaders };
413
+ return opts;
414
+ }
415
+ if (auth && isAsyncAuth(auth)) {
416
+ const customFetch = async (url, init) => {
417
+ const authHeaders = await auth.getHeaders();
418
+ return fetch(url, {
419
+ ...init,
420
+ headers: {
421
+ ...authHeaders,
422
+ ...extraHeaders,
423
+ ...normalizeHeaders(init?.headers)
424
+ }
425
+ });
426
+ };
427
+ return { fetch: customFetch };
428
+ }
429
+ const staticHeaders = {
430
+ ...auth ? auth.getHeaders() : {},
431
+ ...extraHeaders
432
+ };
433
+ return {
434
+ requestInit: { headers: staticHeaders },
435
+ // Also exposed for SSE's EventSource connection (eventsource npm pkg
436
+ // accepts headers in EventSourceInit, unlike the browser API).
437
+ eventSourceInit: { headers: staticHeaders }
438
+ };
439
+ }
440
+ function urlToTransport(url, auth, extraHeaders = {}) {
441
+ const { requestInit, fetch: customFetch, eventSourceInit, authProvider } = buildHttpOptions(auth, extraHeaders);
442
+ const segments = url.pathname.split("/");
443
+ const isSSE = segments.includes("sse") || url.pathname.endsWith("/sse");
444
+ if (isSSE) {
445
+ return new SSEClientTransport(url, {
446
+ ...authProvider ? { authProvider } : {},
447
+ ...requestInit ? { requestInit } : {},
448
+ ...eventSourceInit ? { eventSourceInit } : {},
449
+ ...customFetch ? { fetch: customFetch } : {}
450
+ });
451
+ }
452
+ return new StreamableHTTPClientTransport(url, {
453
+ ...authProvider ? { authProvider } : {},
454
+ ...requestInit ? { requestInit } : {},
455
+ ...customFetch ? { fetch: customFetch } : {}
456
+ });
457
+ }
458
+ function resolveEntryTransport(entry, auth) {
459
+ if (isMcpServerLike(entry)) {
460
+ const [serverSide, clientSide] = InMemoryTransport.createLinkedPair();
461
+ return {
462
+ transport: clientSide,
463
+ beforeConnect: () => entry.connect(serverSide)
464
+ };
465
+ }
466
+ const entryAuth = resolveEntryAuth(entry.auth) ?? auth;
467
+ if ("url" in entry) {
468
+ const url = new URL(entry.url);
469
+ const extraHeaders = entry.headers ?? {};
470
+ return { transport: urlToTransport(url, entryAuth, extraHeaders) };
471
+ }
472
+ const cmd = entry;
473
+ return {
474
+ transport: new StdioClientTransport({
475
+ command: cmd.command,
476
+ args: cmd.args,
477
+ env: cmd.env
478
+ })
479
+ };
480
+ }
481
+ function resolveEntryAuth(auth) {
482
+ if (!auth) return void 0;
483
+ if (typeof auth === "string") return new BearerAuth(auth);
484
+ return auth;
485
+ }
486
+ function resolveTransport(input, auth) {
487
+ if (input instanceof StdioTransport) {
488
+ return {
489
+ transport: new StdioClientTransport({
490
+ command: input.command,
491
+ args: input.args,
492
+ env: input.env,
493
+ cwd: input.cwd
494
+ })
495
+ };
496
+ }
497
+ if (typeof input === "string") {
498
+ const url = new URL(input);
499
+ return { transport: urlToTransport(url, auth) };
500
+ }
501
+ if (typeof input === "object" && input !== null && "start" in input && typeof input.start === "function") {
502
+ return { transport: input };
503
+ }
504
+ if (isMcpConfig(input)) {
505
+ const entries = Object.entries(input.mcpServers);
506
+ if (entries.length === 0) throw new Error("mcpServers config is empty");
507
+ const [, entry] = entries[0];
508
+ return resolveEntryTransport(entry, auth);
509
+ }
510
+ if (isMcpServerLike(input)) {
511
+ const [serverSide, clientSide] = InMemoryTransport.createLinkedPair();
512
+ return {
513
+ transport: clientSide,
514
+ beforeConnect: () => input.connect(serverSide)
515
+ };
516
+ }
517
+ throw new Error(
518
+ "Unrecognized transport input: expected a URL string, FastMCP server instance, mcpServers config object, StdioTransport, or SDK Transport"
519
+ );
520
+ }
521
+
522
+ // src/client/client.ts
523
+ import { resolve } from "path";
524
+ import { pathToFileURL } from "url";
525
+ import { Client as SdkClient2 } from "@modelcontextprotocol/sdk/client";
526
+ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
527
+ import {
528
+ CompatibilityCallToolResultSchema as CompatibilityCallToolResultSchema2,
529
+ CreateMessageRequestSchema as CreateMessageRequestSchema2,
530
+ ElicitRequestSchema as ElicitRequestSchema2,
531
+ ListRootsRequestSchema as ListRootsRequestSchema2,
532
+ LoggingMessageNotificationSchema as LoggingMessageNotificationSchema2,
533
+ ResourceUpdatedNotificationSchema as ResourceUpdatedNotificationSchema2
534
+ } from "@modelcontextprotocol/sdk/types";
535
+
536
+ // src/client/multi-server.ts
537
+ import { Client as SdkClient } from "@modelcontextprotocol/sdk/client";
538
+ import {
539
+ CompatibilityCallToolResultSchema,
540
+ LoggingMessageNotificationSchema,
541
+ ResourceUpdatedNotificationSchema,
542
+ CreateMessageRequestSchema,
543
+ ElicitRequestSchema,
544
+ ListRootsRequestSchema
545
+ } from "@modelcontextprotocol/sdk/types";
546
+ var MultiServerClient = class _MultiServerClient {
547
+ _clients = /* @__PURE__ */ new Map();
548
+ _connectPromise = null;
549
+ _connected = false;
550
+ /** URI (or namespaced template URI) → server name, populated by list calls */
551
+ _uriMap = /* @__PURE__ */ new Map();
552
+ _config;
553
+ _handlers;
554
+ _roots;
555
+ _defaultOptions;
556
+ _resourceSubscriptions = /* @__PURE__ */ new Map();
557
+ constructor(config, options) {
558
+ this._config = config;
559
+ this._handlers = {
560
+ log: options?.handlers?.log ?? defaultLogHandler,
561
+ progress: options?.handlers?.progress ?? defaultProgressHandler,
562
+ sampling: options?.handlers?.sampling,
563
+ elicitation: options?.handlers?.elicitation
564
+ };
565
+ this._roots = options?.roots;
566
+ this._defaultOptions = options?.defaultOptions ?? {};
567
+ }
568
+ // -------------------------------------------------------------------------
569
+ // Lifecycle
570
+ // -------------------------------------------------------------------------
571
+ async connect() {
572
+ if (this._connected) return;
573
+ if (this._connectPromise) {
574
+ await this._connectPromise;
575
+ return;
576
+ }
577
+ this._connectPromise = this._doConnect().finally(() => {
578
+ this._connectPromise = null;
579
+ });
580
+ await this._connectPromise;
581
+ }
582
+ async _doConnect() {
583
+ const entries = Object.entries(this._config.mcpServers);
584
+ if (entries.length === 0) throw new Error("mcpServers config is empty");
585
+ const connected = [];
586
+ try {
587
+ await Promise.all(
588
+ entries.map(async ([name, entry]) => {
589
+ const sdk = this._buildSdkClient();
590
+ this._registerHandlers(sdk);
591
+ const { transport, beforeConnect } = resolveEntryTransport(
592
+ entry
593
+ );
594
+ if (beforeConnect) await beforeConnect();
595
+ await sdk.connect(transport);
596
+ connected.push([name, sdk]);
597
+ })
598
+ );
599
+ } catch (err) {
600
+ await Promise.allSettled(connected.map(([, sdk]) => sdk.close()));
601
+ throw err;
602
+ }
603
+ for (const [name, sdk] of connected) {
604
+ this._clients.set(name, sdk);
605
+ }
606
+ this._connected = true;
607
+ }
608
+ async close() {
609
+ if (!this._connected) return;
610
+ this._connected = false;
611
+ this._uriMap.clear();
612
+ await Promise.allSettled([...this._clients.values()].map((sdk) => sdk.close()));
613
+ this._clients.clear();
614
+ }
615
+ async [Symbol.asyncDispose]() {
616
+ await this.close();
617
+ }
618
+ isConnected() {
619
+ return this._connected;
620
+ }
621
+ static async connect(config, options) {
622
+ const client = new _MultiServerClient(config, options);
623
+ await client.connect();
624
+ return client;
625
+ }
626
+ // -------------------------------------------------------------------------
627
+ // Core protocol
628
+ // -------------------------------------------------------------------------
629
+ async ping(options) {
630
+ this._assertConnected();
631
+ await Promise.all(
632
+ [...this._clients.values()].map(
633
+ (sdk) => sdk.ping(this._toSdkOptions(options))
634
+ )
635
+ );
636
+ return true;
637
+ }
638
+ // -------------------------------------------------------------------------
639
+ // Tools
640
+ // -------------------------------------------------------------------------
641
+ async listTools(options) {
642
+ this._assertConnected();
643
+ const results = await Promise.all(
644
+ [...this._clients.entries()].map(async ([name, sdk]) => {
645
+ const r = await sdk.listTools(
646
+ void 0,
647
+ this._toSdkOptions(options, void 0, this._defaultOptions.tool?.timeout)
648
+ );
649
+ return r.tools.map((t) => ({ ...t, name: `${name}_${t.name}` }));
650
+ })
651
+ );
652
+ return results.flat();
653
+ }
654
+ async callTool(name, args, options) {
655
+ const raw = await this.callToolRaw(name, args, options);
656
+ if (raw.isError) {
657
+ const message = raw.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n") || "Tool returned an error";
658
+ throw new ToolCallError(message, raw.content);
659
+ }
660
+ return raw;
661
+ }
662
+ async callToolRaw(name, args, options) {
663
+ const { serverName, localName } = this._parseNamespacedName(name);
664
+ const sdk = this._sdkForServer(serverName);
665
+ const sdkOptions = this._toSdkOptions(
666
+ options,
667
+ options?.onProgress,
668
+ this._defaultOptions.tool?.timeout
669
+ );
670
+ const result = await sdk.callTool(
671
+ { name: localName, arguments: args ?? {} },
672
+ CompatibilityCallToolResultSchema,
673
+ sdkOptions
674
+ );
675
+ return {
676
+ content: result.content,
677
+ structuredContent: result.structuredContent ?? null,
678
+ isError: result.isError === true
679
+ };
680
+ }
681
+ // -------------------------------------------------------------------------
682
+ // Resources
683
+ // -------------------------------------------------------------------------
684
+ async listResources(options) {
685
+ this._assertConnected();
686
+ const results = await Promise.all(
687
+ [...this._clients.entries()].map(async ([name, sdk]) => {
688
+ const r = await sdk.listResources(
689
+ void 0,
690
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
691
+ );
692
+ const resources = r.resources;
693
+ for (const res of resources) {
694
+ this._uriMap.set(res.uri, name);
695
+ }
696
+ return resources.map((res) => ({
697
+ ...res,
698
+ name: res.name ? `${name}_${res.name}` : res.name
699
+ }));
700
+ })
701
+ );
702
+ return results.flat();
703
+ }
704
+ async listResourceTemplates(options) {
705
+ this._assertConnected();
706
+ const results = await Promise.all(
707
+ [...this._clients.entries()].map(async ([name, sdk]) => {
708
+ const r = await sdk.listResourceTemplates(
709
+ void 0,
710
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
711
+ );
712
+ const templates = r.resourceTemplates;
713
+ for (const tmpl of templates) {
714
+ this._uriMap.set(tmpl.uriTemplate, name);
715
+ }
716
+ return templates.map((tmpl) => ({
717
+ ...tmpl,
718
+ name: tmpl.name ? `${name}_${tmpl.name}` : tmpl.name
719
+ }));
720
+ })
721
+ );
722
+ return results.flat();
723
+ }
724
+ async readResource(uri, options) {
725
+ this._assertConnected();
726
+ const sdkOptions = this._toSdkOptions(
727
+ options,
728
+ void 0,
729
+ this._defaultOptions.resource?.timeout
730
+ );
731
+ const knownServer = this._uriMap.get(uri);
732
+ if (knownServer) {
733
+ const sdk = this._clients.get(knownServer);
734
+ const result = await sdk.readResource({ uri }, sdkOptions);
735
+ return result.contents;
736
+ }
737
+ const errors = [];
738
+ for (const sdk of this._clients.values()) {
739
+ try {
740
+ const result = await sdk.readResource({ uri }, sdkOptions);
741
+ return result.contents;
742
+ } catch (err) {
743
+ errors.push(err);
744
+ }
745
+ }
746
+ throw new Error(
747
+ `Resource not found on any server: ${uri}
748
+ ` + errors.map((e) => String(e)).join("\n")
749
+ );
750
+ }
751
+ // -------------------------------------------------------------------------
752
+ // Prompts
753
+ // -------------------------------------------------------------------------
754
+ async listPrompts(options) {
755
+ this._assertConnected();
756
+ const results = await Promise.all(
757
+ [...this._clients.entries()].map(async ([name, sdk]) => {
758
+ const r = await sdk.listPrompts(
759
+ void 0,
760
+ this._toSdkOptions(options, void 0, this._defaultOptions.prompt?.timeout)
761
+ );
762
+ return r.prompts.map((p) => ({ ...p, name: `${name}_${p.name}` }));
763
+ })
764
+ );
765
+ return results.flat();
766
+ }
767
+ async getPrompt(name, args, options) {
768
+ const { serverName, localName } = this._parseNamespacedName(name);
769
+ const sdk = this._sdkForServer(serverName);
770
+ const result = await sdk.getPrompt(
771
+ { name: localName, arguments: args },
772
+ this._toSdkOptions(options, void 0, this._defaultOptions.prompt?.timeout)
773
+ );
774
+ return result;
775
+ }
776
+ // -------------------------------------------------------------------------
777
+ // Resource subscriptions
778
+ // -------------------------------------------------------------------------
779
+ async subscribeResource(uri, handler, options) {
780
+ this._assertConnected();
781
+ this._resourceSubscriptions.set(uri, handler);
782
+ const serverName = this._uriMap.get(uri);
783
+ if (serverName) {
784
+ await this._clients.get(serverName).subscribeResource(
785
+ { uri },
786
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
787
+ );
788
+ return;
789
+ }
790
+ const errors = [];
791
+ for (const sdk of this._clients.values()) {
792
+ try {
793
+ await sdk.subscribeResource(
794
+ { uri },
795
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
796
+ );
797
+ return;
798
+ } catch (err) {
799
+ errors.push(err);
800
+ }
801
+ }
802
+ throw new Error(`Subscribe failed on all servers:
803
+ ${errors.map(String).join("\n")}`);
804
+ }
805
+ async unsubscribeResource(uri, options) {
806
+ this._assertConnected();
807
+ this._resourceSubscriptions.delete(uri);
808
+ const serverName = this._uriMap.get(uri);
809
+ if (serverName) {
810
+ await this._clients.get(serverName).unsubscribeResource(
811
+ { uri },
812
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
813
+ );
814
+ return;
815
+ }
816
+ await Promise.allSettled(
817
+ [...this._clients.values()].map(
818
+ (sdk) => sdk.unsubscribeResource(
819
+ { uri },
820
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
821
+ )
822
+ )
823
+ );
824
+ }
825
+ // -------------------------------------------------------------------------
826
+ // Completion
827
+ // -------------------------------------------------------------------------
828
+ async complete(ref, argument, context, options) {
829
+ this._assertConnected();
830
+ const sdkOptions = this._toSdkOptions(options);
831
+ if (ref.type === "ref/prompt") {
832
+ const { serverName: serverName2, localName } = this._parseNamespacedName(ref.name);
833
+ const sdk = this._sdkForServer(serverName2);
834
+ const result = await sdk.complete(
835
+ { ref: { type: "ref/prompt", name: localName }, argument, ...context ? { context } : {} },
836
+ sdkOptions
837
+ );
838
+ return result.completion;
839
+ }
840
+ const serverName = this._uriMap.get(ref.uri);
841
+ if (serverName) {
842
+ const result = await this._clients.get(serverName).complete(
843
+ { ref, argument, ...context ? { context } : {} },
844
+ sdkOptions
845
+ );
846
+ return result.completion;
847
+ }
848
+ const errors = [];
849
+ for (const sdk of this._clients.values()) {
850
+ try {
851
+ const result = await sdk.complete(
852
+ { ref, argument, ...context ? { context } : {} },
853
+ sdkOptions
854
+ );
855
+ return result.completion;
856
+ } catch (err) {
857
+ errors.push(err);
858
+ }
859
+ }
860
+ throw new Error(`Completion failed on all servers:
861
+ ${errors.map(String).join("\n")}`);
862
+ }
863
+ // -------------------------------------------------------------------------
864
+ // Logging
865
+ // -------------------------------------------------------------------------
866
+ async setLogLevel(level, options) {
867
+ this._assertConnected();
868
+ await Promise.all(
869
+ [...this._clients.values()].map(
870
+ (sdk) => sdk.setLoggingLevel(level, this._toSdkOptions(options))
871
+ )
872
+ );
873
+ }
874
+ // -------------------------------------------------------------------------
875
+ // Private helpers
876
+ // -------------------------------------------------------------------------
877
+ _assertConnected() {
878
+ if (!this._connected) {
879
+ throw new Error("MultiServerClient is not connected. Call connect() first.");
880
+ }
881
+ }
882
+ _parseNamespacedName(name) {
883
+ const serverNames = [...this._clients.keys()].sort((a, b) => b.length - a.length);
884
+ for (const serverName of serverNames) {
885
+ const prefix = `${serverName}_`;
886
+ if (name.startsWith(prefix)) {
887
+ return { serverName, localName: name.slice(prefix.length) };
888
+ }
889
+ }
890
+ throw new Error(
891
+ `Tool/prompt name "${name}" has no server namespace prefix. Expected format: "<serverName>_<name>".`
892
+ );
893
+ }
894
+ _sdkForServer(serverName) {
895
+ const sdk = this._clients.get(serverName);
896
+ if (!sdk) {
897
+ throw new Error(
898
+ `No server named "${serverName}" in this MultiServerClient. Known servers: ${[...this._clients.keys()].join(", ")}.`
899
+ );
900
+ }
901
+ return sdk;
902
+ }
903
+ _buildSdkClient() {
904
+ return new SdkClient(
905
+ { name: "fastmcp-ts", version: "1.0.0" },
906
+ { capabilities: this._buildCapabilities() }
907
+ );
908
+ }
909
+ _buildCapabilities() {
910
+ return {
911
+ ...this._handlers.sampling ? { sampling: { tools: {} } } : {},
912
+ ...this._handlers.elicitation ? { elicitation: {} } : {},
913
+ ...this._roots ? { roots: { listChanged: false } } : {}
914
+ };
915
+ }
916
+ _registerHandlers(sdk) {
917
+ sdk.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => {
918
+ void this._handlers.log({
919
+ level: notification.params.level,
920
+ logger: notification.params.logger ?? void 0,
921
+ data: notification.params.data
922
+ });
923
+ });
924
+ sdk.setNotificationHandler(ResourceUpdatedNotificationSchema, (notification) => {
925
+ const handler = this._resourceSubscriptions.get(notification.params.uri);
926
+ if (handler) void handler(notification.params.uri);
927
+ });
928
+ if (this._handlers.sampling) {
929
+ const h = this._handlers.sampling;
930
+ sdk.setRequestHandler(CreateMessageRequestSchema, async (req) => h(req.params));
931
+ }
932
+ if (this._handlers.elicitation) {
933
+ const h = this._handlers.elicitation;
934
+ sdk.setRequestHandler(ElicitRequestSchema, async (req) => h(req.params));
935
+ }
936
+ if (this._roots) {
937
+ const roots = this._roots;
938
+ sdk.setRequestHandler(ListRootsRequestSchema, async () => ({
939
+ roots: roots.map((uri) => ({ uri }))
940
+ }));
941
+ }
942
+ }
943
+ _toSdkOptions(options, overrideProgress, scopedTimeoutSeconds) {
944
+ const timeoutSeconds = options?.timeout ?? scopedTimeoutSeconds ?? this._defaultOptions.timeout;
945
+ const progressHandler = overrideProgress ?? this._handlers.progress;
946
+ const sdkOptions = {};
947
+ if (timeoutSeconds != null) sdkOptions.timeout = timeoutSeconds * 1e3;
948
+ if (options?.signal) sdkOptions.signal = options.signal;
949
+ if (progressHandler) {
950
+ sdkOptions.onprogress = ({ progress, total, message }) => {
951
+ void progressHandler(progress, total, message);
952
+ };
953
+ }
954
+ return sdkOptions;
955
+ }
956
+ };
957
+
958
+ // src/client/client.ts
959
+ var ToolCallError = class extends Error {
960
+ content;
961
+ constructor(message, content) {
962
+ super(message);
963
+ this.name = "ToolCallError";
964
+ this.content = content;
965
+ }
966
+ };
967
+ var Client = class _Client {
968
+ _sdkClient = null;
969
+ _refCount = 0;
970
+ _connectPromise = null;
971
+ _resourceSubscriptions = /* @__PURE__ */ new Map();
972
+ _input;
973
+ _auth;
974
+ _handlers;
975
+ _roots;
976
+ _autoInitialize;
977
+ _defaultOptions;
978
+ constructor(input, options) {
979
+ this._input = input;
980
+ this._auth = resolveAuth(options?.auth);
981
+ this._handlers = {
982
+ log: options?.handlers?.log ?? defaultLogHandler,
983
+ progress: options?.handlers?.progress ?? defaultProgressHandler,
984
+ sampling: options?.handlers?.sampling,
985
+ elicitation: options?.handlers?.elicitation,
986
+ onToolsListChanged: options?.handlers?.onToolsListChanged,
987
+ onResourcesListChanged: options?.handlers?.onResourcesListChanged,
988
+ onPromptsListChanged: options?.handlers?.onPromptsListChanged
989
+ };
990
+ this._roots = options?.roots ? normalizeRootsOption(options.roots) : void 0;
991
+ this._autoInitialize = options?.autoInitialize ?? true;
992
+ this._defaultOptions = options?.defaultOptions ?? {};
993
+ }
994
+ // -------------------------------------------------------------------------
995
+ // Lifecycle
996
+ // -------------------------------------------------------------------------
997
+ async connect() {
998
+ this._refCount++;
999
+ if (this._connectPromise) {
1000
+ await this._connectPromise;
1001
+ return;
1002
+ }
1003
+ if (this._sdkClient) return;
1004
+ this._connectPromise = this._doConnect().finally(() => {
1005
+ this._connectPromise = null;
1006
+ });
1007
+ try {
1008
+ await this._connectPromise;
1009
+ } catch (err) {
1010
+ this._refCount = Math.max(0, this._refCount - 1);
1011
+ throw err;
1012
+ }
1013
+ }
1014
+ async _doConnect() {
1015
+ const sdkClient = new SdkClient2(
1016
+ { name: "fastmcp-ts", version: "1.0.0" },
1017
+ {
1018
+ capabilities: this._buildCapabilities(),
1019
+ listChanged: this._buildListChangedConfig()
1020
+ }
1021
+ );
1022
+ this._registerHandlers(sdkClient);
1023
+ const { transport, beforeConnect } = resolveTransport(this._input, this._auth);
1024
+ if (this._auth instanceof OAuth) {
1025
+ const serverUrl = this._extractServerUrl();
1026
+ if (serverUrl) this._auth._bind(serverUrl);
1027
+ }
1028
+ if (beforeConnect) await beforeConnect();
1029
+ try {
1030
+ await sdkClient.connect(transport);
1031
+ } catch (err) {
1032
+ if (err instanceof UnauthorizedError && this._auth instanceof OAuth) {
1033
+ const code = await this._auth.waitForCallback();
1034
+ if ("finishAuth" in transport && typeof transport.finishAuth === "function") {
1035
+ await transport.finishAuth(
1036
+ code
1037
+ );
1038
+ }
1039
+ await sdkClient.connect(transport);
1040
+ } else {
1041
+ throw err;
1042
+ }
1043
+ }
1044
+ this._sdkClient = sdkClient;
1045
+ }
1046
+ _extractServerUrl() {
1047
+ const input = this._input;
1048
+ if (typeof input === "string") return input;
1049
+ if (typeof input === "object" && input !== null && "mcpServers" in input) {
1050
+ const entries = Object.entries(
1051
+ input.mcpServers
1052
+ );
1053
+ if (entries.length > 0) {
1054
+ const [, entry] = entries[0];
1055
+ if (entry && "url" in entry && typeof entry.url === "string") {
1056
+ return entry.url;
1057
+ }
1058
+ }
1059
+ }
1060
+ return void 0;
1061
+ }
1062
+ async close() {
1063
+ this._refCount = Math.max(0, this._refCount - 1);
1064
+ if (this._refCount > 0) return;
1065
+ const sdk = this._sdkClient;
1066
+ this._sdkClient = null;
1067
+ if (sdk) await sdk.close();
1068
+ }
1069
+ async [Symbol.asyncDispose]() {
1070
+ await this.close();
1071
+ }
1072
+ isConnected() {
1073
+ return this._sdkClient !== null;
1074
+ }
1075
+ static async connect(input, options) {
1076
+ if (typeof input === "object" && input !== null && "mcpServers" in input && Object.keys(input.mcpServers).length > 1) {
1077
+ return MultiServerClient.connect(input, options);
1078
+ }
1079
+ const client = new _Client(input, options);
1080
+ await client.connect();
1081
+ return client;
1082
+ }
1083
+ // -------------------------------------------------------------------------
1084
+ // Core protocol
1085
+ // -------------------------------------------------------------------------
1086
+ async ping(options) {
1087
+ await this._sdk().ping(this._toSdkOptions(options));
1088
+ return true;
1089
+ }
1090
+ // -------------------------------------------------------------------------
1091
+ // Tools (IToolsClient)
1092
+ // -------------------------------------------------------------------------
1093
+ async listTools(options) {
1094
+ const result = await this._sdk().listTools(
1095
+ void 0,
1096
+ this._toSdkOptions(options, void 0, this._defaultOptions.tool?.timeout)
1097
+ );
1098
+ return result.tools;
1099
+ }
1100
+ async callTool(name, args, options) {
1101
+ const raw = await this.callToolRaw(name, args, options);
1102
+ if (raw.isError) {
1103
+ const message = raw.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n") || "Tool returned an error";
1104
+ throw new ToolCallError(message, raw.content);
1105
+ }
1106
+ return raw;
1107
+ }
1108
+ /** Returns the full result including isError without throwing. */
1109
+ async callToolRaw(name, args, options) {
1110
+ const sdkOptions = this._toSdkOptions(
1111
+ options,
1112
+ options?.onProgress,
1113
+ this._defaultOptions.tool?.timeout
1114
+ );
1115
+ const result = await this._sdk().callTool(
1116
+ { name, arguments: args ?? {} },
1117
+ CompatibilityCallToolResultSchema2,
1118
+ sdkOptions
1119
+ );
1120
+ return {
1121
+ content: result.content,
1122
+ structuredContent: result.structuredContent ?? null,
1123
+ isError: result.isError === true
1124
+ };
1125
+ }
1126
+ // -------------------------------------------------------------------------
1127
+ // Resources (IResourcesClient)
1128
+ // -------------------------------------------------------------------------
1129
+ async listResources(options) {
1130
+ const result = await this._sdk().listResources(
1131
+ void 0,
1132
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
1133
+ );
1134
+ return result.resources;
1135
+ }
1136
+ async listResourceTemplates(options) {
1137
+ const result = await this._sdk().listResourceTemplates(
1138
+ void 0,
1139
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
1140
+ );
1141
+ return result.resourceTemplates;
1142
+ }
1143
+ async readResource(uri, options) {
1144
+ const result = await this._sdk().readResource(
1145
+ { uri },
1146
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
1147
+ );
1148
+ return result.contents;
1149
+ }
1150
+ /** Returns the raw SDK ReadResourceResult without unwrapping. */
1151
+ async readResourceRaw(uri, options) {
1152
+ return this._sdk().readResource(
1153
+ { uri },
1154
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
1155
+ );
1156
+ }
1157
+ async subscribeResource(uri, handler, options) {
1158
+ this._resourceSubscriptions.set(uri, handler);
1159
+ await this._sdk().subscribeResource(
1160
+ { uri },
1161
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
1162
+ );
1163
+ }
1164
+ async unsubscribeResource(uri, options) {
1165
+ this._resourceSubscriptions.delete(uri);
1166
+ await this._sdk().unsubscribeResource(
1167
+ { uri },
1168
+ this._toSdkOptions(options, void 0, this._defaultOptions.resource?.timeout)
1169
+ );
1170
+ }
1171
+ // -------------------------------------------------------------------------
1172
+ // Prompts (IPromptsClient)
1173
+ // -------------------------------------------------------------------------
1174
+ async listPrompts(options) {
1175
+ const result = await this._sdk().listPrompts(
1176
+ void 0,
1177
+ this._toSdkOptions(options, void 0, this._defaultOptions.prompt?.timeout)
1178
+ );
1179
+ return result.prompts;
1180
+ }
1181
+ async getPrompt(name, args, options) {
1182
+ const result = await this._sdk().getPrompt(
1183
+ { name, arguments: args },
1184
+ this._toSdkOptions(options, void 0, this._defaultOptions.prompt?.timeout)
1185
+ );
1186
+ return result;
1187
+ }
1188
+ // -------------------------------------------------------------------------
1189
+ // Completion
1190
+ // -------------------------------------------------------------------------
1191
+ async complete(ref, argument, context, options) {
1192
+ const result = await this._sdk().complete(
1193
+ { ref, argument, ...context ? { context } : {} },
1194
+ this._toSdkOptions(options)
1195
+ );
1196
+ return result.completion;
1197
+ }
1198
+ // -------------------------------------------------------------------------
1199
+ // Logging
1200
+ // -------------------------------------------------------------------------
1201
+ async setLogLevel(level, options) {
1202
+ await this._sdk().setLoggingLevel(level, this._toSdkOptions(options));
1203
+ }
1204
+ // -------------------------------------------------------------------------
1205
+ // Private helpers
1206
+ // -------------------------------------------------------------------------
1207
+ _sdk() {
1208
+ if (!this._sdkClient) {
1209
+ throw new Error("Client is not connected. Call connect() first.");
1210
+ }
1211
+ return this._sdkClient;
1212
+ }
1213
+ _toSdkOptions(options, overrideProgress, scopedTimeoutSeconds) {
1214
+ const timeoutSeconds = options?.timeout ?? scopedTimeoutSeconds ?? this._defaultOptions.timeout;
1215
+ const progressHandler = overrideProgress ?? this._handlers.progress;
1216
+ const sdkOptions = {};
1217
+ if (timeoutSeconds != null) sdkOptions.timeout = timeoutSeconds * 1e3;
1218
+ if (options?.signal) sdkOptions.signal = options.signal;
1219
+ if (progressHandler) {
1220
+ sdkOptions.onprogress = ({ progress, total, message }) => {
1221
+ void progressHandler(progress, total, message);
1222
+ };
1223
+ }
1224
+ return sdkOptions;
1225
+ }
1226
+ _buildCapabilities() {
1227
+ return {
1228
+ ...this._handlers.sampling ? { sampling: { tools: {} } } : {},
1229
+ ...this._handlers.elicitation ? { elicitation: {} } : {},
1230
+ ...this._roots ? { roots: { listChanged: true } } : {}
1231
+ };
1232
+ }
1233
+ _buildListChangedConfig() {
1234
+ const { onToolsListChanged, onResourcesListChanged, onPromptsListChanged } = this._handlers;
1235
+ if (!onToolsListChanged && !onResourcesListChanged && !onPromptsListChanged) return void 0;
1236
+ const adapt = (h) => ({
1237
+ onChanged: (err, items) => {
1238
+ void h.onChanged(err, items);
1239
+ },
1240
+ ...h.autoRefresh !== void 0 ? { autoRefresh: h.autoRefresh } : {},
1241
+ ...h.debounceMs !== void 0 ? { debounceMs: h.debounceMs } : {}
1242
+ });
1243
+ return {
1244
+ ...onToolsListChanged ? { tools: adapt(onToolsListChanged) } : {},
1245
+ ...onResourcesListChanged ? { resources: adapt(onResourcesListChanged) } : {},
1246
+ ...onPromptsListChanged ? { prompts: adapt(onPromptsListChanged) } : {}
1247
+ };
1248
+ }
1249
+ _registerHandlers(sdk) {
1250
+ sdk.setNotificationHandler(LoggingMessageNotificationSchema2, (notification) => {
1251
+ void this._handlers.log({
1252
+ level: notification.params.level,
1253
+ logger: notification.params.logger ?? void 0,
1254
+ data: notification.params.data
1255
+ });
1256
+ });
1257
+ sdk.setNotificationHandler(ResourceUpdatedNotificationSchema2, (notification) => {
1258
+ const handler = this._resourceSubscriptions.get(notification.params.uri);
1259
+ if (handler) void handler(notification.params.uri);
1260
+ });
1261
+ if (this._handlers.sampling) {
1262
+ const samplingHandler = this._handlers.sampling;
1263
+ sdk.setRequestHandler(CreateMessageRequestSchema2, async (request) => {
1264
+ return samplingHandler(request.params);
1265
+ });
1266
+ }
1267
+ if (this._handlers.elicitation) {
1268
+ const elicitationHandler = this._handlers.elicitation;
1269
+ sdk.setRequestHandler(ElicitRequestSchema2, async (request) => {
1270
+ return elicitationHandler(request.params);
1271
+ });
1272
+ }
1273
+ if (this._roots) {
1274
+ const getRoots = this._roots;
1275
+ sdk.setRequestHandler(ListRootsRequestSchema2, async () => ({
1276
+ roots: await getRoots()
1277
+ }));
1278
+ }
1279
+ }
1280
+ // -------------------------------------------------------------------------
1281
+ // Roots
1282
+ // -------------------------------------------------------------------------
1283
+ /**
1284
+ * Notify the connected server that the client's roots list has changed.
1285
+ * The server should re-issue a roots/list request to get the updated list.
1286
+ */
1287
+ async notifyRootsChanged() {
1288
+ await this._sdk().sendRootsListChanged();
1289
+ }
1290
+ };
1291
+ function resolveAuth(auth) {
1292
+ if (!auth) return void 0;
1293
+ if (typeof auth === "string") return new BearerAuth(auth);
1294
+ return auth;
1295
+ }
1296
+ function normalizeRootsOption(roots) {
1297
+ if (typeof roots === "function") {
1298
+ return async () => (await roots()).map(normalizeRootInput);
1299
+ }
1300
+ const normalized = roots.map(normalizeRootInput);
1301
+ return () => Promise.resolve(normalized);
1302
+ }
1303
+ function normalizeRootInput(input) {
1304
+ if (typeof input === "string") return { uri: normalizeRootUri(input) };
1305
+ return { ...input, uri: normalizeRootUri(input.uri) };
1306
+ }
1307
+ function normalizeRootUri(input) {
1308
+ if (input.startsWith("file://")) return input;
1309
+ return pathToFileURL(resolve(input)).href;
1310
+ }
1311
+
1312
+ // src/client/utils.ts
1313
+ async function toResult(fn) {
1314
+ try {
1315
+ const value = await (typeof fn === "function" ? fn() : fn);
1316
+ return { ok: true, value };
1317
+ } catch (error) {
1318
+ return { ok: false, error };
1319
+ }
1320
+ }
1321
+
1322
+ // src/client/sampling/types.ts
1323
+ function resolveModel(prefs, selector, defaultModel) {
1324
+ if (!selector) {
1325
+ return prefs?.hints?.[0]?.name ?? defaultModel;
1326
+ }
1327
+ if (typeof selector === "string") return selector;
1328
+ return selector(prefs);
1329
+ }
1330
+
1331
+ // src/client/sampling/generic.ts
1332
+ var GenericSamplingAdapter = class {
1333
+ _fn;
1334
+ _options;
1335
+ constructor(fn, options) {
1336
+ this._fn = fn;
1337
+ this._options = options ?? {};
1338
+ }
1339
+ asHandler() {
1340
+ return async (params) => {
1341
+ const model = resolveModel(
1342
+ params.modelPreferences,
1343
+ this._options.modelSelector,
1344
+ this._options.defaultModel ?? "unknown"
1345
+ );
1346
+ return this._fn({
1347
+ model,
1348
+ messages: params.messages,
1349
+ system: params.systemPrompt,
1350
+ maxTokens: params.maxTokens ?? 1024,
1351
+ temperature: params.temperature,
1352
+ stopSequences: params.stopSequences,
1353
+ tools: params.tools,
1354
+ toolChoice: params.toolChoice,
1355
+ onToken: this._options.onToken
1356
+ });
1357
+ };
1358
+ }
1359
+ };
1360
+
1361
+ // src/client/sampling/anthropic.ts
1362
+ function toBlocks(content) {
1363
+ const items = Array.isArray(content) ? content : [content];
1364
+ const out = [];
1365
+ for (const block of items) {
1366
+ if (block.type === "text") {
1367
+ out.push({ type: "text", text: block.text });
1368
+ } else if (block.type === "image") {
1369
+ out.push({
1370
+ type: "image",
1371
+ source: {
1372
+ type: "base64",
1373
+ media_type: block.mimeType,
1374
+ data: block.data
1375
+ }
1376
+ });
1377
+ } else if (block.type === "tool_use") {
1378
+ out.push({
1379
+ type: "tool_use",
1380
+ id: block.id,
1381
+ name: block.name,
1382
+ input: block.input
1383
+ });
1384
+ } else if (block.type === "tool_result") {
1385
+ const resultContent = block.content.flatMap(
1386
+ (c) => c.type === "text" ? [{ type: "text", text: c.text }] : []
1387
+ );
1388
+ out.push({
1389
+ type: "tool_result",
1390
+ tool_use_id: block.toolUseId,
1391
+ content: resultContent,
1392
+ is_error: block.isError
1393
+ });
1394
+ }
1395
+ }
1396
+ return out;
1397
+ }
1398
+ function toAnthropicMessages(messages) {
1399
+ return messages.map((m) => ({
1400
+ role: m.role,
1401
+ content: toBlocks(m.content)
1402
+ }));
1403
+ }
1404
+ function toAnthropicTools(tools) {
1405
+ return tools.map((t) => ({
1406
+ name: t.name,
1407
+ description: t.description ?? "",
1408
+ input_schema: t.inputSchema
1409
+ }));
1410
+ }
1411
+ function toAnthropicToolChoice(choice) {
1412
+ if (!choice) return void 0;
1413
+ if (choice.mode === "required") return { type: "any" };
1414
+ return { type: "auto" };
1415
+ }
1416
+ function mapStopReason(reason) {
1417
+ if (reason === "end_turn") return "endTurn";
1418
+ if (reason === "stop_sequence") return "stopSequence";
1419
+ if (reason === "max_tokens") return "maxTokens";
1420
+ if (reason === "tool_use") return "toolUse";
1421
+ return reason ?? "endTurn";
1422
+ }
1423
+ function buildResult(message) {
1424
+ const stopReason = mapStopReason(message.stop_reason);
1425
+ if (stopReason === "toolUse") {
1426
+ const content = message.content.flatMap((block) => {
1427
+ if (block.type === "tool_use") {
1428
+ return [{
1429
+ type: "tool_use",
1430
+ id: block.id,
1431
+ name: block.name,
1432
+ input: block.input
1433
+ }];
1434
+ }
1435
+ return [];
1436
+ });
1437
+ return { role: "assistant", model: message.model, stopReason, content };
1438
+ }
1439
+ const textBlock = message.content.find((b) => b.type === "text");
1440
+ const text = textBlock?.type === "text" ? textBlock.text : "";
1441
+ return { role: "assistant", model: message.model, stopReason, content: { type: "text", text } };
1442
+ }
1443
+ var AnthropicSamplingAdapter = class {
1444
+ _client;
1445
+ _options;
1446
+ constructor(client, options) {
1447
+ this._client = client;
1448
+ this._options = options ?? {};
1449
+ }
1450
+ asHandler() {
1451
+ return async (params) => {
1452
+ const model = resolveModel(
1453
+ params.modelPreferences,
1454
+ this._options.modelSelector,
1455
+ this._options.defaultModel ?? "claude-opus-4-5"
1456
+ );
1457
+ const omitTools = params.toolChoice?.mode === "none";
1458
+ const tools = !omitTools && params.tools?.length ? toAnthropicTools(params.tools) : void 0;
1459
+ const toolChoice = tools ? toAnthropicToolChoice(params.toolChoice) : void 0;
1460
+ const onToken = this._options.onToken;
1461
+ const stream = this._client.messages.stream({
1462
+ model,
1463
+ messages: toAnthropicMessages(params.messages),
1464
+ max_tokens: params.maxTokens ?? 1024,
1465
+ ...params.systemPrompt !== void 0 ? { system: params.systemPrompt } : {},
1466
+ ...params.temperature !== void 0 ? { temperature: params.temperature } : {},
1467
+ ...params.stopSequences?.length ? { stop_sequences: params.stopSequences } : {},
1468
+ ...tools ? { tools } : {},
1469
+ ...toolChoice ? { tool_choice: toolChoice } : {}
1470
+ });
1471
+ if (onToken) {
1472
+ stream.on("text", (text) => {
1473
+ onToken(text);
1474
+ });
1475
+ }
1476
+ const message = await stream.finalMessage();
1477
+ return buildResult(message);
1478
+ };
1479
+ }
1480
+ };
1481
+
1482
+ // src/client/sampling/openai.ts
1483
+ function toContentParts(content) {
1484
+ const items = Array.isArray(content) ? content : [content];
1485
+ if (items.length === 1 && items[0].type === "text") return items[0].text;
1486
+ return items.flatMap((block) => {
1487
+ if (block.type === "text") {
1488
+ return [{ type: "text", text: block.text }];
1489
+ } else if (block.type === "image") {
1490
+ return [{
1491
+ type: "image_url",
1492
+ image_url: { url: `data:${block.mimeType};base64,${block.data}` }
1493
+ }];
1494
+ }
1495
+ return [];
1496
+ });
1497
+ }
1498
+ function toOpenAIMessages(messages, systemPrompt) {
1499
+ const out = [];
1500
+ if (systemPrompt) {
1501
+ out.push({ role: "system", content: systemPrompt });
1502
+ }
1503
+ for (const m of messages) {
1504
+ const items = Array.isArray(m.content) ? m.content : [m.content];
1505
+ const toolUseItems = items.filter((b) => b.type === "tool_use");
1506
+ if (toolUseItems.length > 0) {
1507
+ out.push({
1508
+ role: "assistant",
1509
+ tool_calls: toolUseItems.map((b) => {
1510
+ if (b.type !== "tool_use") throw new Error("unreachable");
1511
+ return {
1512
+ id: b.id,
1513
+ type: "function",
1514
+ function: { name: b.name, arguments: JSON.stringify(b.input) }
1515
+ };
1516
+ })
1517
+ });
1518
+ continue;
1519
+ }
1520
+ const toolResultItems = items.filter((b) => b.type === "tool_result");
1521
+ if (toolResultItems.length > 0) {
1522
+ for (const b of toolResultItems) {
1523
+ if (b.type !== "tool_result") continue;
1524
+ const text = b.content.filter((c) => c.type === "text").map((c) => c.type === "text" ? c.text : "").join("\n");
1525
+ out.push({ role: "tool", tool_call_id: b.toolUseId, content: text });
1526
+ }
1527
+ continue;
1528
+ }
1529
+ const role = m.role;
1530
+ const content = toContentParts(m.content);
1531
+ if (role === "user") {
1532
+ out.push({ role, content });
1533
+ } else {
1534
+ out.push({ role, content: typeof content === "string" ? content : "" });
1535
+ }
1536
+ }
1537
+ return out;
1538
+ }
1539
+ function toOpenAITools(tools) {
1540
+ return tools.map((t) => ({
1541
+ type: "function",
1542
+ function: {
1543
+ name: t.name,
1544
+ ...t.description ? { description: t.description } : {},
1545
+ parameters: t.inputSchema
1546
+ }
1547
+ }));
1548
+ }
1549
+ function toOpenAIToolChoice(choice) {
1550
+ if (!choice) return void 0;
1551
+ if (choice.mode === "required") return "required";
1552
+ if (choice.mode === "none") return "none";
1553
+ return "auto";
1554
+ }
1555
+ function mapFinishReason(reason) {
1556
+ if (reason === "stop") return "endTurn";
1557
+ if (reason === "length") return "maxTokens";
1558
+ if (reason === "tool_calls") return "toolUse";
1559
+ return "endTurn";
1560
+ }
1561
+ function buildResult2(completion) {
1562
+ const choice = completion.choices[0];
1563
+ const stopReason = mapFinishReason(choice?.finish_reason);
1564
+ const model = completion.model;
1565
+ if (stopReason === "toolUse") {
1566
+ const calls = choice.message.tool_calls ?? [];
1567
+ const content = calls.flatMap((tc) => {
1568
+ if (tc.type !== "function") return [];
1569
+ const fn = tc.function;
1570
+ return [{
1571
+ type: "tool_use",
1572
+ id: tc.id,
1573
+ name: fn.name,
1574
+ input: JSON.parse(fn.arguments)
1575
+ }];
1576
+ });
1577
+ return { role: "assistant", model, stopReason, content };
1578
+ }
1579
+ const text = choice.message.content ?? "";
1580
+ return { role: "assistant", model, stopReason, content: { type: "text", text } };
1581
+ }
1582
+ var OpenAISamplingAdapter = class {
1583
+ _client;
1584
+ _options;
1585
+ constructor(client, options) {
1586
+ this._client = client;
1587
+ this._options = options ?? {};
1588
+ }
1589
+ asHandler() {
1590
+ return async (params) => {
1591
+ const model = resolveModel(
1592
+ params.modelPreferences,
1593
+ this._options.modelSelector,
1594
+ this._options.defaultModel ?? "gpt-4o"
1595
+ );
1596
+ const omitTools = params.toolChoice?.mode === "none";
1597
+ const tools = !omitTools && params.tools?.length ? toOpenAITools(params.tools) : void 0;
1598
+ const toolChoice = tools ? toOpenAIToolChoice(params.toolChoice) : void 0;
1599
+ const onToken = this._options.onToken;
1600
+ const stream = this._client.chat.completions.stream({
1601
+ model,
1602
+ messages: toOpenAIMessages(params.messages, params.systemPrompt),
1603
+ max_completion_tokens: params.maxTokens ?? 1024,
1604
+ ...params.temperature !== void 0 ? { temperature: params.temperature } : {},
1605
+ ...params.stopSequences?.length ? { stop: params.stopSequences } : {},
1606
+ ...tools ? { tools } : {},
1607
+ ...toolChoice !== void 0 ? { tool_choice: toolChoice } : {}
1608
+ });
1609
+ if (onToken) {
1610
+ stream.on("content", (delta) => {
1611
+ onToken(delta);
1612
+ });
1613
+ }
1614
+ const completion = await stream.finalChatCompletion();
1615
+ return buildResult2(completion);
1616
+ };
1617
+ }
1618
+ };
1619
+
1620
+ // src/client/sampling/google.ts
1621
+ function pruneTitle(schema) {
1622
+ const { title: _dropped, ...rest } = schema;
1623
+ if (rest.properties && typeof rest.properties === "object") {
1624
+ rest.properties = Object.fromEntries(
1625
+ Object.entries(rest.properties).map(([k, v]) => [
1626
+ k,
1627
+ pruneTitle(v)
1628
+ ])
1629
+ );
1630
+ }
1631
+ return rest;
1632
+ }
1633
+ function toGeminiRole(role) {
1634
+ return role === "assistant" ? "model" : "user";
1635
+ }
1636
+ function buildToolNameMap(messages) {
1637
+ const map = /* @__PURE__ */ new Map();
1638
+ for (const m of messages) {
1639
+ const items = Array.isArray(m.content) ? m.content : [m.content];
1640
+ for (const b of items) {
1641
+ if (b.type === "tool_use") map.set(b.id, b.name);
1642
+ }
1643
+ }
1644
+ return map;
1645
+ }
1646
+ function toGeminiContents(messages, toolNameById) {
1647
+ return messages.map((m) => {
1648
+ const items = Array.isArray(m.content) ? m.content : [m.content];
1649
+ const parts = items.flatMap((block) => {
1650
+ if (block.type === "text") {
1651
+ return [{ text: block.text }];
1652
+ } else if (block.type === "image") {
1653
+ return [{ inlineData: { data: block.data, mimeType: block.mimeType } }];
1654
+ } else if (block.type === "tool_use") {
1655
+ return [{ functionCall: { name: block.name, args: block.input } }];
1656
+ } else if (block.type === "tool_result") {
1657
+ const name = toolNameById.get(block.toolUseId) ?? block.toolUseId;
1658
+ const text = block.content.filter((c) => c.type === "text").map((c) => c.type === "text" ? c.text : "").join("\n");
1659
+ return [{ functionResponse: { name, response: { content: text } } }];
1660
+ }
1661
+ return [];
1662
+ });
1663
+ return { role: toGeminiRole(m.role), parts };
1664
+ });
1665
+ }
1666
+ function toGeminiFunctionDeclarations(tools) {
1667
+ return tools.map((t) => ({
1668
+ name: t.name,
1669
+ ...t.description ? { description: t.description } : {},
1670
+ parameters: pruneTitle(t.inputSchema)
1671
+ }));
1672
+ }
1673
+ function toGeminiToolConfig(choice) {
1674
+ if (!choice) return void 0;
1675
+ const mode = choice.mode === "required" ? "ANY" : choice.mode === "none" ? "NONE" : "AUTO";
1676
+ return { functionCallingConfig: { mode } };
1677
+ }
1678
+ function buildResult3(response, model) {
1679
+ const functionCalls = response.functionCalls;
1680
+ if (functionCalls?.length) {
1681
+ const content = functionCalls.map((fc, i) => ({
1682
+ type: "tool_use",
1683
+ id: fc.id ?? `tool-${i}`,
1684
+ name: fc.name ?? "",
1685
+ input: fc.args ?? {}
1686
+ }));
1687
+ return { role: "assistant", model, stopReason: "toolUse", content };
1688
+ }
1689
+ const candidate = response.candidates?.[0];
1690
+ const finishReason = candidate?.finishReason;
1691
+ const stopReason = finishReason === "MAX_TOKENS" ? "maxTokens" : "endTurn";
1692
+ const text = response.text ?? "";
1693
+ return { role: "assistant", model, stopReason, content: { type: "text", text } };
1694
+ }
1695
+ var GoogleSamplingAdapter = class {
1696
+ _client;
1697
+ _options;
1698
+ constructor(client, options) {
1699
+ this._client = client;
1700
+ this._options = options ?? {};
1701
+ }
1702
+ asHandler() {
1703
+ return async (params) => {
1704
+ const model = resolveModel(
1705
+ params.modelPreferences,
1706
+ this._options.modelSelector,
1707
+ this._options.defaultModel ?? "gemini-2.0-flash"
1708
+ );
1709
+ const toolNameById = buildToolNameMap(params.messages);
1710
+ const contents = toGeminiContents(params.messages, toolNameById);
1711
+ const onToken = this._options.onToken;
1712
+ const omitTools = params.toolChoice?.mode === "none";
1713
+ const functionDeclarations = !omitTools && params.tools?.length ? toGeminiFunctionDeclarations(params.tools) : void 0;
1714
+ const toolConfig = !omitTools ? toGeminiToolConfig(params.toolChoice) : void 0;
1715
+ const config = {
1716
+ maxOutputTokens: params.maxTokens ?? 1024,
1717
+ ...params.temperature !== void 0 ? { temperature: params.temperature } : {},
1718
+ ...params.stopSequences?.length ? { stopSequences: params.stopSequences } : {},
1719
+ ...params.systemPrompt ? { systemInstruction: { parts: [{ text: params.systemPrompt }] } } : {},
1720
+ ...functionDeclarations ? { tools: [{ functionDeclarations }] } : {},
1721
+ ...toolConfig ? { toolConfig } : {}
1722
+ };
1723
+ const stream = await this._client.models.generateContentStream({
1724
+ model,
1725
+ contents,
1726
+ config
1727
+ });
1728
+ let lastChunk;
1729
+ for await (const chunk of stream) {
1730
+ lastChunk = chunk;
1731
+ if (onToken) {
1732
+ const text = chunk.text;
1733
+ if (text) onToken(text);
1734
+ }
1735
+ }
1736
+ return buildResult3(lastChunk, model);
1737
+ };
1738
+ }
1739
+ };
1740
+ export {
1741
+ AnthropicSamplingAdapter,
1742
+ BearerAuth,
1743
+ Client,
1744
+ ClientCredentials,
1745
+ FileTokenStorage,
1746
+ GenericSamplingAdapter,
1747
+ GoogleSamplingAdapter,
1748
+ InMemoryStore,
1749
+ MultiServerClient,
1750
+ OAuth,
1751
+ OpenAISamplingAdapter,
1752
+ StdioTransport,
1753
+ ToolCallError,
1754
+ defaultLogHandler,
1755
+ defaultProgressHandler,
1756
+ toResult
1757
+ };
1758
+ //# sourceMappingURL=client.js.map