@prefecthq/fastmcp-ts 0.0.2 → 0.0.4

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { CreateMessageResult, CreateMessageResultWithTools, ContentBlock, CreateMessageRequestParams, LoggingLevel, ElicitRequestParams, ElicitResult, Tool, Resource, Prompt, ResourceTemplate, ResourceContents, GetPromptResult, Root, ModelPreferences, SamplingMessage, ToolChoice } from '@modelcontextprotocol/sdk/types.js';
2
- export { ContentBlock, CreateMessageRequestParams, CreateMessageResult, CreateMessageResultWithTools, ElicitRequestParams, ElicitResult, GetPromptResult, LoggingLevel, ModelPreferences, Prompt, PromptArgument, PromptMessage, Resource, ResourceContents, ResourceTemplate, Root, SamplingMessage, Tool, ToolChoice, ToolResultContent, ToolUseContent } from '@modelcontextprotocol/sdk/types.js';
1
+ import { CreateMessageResult, CreateMessageResultWithTools, ContentBlock, CreateMessageRequestParams, LoggingLevel, ElicitRequestParams, ElicitResult, Tool, Resource, Prompt, ResourceTemplate, TextResourceContents, BlobResourceContents, GetPromptResult, Root, ModelPreferences, SamplingMessage, ToolChoice } from '@modelcontextprotocol/sdk/types.js';
2
+ export { BlobResourceContents, ContentBlock, CreateMessageRequestParams, CreateMessageResult, CreateMessageResultWithTools, ElicitRequestParams, ElicitResult, GetPromptResult, LoggingLevel, ModelPreferences, Prompt, PromptArgument, PromptMessage, Resource, ResourceContents, ResourceTemplate, Root, SamplingMessage, TextResourceContents, Tool, ToolChoice, ToolResultContent, ToolUseContent } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { OAuthClientProvider, OAuthDiscoveryState } from '@modelcontextprotocol/sdk/client/auth.js';
4
4
  import { OAuthClientMetadata, OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
5
5
  import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
@@ -75,7 +75,7 @@ interface IToolsClient {
75
75
  interface IResourcesClient {
76
76
  listResources(options?: RequestOptions): Promise<Resource[]>;
77
77
  listResourceTemplates(options?: RequestOptions): Promise<ResourceTemplate[]>;
78
- readResource(uri: string, options?: RequestOptions): Promise<ResourceContents[]>;
78
+ readResource(uri: string, options?: RequestOptions): Promise<Array<TextResourceContents | BlobResourceContents>>;
79
79
  subscribeResource(uri: string, handler: ResourceUpdateHandler, options?: RequestOptions): Promise<void>;
80
80
  unsubscribeResource(uri: string, options?: RequestOptions): Promise<void>;
81
81
  }
@@ -126,8 +126,10 @@ declare class InMemoryStore implements KeyValueStore {
126
126
  delete(key: string): Promise<void>;
127
127
  }
128
128
  declare class FileTokenStorage implements KeyValueStore {
129
- private readonly _path;
129
+ private readonly _explicitPath?;
130
+ private _resolvedPath?;
130
131
  constructor(path?: string);
132
+ private _path;
131
133
  private _readAll;
132
134
  private _writeAll;
133
135
  get(key: string): Promise<string | null>;
@@ -167,9 +169,9 @@ declare class OAuth implements OAuthClientProvider {
167
169
  private readonly _callbackPort;
168
170
  private readonly _onRedirect?;
169
171
  private _codeVerifier;
170
- private _callbackPromise;
171
- private _callbackResolve;
172
- private _callbackReject;
172
+ protected _callbackPromise: Promise<string> | null;
173
+ protected _callbackResolve: ((code: string) => void) | null;
174
+ protected _callbackReject: ((err: Error) => void) | null;
173
175
  private _callbackServer;
174
176
  private _actualCallbackPort;
175
177
  constructor(options?: OAuthOptions);
@@ -185,6 +187,20 @@ declare class OAuth implements OAuthClientProvider {
185
187
  tokens(): Promise<OAuthTokens | undefined>;
186
188
  saveTokens(tokens: OAuthTokens): Promise<void>;
187
189
  redirectToAuthorization(authorizationUrl: URL): Promise<void>;
190
+ /**
191
+ * Creates the pending-callback promise that {@link waitForCallback} awaits.
192
+ * Subclasses (e.g. BrowserOAuth) call this before opening their own redirect.
193
+ */
194
+ protected _armCallbackPromise(): void;
195
+ /** Resolves the pending callback with an authorization code. */
196
+ protected _resolveCallback(code: string): void;
197
+ /** Rejects the pending callback with an error. */
198
+ protected _rejectCallback(err: Error): void;
199
+ /**
200
+ * Races the pending callback promise against a timeout and clears the
201
+ * promise state. Subclasses reuse this and add their own teardown.
202
+ */
203
+ protected _awaitCallback(timeoutMs: number): Promise<string>;
188
204
  saveCodeVerifier(codeVerifier: string): void;
189
205
  codeVerifier(): string;
190
206
  invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): Promise<void>;
@@ -236,6 +252,80 @@ declare class ClientCredentials {
236
252
  private _doFetch;
237
253
  }
238
254
 
255
+ /**
256
+ * KeyValueStore backed by `localStorage`. Synchronous and simple; suitable for
257
+ * OAuth tokens and client registration state in single-page apps. Keys are
258
+ * namespaced by a prefix to avoid collisions with other app state.
259
+ */
260
+ declare class LocalStorageStore implements KeyValueStore {
261
+ private readonly _prefix;
262
+ constructor(prefix?: string);
263
+ get(key: string): Promise<string | null>;
264
+ set(key: string, value: string): Promise<void>;
265
+ delete(key: string): Promise<void>;
266
+ }
267
+ /**
268
+ * KeyValueStore backed by IndexedDB. Preferred when tokens should survive
269
+ * outside the synchronous, size-limited localStorage (e.g. larger payloads or
270
+ * stricter persistence). Opens the database per operation and closes it again.
271
+ */
272
+ declare class IndexedDBStore implements KeyValueStore {
273
+ private readonly _dbName;
274
+ private readonly _storeName;
275
+ constructor(opts?: {
276
+ dbName?: string;
277
+ storeName?: string;
278
+ });
279
+ private _open;
280
+ private _tx;
281
+ get(key: string): Promise<string | null>;
282
+ set(key: string, value: string): Promise<void>;
283
+ delete(key: string): Promise<void>;
284
+ }
285
+
286
+ interface BrowserOAuthOptions extends Omit<OAuthOptions, 'callbackPort' | 'onRedirect'> {
287
+ /**
288
+ * Redirect URI registered with the provider. Must serve a page that calls
289
+ * {@link handleOAuthCallback} (popup mode) or is your app route that reads the
290
+ * returned `?code=` (redirect mode).
291
+ */
292
+ redirectUri: string;
293
+ /**
294
+ * 'popup' (default) opens the authorization URL in a popup window and listens
295
+ * for a postMessage from the callback page. 'redirect' navigates the whole
296
+ * tab; resume with {@link BrowserOAuth.resumeFromRedirect} on return.
297
+ */
298
+ mode?: 'popup' | 'redirect';
299
+ /** `window.open` features string for popup mode. */
300
+ popupFeatures?: string;
301
+ }
302
+ declare class BrowserOAuth extends OAuth {
303
+ private readonly _redirectUri;
304
+ private readonly _mode;
305
+ private readonly _popupFeatures;
306
+ private _popup;
307
+ private _messageHandler;
308
+ constructor(options: BrowserOAuthOptions);
309
+ get redirectUrl(): string;
310
+ get clientMetadata(): OAuthClientMetadata;
311
+ redirectToAuthorization(authorizationUrl: URL): Promise<void>;
312
+ waitForCallback(timeoutMs?: number): Promise<string>;
313
+ /**
314
+ * Redirect-mode resume: parse the authorization code from a returned URL
315
+ * (defaults to the current location). Returns the code, or null if absent.
316
+ * Throws if the provider returned an error.
317
+ */
318
+ resumeFromRedirect(href?: string): string | null;
319
+ private _teardown;
320
+ }
321
+ /**
322
+ * Call from the redirect_uri page (popup mode) to deliver the authorization
323
+ * code back to the opener window and close the popup.
324
+ */
325
+ declare function handleOAuthCallback(opts?: {
326
+ targetOrigin?: string;
327
+ }): void;
328
+
239
329
  interface McpServerLike {
240
330
  connect(transport: Transport): Promise<void>;
241
331
  }
@@ -296,7 +386,7 @@ declare class MultiServerClient implements IClient {
296
386
  callToolRaw<TData = unknown>(name: string, args?: Record<string, unknown>, options?: CallToolOptions): Promise<CallToolResult<TData>>;
297
387
  listResources(options?: RequestOptions): Promise<Resource[]>;
298
388
  listResourceTemplates(options?: RequestOptions): Promise<ResourceTemplate[]>;
299
- readResource(uri: string, options?: RequestOptions): Promise<ResourceContents[]>;
389
+ readResource(uri: string, options?: RequestOptions): Promise<Array<TextResourceContents | BlobResourceContents>>;
300
390
  listPrompts(options?: RequestOptions): Promise<Prompt[]>;
301
391
  getPrompt(name: string, args?: Record<string, string>, options?: RequestOptions): Promise<GetPromptResult>;
302
392
  subscribeResource(uri: string, handler: ResourceUpdateHandler, options?: RequestOptions): Promise<void>;
@@ -397,7 +487,7 @@ declare class Client implements IClient {
397
487
  callToolRaw<TData = unknown>(name: string, args?: Record<string, unknown>, options?: CallToolOptions): Promise<CallToolResult<TData>>;
398
488
  listResources(options?: RequestOptions): Promise<Resource[]>;
399
489
  listResourceTemplates(options?: RequestOptions): Promise<ResourceTemplate[]>;
400
- readResource(uri: string, options?: RequestOptions): Promise<ResourceContents[]>;
490
+ readResource(uri: string, options?: RequestOptions): Promise<Array<TextResourceContents | BlobResourceContents>>;
401
491
  /** Returns the raw SDK ReadResourceResult without unwrapping. */
402
492
  readResourceRaw(uri: string, options?: RequestOptions): Promise<{
403
493
  [x: string]: unknown;
@@ -540,4 +630,4 @@ declare class GoogleSamplingAdapter implements SamplingAdapter {
540
630
  asHandler(): SamplingHandler;
541
631
  }
542
632
 
543
- export { AnthropicSamplingAdapter, type AnySamplingResult, BearerAuth, type CallToolOptions, type CallToolResult, Client, ClientCredentials, type ClientCredentialsOptions, type ClientDefaultOptions, type ClientHandlers, type ClientOptions, type ClientTransportInput, type CompletionResult, type ElicitationHandler, FileTokenStorage, type GenericCompletionFn, type GenericCompletionParams, GenericSamplingAdapter, GoogleSamplingAdapter, type IClient, type IPromptsClient, type IResourcesClient, type IToolsClient, InMemoryStore, type KeyValueStore, type ListChangedHandler, type LogHandler, type LogMessage, type McpConfig, type McpServerEntry, type McpServerLike, type McpServerValue, type ModelSelector, MultiServerClient, type MultiServerOptions, OAuth, type OAuthOptions, type OAuthToken, type OnTokenCallback, OpenAISamplingAdapter, type ProgressHandler, type RequestOptions, type ResourceUpdateHandler, type Result, type RootInput, type RootsValue, type SamplingAdapter, type SamplingAdapterOptions, type SamplingHandler, StdioTransport, ToolCallError, defaultLogHandler, defaultProgressHandler, toResult };
633
+ export { AnthropicSamplingAdapter, type AnySamplingResult, BearerAuth, BrowserOAuth, type BrowserOAuthOptions, type CallToolOptions, type CallToolResult, Client, ClientCredentials, type ClientCredentialsOptions, type ClientDefaultOptions, type ClientHandlers, type ClientOptions, type ClientTransportInput, type CompletionResult, type ElicitationHandler, FileTokenStorage, type GenericCompletionFn, type GenericCompletionParams, GenericSamplingAdapter, GoogleSamplingAdapter, type IClient, type IPromptsClient, type IResourcesClient, type IToolsClient, InMemoryStore, IndexedDBStore, type KeyValueStore, type ListChangedHandler, LocalStorageStore, type LogHandler, type LogMessage, type McpConfig, type McpServerEntry, type McpServerLike, type McpServerValue, type ModelSelector, MultiServerClient, type MultiServerOptions, OAuth, type OAuthOptions, type OAuthToken, type OnTokenCallback, OpenAISamplingAdapter, type ProgressHandler, type RequestOptions, type ResourceUpdateHandler, type Result, type RootInput, type RootsValue, type SamplingAdapter, type SamplingAdapterOptions, type SamplingHandler, StdioTransport, ToolCallError, defaultLogHandler, defaultProgressHandler, handleOAuthCallback, toResult };
package/dist/client.js CHANGED
@@ -25,11 +25,6 @@ function defaultProgressHandler(progress, total, message) {
25
25
  }
26
26
 
27
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
28
  var InMemoryStore = class {
34
29
  _map = /* @__PURE__ */ new Map();
35
30
  async get(key) {
@@ -43,13 +38,22 @@ var InMemoryStore = class {
43
38
  }
44
39
  };
45
40
  var FileTokenStorage = class {
46
- _path;
41
+ _explicitPath;
42
+ _resolvedPath;
47
43
  constructor(path) {
48
- this._path = path ?? join(homedir(), ".fastmcp", "tokens.json");
44
+ this._explicitPath = path;
45
+ }
46
+ async _path() {
47
+ if (this._resolvedPath) return this._resolvedPath;
48
+ if (this._explicitPath) return this._resolvedPath = this._explicitPath;
49
+ const { homedir } = await import("os");
50
+ const { join } = await import("path");
51
+ return this._resolvedPath = join(homedir(), ".fastmcp", "tokens.json");
49
52
  }
50
53
  async _readAll() {
54
+ const fs = await import("fs/promises");
51
55
  try {
52
- const raw = await fs.readFile(this._path, "utf8");
56
+ const raw = await fs.readFile(await this._path(), "utf8");
53
57
  const parsed = JSON.parse(raw);
54
58
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
55
59
  return parsed;
@@ -60,10 +64,13 @@ var FileTokenStorage = class {
60
64
  }
61
65
  }
62
66
  async _writeAll(data) {
63
- await fs.mkdir(dirname(this._path), { recursive: true });
64
- const tmp = `${this._path}.tmp`;
67
+ const fs = await import("fs/promises");
68
+ const { dirname } = await import("path");
69
+ const path = await this._path();
70
+ await fs.mkdir(dirname(path), { recursive: true });
71
+ const tmp = `${path}.tmp`;
65
72
  await fs.writeFile(tmp, JSON.stringify(data, null, 2), "utf8");
66
- await fs.rename(tmp, this._path);
73
+ await fs.rename(tmp, path);
67
74
  }
68
75
  async get(key) {
69
76
  const data = await this._readAll();
@@ -152,17 +159,54 @@ var OAuth = class {
152
159
  await this._store.set(this._key("tokens"), JSON.stringify(tokens));
153
160
  }
154
161
  async redirectToAuthorization(authorizationUrl) {
155
- this._callbackPromise = new Promise((resolve2, reject) => {
156
- this._callbackResolve = resolve2;
157
- this._callbackReject = reject;
158
- });
159
- this._callbackPromise.catch(() => {
160
- });
162
+ this._armCallbackPromise();
161
163
  await this._startCallbackServer();
162
164
  if (this._onRedirect) {
163
165
  await this._onRedirect(authorizationUrl);
164
166
  } else {
165
- openBrowser(authorizationUrl.toString());
167
+ await openBrowser(authorizationUrl.toString());
168
+ }
169
+ }
170
+ /**
171
+ * Creates the pending-callback promise that {@link waitForCallback} awaits.
172
+ * Subclasses (e.g. BrowserOAuth) call this before opening their own redirect.
173
+ */
174
+ _armCallbackPromise() {
175
+ this._callbackPromise = new Promise((resolve, reject) => {
176
+ this._callbackResolve = resolve;
177
+ this._callbackReject = reject;
178
+ });
179
+ this._callbackPromise.catch(() => {
180
+ });
181
+ }
182
+ /** Resolves the pending callback with an authorization code. */
183
+ _resolveCallback(code) {
184
+ this._callbackResolve?.(code);
185
+ }
186
+ /** Rejects the pending callback with an error. */
187
+ _rejectCallback(err) {
188
+ this._callbackReject?.(err);
189
+ }
190
+ /**
191
+ * Races the pending callback promise against a timeout and clears the
192
+ * promise state. Subclasses reuse this and add their own teardown.
193
+ */
194
+ async _awaitCallback(timeoutMs) {
195
+ if (!this._callbackPromise) {
196
+ throw new Error("No pending OAuth callback \u2014 call redirectToAuthorization() first");
197
+ }
198
+ const timeout = new Promise(
199
+ (_, reject) => setTimeout(
200
+ () => reject(new Error("OAuth callback timed out waiting for authorization")),
201
+ timeoutMs
202
+ )
203
+ );
204
+ try {
205
+ return await Promise.race([this._callbackPromise, timeout]);
206
+ } finally {
207
+ this._callbackPromise = null;
208
+ this._callbackResolve = null;
209
+ this._callbackReject = null;
166
210
  }
167
211
  }
168
212
  saveCodeVerifier(codeVerifier) {
@@ -211,30 +255,19 @@ var OAuth = class {
211
255
  * Must be called after the UnauthorizedError thrown by connect() is caught.
212
256
  */
213
257
  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
258
  try {
224
- return await Promise.race([this._callbackPromise, timeout]);
259
+ return await this._awaitCallback(timeoutMs);
225
260
  } finally {
226
- this._callbackPromise = null;
227
- this._callbackResolve = null;
228
- this._callbackReject = null;
229
261
  this._stopCallbackServer();
230
262
  }
231
263
  }
232
264
  _key(type) {
233
265
  return `${this._serverUrl}/${type}`;
234
266
  }
235
- _startCallbackServer() {
236
- if (this._callbackServer) return Promise.resolve();
237
- return new Promise((resolve2, reject) => {
267
+ async _startCallbackServer() {
268
+ if (this._callbackServer) return;
269
+ const http = await import("http");
270
+ await new Promise((resolve, reject) => {
238
271
  const server = http.createServer((req, res) => {
239
272
  const url = new URL(req.url ?? "/", `http://localhost`);
240
273
  const error = url.searchParams.get("error");
@@ -266,7 +299,7 @@ var OAuth = class {
266
299
  this._actualCallbackPort = addr.port;
267
300
  }
268
301
  this._callbackServer = server;
269
- resolve2();
302
+ resolve();
270
303
  });
271
304
  });
272
305
  }
@@ -367,7 +400,8 @@ var ClientCredentials = class {
367
400
  return this._fetchPromise;
368
401
  }
369
402
  };
370
- function openBrowser(url) {
403
+ async function openBrowser(url) {
404
+ const { spawn } = await import("child_process");
371
405
  if (process.platform === "win32") {
372
406
  spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
373
407
  } else if (process.platform === "darwin") {
@@ -377,11 +411,153 @@ function openBrowser(url) {
377
411
  }
378
412
  }
379
413
 
414
+ // src/client/browser-stores.ts
415
+ var LocalStorageStore = class {
416
+ _prefix;
417
+ constructor(prefix = "fastmcp:") {
418
+ this._prefix = prefix;
419
+ }
420
+ async get(key) {
421
+ return localStorage.getItem(this._prefix + key);
422
+ }
423
+ async set(key, value) {
424
+ localStorage.setItem(this._prefix + key, value);
425
+ }
426
+ async delete(key) {
427
+ localStorage.removeItem(this._prefix + key);
428
+ }
429
+ };
430
+ var IndexedDBStore = class {
431
+ _dbName;
432
+ _storeName;
433
+ constructor(opts = {}) {
434
+ this._dbName = opts.dbName ?? "fastmcp";
435
+ this._storeName = opts.storeName ?? "auth";
436
+ }
437
+ _open() {
438
+ return new Promise((resolve, reject) => {
439
+ const req = indexedDB.open(this._dbName, 1);
440
+ req.onupgradeneeded = () => {
441
+ if (!req.result.objectStoreNames.contains(this._storeName)) {
442
+ req.result.createObjectStore(this._storeName);
443
+ }
444
+ };
445
+ req.onsuccess = () => resolve(req.result);
446
+ req.onerror = () => reject(req.error ?? new Error("indexedDB.open failed"));
447
+ });
448
+ }
449
+ async _tx(mode, fn) {
450
+ const db = await this._open();
451
+ try {
452
+ return await new Promise((resolve, reject) => {
453
+ const req = fn(db.transaction(this._storeName, mode).objectStore(this._storeName));
454
+ req.onsuccess = () => resolve(req.result);
455
+ req.onerror = () => reject(req.error ?? new Error("indexedDB request failed"));
456
+ });
457
+ } finally {
458
+ db.close();
459
+ }
460
+ }
461
+ async get(key) {
462
+ const value = await this._tx("readonly", (s) => s.get(key));
463
+ return value ?? null;
464
+ }
465
+ async set(key, value) {
466
+ await this._tx("readwrite", (s) => s.put(value, key));
467
+ }
468
+ async delete(key) {
469
+ await this._tx("readwrite", (s) => s.delete(key));
470
+ }
471
+ };
472
+
473
+ // src/client/browser-oauth.ts
474
+ var MESSAGE_TYPE = "fastmcp:oauth";
475
+ var BrowserOAuth = class extends OAuth {
476
+ _redirectUri;
477
+ _mode;
478
+ _popupFeatures;
479
+ _popup = null;
480
+ _messageHandler = null;
481
+ constructor(options) {
482
+ super(options);
483
+ this._redirectUri = options.redirectUri;
484
+ this._mode = options.mode ?? "popup";
485
+ this._popupFeatures = options.popupFeatures ?? "width=600,height=720";
486
+ }
487
+ get redirectUrl() {
488
+ return this._redirectUri;
489
+ }
490
+ get clientMetadata() {
491
+ return { ...super.clientMetadata, redirect_uris: [this._redirectUri] };
492
+ }
493
+ async redirectToAuthorization(authorizationUrl) {
494
+ if (this._mode === "redirect") {
495
+ window.location.assign(authorizationUrl.toString());
496
+ return;
497
+ }
498
+ const expectedOrigin = new URL(this._redirectUri).origin;
499
+ this._messageHandler = (e) => {
500
+ if (e.origin !== expectedOrigin) return;
501
+ const data = e.data;
502
+ if (data?.type !== MESSAGE_TYPE) return;
503
+ if (data.error) this._rejectCallback(new Error(`OAuth authorization denied: ${data.error}`));
504
+ else if (data.code) this._resolveCallback(data.code);
505
+ };
506
+ window.addEventListener("message", this._messageHandler);
507
+ this._armCallbackPromise();
508
+ this._popup = window.open(authorizationUrl.toString(), "fastmcp-oauth", this._popupFeatures);
509
+ if (!this._popup) {
510
+ this._teardown();
511
+ throw new Error(
512
+ 'Failed to open OAuth popup \u2014 it was likely blocked. Trigger connect from a user gesture or use mode: "redirect".'
513
+ );
514
+ }
515
+ }
516
+ async waitForCallback(timeoutMs = 5 * 60 * 1e3) {
517
+ try {
518
+ return await this._awaitCallback(timeoutMs);
519
+ } finally {
520
+ this._teardown();
521
+ }
522
+ }
523
+ /**
524
+ * Redirect-mode resume: parse the authorization code from a returned URL
525
+ * (defaults to the current location). Returns the code, or null if absent.
526
+ * Throws if the provider returned an error.
527
+ */
528
+ resumeFromRedirect(href = window.location.href) {
529
+ const params = new URL(href).searchParams;
530
+ const error = params.get("error_description") ?? params.get("error");
531
+ if (error) throw new Error(`OAuth authorization denied: ${error}`);
532
+ return params.get("code");
533
+ }
534
+ _teardown() {
535
+ if (this._messageHandler) window.removeEventListener("message", this._messageHandler);
536
+ this._messageHandler = null;
537
+ try {
538
+ this._popup?.close();
539
+ } catch {
540
+ }
541
+ this._popup = null;
542
+ }
543
+ };
544
+ function handleOAuthCallback(opts = {}) {
545
+ const params = new URLSearchParams(window.location.search);
546
+ const code = params.get("code") ?? void 0;
547
+ const error = params.get("error_description") ?? params.get("error") ?? void 0;
548
+ const target = opts.targetOrigin ?? window.location.origin;
549
+ window.opener?.postMessage({ type: MESSAGE_TYPE, code, error }, target);
550
+ window.close();
551
+ }
552
+
380
553
  // src/client/transports.ts
381
554
  import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
382
555
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
383
556
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
384
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
557
+ async function createStdioTransport(opts) {
558
+ const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
559
+ return new StdioClientTransport(opts);
560
+ }
385
561
  function isMcpServerLike(value) {
386
562
  return typeof value === "object" && value !== null && "connect" in value && typeof value.connect === "function";
387
563
  }
@@ -460,7 +636,7 @@ function urlToTransport(url, auth, extraHeaders = {}) {
460
636
  ...customFetch ? { fetch: customFetch } : {}
461
637
  });
462
638
  }
463
- function resolveEntryTransport(entry, auth) {
639
+ async function resolveEntryTransport(entry, auth) {
464
640
  if (isMcpServerLike(entry)) {
465
641
  const [serverSide, clientSide] = InMemoryTransport.createLinkedPair();
466
642
  return {
@@ -476,7 +652,7 @@ function resolveEntryTransport(entry, auth) {
476
652
  }
477
653
  const cmd = entry;
478
654
  return {
479
- transport: new StdioClientTransport({
655
+ transport: await createStdioTransport({
480
656
  command: cmd.command,
481
657
  args: cmd.args,
482
658
  env: cmd.env
@@ -488,10 +664,10 @@ function resolveEntryAuth(auth) {
488
664
  if (typeof auth === "string") return new BearerAuth(auth);
489
665
  return auth;
490
666
  }
491
- function resolveTransport(input, auth) {
667
+ async function resolveTransport(input, auth) {
492
668
  if (input instanceof StdioTransport) {
493
669
  return {
494
- transport: new StdioClientTransport({
670
+ transport: await createStdioTransport({
495
671
  command: input.command,
496
672
  args: input.args,
497
673
  env: input.env,
@@ -510,7 +686,7 @@ function resolveTransport(input, auth) {
510
686
  const entries = Object.entries(input.mcpServers);
511
687
  if (entries.length === 0) throw new Error("mcpServers config is empty");
512
688
  const [, entry] = entries[0];
513
- return resolveEntryTransport(entry, auth);
689
+ return await resolveEntryTransport(entry, auth);
514
690
  }
515
691
  if (isMcpServerLike(input)) {
516
692
  const [serverSide, clientSide] = InMemoryTransport.createLinkedPair();
@@ -525,8 +701,6 @@ function resolveTransport(input, auth) {
525
701
  }
526
702
 
527
703
  // src/client/client.ts
528
- import { resolve } from "path";
529
- import { pathToFileURL } from "url";
530
704
  import { Client as SdkClient2 } from "@modelcontextprotocol/sdk/client";
531
705
  import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
532
706
  import {
@@ -593,7 +767,7 @@ var MultiServerClient = class _MultiServerClient {
593
767
  entries.map(async ([name, entry]) => {
594
768
  const sdk = this._buildSdkClient();
595
769
  this._registerHandlers(sdk);
596
- const { transport, beforeConnect } = resolveEntryTransport(
770
+ const { transport, beforeConnect } = await resolveEntryTransport(
597
771
  entry
598
772
  );
599
773
  if (beforeConnect) await beforeConnect();
@@ -1025,7 +1199,7 @@ var Client = class _Client {
1025
1199
  }
1026
1200
  );
1027
1201
  this._registerHandlers(sdkClient);
1028
- const { transport, beforeConnect } = resolveTransport(this._input, this._auth);
1202
+ const { transport, beforeConnect } = await resolveTransport(this._input, this._auth);
1029
1203
  if (this._auth instanceof OAuth) {
1030
1204
  const serverUrl = this._extractServerUrl();
1031
1205
  if (serverUrl) this._auth._bind(serverUrl);
@@ -1041,7 +1215,9 @@ var Client = class _Client {
1041
1215
  code
1042
1216
  );
1043
1217
  }
1044
- await sdkClient.connect(transport);
1218
+ const retry = await resolveTransport(this._input, this._auth);
1219
+ if (retry.beforeConnect) await retry.beforeConnect();
1220
+ await sdkClient.connect(retry.transport);
1045
1221
  } else {
1046
1222
  throw err;
1047
1223
  }
@@ -1300,17 +1476,23 @@ function resolveAuth(auth) {
1300
1476
  }
1301
1477
  function normalizeRootsOption(roots) {
1302
1478
  if (typeof roots === "function") {
1303
- return async () => (await roots()).map(normalizeRootInput);
1479
+ return async () => Promise.all((await roots()).map(normalizeRootInput));
1304
1480
  }
1305
- const normalized = roots.map(normalizeRootInput);
1306
- return () => Promise.resolve(normalized);
1481
+ return async () => Promise.all(roots.map(normalizeRootInput));
1307
1482
  }
1308
- function normalizeRootInput(input) {
1309
- if (typeof input === "string") return { uri: normalizeRootUri(input) };
1310
- return { ...input, uri: normalizeRootUri(input.uri) };
1483
+ async function normalizeRootInput(input) {
1484
+ if (typeof input === "string") return { uri: await normalizeRootUri(input) };
1485
+ return { ...input, uri: await normalizeRootUri(input.uri) };
1311
1486
  }
1312
- function normalizeRootUri(input) {
1313
- if (input.startsWith("file://")) return input;
1487
+ async function normalizeRootUri(input) {
1488
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(input)) return input;
1489
+ if (typeof process === "undefined" || !process.versions?.node) {
1490
+ throw new Error(
1491
+ `Cannot normalize filesystem root "${input}" in a browser. Pass a file:// URI instead.`
1492
+ );
1493
+ }
1494
+ const { resolve } = await import("path");
1495
+ const { pathToFileURL } = await import("url");
1314
1496
  return pathToFileURL(resolve(input)).href;
1315
1497
  }
1316
1498
 
@@ -1745,12 +1927,15 @@ var GoogleSamplingAdapter = class {
1745
1927
  export {
1746
1928
  AnthropicSamplingAdapter,
1747
1929
  BearerAuth,
1930
+ BrowserOAuth,
1748
1931
  Client,
1749
1932
  ClientCredentials,
1750
1933
  FileTokenStorage,
1751
1934
  GenericSamplingAdapter,
1752
1935
  GoogleSamplingAdapter,
1753
1936
  InMemoryStore,
1937
+ IndexedDBStore,
1938
+ LocalStorageStore,
1754
1939
  MultiServerClient,
1755
1940
  OAuth,
1756
1941
  OpenAISamplingAdapter,
@@ -1758,6 +1943,7 @@ export {
1758
1943
  ToolCallError,
1759
1944
  defaultLogHandler,
1760
1945
  defaultProgressHandler,
1946
+ handleOAuthCallback,
1761
1947
  toResult
1762
1948
  };
1763
1949
  //# sourceMappingURL=client.js.map