@prefecthq/fastmcp-ts 0.0.2-alpha.0 → 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,8 +1,8 @@
1
- import { CreateMessageResult, CreateMessageResultWithTools, ContentBlock, CreateMessageRequestParams, LoggingLevel, ElicitRequestParams, ElicitResult, Tool, Resource, Prompt, ResourceTemplate, ResourceContents, GetPromptResult, Root, ModelPreferences, SamplingMessage, ToolChoice } from '@modelcontextprotocol/sdk/types';
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';
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
- import { Transport } from '@modelcontextprotocol/sdk/shared/transport';
5
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
6
6
  import Anthropic from '@anthropic-ai/sdk';
7
7
  import OpenAI from 'openai';
8
8
  import { GoogleGenAI } from '@google/genai';
@@ -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,12 +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
- 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";
554
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
555
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
556
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
557
+ async function createStdioTransport(opts) {
558
+ const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
559
+ return new StdioClientTransport(opts);
560
+ }
386
561
  function isMcpServerLike(value) {
387
562
  return typeof value === "object" && value !== null && "connect" in value && typeof value.connect === "function";
388
563
  }
@@ -401,6 +576,12 @@ var StdioTransport = class {
401
576
  this.cwd = options?.cwd;
402
577
  }
403
578
  };
579
+ function normalizeHeaders(headers) {
580
+ if (!headers) return {};
581
+ if (headers instanceof Headers) return Object.fromEntries(headers.entries());
582
+ if (Array.isArray(headers)) return Object.fromEntries(headers);
583
+ return { ...headers };
584
+ }
404
585
  function isAsyncAuth(auth) {
405
586
  return "kind" in auth;
406
587
  }
@@ -455,7 +636,7 @@ function urlToTransport(url, auth, extraHeaders = {}) {
455
636
  ...customFetch ? { fetch: customFetch } : {}
456
637
  });
457
638
  }
