agents 0.7.4 → 0.7.6

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.
Files changed (81) hide show
  1. package/dist/ai-chat-agent.js +3 -4
  2. package/dist/ai-chat-agent.js.map +1 -1
  3. package/dist/ai-chat-v5-migration.js +3 -4
  4. package/dist/ai-chat-v5-migration.js.map +1 -1
  5. package/dist/ai-react.js +3 -4
  6. package/dist/ai-react.js.map +1 -1
  7. package/dist/ai-types.js +1 -2
  8. package/dist/ai-types.js.map +1 -1
  9. package/dist/cli/index.js +2 -4
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/client-K8Z-u76l.js +1468 -0
  12. package/dist/client-K8Z-u76l.js.map +1 -0
  13. package/dist/client.js +1 -2
  14. package/dist/client.js.map +1 -1
  15. package/dist/codemode/ai.js +2 -2
  16. package/dist/do-oauth-client-provider-C2jurFjW.d.ts +78 -0
  17. package/dist/email-U_MG7UET.d.ts +157 -0
  18. package/dist/email.d.ts +16 -146
  19. package/dist/email.js +2 -2
  20. package/dist/email.js.map +1 -1
  21. package/dist/experimental/forever.d.ts +26 -71
  22. package/dist/experimental/forever.js +2 -3
  23. package/dist/experimental/forever.js.map +1 -1
  24. package/dist/experimental/memory/session/index.js +3 -12
  25. package/dist/experimental/memory/session/index.js.map +1 -1
  26. package/dist/experimental/workspace.d.ts +273 -0
  27. package/dist/experimental/workspace.js +1265 -0
  28. package/dist/experimental/workspace.js.map +1 -0
  29. package/dist/index-BS_jL8MI.d.ts +492 -0
  30. package/dist/index-WBy5hmm3.d.ts +2840 -0
  31. package/dist/index.d.ts +49 -1320
  32. package/dist/index.js +281 -138
  33. package/dist/index.js.map +1 -1
  34. package/dist/internal_context-DgcmHqS1.d.ts +37 -0
  35. package/dist/internal_context.d.ts +5 -32
  36. package/dist/internal_context.js +1 -2
  37. package/dist/internal_context.js.map +1 -1
  38. package/dist/mcp/client.d.ts +2 -575
  39. package/dist/mcp/client.js +1 -847
  40. package/dist/mcp/do-oauth-client-provider.d.ts +2 -61
  41. package/dist/mcp/do-oauth-client-provider.js +1 -2
  42. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  43. package/dist/mcp/index.d.ts +2 -95
  44. package/dist/mcp/index.js +60 -57
  45. package/dist/mcp/index.js.map +1 -1
  46. package/dist/mcp/x402.js +1 -2
  47. package/dist/mcp/x402.js.map +1 -1
  48. package/dist/observability/index.d.ts +2 -93
  49. package/dist/observability/index.js +4 -3
  50. package/dist/observability/index.js.map +1 -1
  51. package/dist/react.d.ts +1 -2
  52. package/dist/react.js +1 -2
  53. package/dist/react.js.map +1 -1
  54. package/dist/retries-DXMQGhG3.d.ts +79 -0
  55. package/dist/retries.d.ts +7 -72
  56. package/dist/retries.js +1 -1
  57. package/dist/retries.js.map +1 -1
  58. package/dist/schedule.js +1 -2
  59. package/dist/schedule.js.map +1 -1
  60. package/dist/serializable.js +1 -1
  61. package/dist/types-BB1plA51.d.ts +15 -0
  62. package/dist/types.d.ts +1 -14
  63. package/dist/types.js +1 -1
  64. package/dist/types.js.map +1 -1
  65. package/dist/utils.js +1 -1
  66. package/dist/workflow-types-CZNXKj_D.d.ts +260 -0
  67. package/dist/workflow-types.d.ts +23 -235
  68. package/dist/workflow-types.js +1 -1
  69. package/dist/workflow-types.js.map +1 -1
  70. package/dist/workflows.d.ts +22 -23
  71. package/dist/workflows.js +5 -6
  72. package/dist/workflows.js.map +1 -1
  73. package/package.json +25 -13
  74. package/dist/agent-eZnMHidZ.d.ts +0 -273
  75. package/dist/client-connection-D3Wcd6Q6.js +0 -603
  76. package/dist/client-connection-D3Wcd6Q6.js.map +0 -1
  77. package/dist/client-storage-BPjfP_is.d.ts +0 -604
  78. package/dist/experimental/sub-agent.d.ts +0 -205
  79. package/dist/experimental/sub-agent.js +0 -191
  80. package/dist/experimental/sub-agent.js.map +0 -1
  81. package/dist/mcp/client.js.map +0 -1
