hono-agents 0.0.0-cccbd0f → 0.0.0-cd8b7fd

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/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import "partysocket";
2
3
  import { nanoid } from "nanoid";
3
4
  import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker-provider.js";
@@ -5,14 +6,17 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
6
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
6
7
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7
8
  import { ElicitRequestSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
8
- import { AsyncLocalStorage } from "node:async_hooks";
9
9
  import { parseCronExpression } from "cron-schedule";
10
10
  import "cloudflare:email";
11
11
  import { Server, routePartykitRequest } from "partyserver";
12
12
  import { env } from "hono/adapter";
13
13
  import { createMiddleware } from "hono/factory";
14
14
 
15
- //#region ../agents/dist/ai-types-CrMqkwc_.js
15
+ //#region ../agents/dist/context-BkKbAa1R.js
16
+ const agentContext = new AsyncLocalStorage();
17
+
18
+ //#endregion
19
+ //#region ../agents/dist/ai-types-CwgHzwUb.js
16
20
  /**
17
21
  * Enum for message types to improve type safety and maintainability
18
22
  */
@@ -34,7 +38,7 @@ let MessageType = /* @__PURE__ */ function(MessageType$1) {
34
38
  }({});
35
39
 
36
40
  //#endregion
37
- //#region ../agents/dist/client-B3SR12TQ.js
41
+ //#region ../agents/dist/client-CcyhkGfN.js
38
42
  /**
39
43
  * Convert a camelCase string to a kebab-case string
40
44
  * @param str The string to convert
@@ -48,12 +52,14 @@ function camelCaseToKebabCase(str) {
48
52
  }
49
53
 
50
54
  //#endregion
51
- //#region ../agents/dist/do-oauth-client-provider-Cs9QpXYp.js
55
+ //#region ../agents/dist/do-oauth-client-provider-B1fVIshX.js
56
+ const STATE_EXPIRATION_MS = 600 * 1e3;
52
57
  var DurableObjectOAuthClientProvider = class {
53
58
  constructor(storage, clientName, baseRedirectUrl) {
54
59
  this.storage = storage;
55
60
  this.clientName = clientName;
56
61
  this.baseRedirectUrl = baseRedirectUrl;
62
+ if (!storage) throw new Error("DurableObjectOAuthClientProvider requires a valid DurableObjectStorage instance");
57
63
  }
58
64
  get clientMetadata() {
59
65
  return {
@@ -69,7 +75,7 @@ var DurableObjectOAuthClientProvider = class {
69
75
  return new URL(this.redirectUrl).origin;
70
76
  }
71
77
  get redirectUrl() {
72
- return `${this.baseRedirectUrl}/${this.serverId}`;
78
+ return this.baseRedirectUrl;
73
79
  }
74
80
  get clientId() {
75
81
  if (!this._clientId_) throw new Error("Trying to access clientId before it was set");
@@ -112,15 +118,72 @@ var DurableObjectOAuthClientProvider = class {
112
118
  get authUrl() {
113
119
  return this._authUrl_;
114
120
  }
115
- /**
116
- * Because this operates on the server side (but we need browser auth), we send this url back to the user
117
- * and require user interact to initiate the redirect flow
118
- */
119
- redirectToAuthorization(authUrl) {
120
- const stateToken = nanoid();
121
- authUrl.searchParams.set("state", stateToken);
121
+ stateKey(nonce) {
122
+ return `/${this.clientName}/${this.serverId}/state/${nonce}`;
123
+ }
124
+ async state() {
125
+ const nonce = nanoid();
126
+ const state = `${nonce}.${this.serverId}`;
127
+ const storedState = {
128
+ nonce,
129
+ serverId: this.serverId,
130
+ createdAt: Date.now()
131
+ };
132
+ await this.storage.put(this.stateKey(nonce), storedState);
133
+ return state;
134
+ }
135
+ async checkState(state) {
136
+ const parts = state.split(".");
137
+ if (parts.length !== 2) return {
138
+ valid: false,
139
+ error: "Invalid state format"
140
+ };
141
+ const [nonce, serverId] = parts;
142
+ const key = this.stateKey(nonce);
143
+ const storedState = await this.storage.get(key);
144
+ if (!storedState) return {
145
+ valid: false,
146
+ error: "State not found or already used"
147
+ };
148
+ if (storedState.serverId !== serverId) {
149
+ await this.storage.delete(key);
150
+ return {
151
+ valid: false,
152
+ error: "State serverId mismatch"
153
+ };
154
+ }
155
+ if (Date.now() - storedState.createdAt > STATE_EXPIRATION_MS) {
156
+ await this.storage.delete(key);
157
+ return {
158
+ valid: false,
159
+ error: "State expired"
160
+ };
161
+ }
162
+ return {
163
+ valid: true,
164
+ serverId
165
+ };
166
+ }
167
+ async consumeState(state) {
168
+ const parts = state.split(".");
169
+ if (parts.length !== 2) {
170
+ console.warn(`[OAuth] consumeState called with invalid state format: ${state.substring(0, 20)}...`);
171
+ return;
172
+ }
173
+ const [nonce] = parts;
174
+ await this.storage.delete(this.stateKey(nonce));
175
+ }
176
+ async redirectToAuthorization(authUrl) {
122
177
  this._authUrl_ = authUrl.toString();
123
178
  }
179
+ async invalidateCredentials(scope) {
180
+ if (!this._clientId_) return;
181
+ const deleteKeys = [];
182
+ if (scope === "all" || scope === "client") deleteKeys.push(this.clientInfoKey(this.clientId));
183
+ if (scope === "all" || scope === "tokens") deleteKeys.push(this.tokenKey(this.clientId));
184
+ if (scope === "all" || scope === "verifier") deleteKeys.push(this.codeVerifierKey(this.clientId));
185
+ if (deleteKeys.length > 0) await this.storage.delete(deleteKeys);
186
+ }
124
187
  codeVerifierKey(clientId) {
125
188
  return `${this.keyPrefix(clientId)}/code_verifier`;
126
189
  }
@@ -134,10 +197,13 @@ var DurableObjectOAuthClientProvider = class {
134
197
  if (!codeVerifier) throw new Error("No code verifier found");
135
198
  return codeVerifier;
136
199
  }
200
+ async deleteCodeVerifier() {
201
+ await this.storage.delete(this.codeVerifierKey(this.clientId));
202
+ }
137
203
  };
138
204
 
139
205
  //#endregion
140
- //#region ../agents/dist/client-BZ-xTxF5.js
206
+ //#region ../agents/dist/client-QZa2Rq0l.js
141
207
  function toDisposable(fn) {
142
208
  return { dispose: fn };
143
209
  }
@@ -185,6 +251,22 @@ function isTransportNotImplemented(error) {
185
251
  const msg = toErrorMessage(error);
186
252
  return msg.includes("404") || msg.includes("405") || msg.includes("Not Implemented") || msg.includes("not implemented");
187
253
  }
254
+ /**
255
+ * Connection state machine for MCP client connections.
256
+ *
257
+ * State transitions:
258
+ * - Non-OAuth: init() → CONNECTING → DISCOVERING → READY
259
+ * - OAuth: init() → AUTHENTICATING → (callback) → CONNECTING → DISCOVERING → READY
260
+ * - Any state can transition to FAILED on error
261
+ */
262
+ const MCPConnectionState = {
263
+ AUTHENTICATING: "authenticating",
264
+ CONNECTING: "connecting",
265
+ CONNECTED: "connected",
266
+ DISCOVERING: "discovering",
267
+ READY: "ready",
268
+ FAILED: "failed"
269
+ };
188
270
  var MCPClientConnection = class {
189
271
  constructor(url, info, options = {
190
272
  client: {},
@@ -192,7 +274,7 @@ var MCPClientConnection = class {
192
274
  }) {
193
275
  this.url = url;
194
276
  this.options = options;
195
- this.connectionState = "connecting";
277
+ this.connectionState = MCPConnectionState.CONNECTING;
196
278
  this.tools = [];
197
279
  this.prompts = [];
198
280
  this.resources = [];
@@ -208,36 +290,49 @@ var MCPClientConnection = class {
208
290
  });
209
291
  }
210
292
  /**
211
- * Initialize a client connection
293
+ * Initialize a client connection, if authentication is required, the connection will be in the AUTHENTICATING state
294
+ * Sets connection state based on the result and emits observability events
212
295
  *
213
- * @returns
296
+ * @returns Error message if connection failed, undefined otherwise
214
297
  */
215
298
  async init() {
216
299
  const transportType = this.options.transport.type;
217
300
  if (!transportType) throw new Error("Transport type must be specified");
218
- try {
219
- await this.tryConnect(transportType);
220
- } catch (e) {
221
- if (isUnauthorized(e)) {
222
- this.connectionState = "authenticating";
223
- return;
224
- }
301
+ const res = await this.tryConnect(transportType);
302
+ this.connectionState = res.state;
303
+ if (res.state === MCPConnectionState.CONNECTED && res.transport) {
304
+ this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
305
+ return await this.handleElicitationRequest(request);
306
+ });
307
+ this.lastConnectedTransport = res.transport;
308
+ this._onObservabilityEvent.fire({
309
+ type: "mcp:client:connect",
310
+ displayMessage: `Connected successfully using ${res.transport} transport for ${this.url.toString()}`,
311
+ payload: {
312
+ url: this.url.toString(),
313
+ transport: res.transport,
314
+ state: this.connectionState
315
+ },
316
+ timestamp: Date.now(),
317
+ id: nanoid()
318
+ });
319
+ return;
320
+ } else if (res.state === MCPConnectionState.FAILED && res.error) {
321
+ const errorMessage = toErrorMessage(res.error);
225
322
  this._onObservabilityEvent.fire({
226
323
  type: "mcp:client:connect",
227
- displayMessage: `Connection initialization failed for ${this.url.toString()}`,
324
+ displayMessage: `Failed to connect to ${this.url.toString()}: ${errorMessage}`,
228
325
  payload: {
229
326
  url: this.url.toString(),
230
327
  transport: transportType,
231
328
  state: this.connectionState,
232
- error: toErrorMessage(e)
329
+ error: errorMessage
233
330
  },
234
331
  timestamp: Date.now(),
235
332
  id: nanoid()
236
333
  });
237
- this.connectionState = "failed";
238
- return;
334
+ return errorMessage;
239
335
  }
240
- await this.discoverAndRegister();
241
336
  }
242
337
  /**
243
338
  * Finish OAuth by probing transports based on configured type.
@@ -269,113 +364,189 @@ var MCPClientConnection = class {
269
364
  * Complete OAuth authorization
270
365
  */
271
366
  async completeAuthorization(code) {
272
- if (this.connectionState !== "authenticating") throw new Error("Connection must be in authenticating state to complete authorization");
367
+ if (this.connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error("Connection must be in authenticating state to complete authorization");
273
368
  try {
274
369
  await this.finishAuthProbe(code);
275
- this.connectionState = "connecting";
370
+ this.connectionState = MCPConnectionState.CONNECTING;
276
371
  } catch (error) {
277
- this.connectionState = "failed";
372
+ this.connectionState = MCPConnectionState.FAILED;
278
373
  throw error;
279
374
  }
280
375
  }
281
376
  /**
282
- * Establish connection after successful authorization
377
+ * Discover server capabilities and register tools, resources, prompts, and templates.
378
+ * This method does the work but does not manage connection state - that's handled by discover().
283
379
  */
284
- async establishConnection() {
285
- if (this.connectionState !== "connecting") throw new Error("Connection must be in connecting state to establish connection");
380
+ async discoverAndRegister() {
381
+ this.serverCapabilities = this.client.getServerCapabilities();
382
+ if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
383
+ const operations = [];
384
+ const operationNames = [];
385
+ operations.push(Promise.resolve(this.client.getInstructions()));
386
+ operationNames.push("instructions");
387
+ if (this.serverCapabilities.tools) {
388
+ operations.push(this.registerTools());
389
+ operationNames.push("tools");
390
+ }
391
+ if (this.serverCapabilities.resources) {
392
+ operations.push(this.registerResources());
393
+ operationNames.push("resources");
394
+ }
395
+ if (this.serverCapabilities.prompts) {
396
+ operations.push(this.registerPrompts());
397
+ operationNames.push("prompts");
398
+ }
399
+ if (this.serverCapabilities.resources) {
400
+ operations.push(this.registerResourceTemplates());
401
+ operationNames.push("resource templates");
402
+ }
286
403
  try {
287
- const transportType = this.options.transport.type;
288
- if (!transportType) throw new Error("Transport type must be specified");
289
- await this.tryConnect(transportType);
290
- await this.discoverAndRegister();
404
+ const results = await Promise.all(operations);
405
+ for (let i = 0; i < results.length; i++) {
406
+ const result = results[i];
407
+ switch (operationNames[i]) {
408
+ case "instructions":
409
+ this.instructions = result;
410
+ break;
411
+ case "tools":
412
+ this.tools = result;
413
+ break;
414
+ case "resources":
415
+ this.resources = result;
416
+ break;
417
+ case "prompts":
418
+ this.prompts = result;
419
+ break;
420
+ case "resource templates":
421
+ this.resourceTemplates = result;
422
+ break;
423
+ }
424
+ }
291
425
  } catch (error) {
292
- this.connectionState = "failed";
426
+ this._onObservabilityEvent.fire({
427
+ type: "mcp:client:discover",
428
+ displayMessage: `Failed to discover capabilities for ${this.url.toString()}: ${toErrorMessage(error)}`,
429
+ payload: {
430
+ url: this.url.toString(),
431
+ error: toErrorMessage(error)
432
+ },
433
+ timestamp: Date.now(),
434
+ id: nanoid()
435
+ });
293
436
  throw error;
294
437
  }
295
438
  }
296
439
  /**
297
- * Discover server capabilities and register tools, resources, prompts, and templates
440
+ * Discover server capabilities with timeout and cancellation support.
441
+ * If called while a previous discovery is in-flight, the previous discovery will be aborted.
442
+ *
443
+ * @param options Optional configuration
444
+ * @param options.timeoutMs Timeout in milliseconds (default: 15000)
445
+ * @returns Result indicating success/failure with optional error message
298
446
  */
299
- async discoverAndRegister() {
300
- this.connectionState = "discovering";
301
- this.serverCapabilities = this.client.getServerCapabilities();
302
- if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
303
- const [instructionsResult, toolsResult, resourcesResult, promptsResult, resourceTemplatesResult] = await Promise.allSettled([
304
- this.client.getInstructions(),
305
- this.registerTools(),
306
- this.registerResources(),
307
- this.registerPrompts(),
308
- this.registerResourceTemplates()
309
- ]);
310
- const operations = [
311
- {
312
- name: "instructions",
313
- result: instructionsResult
314
- },
315
- {
316
- name: "tools",
317
- result: toolsResult
318
- },
319
- {
320
- name: "resources",
321
- result: resourcesResult
322
- },
323
- {
324
- name: "prompts",
325
- result: promptsResult
326
- },
327
- {
328
- name: "resource templates",
329
- result: resourceTemplatesResult
330
- }
331
- ];
332
- for (const { name, result } of operations) if (result.status === "rejected") {
333
- const url = this.url.toString();
447
+ async discover(options = {}) {
448
+ const { timeoutMs = 15e3 } = options;
449
+ if (this.connectionState !== MCPConnectionState.CONNECTED && this.connectionState !== MCPConnectionState.READY) {
334
450
  this._onObservabilityEvent.fire({
335
451
  type: "mcp:client:discover",
336
- displayMessage: `Failed to discover ${name} for ${url}`,
452
+ displayMessage: `Discovery skipped for ${this.url.toString()}, state is ${this.connectionState}`,
337
453
  payload: {
338
- url,
339
- capability: name,
340
- error: result.reason
454
+ url: this.url.toString(),
455
+ state: this.connectionState
341
456
  },
342
457
  timestamp: Date.now(),
343
458
  id: nanoid()
344
459
  });
460
+ return {
461
+ success: false,
462
+ error: `Discovery skipped - connection in ${this.connectionState} state`
463
+ };
464
+ }
465
+ if (this._discoveryAbortController) {
466
+ this._discoveryAbortController.abort();
467
+ this._discoveryAbortController = void 0;
468
+ }
469
+ const abortController = new AbortController();
470
+ this._discoveryAbortController = abortController;
471
+ this.connectionState = MCPConnectionState.DISCOVERING;
472
+ let timeoutId;
473
+ try {
474
+ const timeoutPromise = new Promise((_, reject) => {
475
+ timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error(`Discovery timed out after ${timeoutMs}ms`)), timeoutMs);
476
+ });
477
+ if (abortController.signal.aborted) throw new Error("Discovery was cancelled");
478
+ const abortPromise = new Promise((_, reject) => {
479
+ abortController.signal.addEventListener("abort", () => {
480
+ reject(/* @__PURE__ */ new Error("Discovery was cancelled"));
481
+ });
482
+ });
483
+ await Promise.race([
484
+ this.discoverAndRegister(),
485
+ timeoutPromise,
486
+ abortPromise
487
+ ]);
488
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
489
+ this.connectionState = MCPConnectionState.READY;
490
+ this._onObservabilityEvent.fire({
491
+ type: "mcp:client:discover",
492
+ displayMessage: `Discovery completed for ${this.url.toString()}`,
493
+ payload: { url: this.url.toString() },
494
+ timestamp: Date.now(),
495
+ id: nanoid()
496
+ });
497
+ return { success: true };
498
+ } catch (e) {
499
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
500
+ this.connectionState = MCPConnectionState.CONNECTED;
501
+ return {
502
+ success: false,
503
+ error: e instanceof Error ? e.message : String(e)
504
+ };
505
+ } finally {
506
+ this._discoveryAbortController = void 0;
507
+ }
508
+ }
509
+ /**
510
+ * Cancel any in-flight discovery operation.
511
+ * Called when closing the connection.
512
+ */
513
+ cancelDiscovery() {
514
+ if (this._discoveryAbortController) {
515
+ this._discoveryAbortController.abort();
516
+ this._discoveryAbortController = void 0;
345
517
  }
346
- this.instructions = instructionsResult.status === "fulfilled" ? instructionsResult.value : void 0;
347
- this.tools = toolsResult.status === "fulfilled" ? toolsResult.value : [];
348
- this.resources = resourcesResult.status === "fulfilled" ? resourcesResult.value : [];
349
- this.prompts = promptsResult.status === "fulfilled" ? promptsResult.value : [];
350
- this.resourceTemplates = resourceTemplatesResult.status === "fulfilled" ? resourceTemplatesResult.value : [];
351
- this.connectionState = "ready";
352
518
  }
353
519
  /**
354
- * Notification handler registration
520
+ * Notification handler registration for tools
521
+ * Should only be called if serverCapabilities.tools exists
355
522
  */
356
523
  async registerTools() {
357
- if (!this.serverCapabilities || !this.serverCapabilities.tools) return [];
358
- if (this.serverCapabilities.tools.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
524
+ if (this.serverCapabilities?.tools?.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
359
525
  this.tools = await this.fetchTools();
360
526
  });
361
527
  return this.fetchTools();
362
528
  }
529
+ /**
530
+ * Notification handler registration for resources
531
+ * Should only be called if serverCapabilities.resources exists
532
+ */
363
533
  async registerResources() {
364
- if (!this.serverCapabilities || !this.serverCapabilities.resources) return [];
365
- if (this.serverCapabilities.resources.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
534
+ if (this.serverCapabilities?.resources?.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
366
535
  this.resources = await this.fetchResources();
367
536
  });
368
537
  return this.fetchResources();
369
538
  }
539
+ /**
540
+ * Notification handler registration for prompts
541
+ * Should only be called if serverCapabilities.prompts exists
542
+ */
370
543
  async registerPrompts() {
371
- if (!this.serverCapabilities || !this.serverCapabilities.prompts) return [];
372
- if (this.serverCapabilities.prompts.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
544
+ if (this.serverCapabilities?.prompts?.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
373
545
  this.prompts = await this.fetchPrompts();
374
546
  });
375
547
  return this.fetchPrompts();
376
548
  }
377
549
  async registerResourceTemplates() {
378
- if (!this.serverCapabilities || !this.serverCapabilities.resources) return [];
379
550
  return this.fetchResourceTemplates();
380
551
  }
381
552
  async fetchTools() {
@@ -441,44 +612,24 @@ var MCPClientConnection = class {
441
612
  const transport = this.getTransport(currentTransportType);
442
613
  try {
443
614
  await this.client.connect(transport);
444
- this.lastConnectedTransport = currentTransportType;
445
- const url = this.url.toString();
446
- this._onObservabilityEvent.fire({
447
- type: "mcp:client:connect",
448
- displayMessage: `Connected successfully using ${currentTransportType} transport for ${url}`,
449
- payload: {
450
- url,
451
- transport: currentTransportType,
452
- state: this.connectionState
453
- },
454
- timestamp: Date.now(),
455
- id: nanoid()
456
- });
457
- break;
615
+ return {
616
+ state: MCPConnectionState.CONNECTED,
617
+ transport: currentTransportType
618
+ };
458
619
  } catch (e) {
459
620
  const error = e instanceof Error ? e : new Error(String(e));
460
- if (isUnauthorized(error)) throw e;
461
- if (hasFallback && isTransportNotImplemented(error)) {
462
- const url = this.url.toString();
463
- this._onObservabilityEvent.fire({
464
- type: "mcp:client:connect",
465
- displayMessage: `${currentTransportType} transport not available, trying ${transports[transports.indexOf(currentTransportType) + 1]} for ${url}`,
466
- payload: {
467
- url,
468
- transport: currentTransportType,
469
- state: this.connectionState
470
- },
471
- timestamp: Date.now(),
472
- id: nanoid()
473
- });
474
- continue;
475
- }
476
- throw e;
621
+ if (isUnauthorized(error)) return { state: MCPConnectionState.AUTHENTICATING };
622
+ if (isTransportNotImplemented(error) && hasFallback) continue;
623
+ return {
624
+ state: MCPConnectionState.FAILED,
625
+ error
626
+ };
477
627
  }
478
628
  }
479
- this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
480
- return await this.handleElicitationRequest(request);
481
- });
629
+ return {
630
+ state: MCPConnectionState.FAILED,
631
+ error: /* @__PURE__ */ new Error("No transports available")
632
+ };
482
633
  }
483
634
  _capabilityErrorHandler(empty, method) {
484
635
  return (e) => {
@@ -522,13 +673,32 @@ var MCPClientManager = class {
522
673
  this.onObservabilityEvent = this._onObservabilityEvent.event;
523
674
  this._onServerStateChanged = new Emitter();
524
675
  this.onServerStateChanged = this._onServerStateChanged.event;
676
+ if (!options.storage) throw new Error("MCPClientManager requires a valid DurableObjectStorage instance");
525
677
  this._storage = options.storage;
526
678
  }
679
+ sql(query, ...bindings) {
680
+ return [...this._storage.sql.exec(query, ...bindings)];
681
+ }
682
+ saveServerToStorage(server) {
683
+ this.sql(`INSERT OR REPLACE INTO cf_agents_mcp_servers (
684
+ id, name, server_url, client_id, auth_url, callback_url, server_options
685
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`, server.id, server.name, server.server_url, server.client_id ?? null, server.auth_url ?? null, server.callback_url, server.server_options ?? null);
686
+ }
687
+ removeServerFromStorage(serverId) {
688
+ this.sql("DELETE FROM cf_agents_mcp_servers WHERE id = ?", serverId);
689
+ }
690
+ getServersFromStorage() {
691
+ return this.sql("SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers");
692
+ }
693
+ clearServerAuthUrl(serverId) {
694
+ this.sql("UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?", serverId);
695
+ }
527
696
  /**
528
697
  * Create an auth provider for a server
529
698
  * @internal
530
699
  */
531
700
  createAuthProvider(serverId, callbackUrl, clientName, clientId) {
701
+ if (!this._storage) throw new Error("Cannot create auth provider: storage is not initialized");
532
702
  const authProvider = new DurableObjectOAuthClientProvider(this._storage, clientName, callbackUrl);
533
703
  authProvider.serverId = serverId;
534
704
  if (clientId) authProvider.clientId = clientId;
@@ -542,7 +712,7 @@ var MCPClientManager = class {
542
712
  */
543
713
  async restoreConnectionsFromStorage(clientName) {
544
714
  if (this._isRestored) return;
545
- const servers = await this._storage.listServers();
715
+ const servers = this.getServersFromStorage();
546
716
  if (!servers || servers.length === 0) {
547
717
  this._isRestored = true;
548
718
  return;
@@ -550,12 +720,12 @@ var MCPClientManager = class {
550
720
  for (const server of servers) {
551
721
  const existingConn = this.mcpConnections[server.id];
552
722
  if (existingConn) {
553
- if (existingConn.connectionState === "ready") {
723
+ if (existingConn.connectionState === MCPConnectionState.READY) {
554
724
  console.warn(`[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`);
555
725
  continue;
556
726
  }
557
- if (existingConn.connectionState === "authenticating" || existingConn.connectionState === "connecting" || existingConn.connectionState === "discovering") continue;
558
- if (existingConn.connectionState === "failed") {
727
+ if (existingConn.connectionState === MCPConnectionState.AUTHENTICATING || existingConn.connectionState === MCPConnectionState.CONNECTING || existingConn.connectionState === MCPConnectionState.DISCOVERING) continue;
728
+ if (existingConn.connectionState === MCPConnectionState.FAILED) {
559
729
  try {
560
730
  await existingConn.client.close();
561
731
  } catch (error) {
@@ -568,7 +738,7 @@ var MCPClientManager = class {
568
738
  }
569
739
  const parsedOptions = server.server_options ? JSON.parse(server.server_options) : null;
570
740
  const authProvider = this.createAuthProvider(server.id, server.callback_url, clientName, server.client_id ?? void 0);
571
- this.createConnection(server.id, server.server_url, {
741
+ const conn = this.createConnection(server.id, server.server_url, {
572
742
  client: parsedOptions?.client ?? {},
573
743
  transport: {
574
744
  ...parsedOptions?.transport ?? {},
@@ -576,13 +746,27 @@ var MCPClientManager = class {
576
746
  authProvider
577
747
  }
578
748
  });
579
- await this.connectToServer(server.id).catch((error) => {
580
- console.error(`Error restoring ${server.id}:`, error);
581
- });
749
+ if (server.auth_url) {
750
+ conn.connectionState = MCPConnectionState.AUTHENTICATING;
751
+ continue;
752
+ }
753
+ this._restoreServer(server.id);
582
754
  }
583
755
  this._isRestored = true;
584
756
  }
585
757
  /**
758
+ * Internal method to restore a single server connection and discovery
759
+ */
760
+ async _restoreServer(serverId) {
761
+ if ((await this.connectToServer(serverId).catch((error) => {
762
+ console.error(`Error connecting to ${serverId}:`, error);
763
+ return null;
764
+ }))?.state === MCPConnectionState.CONNECTED) {
765
+ const discoverResult = await this.discoverIfConnected(serverId);
766
+ if (discoverResult && !discoverResult.success) console.error(`Error discovering ${serverId}:`, discoverResult.error);
767
+ }
768
+ }
769
+ /**
586
770
  * Connect to and register an MCP server
587
771
  *
588
772
  * @deprecated This method is maintained for backward compatibility.
@@ -629,7 +813,7 @@ var MCPClientManager = class {
629
813
  await this.mcpConnections[id].init();
630
814
  if (options.reconnect?.oauthCode) try {
631
815
  await this.mcpConnections[id].completeAuthorization(options.reconnect.oauthCode);
632
- await this.mcpConnections[id].establishConnection();
816
+ await this.mcpConnections[id].init();
633
817
  } catch (error) {
634
818
  this._onObservabilityEvent.fire({
635
819
  type: "mcp:client:connect",
@@ -646,19 +830,22 @@ var MCPClientManager = class {
646
830
  throw error;
647
831
  }
648
832
  const authUrl = options.transport?.authProvider?.authUrl;
649
- if (this.mcpConnections[id].connectionState === "authenticating" && authUrl && options.transport?.authProvider?.redirectUrl) return {
833
+ if (this.mcpConnections[id].connectionState === MCPConnectionState.AUTHENTICATING && authUrl && options.transport?.authProvider?.redirectUrl) return {
650
834
  authUrl,
651
835
  clientId: options.transport?.authProvider?.clientId,
652
836
  id
653
837
  };
838
+ const discoverResult = await this.discoverIfConnected(id);
839
+ if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover server capabilities: ${discoverResult.error}`);
654
840
  return { id };
655
841
  }
656
842
  /**
657
843
  * Create an in-memory connection object and set up observability
658
844
  * Does NOT save to storage - use registerServer() for that
845
+ * @returns The connection object (existing or newly created)
659
846
  */
660
847
  createConnection(id, url, options) {
661
- if (this.mcpConnections[id]) return;
848
+ if (this.mcpConnections[id]) return this.mcpConnections[id];
662
849
  const normalizedTransport = {
663
850
  ...options.transport,
664
851
  type: options.transport?.type ?? "auto"
@@ -680,6 +867,7 @@ var MCPClientManager = class {
680
867
  store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
681
868
  this._onObservabilityEvent.fire(event);
682
869
  }));
870
+ return this.mcpConnections[id];
683
871
  }
684
872
  /**
685
873
  * Register an MCP server connection without connecting
@@ -697,7 +885,8 @@ var MCPClientManager = class {
697
885
  type: options.transport?.type ?? "auto"
698
886
  }
699
887
  });
700
- await this._storage.saveServer({
888
+ const { authProvider: _, ...transportWithoutAuth } = options.transport ?? {};
889
+ this.saveServerToStorage({
701
890
  id,
702
891
  name: options.name,
703
892
  server_url: options.url,
@@ -706,7 +895,7 @@ var MCPClientManager = class {
706
895
  auth_url: options.authUrl ?? null,
707
896
  server_options: JSON.stringify({
708
897
  client: options.client,
709
- transport: options.transport
898
+ transport: transportWithoutAuth
710
899
  })
711
900
  });
712
901
  this._onServerStateChanged.fire();
@@ -715,14 +904,13 @@ var MCPClientManager = class {
715
904
  /**
716
905
  * Connect to an already registered MCP server and initialize the connection.
717
906
  *
718
- * For OAuth servers, this returns `{ state: "authenticating", authUrl, clientId? }`
719
- * without establishing the connection. The user must complete the OAuth flow via
720
- * the authUrl, which will trigger a callback handled by `handleCallbackRequest()`.
721
- *
722
- * For non-OAuth servers, this establishes the connection immediately and returns
723
- * `{ state: "ready" }`.
907
+ * For OAuth servers, returns `{ state: "authenticating", authUrl, clientId? }`.
908
+ * The user must complete the OAuth flow via the authUrl, which triggers a
909
+ * callback handled by `handleCallbackRequest()`.
724
910
  *
725
- * Updates storage with auth URL and client ID after connection.
911
+ * For non-OAuth servers, establishes the transport connection and returns
912
+ * `{ state: "connected" }`. Call `discoverIfConnected()` afterwards to
913
+ * discover capabilities and transition to "ready" state.
726
914
  *
727
915
  * @param id Server ID (must be registered first via registerServer())
728
916
  * @returns Connection result with current state and OAuth info (if applicable)
@@ -730,70 +918,99 @@ var MCPClientManager = class {
730
918
  async connectToServer(id) {
731
919
  const conn = this.mcpConnections[id];
732
920
  if (!conn) throw new Error(`Server ${id} is not registered. Call registerServer() first.`);
733
- await conn.init();
734
- const authUrl = conn.options.transport.authProvider?.authUrl;
735
- if (conn.connectionState === "authenticating" && authUrl && conn.options.transport.authProvider?.redirectUrl) {
736
- const clientId = conn.options.transport.authProvider?.clientId;
737
- const serverRow = (await this._storage.listServers()).find((s) => s.id === id);
738
- if (serverRow) await this._storage.saveServer({
739
- ...serverRow,
740
- auth_url: authUrl,
741
- client_id: clientId ?? null
742
- });
743
- this._onServerStateChanged.fire();
744
- return {
745
- state: "authenticating",
746
- authUrl,
747
- clientId
921
+ const error = await conn.init();
922
+ this._onServerStateChanged.fire();
923
+ switch (conn.connectionState) {
924
+ case MCPConnectionState.FAILED: return {
925
+ state: conn.connectionState,
926
+ error: error ?? "Unknown connection error"
927
+ };
928
+ case MCPConnectionState.AUTHENTICATING: {
929
+ const authUrl = conn.options.transport.authProvider?.authUrl;
930
+ const redirectUrl = conn.options.transport.authProvider?.redirectUrl;
931
+ if (!authUrl || !redirectUrl) return {
932
+ state: MCPConnectionState.FAILED,
933
+ error: `OAuth configuration incomplete: missing ${!authUrl ? "authUrl" : "redirectUrl"}`
934
+ };
935
+ const clientId = conn.options.transport.authProvider?.clientId;
936
+ const serverRow = this.getServersFromStorage().find((s) => s.id === id);
937
+ if (serverRow) {
938
+ this.saveServerToStorage({
939
+ ...serverRow,
940
+ auth_url: authUrl,
941
+ client_id: clientId ?? null
942
+ });
943
+ this._onServerStateChanged.fire();
944
+ }
945
+ return {
946
+ state: conn.connectionState,
947
+ authUrl,
948
+ clientId
949
+ };
950
+ }
951
+ case MCPConnectionState.CONNECTED: return { state: conn.connectionState };
952
+ default: return {
953
+ state: MCPConnectionState.FAILED,
954
+ error: `Unexpected connection state after init: ${conn.connectionState}`
748
955
  };
749
956
  }
750
- if (conn.connectionState === "ready") this._onServerStateChanged.fire();
751
- return { state: "ready" };
752
957
  }
753
- async isCallbackRequest(req) {
958
+ extractServerIdFromState(state) {
959
+ if (!state) return null;
960
+ const parts = state.split(".");
961
+ return parts.length === 2 ? parts[1] : null;
962
+ }
963
+ isCallbackRequest(req) {
754
964
  if (req.method !== "GET") return false;
755
965
  if (!req.url.includes("/callback")) return false;
756
- return (await this._storage.listServers()).some((server) => server.callback_url && req.url.startsWith(server.callback_url));
966
+ const state = new URL(req.url).searchParams.get("state");
967
+ const serverId = this.extractServerIdFromState(state);
968
+ if (!serverId) return false;
969
+ return this.getServersFromStorage().some((server) => server.id === serverId);
757
970
  }
758
971
  async handleCallbackRequest(req) {
759
972
  const url = new URL(req.url);
760
- const matchingServer = (await this._storage.listServers()).find((server) => {
761
- return server.callback_url && req.url.startsWith(server.callback_url);
762
- });
763
- if (!matchingServer) throw new Error(`No callback URI match found for the request url: ${req.url}. Was the request matched with \`isCallbackRequest()\`?`);
764
- const serverId = matchingServer.id;
765
973
  const code = url.searchParams.get("code");
766
974
  const state = url.searchParams.get("state");
767
975
  const error = url.searchParams.get("error");
768
976
  const errorDescription = url.searchParams.get("error_description");
977
+ if (!state) throw new Error("Unauthorized: no state provided");
978
+ const serverId = this.extractServerIdFromState(state);
979
+ if (!serverId) throw new Error("No serverId found in state parameter. Expected format: {nonce}.{serverId}");
980
+ if (!this.getServersFromStorage().some((server) => server.id === serverId)) throw new Error(`No server found with id "${serverId}". Was the request matched with \`isCallbackRequest()\`?`);
981
+ if (this.mcpConnections[serverId] === void 0) throw new Error(`Could not find serverId: ${serverId}`);
982
+ const conn = this.mcpConnections[serverId];
983
+ if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
984
+ const authProvider = conn.options.transport.authProvider;
985
+ authProvider.serverId = serverId;
986
+ const stateValidation = await authProvider.checkState(state);
987
+ if (!stateValidation.valid) throw new Error(`Invalid state: ${stateValidation.error}`);
769
988
  if (error) return {
770
989
  serverId,
771
990
  authSuccess: false,
772
991
  authError: errorDescription || error
773
992
  };
774
993
  if (!code) throw new Error("Unauthorized: no code provided");
775
- if (!state) throw new Error("Unauthorized: no state provided");
776
- if (this.mcpConnections[serverId] === void 0) throw new Error(`Could not find serverId: ${serverId}`);
777
- if (this.mcpConnections[serverId].connectionState === "ready") return {
778
- serverId,
779
- authSuccess: true
780
- };
781
- if (this.mcpConnections[serverId].connectionState !== "authenticating") throw new Error(`Failed to authenticate: the client is in "${this.mcpConnections[serverId].connectionState}" state, expected "authenticating"`);
782
- const conn = this.mcpConnections[serverId];
783
- if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
784
- const clientId = conn.options.transport.authProvider.clientId || state;
785
- conn.options.transport.authProvider.clientId = clientId;
786
- conn.options.transport.authProvider.serverId = serverId;
994
+ if (this.mcpConnections[serverId].connectionState === MCPConnectionState.READY || this.mcpConnections[serverId].connectionState === MCPConnectionState.CONNECTED) {
995
+ this.clearServerAuthUrl(serverId);
996
+ return {
997
+ serverId,
998
+ authSuccess: true
999
+ };
1000
+ }
1001
+ if (this.mcpConnections[serverId].connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error(`Failed to authenticate: the client is in "${this.mcpConnections[serverId].connectionState}" state, expected "authenticating"`);
787
1002
  try {
1003
+ await authProvider.consumeState(state);
788
1004
  await conn.completeAuthorization(code);
789
- await this._storage.clearAuthUrl(serverId);
1005
+ await authProvider.deleteCodeVerifier();
1006
+ this.clearServerAuthUrl(serverId);
790
1007
  this._onServerStateChanged.fire();
791
1008
  return {
792
1009
  serverId,
793
1010
  authSuccess: true
794
1011
  };
795
- } catch (error$1) {
796
- const errorMessage = error$1 instanceof Error ? error$1.message : String(error$1);
1012
+ } catch (authError) {
1013
+ const errorMessage = authError instanceof Error ? authError.message : String(authError);
797
1014
  this._onServerStateChanged.fire();
798
1015
  return {
799
1016
  serverId,
@@ -803,8 +1020,40 @@ var MCPClientManager = class {
803
1020
  }
804
1021
  }
805
1022
  /**
1023
+ * Discover server capabilities if connection is in CONNECTED or READY state.
1024
+ * Transitions to DISCOVERING then READY (or CONNECTED on error).
1025
+ * Can be called to refresh server capabilities (e.g., from a UI refresh button).
1026
+ *
1027
+ * If called while a previous discovery is in-flight for the same server,
1028
+ * the previous discovery will be aborted.
1029
+ *
1030
+ * @param serverId The server ID to discover
1031
+ * @param options Optional configuration
1032
+ * @param options.timeoutMs Timeout in milliseconds (default: 30000)
1033
+ * @returns Result with current state and optional error, or undefined if connection not found
1034
+ */
1035
+ async discoverIfConnected(serverId, options = {}) {
1036
+ const conn = this.mcpConnections[serverId];
1037
+ if (!conn) {
1038
+ this._onObservabilityEvent.fire({
1039
+ type: "mcp:client:discover",
1040
+ displayMessage: `Connection not found for ${serverId}`,
1041
+ payload: {},
1042
+ timestamp: Date.now(),
1043
+ id: nanoid()
1044
+ });
1045
+ return;
1046
+ }
1047
+ const result = await conn.discover(options);
1048
+ this._onServerStateChanged.fire();
1049
+ return {
1050
+ ...result,
1051
+ state: conn.connectionState
1052
+ };
1053
+ }
1054
+ /**
806
1055
  * Establish connection in the background after OAuth completion
807
- * This method is called asynchronously and doesn't block the OAuth callback response
1056
+ * This method connects to the server and discovers its capabilities
808
1057
  * @param serverId The server ID to establish connection for
809
1058
  */
810
1059
  async establishConnection(serverId) {
@@ -819,25 +1068,34 @@ var MCPClientManager = class {
819
1068
  });
820
1069
  return;
821
1070
  }
822
- try {
823
- await conn.establishConnection();
824
- this._onServerStateChanged.fire();
825
- } catch (error) {
826
- const url = conn.url.toString();
1071
+ if (conn.connectionState === MCPConnectionState.DISCOVERING || conn.connectionState === MCPConnectionState.READY) {
827
1072
  this._onObservabilityEvent.fire({
828
1073
  type: "mcp:client:connect",
829
- displayMessage: `Failed to establish connection to server ${serverId} with url ${url}`,
1074
+ displayMessage: `establishConnection skipped for ${serverId}, already in ${conn.connectionState} state`,
830
1075
  payload: {
831
- url,
832
- transport: conn.options.transport.type ?? "auto",
833
- state: conn.connectionState,
834
- error: toErrorMessage(error)
1076
+ url: conn.url.toString(),
1077
+ transport: conn.options.transport.type || "unknown",
1078
+ state: conn.connectionState
835
1079
  },
836
1080
  timestamp: Date.now(),
837
1081
  id: nanoid()
838
1082
  });
839
- this._onServerStateChanged.fire();
1083
+ return;
840
1084
  }
1085
+ const connectResult = await this.connectToServer(serverId);
1086
+ this._onServerStateChanged.fire();
1087
+ if (connectResult.state === MCPConnectionState.CONNECTED) await this.discoverIfConnected(serverId);
1088
+ this._onObservabilityEvent.fire({
1089
+ type: "mcp:client:connect",
1090
+ displayMessage: `establishConnection completed for ${serverId}, final state: ${conn.connectionState}`,
1091
+ payload: {
1092
+ url: conn.url.toString(),
1093
+ transport: conn.options.transport.type || "unknown",
1094
+ state: conn.connectionState
1095
+ },
1096
+ timestamp: Date.now(),
1097
+ id: nanoid()
1098
+ });
841
1099
  }
842
1100
  /**
843
1101
  * Configure OAuth callback handling
@@ -881,7 +1139,7 @@ var MCPClientManager = class {
881
1139
  */
882
1140
  getAITools() {
883
1141
  if (!this.jsonSchema) throw new Error("jsonSchema not initialized.");
884
- for (const [id, conn] of Object.entries(this.mcpConnections)) if (conn.connectionState !== "ready" && conn.connectionState !== "authenticating") console.warn(`[getAITools] WARNING: Reading tools from connection ${id} in state "${conn.connectionState}". Tools may not be loaded yet.`);
1142
+ 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.`);
885
1143
  return Object.fromEntries(getNamespacedData(this.mcpConnections, "tools").map((tool) => {
886
1144
  return [`tool_${tool.serverId.replace(/-/g, "")}_${tool.name}`, {
887
1145
  description: tool.description,
@@ -911,10 +1169,18 @@ var MCPClientManager = class {
911
1169
  return this.getAITools();
912
1170
  }
913
1171
  /**
914
- * Closes all connections to MCP servers
1172
+ * Closes all active in-memory connections to MCP servers.
1173
+ *
1174
+ * Note: This only closes the transport connections - it does NOT remove
1175
+ * servers from storage. Servers will still be listed and their callback
1176
+ * URLs will still match incoming OAuth requests.
1177
+ *
1178
+ * Use removeServer() instead if you want to fully clean up a server
1179
+ * (closes connection AND removes from storage).
915
1180
  */
916
1181
  async closeAllConnections() {
917
1182
  const ids = Object.keys(this.mcpConnections);
1183
+ for (const id of ids) this.mcpConnections[id].cancelDiscovery();
918
1184
  await Promise.all(ids.map(async (id) => {
919
1185
  await this.mcpConnections[id].client.close();
920
1186
  }));
@@ -931,6 +1197,7 @@ var MCPClientManager = class {
931
1197
  */
932
1198
  async closeConnection(id) {
933
1199
  if (!this.mcpConnections[id]) throw new Error(`Connection with id "${id}" does not exist.`);
1200
+ this.mcpConnections[id].cancelDiscovery();
934
1201
  await this.mcpConnections[id].client.close();
935
1202
  delete this.mcpConnections[id];
936
1203
  const store = this._connectionDisposables.get(id);
@@ -938,17 +1205,20 @@ var MCPClientManager = class {
938
1205
  this._connectionDisposables.delete(id);
939
1206
  }
940
1207
  /**
941
- * Remove an MCP server from storage
1208
+ * Remove an MCP server - closes connection if active and removes from storage.
942
1209
  */
943
1210
  async removeServer(serverId) {
944
- await this._storage.removeServer(serverId);
1211
+ if (this.mcpConnections[serverId]) try {
1212
+ await this.closeConnection(serverId);
1213
+ } catch (_e) {}
1214
+ this.removeServerFromStorage(serverId);
945
1215
  this._onServerStateChanged.fire();
946
1216
  }
947
1217
  /**
948
1218
  * List all MCP servers from storage
949
1219
  */
950
- async listServers() {
951
- return await this._storage.listServers();
1220
+ listServers() {
1221
+ return this.getServersFromStorage();
952
1222
  }
953
1223
  /**
954
1224
  * Dispose the manager and all resources.
@@ -1019,72 +1289,7 @@ function getNamespacedData(mcpClients, type) {
1019
1289
  }
1020
1290
 
1021
1291
  //#endregion
1022
- //#region ../agents/dist/src-C6rC6ZpH.js
1023
- /**
1024
- * SQL-based storage adapter that wraps SQL operations
1025
- * Used by Agent class to provide SQL access to MCPClientManager
1026
- */
1027
- var AgentMCPClientStorage = class {
1028
- constructor(sql, kv) {
1029
- this.sql = sql;
1030
- this.kv = kv;
1031
- }
1032
- async saveServer(server) {
1033
- this.sql`
1034
- INSERT OR REPLACE INTO cf_agents_mcp_servers (
1035
- id,
1036
- name,
1037
- server_url,
1038
- client_id,
1039
- auth_url,
1040
- callback_url,
1041
- server_options
1042
- )
1043
- VALUES (
1044
- ${server.id},
1045
- ${server.name},
1046
- ${server.server_url},
1047
- ${server.client_id ?? null},
1048
- ${server.auth_url ?? null},
1049
- ${server.callback_url},
1050
- ${server.server_options ?? null}
1051
- )
1052
- `;
1053
- }
1054
- async removeServer(serverId) {
1055
- this.sql`
1056
- DELETE FROM cf_agents_mcp_servers WHERE id = ${serverId}
1057
- `;
1058
- }
1059
- async listServers() {
1060
- return this.sql`
1061
- SELECT id, name, server_url, client_id, auth_url, callback_url, server_options
1062
- FROM cf_agents_mcp_servers
1063
- `;
1064
- }
1065
- async getServerByCallbackUrl(callbackUrl) {
1066
- const results = this.sql`
1067
- SELECT id, name, server_url, client_id, auth_url, callback_url, server_options
1068
- FROM cf_agents_mcp_servers
1069
- WHERE callback_url = ${callbackUrl}
1070
- LIMIT 1
1071
- `;
1072
- return results.length > 0 ? results[0] : null;
1073
- }
1074
- async clearAuthUrl(serverId) {
1075
- this.sql`
1076
- UPDATE cf_agents_mcp_servers
1077
- SET auth_url = NULL
1078
- WHERE id = ${serverId}
1079
- `;
1080
- }
1081
- async get(key) {
1082
- return this.kv.get(key);
1083
- }
1084
- async put(key, value) {
1085
- return this.kv.put(key, value);
1086
- }
1087
- };
1292
+ //#region ../agents/dist/src-BmbDclOA.js
1088
1293
  /**
1089
1294
  * A generic observability implementation that logs events to the console.
1090
1295
  */
@@ -1122,7 +1327,6 @@ function getNextCronTime(cron) {
1122
1327
  const STATE_ROW_ID = "cf_state_row_id";
1123
1328
  const STATE_WAS_CHANGED = "cf_state_was_changed";
1124
1329
  const DEFAULT_STATE = {};
1125
- const agentContext = new AsyncLocalStorage();
1126
1330
  function getCurrentAgent() {
1127
1331
  const store = agentContext.getStore();
1128
1332
  if (!store) return {
@@ -1258,17 +1462,10 @@ var Agent = class Agent$1 extends Server {
1258
1462
  if (this._destroyed) return;
1259
1463
  await this._scheduleNextAlarm();
1260
1464
  };
1261
- this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", { storage: new AgentMCPClientStorage(this.sql.bind(this), this.ctx.storage.kv) });
1262
1465
  if (!wrappedClasses.has(this.constructor)) {
1263
1466
  this._autoWrapCustomMethods();
1264
1467
  wrappedClasses.add(this.constructor);
1265
1468
  }
1266
- this._disposables.add(this.mcp.onServerStateChanged(async () => {
1267
- await this.broadcastMcpServers();
1268
- }));
1269
- this._disposables.add(this.mcp.onObservabilityEvent((event) => {
1270
- this.observability?.emit(event);
1271
- }));
1272
1469
  this.sql`
1273
1470
  CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
1274
1471
  id TEXT PRIMARY KEY NOT NULL,
@@ -1306,6 +1503,13 @@ var Agent = class Agent$1 extends Server {
1306
1503
  created_at INTEGER DEFAULT (unixepoch())
1307
1504
  )
1308
1505
  `;
1506
+ this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", { storage: this.ctx.storage });
1507
+ this._disposables.add(this.mcp.onServerStateChanged(async () => {
1508
+ this.broadcastMcpServers();
1509
+ }));
1510
+ this._disposables.add(this.mcp.onObservabilityEvent((event) => {
1511
+ this.observability?.emit(event);
1512
+ }));
1309
1513
  const _onRequest = this.onRequest.bind(this);
1310
1514
  this.onRequest = (request) => {
1311
1515
  return agentContext.run({
@@ -1399,7 +1603,7 @@ var Agent = class Agent$1 extends Server {
1399
1603
  type: MessageType.CF_AGENT_STATE
1400
1604
  }));
1401
1605
  connection.send(JSON.stringify({
1402
- mcp: await this.getMcpServers(),
1606
+ mcp: this.getMcpServers(),
1403
1607
  type: MessageType.CF_AGENT_MCP_SERVERS
1404
1608
  }));
1405
1609
  this.observability?.emit({
@@ -1422,7 +1626,7 @@ var Agent = class Agent$1 extends Server {
1422
1626
  }, async () => {
1423
1627
  await this._tryCatch(async () => {
1424
1628
  await this.mcp.restoreConnectionsFromStorage(this.name);
1425
- await this.broadcastMcpServers();
1629
+ this.broadcastMcpServers();
1426
1630
  return _onStart(props);
1427
1631
  });
1428
1632
  });
@@ -1767,10 +1971,7 @@ var Agent = class Agent$1 extends Server {
1767
1971
  const result = this.sql`
1768
1972
  SELECT * FROM cf_agents_schedules WHERE id = ${id}
1769
1973
  `;
1770
- if (!result) {
1771
- console.error(`schedule ${id} not found`);
1772
- return;
1773
- }
1974
+ if (!result || result.length === 0) return;
1774
1975
  return {
1775
1976
  ...result[0],
1776
1977
  payload: JSON.parse(result[0].payload)
@@ -1807,11 +2008,12 @@ var Agent = class Agent$1 extends Server {
1807
2008
  /**
1808
2009
  * Cancel a scheduled task
1809
2010
  * @param id ID of the task to cancel
1810
- * @returns true if the task was cancelled, false otherwise
2011
+ * @returns true if the task was cancelled, false if the task was not found
1811
2012
  */
1812
2013
  async cancelSchedule(id) {
1813
2014
  const schedule = await this.getSchedule(id);
1814
- if (schedule) this.observability?.emit({
2015
+ if (!schedule) return false;
2016
+ this.observability?.emit({
1815
2017
  displayMessage: `Schedule ${id} cancelled`,
1816
2018
  id: nanoid(),
1817
2019
  payload: {
@@ -1877,7 +2079,8 @@ var Agent = class Agent$1 extends Server {
1877
2079
  * @param callbackHost Base host for the agent, used for the redirect URI. If not provided, will be derived from the current request.
1878
2080
  * @param agentsPrefix agents routing prefix if not using `agents`
1879
2081
  * @param options MCP client and transport options
1880
- * @returns authUrl
2082
+ * @returns Server id and state - either "authenticating" with authUrl, or "ready"
2083
+ * @throws If connection or discovery fails
1881
2084
  */
1882
2085
  async addMcpServer(serverName, url, callbackHost, agentsPrefix = "agents", options) {
1883
2086
  let resolvedCallbackHost = callbackHost;
@@ -1890,7 +2093,7 @@ var Agent = class Agent$1 extends Server {
1890
2093
  const callbackUrl = `${resolvedCallbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
1891
2094
  await this.mcp.ensureJsonSchema();
1892
2095
  const id = nanoid(8);
1893
- const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage.kv, this.name, callbackUrl);
2096
+ const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage, this.name, callbackUrl);
1894
2097
  authProvider.serverId = id;
1895
2098
  const transportType = options?.transport?.type ?? "auto";
1896
2099
  let headerTransportOpts = {};
@@ -1913,23 +2116,30 @@ var Agent = class Agent$1 extends Server {
1913
2116
  }
1914
2117
  });
1915
2118
  const result = await this.mcp.connectToServer(id);
2119
+ if (result.state === MCPConnectionState.FAILED) throw new Error(`Failed to connect to MCP server at ${url}: ${result.error}`);
2120
+ if (result.state === MCPConnectionState.AUTHENTICATING) return {
2121
+ id,
2122
+ state: result.state,
2123
+ authUrl: result.authUrl
2124
+ };
2125
+ const discoverResult = await this.mcp.discoverIfConnected(id);
2126
+ if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover MCP server capabilities: ${discoverResult.error}`);
1916
2127
  return {
1917
2128
  id,
1918
- authUrl: result.state === "authenticating" ? result.authUrl : void 0
2129
+ state: MCPConnectionState.READY
1919
2130
  };
1920
2131
  }
1921
2132
  async removeMcpServer(id) {
1922
- if (this.mcp.mcpConnections[id]) await this.mcp.closeConnection(id);
1923
2133
  await this.mcp.removeServer(id);
1924
2134
  }
1925
- async getMcpServers() {
2135
+ getMcpServers() {
1926
2136
  const mcpState = {
1927
2137
  prompts: this.mcp.listPrompts(),
1928
2138
  resources: this.mcp.listResources(),
1929
2139
  servers: {},
1930
2140
  tools: this.mcp.listTools()
1931
2141
  };
1932
- const servers = await this.mcp.listServers();
2142
+ const servers = this.mcp.listServers();
1933
2143
  if (servers && Array.isArray(servers) && servers.length > 0) for (const server of servers) {
1934
2144
  const serverConn = this.mcp.mcpConnections[server.id];
1935
2145
  let defaultState = "not-connected";
@@ -1945,9 +2155,9 @@ var Agent = class Agent$1 extends Server {
1945
2155
  }
1946
2156
  return mcpState;
1947
2157
  }
1948
- async broadcastMcpServers() {
2158
+ broadcastMcpServers() {
1949
2159
  this.broadcast(JSON.stringify({
1950
- mcp: await this.getMcpServers(),
2160
+ mcp: this.getMcpServers(),
1951
2161
  type: MessageType.CF_AGENT_MCP_SERVERS
1952
2162
  }));
1953
2163
  }
@@ -1965,16 +2175,12 @@ var Agent = class Agent$1 extends Server {
1965
2175
  * @returns Response if this was an OAuth callback, null otherwise
1966
2176
  */
1967
2177
  async handleMcpOAuthCallback(request) {
1968
- if (!await this.mcp.isCallbackRequest(request)) return null;
2178
+ if (!this.mcp.isCallbackRequest(request)) return null;
1969
2179
  const result = await this.mcp.handleCallbackRequest(request);
1970
- if (result.authSuccess) {
1971
- this.broadcastMcpServers();
1972
- this.mcp.establishConnection(result.serverId).catch((error) => {
1973
- console.error("[Agent handleMcpOAuthCallback] Connection establishment failed:", error);
1974
- }).finally(() => {
1975
- this.broadcastMcpServers();
1976
- });
1977
- }
2180
+ if (result.authSuccess) this.mcp.establishConnection(result.serverId).catch((error) => {
2181
+ console.error("[Agent handleMcpOAuthCallback] Connection establishment failed:", error);
2182
+ });
2183
+ this.broadcastMcpServers();
1978
2184
  return this.handleOAuthCallbackResponse(result, request);
1979
2185
  }
1980
2186
  /**