458
- function resolveEntryTransport(entry, auth) {
639
+ async function resolveEntryTransport(entry, auth) {
459
640
  if (isMcpServerLike(entry)) {
460
641
  const [serverSide, clientSide] = InMemoryTransport.createLinkedPair();
461
642
  return {
@@ -471,7 +652,7 @@ function resolveEntryTransport(entry, auth) {
471
652
  }
472
653
  const cmd = entry;
473
654
  return {
474
- transport: new StdioClientTransport({
655
+ transport: await createStdioTransport({
475
656
  command: cmd.command,
476
657
  args: cmd.args,
477
658
  env: cmd.env
@@ -483,10 +664,10 @@ function resolveEntryAuth(auth) {
483
664
  if (typeof auth === "string") return new BearerAuth(auth);
484
665
  return auth;
485
666
  }
486
- function resolveTransport(input, auth) {
667
+ async function resolveTransport(input, auth) {
487
668
  if (input instanceof StdioTransport) {
488
669
  return {
489
- transport: new StdioClientTransport({
670
+ transport: await createStdioTransport({
490
671
  command: input.command,
491
672
  args: input.args,
492
673
  env: input.env,
@@ -505,7 +686,7 @@ function resolveTransport(input, auth) {
505
686
  const entries = Object.entries(input.mcpServers);
506
687
  if (entries.length === 0) throw new Error("mcpServers config is empty");
507
688
  const [, entry] = entries[0];
508
- return resolveEntryTransport(entry, auth);
689
+ return await resolveEntryTransport(entry, auth);
509
690
  }
510
691
  if (isMcpServerLike(input)) {
511
692
  const [serverSide, clientSide] = InMemoryTransport.createLinkedPair();
@@ -520,8 +701,6 @@ function resolveTransport(input, auth) {
520
701
  }
521
702
 
522
703
  // src/client/client.ts
523
- import { resolve } from "path";
524
- import { pathToFileURL } from "url";
525
704
  import { Client as SdkClient2 } from "@modelcontextprotocol/sdk/client";
526
705
  import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
527
706
  import {
@@ -531,7 +710,7 @@ import {
531
710
  ListRootsRequestSchema as ListRootsRequestSchema2,
532
711
  LoggingMessageNotificationSchema as LoggingMessageNotificationSchema2,
533
712
  ResourceUpdatedNotificationSchema as ResourceUpdatedNotificationSchema2
534
- } from "@modelcontextprotocol/sdk/types";
713
+ } from "@modelcontextprotocol/sdk/types.js";
535
714
 
536
715
  // src/client/multi-server.ts
537
716
  import { Client as SdkClient } from "@modelcontextprotocol/sdk/client";
@@ -542,7 +721,7 @@ import {
542
721
  CreateMessageRequestSchema,
543
722
  ElicitRequestSchema,
544
723
  ListRootsRequestSchema
545
- } from "@modelcontextprotocol/sdk/types";
724
+ } from "@modelcontextprotocol/sdk/types.js";
546
725
  var MultiServerClient = class _MultiServerClient {
547
726
  _clients = /* @__PURE__ */ new Map();
548
727
  _connectPromise = null;
@@ -588,7 +767,7 @@ var MultiServerClient = class _MultiServerClient {
588
767
  entries.map(async ([name, entry]) => {
589
768
  const sdk = this._buildSdkClient();
590
769
  this._registerHandlers(sdk);
591
- const { transport, beforeConnect } = resolveEntryTransport(
770
+ const { transport, beforeConnect } = await resolveEntryTransport(
592
771
  entry
593
772
  );
594
773
  if (beforeConnect) await beforeConnect();
@@ -1020,7 +1199,7 @@ var Client = class _Client {
1020
1199
  }
1021
1200
  );
1022
1201
  this._registerHandlers(sdkClient);
1023
- const { transport, beforeConnect } = resolveTransport(this._input, this._auth);
1202
+ const { transport, beforeConnect } = await resolveTransport(this._input, this._auth);
1024
1203
  if (this._auth instanceof OAuth) {
1025
1204
  const serverUrl = this._extractServerUrl();
1026
1205
  if (serverUrl) this._auth._bind(serverUrl);
@@ -1036,7 +1215,9 @@ var Client = class _Client {
1036
1215
  code
1037
1216
  );
1038
1217
  }
1039
- 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);
1040
1221
  } else {
1041
1222
  throw err;
1042
1223
  }
@@ -1295,17 +1476,23 @@ function resolveAuth(auth) {
1295
1476
  }
1296
1477
  function normalizeRootsOption(roots) {
1297
1478
  if (typeof roots === "function") {
1298
- return async () => (await roots()).map(normalizeRootInput);
1479
+ return async () => Promise.all((await roots()).map(normalizeRootInput));
1299
1480
  }
1300
- const normalized = roots.map(normalizeRootInput);
1301
- return () => Promise.resolve(normalized);
1481
+ return async () => Promise.all(roots.map(normalizeRootInput));
1302
1482
  }
1303
- function normalizeRootInput(input) {
1304
- if (typeof input === "string") return { uri: normalizeRootUri(input) };
1305
- 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) };
1306
1486
  }
1307
- function normalizeRootUri(input) {
1308
- 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");
1309
1496
  return pathToFileURL(resolve(input)).href;
1310
1497
  }
1311
1498
 
@@ -1740,12 +1927,15 @@ var GoogleSamplingAdapter = class {
1740
1927
  export {
1741
1928
  AnthropicSamplingAdapter,
1742
1929
  BearerAuth,
1930
+ BrowserOAuth,
1743
1931
  Client,
1744
1932
  ClientCredentials,
1745
1933
  FileTokenStorage,
1746
1934
  GenericSamplingAdapter,
1747
1935
  GoogleSamplingAdapter,
1748
1936
  InMemoryStore,
1937
+ IndexedDBStore,
1938
+ LocalStorageStore,
1749
1939
  MultiServerClient,
1750
1940
  OAuth,
1751
1941
  OpenAISamplingAdapter,
@@ -1753,6 +1943,7 @@ export {
1753
1943
  ToolCallError,
1754
1944
  defaultLogHandler,
1755
1945
  defaultProgressHandler,
1946
+ handleOAuthCallback,
1756
1947
  toResult
1757
1948
  };
1758
1949
  //# sourceMappingURL=client.js.map