@@ -0,0 +1,1468 @@
1
+ import { tryN } from "./retries.js";
2
+ import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider.js";
3
+ import { nanoid } from "nanoid";
4
+ import { getServerByName } from "partyserver";
5
+ import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker-provider.js";
6
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
8
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
9
+ import { ElicitRequestSchema, JSONRPCMessageSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
10
+ //#region src/core/events.ts
11
+ function toDisposable(fn) {
12
+ return { dispose: fn };
13
+ }
14
+ var DisposableStore = class {
15
+ constructor() {
16
+ this._items = [];
17
+ }
18
+ add(d) {
19
+ this._items.push(d);
20
+ return d;
21
+ }
22
+ dispose() {
23
+ while (this._items.length) try {
24
+ this._items.pop().dispose();
25
+ } catch {}
26
+ }
27
+ };
28
+ var Emitter = class {
29
+ constructor() {
30
+ this._listeners = /* @__PURE__ */ new Set();
31
+ this.event = (listener) => {
32
+ this._listeners.add(listener);
33
+ return toDisposable(() => this._listeners.delete(listener));
34
+ };
35
+ }
36
+ fire(data) {
37
+ for (const listener of [...this._listeners]) try {
38
+ listener(data);
39
+ } catch (err) {
40
+ console.error("Emitter listener error:", err);
41
+ }
42
+ }
43
+ dispose() {
44
+ this._listeners.clear();
45
+ }
46
+ };
47
+ //#endregion
48
+ //#region src/mcp/errors.ts
49
+ function toErrorMessage(error) {
50
+ return error instanceof Error ? error.message : String(error);
51
+ }
52
+ function getErrorCode(error) {
53
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "number") return error.code;
54
+ }
55
+ function isUnauthorized(error) {
56
+ if (getErrorCode(error) === 401) return true;
57
+ const msg = toErrorMessage(error);
58
+ return msg.includes("Unauthorized") || msg.includes("401");
59
+ }
60
+ function isTransportNotImplemented(error) {
61
+ const code = getErrorCode(error);
62
+ if (code === 404 || code === 405) return true;
63
+ const msg = toErrorMessage(error);
64
+ return msg.includes("404") || msg.includes("405") || msg.includes("Not Implemented") || msg.includes("not implemented");
65
+ }
66
+ //#endregion
67
+ //#region src/mcp/rpc.ts
68
+ const RPC_DO_PREFIX = "rpc:";
69
+ function makeInvalidRequestError(id) {
70
+ return {
71
+ jsonrpc: "2.0",
72
+ id: id ?? null,
73
+ error: {
74
+ code: -32600,
75
+ message: "Invalid Request"
76
+ }
77
+ };
78
+ }
79
+ function validateBatch(batch) {
80
+ if (batch.length === 0) throw new Error("Invalid JSON-RPC batch: array must not be empty");
81
+ }
82
+ var RPCClientTransport = class {
83
+ constructor(options) {
84
+ this._started = false;
85
+ this._namespace = options.namespace;
86
+ this._name = options.name;
87
+ this._props = options.props;
88
+ }
89
+ setProtocolVersion(version) {
90
+ this._protocolVersion = version;
91
+ }
92
+ getProtocolVersion() {
93
+ return this._protocolVersion;
94
+ }
95
+ async start() {
96
+ if (this._started) throw new Error("Transport already started");
97
+ const doName = `${RPC_DO_PREFIX}${this._name}`;
98
+ this._stub = await getServerByName(this._namespace, doName, { props: this._props });
99
+ this._started = true;
100
+ }
101
+ async close() {
102
+ this._started = false;
103
+ this._stub = void 0;
104
+ this.onclose?.();
105
+ }
106
+ async send(message, options) {
107
+ if (!this._started || !this._stub) throw new Error("Transport not started");
108
+ try {
109
+ const result = await this._stub.handleMcpMessage(message);
110
+ if (!result) return;
111
+ const extra = options?.relatedRequestId ? { requestInfo: { headers: {} } } : void 0;
112
+ const messages = Array.isArray(result) ? result : [result];
113
+ for (const msg of messages) this.onmessage?.(msg, extra);
114
+ } catch (error) {
115
+ this.onerror?.(error);
116
+ throw error;
117
+ }
118
+ }
119
+ };
120
+ var RPCServerTransport = class {
121
+ constructor(options) {
122
+ this._started = false;
123
+ this._pendingResponse = null;
124
+ this._responseResolver = null;
125
+ this._timeout = options?.timeout ?? 6e4;
126
+ }
127
+ setProtocolVersion(version) {
128
+ this._protocolVersion = version;
129
+ }
130
+ getProtocolVersion() {
131
+ return this._protocolVersion;
132
+ }
133
+ async start() {
134
+ if (this._started) throw new Error("Transport already started");
135
+ this._started = true;
136
+ }
137
+ async close() {
138
+ this._started = false;
139
+ this.onclose?.();
140
+ if (this._responseResolver) {
141
+ this._responseResolver();
142
+ this._responseResolver = null;
143
+ }
144
+ }
145
+ async send(message, _options) {
146
+ if (!this._started) throw new Error("Transport not started");
147
+ if (!this._pendingResponse) this._pendingResponse = message;
148
+ else if (Array.isArray(this._pendingResponse)) this._pendingResponse.push(message);
149
+ else this._pendingResponse = [this._pendingResponse, message];
150
+ if (this._responseResolver) {
151
+ const resolver = this._responseResolver;
152
+ queueMicrotask(() => resolver());
153
+ }
154
+ }
155
+ async handle(message) {
156
+ if (!this._started) throw new Error("Transport not started");
157
+ if (Array.isArray(message)) {
158
+ validateBatch(message);
159
+ const responses = [];
160
+ for (const msg of message) {
161
+ const response = await this.handle(msg);
162
+ if (response !== void 0) if (Array.isArray(response)) responses.push(...response);
163
+ else responses.push(response);
164
+ }
165
+ return responses.length === 0 ? void 0 : responses;
166
+ }
167
+ try {
168
+ JSONRPCMessageSchema.parse(message);
169
+ } catch {
170
+ return makeInvalidRequestError(typeof message === "object" && message !== null && "id" in message ? message.id : null);
171
+ }
172
+ this._pendingResponse = null;
173
+ if (!("id" in message)) {
174
+ this.onmessage?.(message);
175
+ return;
176
+ }
177
+ let timeoutId = null;
178
+ const responsePromise = new Promise((resolve, reject) => {
179
+ timeoutId = setTimeout(() => {
180
+ this._responseResolver = null;
181
+ reject(/* @__PURE__ */ new Error(`Request timeout: No response received within ${this._timeout}ms`));
182
+ }, this._timeout);
183
+ this._responseResolver = () => {
184
+ if (timeoutId) {
185
+ clearTimeout(timeoutId);
186
+ timeoutId = null;
187
+ }
188
+ this._responseResolver = null;
189
+ resolve();
190
+ };
191
+ });
192
+ this.onmessage?.(message);
193
+ try {
194
+ await responsePromise;
195
+ } catch (error) {
196
+ this._pendingResponse = null;
197
+ this._responseResolver = null;
198
+ throw error;
199
+ }
200
+ const response = this._pendingResponse;
201
+ this._pendingResponse = null;
202
+ return response ?? void 0;
203
+ }
204
+ };
205
+ //#endregion
206
+ //#region src/mcp/client-connection.ts
207
+ /**
208
+ * Connection state machine for MCP client connections.
209
+ *
210
+ * State transitions:
211
+ * - Non-OAuth: init() → CONNECTING → DISCOVERING → READY
212
+ * - OAuth: init() → AUTHENTICATING → (callback) → CONNECTING → DISCOVERING → READY
213
+ * - Any state can transition to FAILED on error
214
+ */
215
+ const MCPConnectionState = {
216
+ AUTHENTICATING: "authenticating",
217
+ CONNECTING: "connecting",
218
+ CONNECTED: "connected",
219
+ DISCOVERING: "discovering",
220
+ READY: "ready",
221
+ FAILED: "failed"
222
+ };
223
+ var MCPClientConnection = class {
224
+ constructor(url, info, options = {
225
+ client: {},
226
+ transport: {}
227
+ }) {
228
+ this.url = url;
229
+ this.options = options;
230
+ this.connectionState = MCPConnectionState.CONNECTING;
231
+ this.connectionError = null;
232
+ this.tools = [];
233
+ this.prompts = [];
234
+ this.resources = [];
235
+ this.resourceTemplates = [];
236
+ this._onObservabilityEvent = new Emitter();
237
+ this.onObservabilityEvent = this._onObservabilityEvent.event;
238
+ this.client = new Client(info, {
239
+ ...options.client,
240
+ capabilities: {
241
+ ...options.client?.capabilities,
242
+ elicitation: {}
243
+ }
244
+ });
245
+ }
246
+ /**
247
+ * Initialize a client connection, if authentication is required, the connection will be in the AUTHENTICATING state
248
+ * Sets connection state based on the result and emits observability events
249
+ *
250
+ * @returns Error message if connection failed, undefined otherwise
251
+ */
252
+ async init() {
253
+ const transportType = this.options.transport.type;
254
+ if (!transportType) throw new Error("Transport type must be specified");
255
+ const res = await this.tryConnect(transportType);
256
+ this.connectionState = res.state;
257
+ if (res.state === MCPConnectionState.CONNECTED && res.transport) {
258
+ this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
259
+ return await this.handleElicitationRequest(request);
260
+ });
261
+ this.lastConnectedTransport = res.transport;
262
+ this._onObservabilityEvent.fire({
263
+ type: "mcp:client:connect",
264
+ payload: {
265
+ url: this.url.toString(),
266
+ transport: res.transport,
267
+ state: this.connectionState
268
+ },
269
+ timestamp: Date.now()
270
+ });
271
+ return;
272
+ } else if (res.state === MCPConnectionState.FAILED && res.error) {
273
+ const errorMessage = toErrorMessage(res.error);
274
+ this._onObservabilityEvent.fire({
275
+ type: "mcp:client:connect",
276
+ payload: {
277
+ url: this.url.toString(),
278
+ transport: transportType,
279
+ state: this.connectionState,
280
+ error: errorMessage
281
+ },
282
+ timestamp: Date.now()
283
+ });
284
+ return errorMessage;
285
+ }
286
+ }
287
+ /**
288
+ * Finish OAuth by probing transports based on configured type.
289
+ * - Explicit: finish on that transport
290
+ * - Auto: try streamable-http, then sse on 404/405/Not Implemented
291
+ */
292
+ async finishAuthProbe(code) {
293
+ if (!this.options.transport.authProvider) throw new Error("No auth provider configured");
294
+ const configuredType = this.options.transport.type;
295
+ if (!configuredType) throw new Error("Transport type must be specified");
296
+ const finishAuth = async (base) => {
297
+ const transport = this.getTransport(base);
298
+ if ("finishAuth" in transport && typeof transport.finishAuth === "function") await transport.finishAuth(code);
299
+ };
300
+ if (configuredType === "rpc") throw new Error("RPC transport does not support authentication");
301
+ if (configuredType === "sse" || configuredType === "streamable-http") {
302
+ await finishAuth(configuredType);
303
+ return;
304
+ }
305
+ try {
306
+ await finishAuth("streamable-http");
307
+ } catch (e) {
308
+ if (isTransportNotImplemented(e)) {
309
+ await finishAuth("sse");
310
+ return;
311
+ }
312
+ throw e;
313
+ }
314
+ }
315
+ /**
316
+ * Complete OAuth authorization
317
+ */
318
+ async completeAuthorization(code) {
319
+ if (this.connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error("Connection must be in authenticating state to complete authorization");
320
+ try {
321
+ await this.finishAuthProbe(code);
322
+ this.connectionState = MCPConnectionState.CONNECTING;
323
+ } catch (error) {
324
+ this.connectionState = MCPConnectionState.FAILED;
325
+ throw error;
326
+ }
327
+ }
328
+ /**
329
+ * Discover server capabilities and register tools, resources, prompts, and templates.
330
+ * This method does the work but does not manage connection state - that's handled by discover().
331
+ */
332
+ async discoverAndRegister() {
333
+ this.serverCapabilities = this.client.getServerCapabilities();
334
+ if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
335
+ const operations = [];
336
+ const operationNames = [];
337
+ operations.push(Promise.resolve(this.client.getInstructions()));
338
+ operationNames.push("instructions");
339
+ if (this.serverCapabilities.tools) {
340
+ operations.push(this.registerTools());
341
+ operationNames.push("tools");
342
+ }
343
+ if (this.serverCapabilities.resources) {
344
+ operations.push(this.registerResources());
345
+ operationNames.push("resources");
346
+ }
347
+ if (this.serverCapabilities.prompts) {
348
+ operations.push(this.registerPrompts());
349
+ operationNames.push("prompts");
350
+ }
351
+ if (this.serverCapabilities.resources) {
352
+ operations.push(this.registerResourceTemplates());
353
+ operationNames.push("resource templates");
354
+ }
355
+ try {
356
+ const results = await Promise.all(operations);
357
+ for (let i = 0; i < results.length; i++) {
358
+ const result = results[i];
359
+ switch (operationNames[i]) {
360
+ case "instructions":
361
+ this.instructions = result;
362
+ break;
363
+ case "tools":
364
+ this.tools = result;
365
+ break;
366
+ case "resources":
367
+ this.resources = result;
368
+ break;
369
+ case "prompts":
370
+ this.prompts = result;
371
+ break;
372
+ case "resource templates":
373
+ this.resourceTemplates = result;
374
+ break;
375
+ }
376
+ }
377
+ } catch (error) {
378
+ this._onObservabilityEvent.fire({
379
+ type: "mcp:client:discover",
380
+ payload: {
381
+ url: this.url.toString(),
382
+ error: toErrorMessage(error)
383
+ },
384
+ timestamp: Date.now()
385
+ });
386
+ throw error;
387
+ }
388
+ }
389
+ /**
390
+ * Discover server capabilities with timeout and cancellation support.
391
+ * If called while a previous discovery is in-flight, the previous discovery will be aborted.
392
+ *
393
+ * @param options Optional configuration
394
+ * @param options.timeoutMs Timeout in milliseconds (default: 15000)
395
+ * @returns Result indicating success/failure with optional error message
396
+ */
397
+ async discover(options = {}) {
398
+ const { timeoutMs = 15e3 } = options;
399
+ if (this.connectionState !== MCPConnectionState.CONNECTED && this.connectionState !== MCPConnectionState.READY) {
400
+ this._onObservabilityEvent.fire({
401
+ type: "mcp:client:discover",
402
+ payload: {
403
+ url: this.url.toString(),
404
+ state: this.connectionState
405
+ },
406
+ timestamp: Date.now()
407
+ });
408
+ return {
409
+ success: false,
410
+ error: `Discovery skipped - connection in ${this.connectionState} state`
411
+ };
412
+ }
413
+ if (this._discoveryAbortController) {
414
+ this._discoveryAbortController.abort();
415
+ this._discoveryAbortController = void 0;
416
+ }
417
+ const abortController = new AbortController();
418
+ this._discoveryAbortController = abortController;
419
+ this.connectionState = MCPConnectionState.DISCOVERING;
420
+ let timeoutId;
421
+ try {
422
+ const timeoutPromise = new Promise((_, reject) => {
423
+ timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error(`Discovery timed out after ${timeoutMs}ms`)), timeoutMs);
424
+ });
425
+ if (abortController.signal.aborted) throw new Error("Discovery was cancelled");
426
+ const abortPromise = new Promise((_, reject) => {
427
+ abortController.signal.addEventListener("abort", () => {
428
+ reject(/* @__PURE__ */ new Error("Discovery was cancelled"));
429
+ });
430
+ });
431
+ await Promise.race([
432
+ this.discoverAndRegister(),
433
+ timeoutPromise,
434
+ abortPromise
435
+ ]);
436
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
437
+ this.connectionState = MCPConnectionState.READY;
438
+ this._onObservabilityEvent.fire({
439
+ type: "mcp:client:discover",
440
+ payload: { url: this.url.toString() },
441
+ timestamp: Date.now()
442
+ });
443
+ return { success: true };
444
+ } catch (e) {
445
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
446
+ this.connectionState = MCPConnectionState.CONNECTED;
447
+ return {
448
+ success: false,
449
+ error: e instanceof Error ? e.message : String(e)
450
+ };
451
+ } finally {
452
+ this._discoveryAbortController = void 0;
453
+ }
454
+ }
455
+ /**
456
+ * Cancel any in-flight discovery operation.
457
+ * Called when closing the connection.
458
+ */
459
+ cancelDiscovery() {
460
+ if (this._discoveryAbortController) {
461
+ this._discoveryAbortController.abort();
462
+ this._discoveryAbortController = void 0;
463
+ }
464
+ }
465
+ /**
466
+ * Notification handler registration for tools
467
+ * Should only be called if serverCapabilities.tools exists
468
+ */
469
+ async registerTools() {
470
+ if (this.serverCapabilities?.tools?.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
471
+ this.tools = await this.fetchTools();
472
+ });
473
+ return this.fetchTools();
474
+ }
475
+ /**
476
+ * Notification handler registration for resources
477
+ * Should only be called if serverCapabilities.resources exists
478
+ */
479
+ async registerResources() {
480
+ if (this.serverCapabilities?.resources?.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
481
+ this.resources = await this.fetchResources();
482
+ });
483
+ return this.fetchResources();
484
+ }
485
+ /**
486
+ * Notification handler registration for prompts
487
+ * Should only be called if serverCapabilities.prompts exists
488
+ */
489
+ async registerPrompts() {
490
+ if (this.serverCapabilities?.prompts?.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
491
+ this.prompts = await this.fetchPrompts();
492
+ });
493
+ return this.fetchPrompts();
494
+ }
495
+ async registerResourceTemplates() {
496
+ return this.fetchResourceTemplates();
497
+ }
498
+ async fetchTools() {
499
+ let toolsAgg = [];
500
+ let toolsResult = { tools: [] };
501
+ do {
502
+ toolsResult = await this.client.listTools({ cursor: toolsResult.nextCursor }).catch(this._capabilityErrorHandler({ tools: [] }, "tools/list"));
503
+ toolsAgg = toolsAgg.concat(toolsResult.tools);
504
+ } while (toolsResult.nextCursor);
505
+ return toolsAgg;
506
+ }
507
+ async fetchResources() {
508
+ let resourcesAgg = [];
509
+ let resourcesResult = { resources: [] };
510
+ do {
511
+ resourcesResult = await this.client.listResources({ cursor: resourcesResult.nextCursor }).catch(this._capabilityErrorHandler({ resources: [] }, "resources/list"));
512
+ resourcesAgg = resourcesAgg.concat(resourcesResult.resources);
513
+ } while (resourcesResult.nextCursor);
514
+ return resourcesAgg;
515
+ }
516
+ async fetchPrompts() {
517
+ let promptsAgg = [];
518
+ let promptsResult = { prompts: [] };
519
+ do {
520
+ promptsResult = await this.client.listPrompts({ cursor: promptsResult.nextCursor }).catch(this._capabilityErrorHandler({ prompts: [] }, "prompts/list"));
521
+ promptsAgg = promptsAgg.concat(promptsResult.prompts);
522
+ } while (promptsResult.nextCursor);
523
+ return promptsAgg;
524
+ }
525
+ async fetchResourceTemplates() {
526
+ let templatesAgg = [];
527
+ let templatesResult = { resourceTemplates: [] };
528
+ do {
529
+ templatesResult = await this.client.listResourceTemplates({ cursor: templatesResult.nextCursor }).catch(this._capabilityErrorHandler({ resourceTemplates: [] }, "resources/templates/list"));
530
+ templatesAgg = templatesAgg.concat(templatesResult.resourceTemplates);
531
+ } while (templatesResult.nextCursor);
532
+ return templatesAgg;
533
+ }
534
+ /**
535
+ * Handle elicitation request from server
536
+ * Automatically uses the Agent's built-in elicitation handling if available
537
+ */
538
+ async handleElicitationRequest(_request) {
539
+ throw new Error("Elicitation handler must be implemented for your platform. Override handleElicitationRequest method.");
540
+ }
541
+ /**
542
+ * Get the transport for the client
543
+ * @param transportType - The transport type to get
544
+ * @returns The transport for the client
545
+ */
546
+ getTransport(transportType) {
547
+ switch (transportType) {
548
+ case "streamable-http": return new StreamableHTTPClientTransport(this.url, this.options.transport);
549
+ case "sse": return new SSEClientTransport(this.url, this.options.transport);
550
+ case "rpc": return new RPCClientTransport(this.options.transport);
551
+ default: throw new Error(`Unsupported transport type: ${transportType}`);
552
+ }
553
+ }
554
+ async tryConnect(transportType) {
555
+ const transports = transportType === "auto" ? ["streamable-http", "sse"] : [transportType];
556
+ for (const currentTransportType of transports) {
557
+ const isLastTransport = currentTransportType === transports[transports.length - 1];
558
+ const hasFallback = transportType === "auto" && currentTransportType === "streamable-http" && !isLastTransport;
559
+ const transport = this.getTransport(currentTransportType);
560
+ try {
561
+ await this.client.connect(transport);
562
+ return {
563
+ state: MCPConnectionState.CONNECTED,
564
+ transport: currentTransportType
565
+ };
566
+ } catch (e) {
567
+ const error = e instanceof Error ? e : new Error(String(e));
568
+ if (isUnauthorized(error)) return { state: MCPConnectionState.AUTHENTICATING };
569
+ if (isTransportNotImplemented(error) && hasFallback) continue;
570
+ return {
571
+ state: MCPConnectionState.FAILED,
572
+ error
573
+ };
574
+ }
575
+ }
576
+ return {
577
+ state: MCPConnectionState.FAILED,
578
+ error: /* @__PURE__ */ new Error("No transports available")
579
+ };
580
+ }
581
+ _capabilityErrorHandler(empty, method) {
582
+ return (e) => {
583
+ if (e.code === -32601) {
584
+ const url = this.url.toString();
585
+ this._onObservabilityEvent.fire({
586
+ type: "mcp:client:discover",
587
+ payload: {
588
+ url,
589
+ capability: method.split("/")[0],
590
+ error: toErrorMessage(e)
591
+ },
592
+ timestamp: Date.now()
593
+ });
594
+ return empty;
595
+ }
596
+ throw e;
597
+ };
598
+ }
599
+ };
600
+ //#endregion
601
+ //#region src/mcp/client.ts
602
+ const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
603
+ /**
604
+ * Blocked hostname patterns for SSRF protection.
605
+ * Prevents MCP client from connecting to internal/private network addresses
606
+ * while allowing loopback hosts for local development.
607
+ */
608
+ const BLOCKED_HOSTNAMES = new Set([
609
+ "0.0.0.0",
610
+ "[::]",
611
+ "metadata.google.internal"
612
+ ]);
613
+ /**
614
+ * Check whether four IPv4 octets belong to a private/reserved range.
615
+ * Blocks RFC 1918, link-local, cloud metadata, and unspecified addresses.
616
+ */
617
+ function isPrivateIPv4(octets) {
618
+ const [a, b] = octets;
619
+ if (a === 10) return true;
620
+ if (a === 172 && b >= 16 && b <= 31) return true;
621
+ if (a === 192 && b === 168) return true;
622
+ if (a === 169 && b === 254) return true;
623
+ if (a === 0) return true;
624
+ return false;
625
+ }
626
+ /**
627
+ * Check whether a hostname looks like a private/internal IP address.
628
+ * Blocks RFC 1918, link-local, unique-local, unspecified,
629
+ * and cloud metadata endpoints. Also detects IPv4-mapped IPv6 addresses.
630
+ */
631
+ function isBlockedUrl(url) {
632
+ let parsed;
633
+ try {
634
+ parsed = new URL(url);
635
+ } catch {
636
+ return true;
637
+ }
638
+ const hostname = parsed.hostname;
639
+ if (BLOCKED_HOSTNAMES.has(hostname)) return true;
640
+ const ipv4Parts = hostname.split(".");
641
+ if (ipv4Parts.length === 4 && ipv4Parts.every((p) => /^\d{1,3}$/.test(p))) {
642
+ if (isPrivateIPv4(ipv4Parts.map(Number))) return true;
643
+ }
644
+ if (hostname.startsWith("[") && hostname.endsWith("]")) {
645
+ const addr = hostname.slice(1, -1).toLowerCase();
646
+ if (addr.startsWith("fc") || addr.startsWith("fd")) return true;
647
+ if (addr.startsWith("fe80")) return true;
648
+ if (addr.startsWith("::ffff:")) {
649
+ const mapped = addr.slice(7);
650
+ const dotParts = mapped.split(".");
651
+ if (dotParts.length === 4 && dotParts.every((p) => /^\d{1,3}$/.test(p))) {
652
+ if (isPrivateIPv4(dotParts.map(Number))) return true;
653
+ } else {
654
+ const hexParts = mapped.split(":");
655
+ if (hexParts.length === 2) {
656
+ const hi = parseInt(hexParts[0], 16);
657
+ const lo = parseInt(hexParts[1], 16);
658
+ if (isPrivateIPv4([
659
+ hi >> 8 & 255,
660
+ hi & 255,
661
+ lo >> 8 & 255,
662
+ lo & 255
663
+ ])) return true;
664
+ }
665
+ }
666
+ }
667
+ }
668
+ return false;
669
+ }
670
+ /**
671
+ * Utility class that aggregates multiple MCP clients into one
672
+ */
673
+ var MCPClientManager = class {
674
+ /**
675
+ * @param _name Name of the MCP client
676
+ * @param _version Version of the MCP Client
677
+ * @param options Storage adapter for persisting MCP server state
678
+ */
679
+ constructor(_name, _version, options) {
680
+ this._name = _name;
681
+ this._version = _version;
682
+ this.mcpConnections = {};
683
+ this._didWarnAboutUnstableGetAITools = false;
684
+ this._connectionDisposables = /* @__PURE__ */ new Map();
685
+ this._isRestored = false;
686
+ this._pendingConnections = /* @__PURE__ */ new Map();
687
+ this._onObservabilityEvent = new Emitter();
688
+ this.onObservabilityEvent = this._onObservabilityEvent.event;
689
+ this._onServerStateChanged = new Emitter();
690
+ this.onServerStateChanged = this._onServerStateChanged.event;
691
+ if (!options.storage) throw new Error("MCPClientManager requires a valid DurableObjectStorage instance");
692
+ this._storage = options.storage;
693
+ this._createAuthProviderFn = options.createAuthProvider;
694
+ }
695
+ sql(query, ...bindings) {
696
+ return [...this._storage.sql.exec(query, ...bindings)];
697
+ }
698
+ saveServerToStorage(server) {
699
+ this.sql(`INSERT OR REPLACE INTO cf_agents_mcp_servers (
700
+ id, name, server_url, client_id, auth_url, callback_url, server_options
701
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`, server.id, server.name, server.server_url, server.client_id ?? null, server.auth_url ?? null, server.callback_url, server.server_options ?? null);
702
+ }
703
+ removeServerFromStorage(serverId) {
704
+ this.sql("DELETE FROM cf_agents_mcp_servers WHERE id = ?", serverId);
705
+ }
706
+ getServersFromStorage() {
707
+ return this.sql("SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers");
708
+ }
709
+ /**
710
+ * Get the retry options for a server from stored server_options
711
+ */
712
+ getServerRetryOptions(serverId) {
713
+ const rows = this.sql("SELECT server_options FROM cf_agents_mcp_servers WHERE id = ?", serverId);
714
+ if (!rows.length || !rows[0].server_options) return void 0;
715
+ return JSON.parse(rows[0].server_options).retry;
716
+ }
717
+ clearServerAuthUrl(serverId) {
718
+ this.sql("UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?", serverId);
719
+ }
720
+ failConnection(serverId, error) {
721
+ this.clearServerAuthUrl(serverId);
722
+ if (this.mcpConnections[serverId]) {
723
+ this.mcpConnections[serverId].connectionState = MCPConnectionState.FAILED;
724
+ this.mcpConnections[serverId].connectionError = error;
725
+ }
726
+ this._onServerStateChanged.fire();
727
+ return {
728
+ serverId,
729
+ authSuccess: false,
730
+ authError: error
731
+ };
732
+ }
733
+ /**
734
+ * Create an auth provider for a server
735
+ * @internal
736
+ */
737
+ createAuthProvider(serverId, callbackUrl, clientName, clientId) {
738
+ if (!this._storage) throw new Error("Cannot create auth provider: storage is not initialized");
739
+ const authProvider = new DurableObjectOAuthClientProvider(this._storage, clientName, callbackUrl);
740
+ authProvider.serverId = serverId;
741
+ if (clientId) authProvider.clientId = clientId;
742
+ return authProvider;
743
+ }
744
+ /**
745
+ * Get saved RPC servers from storage (servers with rpc:// URLs).
746
+ * These are restored separately by the Agent class since they need env bindings.
747
+ */
748
+ getRpcServersFromStorage() {
749
+ return this.getServersFromStorage().filter((s) => s.server_url.startsWith(RPC_DO_PREFIX));
750
+ }
751
+ /**
752
+ * Save an RPC server to storage for hibernation recovery.
753
+ * The bindingName is stored in server_options so the Agent can look up
754
+ * the namespace from env during restore.
755
+ */
756
+ saveRpcServerToStorage(id, name, normalizedName, bindingName, props) {
757
+ this.saveServerToStorage({
758
+ id,
759
+ name,
760
+ server_url: `${RPC_DO_PREFIX}${normalizedName}`,
761
+ client_id: null,
762
+ auth_url: null,
763
+ callback_url: "",
764
+ server_options: JSON.stringify({
765
+ bindingName,
766
+ props
767
+ })
768
+ });
769
+ }
770
+ /**
771
+ * Restore MCP server connections from storage
772
+ * This method is called on Agent initialization to restore previously connected servers.
773
+ * RPC servers (rpc:// URLs) are skipped here -- they are restored by the Agent class
774
+ * which has access to env bindings.
775
+ *
776
+ * @param clientName Name to use for OAuth client (typically the agent instance name)
777
+ */
778
+ async restoreConnectionsFromStorage(clientName) {
779
+ if (this._isRestored) return;
780
+ const servers = this.getServersFromStorage();
781
+ if (!servers || servers.length === 0) {
782
+ this._isRestored = true;
783
+ return;
784
+ }
785
+ for (const server of servers) {
786
+ if (server.server_url.startsWith("rpc:")) continue;
787
+ const existingConn = this.mcpConnections[server.id];
788
+ if (existingConn) {
789
+ if (existingConn.connectionState === MCPConnectionState.READY) {
790
+ console.warn(`[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`);
791
+ continue;
792
+ }
793
+ if (existingConn.connectionState === MCPConnectionState.AUTHENTICATING || existingConn.connectionState === MCPConnectionState.CONNECTING || existingConn.connectionState === MCPConnectionState.DISCOVERING) continue;
794
+ if (existingConn.connectionState === MCPConnectionState.FAILED) {
795
+ try {
796
+ await existingConn.client.close();
797
+ } catch (error) {
798
+ console.warn(`[MCPClientManager] Error closing failed connection ${server.id}:`, error);
799
+ }
800
+ delete this.mcpConnections[server.id];
801
+ this._connectionDisposables.get(server.id)?.dispose();
802
+ this._connectionDisposables.delete(server.id);
803
+ }
804
+ }
805
+ const parsedOptions = server.server_options ? JSON.parse(server.server_options) : null;
806
+ let authProvider;
807
+ if (server.callback_url) {
808
+ authProvider = this._createAuthProviderFn ? this._createAuthProviderFn(server.callback_url) : this.createAuthProvider(server.id, server.callback_url, clientName, server.client_id ?? void 0);
809
+ authProvider.serverId = server.id;
810
+ if (server.client_id) authProvider.clientId = server.client_id;
811
+ }
812
+ const conn = this.createConnection(server.id, server.server_url, {
813
+ client: parsedOptions?.client ?? {},
814
+ transport: {
815
+ ...parsedOptions?.transport ?? {},
816
+ type: parsedOptions?.transport?.type ?? "auto",
817
+ authProvider
818
+ }
819
+ });
820
+ if (server.auth_url) {
821
+ conn.connectionState = MCPConnectionState.AUTHENTICATING;
822
+ continue;
823
+ }
824
+ this._trackConnection(server.id, this._restoreServer(server.id, parsedOptions?.retry));
825
+ }
826
+ this._isRestored = true;
827
+ }
828
+ /**
829
+ * Track a pending connection promise for a server.
830
+ * The promise is removed from the map when it settles.
831
+ */
832
+ _trackConnection(serverId, promise) {
833
+ const tracked = promise.finally(() => {
834
+ if (this._pendingConnections.get(serverId) === tracked) this._pendingConnections.delete(serverId);
835
+ });
836
+ this._pendingConnections.set(serverId, tracked);
837
+ }
838
+ /**
839
+ * Wait for all in-flight connection and discovery operations to settle.
840
+ * This is useful when you need MCP tools to be available before proceeding,
841
+ * e.g. before calling getAITools() after the agent wakes from hibernation.
842
+ *
843
+ * Returns once every pending connection has either connected and discovered,
844
+ * failed, or timed out. Never rejects.
845
+ *
846
+ * @param options.timeout - Maximum time in milliseconds to wait.
847
+ * `0` returns immediately without waiting.
848
+ * `undefined` (default) waits indefinitely.
849
+ */
850
+ async waitForConnections(options) {
851
+ if (this._pendingConnections.size === 0) return;
852
+ if (options?.timeout != null && options.timeout <= 0) return;
853
+ const settled = Promise.allSettled(this._pendingConnections.values());
854
+ if (options?.timeout != null && options.timeout > 0) {
855
+ let timerId;
856
+ const timer = new Promise((resolve) => {
857
+ timerId = setTimeout(resolve, options.timeout);
858
+ });
859
+ await Promise.race([settled, timer]);
860
+ clearTimeout(timerId);
861
+ } else await settled;
862
+ }
863
+ /**
864
+ * Internal method to restore a single server connection and discovery
865
+ */
866
+ async _restoreServer(serverId, retry) {
867
+ const maxAttempts = retry?.maxAttempts ?? 3;
868
+ if ((await tryN(maxAttempts, async () => this.connectToServer(serverId), {
869
+ baseDelayMs: retry?.baseDelayMs ?? 500,
870
+ maxDelayMs: retry?.maxDelayMs ?? 5e3
871
+ }).catch((error) => {
872
+ console.error(`Error connecting to ${serverId} after ${maxAttempts} attempts:`, error);
873
+ return null;
874
+ }))?.state === MCPConnectionState.CONNECTED) {
875
+ const discoverResult = await this.discoverIfConnected(serverId);
876
+ if (discoverResult && !discoverResult.success) console.error(`Error discovering ${serverId}:`, discoverResult.error);
877
+ }
878
+ }
879
+ /**
880
+ * Connect to and register an MCP server
881
+ *
882
+ * @deprecated This method is maintained for backward compatibility.
883
+ * For new code, use registerServer() and connectToServer() separately.
884
+ *
885
+ * @param url Server URL
886
+ * @param options Connection options
887
+ * @returns Object with server ID, auth URL (if OAuth), and client ID (if OAuth)
888
+ */
889
+ async connect(url, options = {}) {
890
+ /**
891
+ * We need to delay loading ai sdk, because putting it in module scope is
892
+ * causing issues with startup time.
893
+ * The only place it's used is in getAITools, which only matters after
894
+ * .connect() is called on at least one server.
895
+ * So it's safe to delay loading it until .connect() is called.
896
+ */
897
+ await this.ensureJsonSchema();
898
+ const id = options.reconnect?.id ?? nanoid(8);
899
+ if (options.transport?.authProvider) {
900
+ options.transport.authProvider.serverId = id;
901
+ if (options.reconnect?.oauthClientId) options.transport.authProvider.clientId = options.reconnect?.oauthClientId;
902
+ }
903
+ if (isBlockedUrl(url)) throw new Error(`Blocked URL: ${url} — MCP client connections to private/internal addresses are not allowed`);
904
+ if (!options.reconnect?.oauthCode || !this.mcpConnections[id]) {
905
+ const normalizedTransport = {
906
+ ...options.transport,
907
+ type: options.transport?.type ?? "auto"
908
+ };
909
+ this.mcpConnections[id] = new MCPClientConnection(new URL(url), {
910
+ name: this._name,
911
+ version: this._version
912
+ }, {
913
+ client: options.client ?? {},
914
+ transport: normalizedTransport
915
+ });
916
+ const store = new DisposableStore();
917
+ const existing = this._connectionDisposables.get(id);
918
+ if (existing) existing.dispose();
919
+ this._connectionDisposables.set(id, store);
920
+ store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
921
+ this._onObservabilityEvent.fire(event);
922
+ }));
923
+ }
924
+ await this.mcpConnections[id].init();
925
+ if (options.reconnect?.oauthCode) try {
926
+ await this.mcpConnections[id].completeAuthorization(options.reconnect.oauthCode);
927
+ await this.mcpConnections[id].init();
928
+ } catch (error) {
929
+ this._onObservabilityEvent.fire({
930
+ type: "mcp:client:connect",
931
+ payload: {
932
+ url,
933
+ transport: options.transport?.type ?? "auto",
934
+ state: this.mcpConnections[id].connectionState,
935
+ error: toErrorMessage(error)
936
+ },
937
+ timestamp: Date.now()
938
+ });
939
+ throw error;
940
+ }
941
+ const authUrl = options.transport?.authProvider?.authUrl;
942
+ if (this.mcpConnections[id].connectionState === MCPConnectionState.AUTHENTICATING && authUrl && options.transport?.authProvider?.redirectUrl) return {
943
+ authUrl,
944
+ clientId: options.transport?.authProvider?.clientId,
945
+ id
946
+ };
947
+ const discoverResult = await this.discoverIfConnected(id);
948
+ if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover server capabilities: ${discoverResult.error}`);
949
+ return { id };
950
+ }
951
+ /**
952
+ * Create an in-memory connection object and set up observability
953
+ * Does NOT save to storage - use registerServer() for that
954
+ * @returns The connection object (existing or newly created)
955
+ */
956
+ createConnection(id, url, options) {
957
+ if (this.mcpConnections[id]) return this.mcpConnections[id];
958
+ const normalizedTransport = {
959
+ ...options.transport,
960
+ type: options.transport?.type ?? "auto"
961
+ };
962
+ this.mcpConnections[id] = new MCPClientConnection(new URL(url), {
963
+ name: this._name,
964
+ version: this._version
965
+ }, {
966
+ client: {
967
+ ...defaultClientOptions,
968
+ ...options.client
969
+ },
970
+ transport: normalizedTransport
971
+ });
972
+ const store = new DisposableStore();
973
+ const existing = this._connectionDisposables.get(id);
974
+ if (existing) existing.dispose();
975
+ this._connectionDisposables.set(id, store);
976
+ store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
977
+ this._onObservabilityEvent.fire(event);
978
+ }));
979
+ return this.mcpConnections[id];
980
+ }
981
+ /**
982
+ * Register an MCP server connection without connecting
983
+ * Creates the connection object, sets up observability, and saves to storage
984
+ *
985
+ * @param id Server ID
986
+ * @param options Registration options including URL, name, callback URL, and connection config
987
+ * @returns Server ID
988
+ */
989
+ async registerServer(id, options) {
990
+ if (isBlockedUrl(options.url)) throw new Error(`Blocked URL: ${options.url} — MCP client connections to private/internal addresses are not allowed`);
991
+ this.createConnection(id, options.url, {
992
+ client: options.client,
993
+ transport: {
994
+ ...options.transport,
995
+ type: options.transport?.type ?? "auto"
996
+ }
997
+ });
998
+ const { authProvider: _, ...transportWithoutAuth } = options.transport ?? {};
999
+ this.saveServerToStorage({
1000
+ id,
1001
+ name: options.name,
1002
+ server_url: options.url,
1003
+ callback_url: options.callbackUrl ?? "",
1004
+ client_id: options.clientId ?? null,
1005
+ auth_url: options.authUrl ?? null,
1006
+ server_options: JSON.stringify({
1007
+ client: options.client,
1008
+ transport: transportWithoutAuth,
1009
+ retry: options.retry
1010
+ })
1011
+ });
1012
+ this._onServerStateChanged.fire();
1013
+ return id;
1014
+ }
1015
+ /**
1016
+ * Connect to an already registered MCP server and initialize the connection.
1017
+ *
1018
+ * For OAuth servers, returns `{ state: "authenticating", authUrl, clientId? }`.
1019
+ * The user must complete the OAuth flow via the authUrl, which triggers a
1020
+ * callback handled by `handleCallbackRequest()`.
1021
+ *
1022
+ * For non-OAuth servers, establishes the transport connection and returns
1023
+ * `{ state: "connected" }`. Call `discoverIfConnected()` afterwards to
1024
+ * discover capabilities and transition to "ready" state.
1025
+ *
1026
+ * @param id Server ID (must be registered first via registerServer())
1027
+ * @returns Connection result with current state and OAuth info (if applicable)
1028
+ */
1029
+ async connectToServer(id) {
1030
+ const conn = this.mcpConnections[id];
1031
+ if (!conn) throw new Error(`Server ${id} is not registered. Call registerServer() first.`);
1032
+ const error = await conn.init();
1033
+ this._onServerStateChanged.fire();
1034
+ switch (conn.connectionState) {
1035
+ case MCPConnectionState.FAILED: return {
1036
+ state: conn.connectionState,
1037
+ error: error ?? "Unknown connection error"
1038
+ };
1039
+ case MCPConnectionState.AUTHENTICATING: {
1040
+ const authUrl = conn.options.transport.authProvider?.authUrl;
1041
+ const redirectUrl = conn.options.transport.authProvider?.redirectUrl;
1042
+ if (!authUrl || !redirectUrl) return {
1043
+ state: MCPConnectionState.FAILED,
1044
+ error: `OAuth configuration incomplete: missing ${!authUrl ? "authUrl" : "redirectUrl"}`
1045
+ };
1046
+ const clientId = conn.options.transport.authProvider?.clientId;
1047
+ const serverRow = this.getServersFromStorage().find((s) => s.id === id);
1048
+ if (serverRow) {
1049
+ this.saveServerToStorage({
1050
+ ...serverRow,
1051
+ auth_url: authUrl,
1052
+ client_id: clientId ?? null
1053
+ });
1054
+ this._onServerStateChanged.fire();
1055
+ }
1056
+ this._onObservabilityEvent.fire({
1057
+ type: "mcp:client:authorize",
1058
+ payload: {
1059
+ serverId: id,
1060
+ authUrl,
1061
+ clientId
1062
+ },
1063
+ timestamp: Date.now()
1064
+ });
1065
+ return {
1066
+ state: conn.connectionState,
1067
+ authUrl,
1068
+ clientId
1069
+ };
1070
+ }
1071
+ case MCPConnectionState.CONNECTED: return { state: conn.connectionState };
1072
+ default: return {
1073
+ state: MCPConnectionState.FAILED,
1074
+ error: `Unexpected connection state after init: ${conn.connectionState}`
1075
+ };
1076
+ }
1077
+ }
1078
+ extractServerIdFromState(state) {
1079
+ if (!state) return null;
1080
+ const parts = state.split(".");
1081
+ return parts.length === 2 ? parts[1] : null;
1082
+ }
1083
+ isCallbackRequest(req) {
1084
+ if (req.method !== "GET") return false;
1085
+ const url = new URL(req.url);
1086
+ const state = url.searchParams.get("state");
1087
+ const serverId = this.extractServerIdFromState(state);
1088
+ if (!serverId) return false;
1089
+ return this.getServersFromStorage().some((server) => {
1090
+ if (server.id !== serverId) return false;
1091
+ try {
1092
+ const storedUrl = new URL(server.callback_url);
1093
+ return storedUrl.origin === url.origin && storedUrl.pathname === url.pathname;
1094
+ } catch {
1095
+ return false;
1096
+ }
1097
+ });
1098
+ }
1099
+ validateCallbackRequest(req) {
1100
+ const url = new URL(req.url);
1101
+ const code = url.searchParams.get("code");
1102
+ const state = url.searchParams.get("state");
1103
+ const error = url.searchParams.get("error");
1104
+ const errorDescription = url.searchParams.get("error_description");
1105
+ if (!state) return {
1106
+ valid: false,
1107
+ error: "Unauthorized: no state provided"
1108
+ };
1109
+ const serverId = this.extractServerIdFromState(state);
1110
+ if (!serverId) return {
1111
+ valid: false,
1112
+ error: "No serverId found in state parameter. Expected format: {nonce}.{serverId}"
1113
+ };
1114
+ if (error) return {
1115
+ serverId,
1116
+ valid: false,
1117
+ error: errorDescription || error
1118
+ };
1119
+ if (!code) return {
1120
+ serverId,
1121
+ valid: false,
1122
+ error: "Unauthorized: no code provided"
1123
+ };
1124
+ if (!this.getServersFromStorage().some((server) => server.id === serverId)) return {
1125
+ serverId,
1126
+ valid: false,
1127
+ error: `No server found with id "${serverId}". Was the request matched with \`isCallbackRequest()\`?`
1128
+ };
1129
+ if (this.mcpConnections[serverId] === void 0) return {
1130
+ serverId,
1131
+ valid: false,
1132
+ error: `No connection found for serverId "${serverId}".`
1133
+ };
1134
+ return {
1135
+ valid: true,
1136
+ serverId,
1137
+ code,
1138
+ state
1139
+ };
1140
+ }
1141
+ async handleCallbackRequest(req) {
1142
+ const validation = this.validateCallbackRequest(req);
1143
+ if (!validation.valid) {
1144
+ if (validation.serverId && this.mcpConnections[validation.serverId]) return this.failConnection(validation.serverId, validation.error);
1145
+ return {
1146
+ serverId: validation.serverId,
1147
+ authSuccess: false,
1148
+ authError: validation.error
1149
+ };
1150
+ }
1151
+ const { serverId, code, state } = validation;
1152
+ const conn = this.mcpConnections[serverId];
1153
+ try {
1154
+ if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
1155
+ const authProvider = conn.options.transport.authProvider;
1156
+ authProvider.serverId = serverId;
1157
+ const stateValidation = await authProvider.checkState(state);
1158
+ if (!stateValidation.valid) throw new Error(stateValidation.error || "Invalid state");
1159
+ if (conn.connectionState === MCPConnectionState.READY || conn.connectionState === MCPConnectionState.CONNECTED) {
1160
+ this.clearServerAuthUrl(serverId);
1161
+ return {
1162
+ serverId,
1163
+ authSuccess: true
1164
+ };
1165
+ }
1166
+ if (conn.connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error(`Failed to authenticate: the client is in "${conn.connectionState}" state, expected "authenticating"`);
1167
+ await authProvider.consumeState(state);
1168
+ await conn.completeAuthorization(code);
1169
+ await authProvider.deleteCodeVerifier();
1170
+ this.clearServerAuthUrl(serverId);
1171
+ conn.connectionError = null;
1172
+ this._onServerStateChanged.fire();
1173
+ return {
1174
+ serverId,
1175
+ authSuccess: true
1176
+ };
1177
+ } catch (err) {
1178
+ const message = err instanceof Error ? err.message : String(err);
1179
+ return this.failConnection(serverId, message);
1180
+ }
1181
+ }
1182
+ /**
1183
+ * Discover server capabilities if connection is in CONNECTED or READY state.
1184
+ * Transitions to DISCOVERING then READY (or CONNECTED on error).
1185
+ * Can be called to refresh server capabilities (e.g., from a UI refresh button).
1186
+ *
1187
+ * If called while a previous discovery is in-flight for the same server,
1188
+ * the previous discovery will be aborted.
1189
+ *
1190
+ * @param serverId The server ID to discover
1191
+ * @param options Optional configuration
1192
+ * @param options.timeoutMs Timeout in milliseconds (default: 30000)
1193
+ * @returns Result with current state and optional error, or undefined if connection not found
1194
+ */
1195
+ async discoverIfConnected(serverId, options = {}) {
1196
+ const conn = this.mcpConnections[serverId];
1197
+ if (!conn) {
1198
+ this._onObservabilityEvent.fire({
1199
+ type: "mcp:client:discover",
1200
+ payload: {},
1201
+ timestamp: Date.now()
1202
+ });
1203
+ return;
1204
+ }
1205
+ const result = await conn.discover(options);
1206
+ this._onServerStateChanged.fire();
1207
+ return {
1208
+ ...result,
1209
+ state: conn.connectionState
1210
+ };
1211
+ }
1212
+ /**
1213
+ * Establish connection in the background after OAuth completion.
1214
+ * This method connects to the server and discovers its capabilities.
1215
+ * The connection is automatically tracked so that `waitForConnections()`
1216
+ * will include it.
1217
+ * @param serverId The server ID to establish connection for
1218
+ */
1219
+ async establishConnection(serverId) {
1220
+ const promise = this._doEstablishConnection(serverId);
1221
+ this._trackConnection(serverId, promise);
1222
+ return promise;
1223
+ }
1224
+ async _doEstablishConnection(serverId) {
1225
+ const conn = this.mcpConnections[serverId];
1226
+ if (!conn) {
1227
+ this._onObservabilityEvent.fire({
1228
+ type: "mcp:client:preconnect",
1229
+ payload: { serverId },
1230
+ timestamp: Date.now()
1231
+ });
1232
+ return;
1233
+ }
1234
+ if (conn.connectionState === MCPConnectionState.DISCOVERING || conn.connectionState === MCPConnectionState.READY) {
1235
+ this._onObservabilityEvent.fire({
1236
+ type: "mcp:client:connect",
1237
+ payload: {
1238
+ url: conn.url.toString(),
1239
+ transport: conn.options.transport.type || "unknown",
1240
+ state: conn.connectionState
1241
+ },
1242
+ timestamp: Date.now()
1243
+ });
1244
+ return;
1245
+ }
1246
+ const retry = this.getServerRetryOptions(serverId);
1247
+ const connectResult = await tryN(retry?.maxAttempts ?? 3, async () => this.connectToServer(serverId), {
1248
+ baseDelayMs: retry?.baseDelayMs ?? 500,
1249
+ maxDelayMs: retry?.maxDelayMs ?? 5e3
1250
+ });
1251
+ this._onServerStateChanged.fire();
1252
+ if (connectResult.state === MCPConnectionState.CONNECTED) await this.discoverIfConnected(serverId);
1253
+ this._onObservabilityEvent.fire({
1254
+ type: "mcp:client:connect",
1255
+ payload: {
1256
+ url: conn.url.toString(),
1257
+ transport: conn.options.transport.type || "unknown",
1258
+ state: conn.connectionState
1259
+ },
1260
+ timestamp: Date.now()
1261
+ });
1262
+ }
1263
+ /**
1264
+ * Configure OAuth callback handling
1265
+ * @param config OAuth callback configuration
1266
+ */
1267
+ configureOAuthCallback(config) {
1268
+ this._oauthCallbackConfig = config;
1269
+ }
1270
+ /**
1271
+ * Get the current OAuth callback configuration
1272
+ * @returns The current OAuth callback configuration
1273
+ */
1274
+ getOAuthCallbackConfig() {
1275
+ return this._oauthCallbackConfig;
1276
+ }
1277
+ /**
1278
+ * @returns namespaced list of tools
1279
+ */
1280
+ listTools() {
1281
+ return getNamespacedData(this.mcpConnections, "tools");
1282
+ }
1283
+ /**
1284
+ * Lazy-loads the jsonSchema function from the AI SDK.
1285
+ *
1286
+ * This defers importing the "ai" package until it's actually needed, which helps reduce
1287
+ * initial bundle size and startup time. The jsonSchema function is required for converting
1288
+ * MCP tools into AI SDK tool definitions via getAITools().
1289
+ *
1290
+ * @internal This method is for internal use only. It's automatically called before operations
1291
+ * that need jsonSchema (like getAITools() or OAuth flows). External consumers should not need
1292
+ * to call this directly.
1293
+ */
1294
+ async ensureJsonSchema() {
1295
+ if (!this.jsonSchema) {
1296
+ const { jsonSchema } = await import("ai");
1297
+ this.jsonSchema = jsonSchema;
1298
+ }
1299
+ }
1300
+ /**
1301
+ * @returns a set of tools that you can use with the AI SDK
1302
+ */
1303
+ getAITools() {
1304
+ if (!this.jsonSchema) throw new Error("jsonSchema not initialized.");
1305
+ for (const [id, conn] of Object.entries(this.mcpConnections)) if (conn.connectionState !== MCPConnectionState.READY && conn.connectionState !== MCPConnectionState.AUTHENTICATING) console.warn(`[getAITools] WARNING: Reading tools from connection ${id} in state "${conn.connectionState}". Tools may not be loaded yet.`);
1306
+ const entries = [];
1307
+ for (const tool of getNamespacedData(this.mcpConnections, "tools")) try {
1308
+ const toolKey = `tool_${tool.serverId.replace(/-/g, "")}_${tool.name}`;
1309
+ entries.push([toolKey, {
1310
+ description: tool.description,
1311
+ execute: async (args) => {
1312
+ const result = await this.callTool({
1313
+ arguments: args,
1314
+ name: tool.name,
1315
+ serverId: tool.serverId
1316
+ });
1317
+ if (result.isError) {
1318
+ const textContent = result.content?.[0];
1319
+ const message = textContent?.type === "text" && textContent.text ? textContent.text : "Tool call failed";
1320
+ throw new Error(message);
1321
+ }
1322
+ return result;
1323
+ },
1324
+ inputSchema: tool.inputSchema ? this.jsonSchema(tool.inputSchema) : this.jsonSchema({ type: "object" }),
1325
+ outputSchema: tool.outputSchema ? this.jsonSchema(tool.outputSchema) : void 0
1326
+ }]);
1327
+ } catch (e) {
1328
+ console.warn(`[getAITools] Skipping tool "${tool.name}" from "${tool.serverId}": ${e}`);
1329
+ }
1330
+ return Object.fromEntries(entries);
1331
+ }
1332
+ /**
1333
+ * @deprecated this has been renamed to getAITools(), and unstable_getAITools will be removed in the next major version
1334
+ * @returns a set of tools that you can use with the AI SDK
1335
+ */
1336
+ unstable_getAITools() {
1337
+ if (!this._didWarnAboutUnstableGetAITools) {
1338
+ this._didWarnAboutUnstableGetAITools = true;
1339
+ console.warn("unstable_getAITools is deprecated, use getAITools instead. unstable_getAITools will be removed in the next major version.");
1340
+ }
1341
+ return this.getAITools();
1342
+ }
1343
+ /**
1344
+ * Closes all active in-memory connections to MCP servers.
1345
+ *
1346
+ * Note: This only closes the transport connections - it does NOT remove
1347
+ * servers from storage. Servers will still be listed and their callback
1348
+ * URLs will still match incoming OAuth requests.
1349
+ *
1350
+ * Use removeServer() instead if you want to fully clean up a server
1351
+ * (closes connection AND removes from storage).
1352
+ */
1353
+ async closeAllConnections() {
1354
+ const ids = Object.keys(this.mcpConnections);
1355
+ this._pendingConnections.clear();
1356
+ for (const id of ids) this.mcpConnections[id].cancelDiscovery();
1357
+ await Promise.all(ids.map(async (id) => {
1358
+ await this.mcpConnections[id].client.close();
1359
+ }));
1360
+ for (const id of ids) {
1361
+ const store = this._connectionDisposables.get(id);
1362
+ if (store) store.dispose();
1363
+ this._connectionDisposables.delete(id);
1364
+ delete this.mcpConnections[id];
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Closes a connection to an MCP server
1369
+ * @param id The id of the connection to close
1370
+ */
1371
+ async closeConnection(id) {
1372
+ if (!this.mcpConnections[id]) throw new Error(`Connection with id "${id}" does not exist.`);
1373
+ this.mcpConnections[id].cancelDiscovery();
1374
+ this._pendingConnections.delete(id);
1375
+ await this.mcpConnections[id].client.close();
1376
+ delete this.mcpConnections[id];
1377
+ const store = this._connectionDisposables.get(id);
1378
+ if (store) store.dispose();
1379
+ this._connectionDisposables.delete(id);
1380
+ }
1381
+ /**
1382
+ * Remove an MCP server - closes connection if active and removes from storage.
1383
+ */
1384
+ async removeServer(serverId) {
1385
+ if (this.mcpConnections[serverId]) try {
1386
+ await this.closeConnection(serverId);
1387
+ } catch (_e) {}
1388
+ this.removeServerFromStorage(serverId);
1389
+ this._onServerStateChanged.fire();
1390
+ }
1391
+ /**
1392
+ * List all MCP servers from storage
1393
+ */
1394
+ listServers() {
1395
+ return this.getServersFromStorage();
1396
+ }
1397
+ /**
1398
+ * Dispose the manager and all resources.
1399
+ */
1400
+ async dispose() {
1401
+ try {
1402
+ await this.closeAllConnections();
1403
+ } finally {
1404
+ this._onServerStateChanged.dispose();
1405
+ this._onObservabilityEvent.dispose();
1406
+ }
1407
+ }
1408
+ /**
1409
+ * @returns namespaced list of prompts
1410
+ */
1411
+ listPrompts() {
1412
+ return getNamespacedData(this.mcpConnections, "prompts");
1413
+ }
1414
+ /**
1415
+ * @returns namespaced list of tools
1416
+ */
1417
+ listResources() {
1418
+ return getNamespacedData(this.mcpConnections, "resources");
1419
+ }
1420
+ /**
1421
+ * @returns namespaced list of resource templates
1422
+ */
1423
+ listResourceTemplates() {
1424
+ return getNamespacedData(this.mcpConnections, "resourceTemplates");
1425
+ }
1426
+ /**
1427
+ * Namespaced version of callTool
1428
+ */
1429
+ async callTool(params, resultSchema, options) {
1430
+ const { serverId, ...mcpParams } = params;
1431
+ const unqualifiedName = mcpParams.name.replace(`${serverId}.`, "");
1432
+ return this.mcpConnections[serverId].client.callTool({
1433
+ ...mcpParams,
1434
+ name: unqualifiedName
1435
+ }, resultSchema, options);
1436
+ }
1437
+ /**
1438
+ * Namespaced version of readResource
1439
+ */
1440
+ readResource(params, options) {
1441
+ return this.mcpConnections[params.serverId].client.readResource(params, options);
1442
+ }
1443
+ /**
1444
+ * Namespaced version of getPrompt
1445
+ */
1446
+ getPrompt(params, options) {
1447
+ return this.mcpConnections[params.serverId].client.getPrompt(params, options);
1448
+ }
1449
+ };
1450
+ function getNamespacedData(mcpClients, type) {
1451
+ return Object.entries(mcpClients).map(([name, conn]) => {
1452
+ return {
1453
+ data: conn[type],
1454
+ name
1455
+ };
1456
+ }).flatMap(({ name: serverId, data }) => {
1457
+ return data.map((item) => {
1458
+ return {
1459
+ ...item,
1460
+ serverId
1461
+ };
1462
+ });
1463
+ });
1464
+ }
1465
+ //#endregion
1466
+ export { RPCServerTransport as a, RPCClientTransport as i, getNamespacedData as n, RPC_DO_PREFIX as o, MCPConnectionState as r, DisposableStore as s, MCPClientManager as t };
1467
+
1468
+ //# sourceMappingURL=client-K8Z-u76l.js.map