agents 0.0.0-cf3b3d7 → 0.0.0-d08612f

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 (69) hide show
  1. package/README.md +5 -3
  2. package/dist/ai-chat-agent.d.ts +95 -13
  3. package/dist/ai-chat-agent.js +354 -74
  4. package/dist/ai-chat-agent.js.map +1 -1
  5. package/dist/{ai-chat-v5-migration-DBHGW4Hv.js → ai-chat-v5-migration-DguhuLKF.js} +1 -1
  6. package/dist/{ai-chat-v5-migration-DBHGW4Hv.js.map → ai-chat-v5-migration-DguhuLKF.js.map} +1 -1
  7. package/dist/ai-chat-v5-migration.js +1 -1
  8. package/dist/ai-react.d.ts +15 -9
  9. package/dist/ai-react.js +171 -26
  10. package/dist/ai-react.js.map +1 -1
  11. package/dist/{ai-types-B3aQaFv3.js → ai-types-CwgHzwUb.js} +5 -1
  12. package/dist/ai-types-CwgHzwUb.js.map +1 -0
  13. package/dist/{ai-types-D5YoPrBZ.d.ts → ai-types-D_hTbf25.d.ts} +15 -7
  14. package/dist/ai-types.d.ts +1 -1
  15. package/dist/ai-types.js +1 -1
  16. package/dist/cli/index.d.ts +1 -0
  17. package/dist/{cli.js → cli/index.js} +7 -6
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/{client-BfiZ3HQd.js → client-CcyhkGfN.js} +2 -2
  20. package/dist/{client-BfiZ3HQd.js.map → client-CcyhkGfN.js.map} +1 -1
  21. package/dist/{client-CbWe9FBd.d.ts → client-ClORm6f0.d.ts} +2 -2
  22. package/dist/client-DfIOsabL.d.ts +834 -0
  23. package/dist/{client-DpkZyXgJ.js → client-QZa2Rq0l.js} +398 -194
  24. package/dist/client-QZa2Rq0l.js.map +1 -0
  25. package/dist/client.d.ts +2 -2
  26. package/dist/client.js +2 -2
  27. package/dist/codemode/ai.js +6 -5
  28. package/dist/codemode/ai.js.map +1 -1
  29. package/dist/context-BkKbAa1R.js +8 -0
  30. package/dist/context-BkKbAa1R.js.map +1 -0
  31. package/dist/context-_sPQqJWv.d.ts +24 -0
  32. package/dist/context.d.ts +6 -0
  33. package/dist/context.js +3 -0
  34. package/dist/do-oauth-client-provider-B-ryFIPr.d.ts +70 -0
  35. package/dist/{do-oauth-client-provider-D2P1lSft.js → do-oauth-client-provider-B1fVIshX.js} +71 -9
  36. package/dist/do-oauth-client-provider-B1fVIshX.js.map +1 -0
  37. package/dist/{index-DhJCaDWd.d.ts → index-CyDpAVHZ.d.ts} +2 -2
  38. package/dist/{index-DCRAdW9R.d.ts → index-DPJ32qQn.d.ts} +60 -55
  39. package/dist/index.d.ts +34 -34
  40. package/dist/index.js +6 -5
  41. package/dist/mcp/client.d.ts +4 -4
  42. package/dist/mcp/client.js +2 -2
  43. package/dist/mcp/do-oauth-client-provider.d.ts +1 -1
  44. package/dist/mcp/do-oauth-client-provider.js +1 -1
  45. package/dist/mcp/index.d.ts +10 -9
  46. package/dist/mcp/index.js +19 -11
  47. package/dist/mcp/index.js.map +1 -1
  48. package/dist/mcp/x402.js +10 -6
  49. package/dist/mcp/x402.js.map +1 -1
  50. package/dist/{mcp-Dw5vDrY8.d.ts → mcp-CzbSsLfc.d.ts} +1 -1
  51. package/dist/observability/index.d.ts +2 -2
  52. package/dist/observability/index.js +6 -5
  53. package/dist/{react-DM_FD53F.d.ts → react-DTzwSLAh.d.ts} +29 -5
  54. package/dist/react.d.ts +15 -10
  55. package/dist/react.js +11 -4
  56. package/dist/react.js.map +1 -1
  57. package/dist/{serializable-CymX8ovI.d.ts → serializable-C4GLimgv.d.ts} +1 -1
  58. package/dist/serializable.d.ts +1 -1
  59. package/dist/{src-Dk8lwxHf.js → src-BmbDclOA.js} +38 -100
  60. package/dist/src-BmbDclOA.js.map +1 -0
  61. package/package.json +8 -8
  62. package/dist/ai-types-B3aQaFv3.js.map +0 -1
  63. package/dist/cli.d.ts +0 -8
  64. package/dist/cli.js.map +0 -1
  65. package/dist/client-BaCHMay9.d.ts +0 -5427
  66. package/dist/client-DpkZyXgJ.js.map +0 -1
  67. package/dist/do-oauth-client-provider-CnbnngL2.d.ts +0 -134
  68. package/dist/do-oauth-client-provider-D2P1lSft.js.map +0 -1
  69. package/dist/src-Dk8lwxHf.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { t as DurableObjectOAuthClientProvider } from "./do-oauth-client-provider-D2P1lSft.js";
1
+ import { t as DurableObjectOAuthClientProvider } from "./do-oauth-client-provider-B1fVIshX.js";
2
2
  import { nanoid } from "nanoid";
3
3
  import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker-provider.js";
4
4
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -60,6 +60,22 @@ function isTransportNotImplemented(error) {
60
60
 
61
61
  //#endregion
62
62
  //#region src/mcp/client-connection.ts
63
+ /**
64
+ * Connection state machine for MCP client connections.
65
+ *
66
+ * State transitions:
67
+ * - Non-OAuth: init() → CONNECTING → DISCOVERING → READY
68
+ * - OAuth: init() → AUTHENTICATING → (callback) → CONNECTING → DISCOVERING → READY
69
+ * - Any state can transition to FAILED on error
70
+ */
71
+ const MCPConnectionState = {
72
+ AUTHENTICATING: "authenticating",
73
+ CONNECTING: "connecting",
74
+ CONNECTED: "connected",
75
+ DISCOVERING: "discovering",
76
+ READY: "ready",
77
+ FAILED: "failed"
78
+ };
63
79
  var MCPClientConnection = class {
64
80
  constructor(url, info, options = {
65
81
  client: {},
@@ -67,7 +83,7 @@ var MCPClientConnection = class {
67
83
  }) {
68
84
  this.url = url;
69
85
  this.options = options;
70
- this.connectionState = "connecting";
86
+ this.connectionState = MCPConnectionState.CONNECTING;
71
87
  this.tools = [];
72
88
  this.prompts = [];
73
89
  this.resources = [];
@@ -83,36 +99,49 @@ var MCPClientConnection = class {
83
99
  });
84
100
  }
85
101
  /**
86
- * Initialize a client connection
102
+ * Initialize a client connection, if authentication is required, the connection will be in the AUTHENTICATING state
103
+ * Sets connection state based on the result and emits observability events
87
104
  *
88
- * @returns
105
+ * @returns Error message if connection failed, undefined otherwise
89
106
  */
90
107
  async init() {
91
108
  const transportType = this.options.transport.type;
92
109
  if (!transportType) throw new Error("Transport type must be specified");
93
- try {
94
- await this.tryConnect(transportType);
95
- } catch (e) {
96
- if (isUnauthorized(e)) {
97
- this.connectionState = "authenticating";
98
- return;
99
- }
110
+ const res = await this.tryConnect(transportType);
111
+ this.connectionState = res.state;
112
+ if (res.state === MCPConnectionState.CONNECTED && res.transport) {
113
+ this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
114
+ return await this.handleElicitationRequest(request);
115
+ });
116
+ this.lastConnectedTransport = res.transport;
117
+ this._onObservabilityEvent.fire({
118
+ type: "mcp:client:connect",
119
+ displayMessage: `Connected successfully using ${res.transport} transport for ${this.url.toString()}`,
120
+ payload: {
121
+ url: this.url.toString(),
122
+ transport: res.transport,
123
+ state: this.connectionState
124
+ },
125
+ timestamp: Date.now(),
126
+ id: nanoid()
127
+ });
128
+ return;
129
+ } else if (res.state === MCPConnectionState.FAILED && res.error) {
130
+ const errorMessage = toErrorMessage(res.error);
100
131
  this._onObservabilityEvent.fire({
101
132
  type: "mcp:client:connect",
102
- displayMessage: `Connection initialization failed for ${this.url.toString()}`,
133
+ displayMessage: `Failed to connect to ${this.url.toString()}: ${errorMessage}`,
103
134
  payload: {
104
135
  url: this.url.toString(),
105
136
  transport: transportType,
106
137
  state: this.connectionState,
107
- error: toErrorMessage(e)
138
+ error: errorMessage
108
139
  },
109
140
  timestamp: Date.now(),
110
141
  id: nanoid()
111
142
  });
112
- this.connectionState = "failed";
113
- return;
143
+ return errorMessage;
114
144
  }
115
- await this.discoverAndRegister();
116
145
  }
117
146
  /**
118
147
  * Finish OAuth by probing transports based on configured type.
@@ -144,113 +173,189 @@ var MCPClientConnection = class {
144
173
  * Complete OAuth authorization
145
174
  */
146
175
  async completeAuthorization(code) {
147
- if (this.connectionState !== "authenticating") throw new Error("Connection must be in authenticating state to complete authorization");
176
+ if (this.connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error("Connection must be in authenticating state to complete authorization");
148
177
  try {
149
178
  await this.finishAuthProbe(code);
150
- this.connectionState = "connecting";
179
+ this.connectionState = MCPConnectionState.CONNECTING;
151
180
  } catch (error) {
152
- this.connectionState = "failed";
181
+ this.connectionState = MCPConnectionState.FAILED;
153
182
  throw error;
154
183
  }
155
184
  }
156
185
  /**
157
- * Establish connection after successful authorization
186
+ * Discover server capabilities and register tools, resources, prompts, and templates.
187
+ * This method does the work but does not manage connection state - that's handled by discover().
158
188
  */
159
- async establishConnection() {
160
- if (this.connectionState !== "connecting") throw new Error("Connection must be in connecting state to establish connection");
189
+ async discoverAndRegister() {
190
+ this.serverCapabilities = this.client.getServerCapabilities();
191
+ if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
192
+ const operations = [];
193
+ const operationNames = [];
194
+ operations.push(Promise.resolve(this.client.getInstructions()));
195
+ operationNames.push("instructions");
196
+ if (this.serverCapabilities.tools) {
197
+ operations.push(this.registerTools());
198
+ operationNames.push("tools");
199
+ }
200
+ if (this.serverCapabilities.resources) {
201
+ operations.push(this.registerResources());
202
+ operationNames.push("resources");
203
+ }
204
+ if (this.serverCapabilities.prompts) {
205
+ operations.push(this.registerPrompts());
206
+ operationNames.push("prompts");
207
+ }
208
+ if (this.serverCapabilities.resources) {
209
+ operations.push(this.registerResourceTemplates());
210
+ operationNames.push("resource templates");
211
+ }
161
212
  try {
162
- const transportType = this.options.transport.type;
163
- if (!transportType) throw new Error("Transport type must be specified");
164
- await this.tryConnect(transportType);
165
- await this.discoverAndRegister();
213
+ const results = await Promise.all(operations);
214
+ for (let i = 0; i < results.length; i++) {
215
+ const result = results[i];
216
+ switch (operationNames[i]) {
217
+ case "instructions":
218
+ this.instructions = result;
219
+ break;
220
+ case "tools":
221
+ this.tools = result;
222
+ break;
223
+ case "resources":
224
+ this.resources = result;
225
+ break;
226
+ case "prompts":
227
+ this.prompts = result;
228
+ break;
229
+ case "resource templates":
230
+ this.resourceTemplates = result;
231
+ break;
232
+ }
233
+ }
166
234
  } catch (error) {
167
- this.connectionState = "failed";
235
+ this._onObservabilityEvent.fire({
236
+ type: "mcp:client:discover",
237
+ displayMessage: `Failed to discover capabilities for ${this.url.toString()}: ${toErrorMessage(error)}`,
238
+ payload: {
239
+ url: this.url.toString(),
240
+ error: toErrorMessage(error)
241
+ },
242
+ timestamp: Date.now(),
243
+ id: nanoid()
244
+ });
168
245
  throw error;
169
246
  }
170
247
  }
171
248
  /**
172
- * Discover server capabilities and register tools, resources, prompts, and templates
249
+ * Discover server capabilities with timeout and cancellation support.
250
+ * If called while a previous discovery is in-flight, the previous discovery will be aborted.
251
+ *
252
+ * @param options Optional configuration
253
+ * @param options.timeoutMs Timeout in milliseconds (default: 15000)
254
+ * @returns Result indicating success/failure with optional error message
173
255
  */
174
- async discoverAndRegister() {
175
- this.connectionState = "discovering";
176
- this.serverCapabilities = this.client.getServerCapabilities();
177
- if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
178
- const [instructionsResult, toolsResult, resourcesResult, promptsResult, resourceTemplatesResult] = await Promise.allSettled([
179
- this.client.getInstructions(),
180
- this.registerTools(),
181
- this.registerResources(),
182
- this.registerPrompts(),
183
- this.registerResourceTemplates()
184
- ]);
185
- const operations = [
186
- {
187
- name: "instructions",
188
- result: instructionsResult
189
- },
190
- {
191
- name: "tools",
192
- result: toolsResult
193
- },
194
- {
195
- name: "resources",
196
- result: resourcesResult
197
- },
198
- {
199
- name: "prompts",
200
- result: promptsResult
201
- },
202
- {
203
- name: "resource templates",
204
- result: resourceTemplatesResult
205
- }
206
- ];
207
- for (const { name, result } of operations) if (result.status === "rejected") {
208
- const url = this.url.toString();
256
+ async discover(options = {}) {
257
+ const { timeoutMs = 15e3 } = options;
258
+ if (this.connectionState !== MCPConnectionState.CONNECTED && this.connectionState !== MCPConnectionState.READY) {
209
259
  this._onObservabilityEvent.fire({
210
260
  type: "mcp:client:discover",
211
- displayMessage: `Failed to discover ${name} for ${url}`,
261
+ displayMessage: `Discovery skipped for ${this.url.toString()}, state is ${this.connectionState}`,
212
262
  payload: {
213
- url,
214
- capability: name,
215
- error: result.reason
263
+ url: this.url.toString(),
264
+ state: this.connectionState
216
265
  },
217
266
  timestamp: Date.now(),
218
267
  id: nanoid()
219
268
  });
269
+ return {
270
+ success: false,
271
+ error: `Discovery skipped - connection in ${this.connectionState} state`
272
+ };
273
+ }
274
+ if (this._discoveryAbortController) {
275
+ this._discoveryAbortController.abort();
276
+ this._discoveryAbortController = void 0;
277
+ }
278
+ const abortController = new AbortController();
279
+ this._discoveryAbortController = abortController;
280
+ this.connectionState = MCPConnectionState.DISCOVERING;
281
+ let timeoutId;
282
+ try {
283
+ const timeoutPromise = new Promise((_, reject) => {
284
+ timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error(`Discovery timed out after ${timeoutMs}ms`)), timeoutMs);
285
+ });
286
+ if (abortController.signal.aborted) throw new Error("Discovery was cancelled");
287
+ const abortPromise = new Promise((_, reject) => {
288
+ abortController.signal.addEventListener("abort", () => {
289
+ reject(/* @__PURE__ */ new Error("Discovery was cancelled"));
290
+ });
291
+ });
292
+ await Promise.race([
293
+ this.discoverAndRegister(),
294
+ timeoutPromise,
295
+ abortPromise
296
+ ]);
297
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
298
+ this.connectionState = MCPConnectionState.READY;
299
+ this._onObservabilityEvent.fire({
300
+ type: "mcp:client:discover",
301
+ displayMessage: `Discovery completed for ${this.url.toString()}`,
302
+ payload: { url: this.url.toString() },
303
+ timestamp: Date.now(),
304
+ id: nanoid()
305
+ });
306
+ return { success: true };
307
+ } catch (e) {
308
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
309
+ this.connectionState = MCPConnectionState.CONNECTED;
310
+ return {
311
+ success: false,
312
+ error: e instanceof Error ? e.message : String(e)
313
+ };
314
+ } finally {
315
+ this._discoveryAbortController = void 0;
220
316
  }
221
- this.instructions = instructionsResult.status === "fulfilled" ? instructionsResult.value : void 0;
222
- this.tools = toolsResult.status === "fulfilled" ? toolsResult.value : [];
223
- this.resources = resourcesResult.status === "fulfilled" ? resourcesResult.value : [];
224
- this.prompts = promptsResult.status === "fulfilled" ? promptsResult.value : [];
225
- this.resourceTemplates = resourceTemplatesResult.status === "fulfilled" ? resourceTemplatesResult.value : [];
226
- this.connectionState = "ready";
227
317
  }
228
318
  /**
229
- * Notification handler registration
319
+ * Cancel any in-flight discovery operation.
320
+ * Called when closing the connection.
321
+ */
322
+ cancelDiscovery() {
323
+ if (this._discoveryAbortController) {
324
+ this._discoveryAbortController.abort();
325
+ this._discoveryAbortController = void 0;
326
+ }
327
+ }
328
+ /**
329
+ * Notification handler registration for tools
330
+ * Should only be called if serverCapabilities.tools exists
230
331
  */
231
332
  async registerTools() {
232
- if (!this.serverCapabilities || !this.serverCapabilities.tools) return [];
233
- if (this.serverCapabilities.tools.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
333
+ if (this.serverCapabilities?.tools?.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
234
334
  this.tools = await this.fetchTools();
235
335
  });
236
336
  return this.fetchTools();
237
337
  }
338
+ /**
339
+ * Notification handler registration for resources
340
+ * Should only be called if serverCapabilities.resources exists
341
+ */
238
342
  async registerResources() {
239
- if (!this.serverCapabilities || !this.serverCapabilities.resources) return [];
240
- if (this.serverCapabilities.resources.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
343
+ if (this.serverCapabilities?.resources?.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
241
344
  this.resources = await this.fetchResources();
242
345
  });
243
346
  return this.fetchResources();
244
347
  }
348
+ /**
349
+ * Notification handler registration for prompts
350
+ * Should only be called if serverCapabilities.prompts exists
351
+ */
245
352
  async registerPrompts() {
246
- if (!this.serverCapabilities || !this.serverCapabilities.prompts) return [];
247
- if (this.serverCapabilities.prompts.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
353
+ if (this.serverCapabilities?.prompts?.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
248
354
  this.prompts = await this.fetchPrompts();
249
355
  });
250
356
  return this.fetchPrompts();
251
357
  }
252
358
  async registerResourceTemplates() {
253
- if (!this.serverCapabilities || !this.serverCapabilities.resources) return [];
254
359
  return this.fetchResourceTemplates();
255
360
  }
256
361
  async fetchTools() {
@@ -316,44 +421,24 @@ var MCPClientConnection = class {
316
421
  const transport = this.getTransport(currentTransportType);
317
422
  try {
318
423
  await this.client.connect(transport);
319
- this.lastConnectedTransport = currentTransportType;
320
- const url = this.url.toString();
321
- this._onObservabilityEvent.fire({
322
- type: "mcp:client:connect",
323
- displayMessage: `Connected successfully using ${currentTransportType} transport for ${url}`,
324
- payload: {
325
- url,
326
- transport: currentTransportType,
327
- state: this.connectionState
328
- },
329
- timestamp: Date.now(),
330
- id: nanoid()
331
- });
332
- break;
424
+ return {
425
+ state: MCPConnectionState.CONNECTED,
426
+ transport: currentTransportType
427
+ };
333
428
  } catch (e) {
334
429
  const error = e instanceof Error ? e : new Error(String(e));
335
- if (isUnauthorized(error)) throw e;
336
- if (hasFallback && isTransportNotImplemented(error)) {
337
- const url = this.url.toString();
338
- this._onObservabilityEvent.fire({
339
- type: "mcp:client:connect",
340
- displayMessage: `${currentTransportType} transport not available, trying ${transports[transports.indexOf(currentTransportType) + 1]} for ${url}`,
341
- payload: {
342
- url,
343
- transport: currentTransportType,
344
- state: this.connectionState
345
- },
346
- timestamp: Date.now(),
347
- id: nanoid()
348
- });
349
- continue;
350
- }
351
- throw e;
430
+ if (isUnauthorized(error)) return { state: MCPConnectionState.AUTHENTICATING };
431
+ if (isTransportNotImplemented(error) && hasFallback) continue;
432
+ return {
433
+ state: MCPConnectionState.FAILED,
434
+ error
435
+ };
352
436
  }
353
437
  }
354
- this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
355
- return await this.handleElicitationRequest(request);
356
- });
438
+ return {
439
+ state: MCPConnectionState.FAILED,
440
+ error: /* @__PURE__ */ new Error("No transports available")
441
+ };
357
442
  }
358
443
  _capabilityErrorHandler(empty, method) {
359
444
  return (e) => {
@@ -400,13 +485,32 @@ var MCPClientManager = class {
400
485
  this.onObservabilityEvent = this._onObservabilityEvent.event;
401
486
  this._onServerStateChanged = new Emitter();
402
487
  this.onServerStateChanged = this._onServerStateChanged.event;
488
+ if (!options.storage) throw new Error("MCPClientManager requires a valid DurableObjectStorage instance");
403
489
  this._storage = options.storage;
404
490
  }
491
+ sql(query, ...bindings) {
492
+ return [...this._storage.sql.exec(query, ...bindings)];
493
+ }
494
+ saveServerToStorage(server) {
495
+ this.sql(`INSERT OR REPLACE INTO cf_agents_mcp_servers (
496
+ id, name, server_url, client_id, auth_url, callback_url, server_options
497
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`, server.id, server.name, server.server_url, server.client_id ?? null, server.auth_url ?? null, server.callback_url, server.server_options ?? null);
498
+ }
499
+ removeServerFromStorage(serverId) {
500
+ this.sql("DELETE FROM cf_agents_mcp_servers WHERE id = ?", serverId);
501
+ }
502
+ getServersFromStorage() {
503
+ return this.sql("SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers");
504
+ }
505
+ clearServerAuthUrl(serverId) {
506
+ this.sql("UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?", serverId);
507
+ }
405
508
  /**
406
509
  * Create an auth provider for a server
407
510
  * @internal
408
511
  */
409
512
  createAuthProvider(serverId, callbackUrl, clientName, clientId) {
513
+ if (!this._storage) throw new Error("Cannot create auth provider: storage is not initialized");
410
514
  const authProvider = new DurableObjectOAuthClientProvider(this._storage, clientName, callbackUrl);
411
515
  authProvider.serverId = serverId;
412
516
  if (clientId) authProvider.clientId = clientId;
@@ -420,7 +524,7 @@ var MCPClientManager = class {
420
524
  */
421
525
  async restoreConnectionsFromStorage(clientName) {
422
526
  if (this._isRestored) return;
423
- const servers = await this._storage.listServers();
527
+ const servers = this.getServersFromStorage();
424
528
  if (!servers || servers.length === 0) {
425
529
  this._isRestored = true;
426
530
  return;
@@ -428,12 +532,12 @@ var MCPClientManager = class {
428
532
  for (const server of servers) {
429
533
  const existingConn = this.mcpConnections[server.id];
430
534
  if (existingConn) {
431
- if (existingConn.connectionState === "ready") {
535
+ if (existingConn.connectionState === MCPConnectionState.READY) {
432
536
  console.warn(`[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`);
433
537
  continue;
434
538
  }
435
- if (existingConn.connectionState === "authenticating" || existingConn.connectionState === "connecting" || existingConn.connectionState === "discovering") continue;
436
- if (existingConn.connectionState === "failed") {
539
+ if (existingConn.connectionState === MCPConnectionState.AUTHENTICATING || existingConn.connectionState === MCPConnectionState.CONNECTING || existingConn.connectionState === MCPConnectionState.DISCOVERING) continue;
540
+ if (existingConn.connectionState === MCPConnectionState.FAILED) {
437
541
  try {
438
542
  await existingConn.client.close();
439
543
  } catch (error) {
@@ -446,7 +550,7 @@ var MCPClientManager = class {
446
550
  }
447
551
  const parsedOptions = server.server_options ? JSON.parse(server.server_options) : null;
448
552
  const authProvider = this.createAuthProvider(server.id, server.callback_url, clientName, server.client_id ?? void 0);
449
- this.createConnection(server.id, server.server_url, {
553
+ const conn = this.createConnection(server.id, server.server_url, {
450
554
  client: parsedOptions?.client ?? {},
451
555
  transport: {
452
556
  ...parsedOptions?.transport ?? {},
@@ -454,13 +558,27 @@ var MCPClientManager = class {
454
558
  authProvider
455
559
  }
456
560
  });
457
- await this.connectToServer(server.id).catch((error) => {
458
- console.error(`Error restoring ${server.id}:`, error);
459
- });
561
+ if (server.auth_url) {
562
+ conn.connectionState = MCPConnectionState.AUTHENTICATING;
563
+ continue;
564
+ }
565
+ this._restoreServer(server.id);
460
566
  }
461
567
  this._isRestored = true;
462
568
  }
463
569
  /**
570
+ * Internal method to restore a single server connection and discovery
571
+ */
572
+ async _restoreServer(serverId) {
573
+ if ((await this.connectToServer(serverId).catch((error) => {
574
+ console.error(`Error connecting to ${serverId}:`, error);
575
+ return null;
576
+ }))?.state === MCPConnectionState.CONNECTED) {
577
+ const discoverResult = await this.discoverIfConnected(serverId);
578
+ if (discoverResult && !discoverResult.success) console.error(`Error discovering ${serverId}:`, discoverResult.error);
579
+ }
580
+ }
581
+ /**
464
582
  * Connect to and register an MCP server
465
583
  *
466
584
  * @deprecated This method is maintained for backward compatibility.
@@ -507,7 +625,7 @@ var MCPClientManager = class {
507
625
  await this.mcpConnections[id].init();
508
626
  if (options.reconnect?.oauthCode) try {
509
627
  await this.mcpConnections[id].completeAuthorization(options.reconnect.oauthCode);
510
- await this.mcpConnections[id].establishConnection();
628
+ await this.mcpConnections[id].init();
511
629
  } catch (error) {
512
630
  this._onObservabilityEvent.fire({
513
631
  type: "mcp:client:connect",
@@ -524,19 +642,22 @@ var MCPClientManager = class {
524
642
  throw error;
525
643
  }
526
644
  const authUrl = options.transport?.authProvider?.authUrl;
527
- if (this.mcpConnections[id].connectionState === "authenticating" && authUrl && options.transport?.authProvider?.redirectUrl) return {
645
+ if (this.mcpConnections[id].connectionState === MCPConnectionState.AUTHENTICATING && authUrl && options.transport?.authProvider?.redirectUrl) return {
528
646
  authUrl,
529
647
  clientId: options.transport?.authProvider?.clientId,
530
648
  id
531
649
  };
650
+ const discoverResult = await this.discoverIfConnected(id);
651
+ if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover server capabilities: ${discoverResult.error}`);
532
652
  return { id };
533
653
  }
534
654
  /**
535
655
  * Create an in-memory connection object and set up observability
536
656
  * Does NOT save to storage - use registerServer() for that
657
+ * @returns The connection object (existing or newly created)
537
658
  */
538
659
  createConnection(id, url, options) {
539
- if (this.mcpConnections[id]) return;
660
+ if (this.mcpConnections[id]) return this.mcpConnections[id];
540
661
  const normalizedTransport = {
541
662
  ...options.transport,
542
663
  type: options.transport?.type ?? "auto"
@@ -558,6 +679,7 @@ var MCPClientManager = class {
558
679
  store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
559
680
  this._onObservabilityEvent.fire(event);
560
681
  }));
682
+ return this.mcpConnections[id];
561
683
  }
562
684
  /**
563
685
  * Register an MCP server connection without connecting
@@ -575,7 +697,8 @@ var MCPClientManager = class {
575
697
  type: options.transport?.type ?? "auto"
576
698
  }
577
699
  });
578
- await this._storage.saveServer({
700
+ const { authProvider: _, ...transportWithoutAuth } = options.transport ?? {};
701
+ this.saveServerToStorage({
579
702
  id,
580
703
  name: options.name,
581
704
  server_url: options.url,
@@ -584,7 +707,7 @@ var MCPClientManager = class {
584
707
  auth_url: options.authUrl ?? null,
585
708
  server_options: JSON.stringify({
586
709
  client: options.client,
587
- transport: options.transport
710
+ transport: transportWithoutAuth
588
711
  })
589
712
  });
590
713
  this._onServerStateChanged.fire();
@@ -593,14 +716,13 @@ var MCPClientManager = class {
593
716
  /**
594
717
  * Connect to an already registered MCP server and initialize the connection.
595
718
  *
596
- * For OAuth servers, this returns `{ state: "authenticating", authUrl, clientId? }`
597
- * without establishing the connection. The user must complete the OAuth flow via
598
- * the authUrl, which will trigger a callback handled by `handleCallbackRequest()`.
599
- *
600
- * For non-OAuth servers, this establishes the connection immediately and returns
601
- * `{ state: "ready" }`.
719
+ * For OAuth servers, returns `{ state: "authenticating", authUrl, clientId? }`.
720
+ * The user must complete the OAuth flow via the authUrl, which triggers a
721
+ * callback handled by `handleCallbackRequest()`.
602
722
  *
603
- * Updates storage with auth URL and client ID after connection.
723
+ * For non-OAuth servers, establishes the transport connection and returns
724
+ * `{ state: "connected" }`. Call `discoverIfConnected()` afterwards to
725
+ * discover capabilities and transition to "ready" state.
604
726
  *
605
727
  * @param id Server ID (must be registered first via registerServer())
606
728
  * @returns Connection result with current state and OAuth info (if applicable)
@@ -608,70 +730,99 @@ var MCPClientManager = class {
608
730
  async connectToServer(id) {
609
731
  const conn = this.mcpConnections[id];
610
732
  if (!conn) throw new Error(`Server ${id} is not registered. Call registerServer() first.`);
611
- await conn.init();
612
- const authUrl = conn.options.transport.authProvider?.authUrl;
613
- if (conn.connectionState === "authenticating" && authUrl && conn.options.transport.authProvider?.redirectUrl) {
614
- const clientId = conn.options.transport.authProvider?.clientId;
615
- const serverRow = (await this._storage.listServers()).find((s) => s.id === id);
616
- if (serverRow) await this._storage.saveServer({
617
- ...serverRow,
618
- auth_url: authUrl,
619
- client_id: clientId ?? null
620
- });
621
- this._onServerStateChanged.fire();
622
- return {
623
- state: "authenticating",
624
- authUrl,
625
- clientId
733
+ const error = await conn.init();
734
+ this._onServerStateChanged.fire();
735
+ switch (conn.connectionState) {
736
+ case MCPConnectionState.FAILED: return {
737
+ state: conn.connectionState,
738
+ error: error ?? "Unknown connection error"
739
+ };
740
+ case MCPConnectionState.AUTHENTICATING: {
741
+ const authUrl = conn.options.transport.authProvider?.authUrl;
742
+ const redirectUrl = conn.options.transport.authProvider?.redirectUrl;
743
+ if (!authUrl || !redirectUrl) return {
744
+ state: MCPConnectionState.FAILED,
745
+ error: `OAuth configuration incomplete: missing ${!authUrl ? "authUrl" : "redirectUrl"}`
746
+ };
747
+ const clientId = conn.options.transport.authProvider?.clientId;
748
+ const serverRow = this.getServersFromStorage().find((s) => s.id === id);
749
+ if (serverRow) {
750
+ this.saveServerToStorage({
751
+ ...serverRow,
752
+ auth_url: authUrl,
753
+ client_id: clientId ?? null
754
+ });
755
+ this._onServerStateChanged.fire();
756
+ }
757
+ return {
758
+ state: conn.connectionState,
759
+ authUrl,
760
+ clientId
761
+ };
762
+ }
763
+ case MCPConnectionState.CONNECTED: return { state: conn.connectionState };
764
+ default: return {
765
+ state: MCPConnectionState.FAILED,
766
+ error: `Unexpected connection state after init: ${conn.connectionState}`
626
767
  };
627
768
  }
628
- if (conn.connectionState === "ready") this._onServerStateChanged.fire();
629
- return { state: "ready" };
630
769
  }
631
- async isCallbackRequest(req) {
770
+ extractServerIdFromState(state) {
771
+ if (!state) return null;
772
+ const parts = state.split(".");
773
+ return parts.length === 2 ? parts[1] : null;
774
+ }
775
+ isCallbackRequest(req) {
632
776
  if (req.method !== "GET") return false;
633
777
  if (!req.url.includes("/callback")) return false;
634
- return (await this._storage.listServers()).some((server) => server.callback_url && req.url.startsWith(server.callback_url));
778
+ const state = new URL(req.url).searchParams.get("state");
779
+ const serverId = this.extractServerIdFromState(state);
780
+ if (!serverId) return false;
781
+ return this.getServersFromStorage().some((server) => server.id === serverId);
635
782
  }
636
783
  async handleCallbackRequest(req) {
637
784
  const url = new URL(req.url);
638
- const matchingServer = (await this._storage.listServers()).find((server) => {
639
- return server.callback_url && req.url.startsWith(server.callback_url);
640
- });
641
- if (!matchingServer) throw new Error(`No callback URI match found for the request url: ${req.url}. Was the request matched with \`isCallbackRequest()\`?`);
642
- const serverId = matchingServer.id;
643
785
  const code = url.searchParams.get("code");
644
786
  const state = url.searchParams.get("state");
645
787
  const error = url.searchParams.get("error");
646
788
  const errorDescription = url.searchParams.get("error_description");
789
+ if (!state) throw new Error("Unauthorized: no state provided");
790
+ const serverId = this.extractServerIdFromState(state);
791
+ if (!serverId) throw new Error("No serverId found in state parameter. Expected format: {nonce}.{serverId}");
792
+ if (!this.getServersFromStorage().some((server) => server.id === serverId)) throw new Error(`No server found with id "${serverId}". Was the request matched with \`isCallbackRequest()\`?`);
793
+ if (this.mcpConnections[serverId] === void 0) throw new Error(`Could not find serverId: ${serverId}`);
794
+ const conn = this.mcpConnections[serverId];
795
+ if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
796
+ const authProvider = conn.options.transport.authProvider;
797
+ authProvider.serverId = serverId;
798
+ const stateValidation = await authProvider.checkState(state);
799
+ if (!stateValidation.valid) throw new Error(`Invalid state: ${stateValidation.error}`);
647
800
  if (error) return {
648
801
  serverId,
649
802
  authSuccess: false,
650
803
  authError: errorDescription || error
651
804
  };
652
805
  if (!code) throw new Error("Unauthorized: no code provided");
653
- if (!state) throw new Error("Unauthorized: no state provided");
654
- if (this.mcpConnections[serverId] === void 0) throw new Error(`Could not find serverId: ${serverId}`);
655
- if (this.mcpConnections[serverId].connectionState === "ready") return {
656
- serverId,
657
- authSuccess: true
658
- };
659
- if (this.mcpConnections[serverId].connectionState !== "authenticating") throw new Error(`Failed to authenticate: the client is in "${this.mcpConnections[serverId].connectionState}" state, expected "authenticating"`);
660
- const conn = this.mcpConnections[serverId];
661
- if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
662
- const clientId = conn.options.transport.authProvider.clientId || state;
663
- conn.options.transport.authProvider.clientId = clientId;
664
- conn.options.transport.authProvider.serverId = serverId;
806
+ if (this.mcpConnections[serverId].connectionState === MCPConnectionState.READY || this.mcpConnections[serverId].connectionState === MCPConnectionState.CONNECTED) {
807
+ this.clearServerAuthUrl(serverId);
808
+ return {
809
+ serverId,
810
+ authSuccess: true
811
+ };
812
+ }
813
+ if (this.mcpConnections[serverId].connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error(`Failed to authenticate: the client is in "${this.mcpConnections[serverId].connectionState}" state, expected "authenticating"`);
665
814
  try {
815
+ await authProvider.consumeState(state);
666
816
  await conn.completeAuthorization(code);
667
- await this._storage.clearAuthUrl(serverId);
817
+ await authProvider.deleteCodeVerifier();
818
+ this.clearServerAuthUrl(serverId);
668
819
  this._onServerStateChanged.fire();
669
820
  return {
670
821
  serverId,
671
822
  authSuccess: true
672
823
  };
673
- } catch (error$1) {
674
- const errorMessage = error$1 instanceof Error ? error$1.message : String(error$1);
824
+ } catch (authError) {
825
+ const errorMessage = authError instanceof Error ? authError.message : String(authError);
675
826
  this._onServerStateChanged.fire();
676
827
  return {
677
828
  serverId,
@@ -681,8 +832,40 @@ var MCPClientManager = class {
681
832
  }
682
833
  }
683
834
  /**
835
+ * Discover server capabilities if connection is in CONNECTED or READY state.
836
+ * Transitions to DISCOVERING then READY (or CONNECTED on error).
837
+ * Can be called to refresh server capabilities (e.g., from a UI refresh button).
838
+ *
839
+ * If called while a previous discovery is in-flight for the same server,
840
+ * the previous discovery will be aborted.
841
+ *
842
+ * @param serverId The server ID to discover
843
+ * @param options Optional configuration
844
+ * @param options.timeoutMs Timeout in milliseconds (default: 30000)
845
+ * @returns Result with current state and optional error, or undefined if connection not found
846
+ */
847
+ async discoverIfConnected(serverId, options = {}) {
848
+ const conn = this.mcpConnections[serverId];
849
+ if (!conn) {
850
+ this._onObservabilityEvent.fire({
851
+ type: "mcp:client:discover",
852
+ displayMessage: `Connection not found for ${serverId}`,
853
+ payload: {},
854
+ timestamp: Date.now(),
855
+ id: nanoid()
856
+ });
857
+ return;
858
+ }
859
+ const result = await conn.discover(options);
860
+ this._onServerStateChanged.fire();
861
+ return {
862
+ ...result,
863
+ state: conn.connectionState
864
+ };
865
+ }
866
+ /**
684
867
  * Establish connection in the background after OAuth completion
685
- * This method is called asynchronously and doesn't block the OAuth callback response
868
+ * This method connects to the server and discovers its capabilities
686
869
  * @param serverId The server ID to establish connection for
687
870
  */
688
871
  async establishConnection(serverId) {
@@ -697,25 +880,34 @@ var MCPClientManager = class {
697
880
  });
698
881
  return;
699
882
  }
700
- try {
701
- await conn.establishConnection();
702
- this._onServerStateChanged.fire();
703
- } catch (error) {
704
- const url = conn.url.toString();
883
+ if (conn.connectionState === MCPConnectionState.DISCOVERING || conn.connectionState === MCPConnectionState.READY) {
705
884
  this._onObservabilityEvent.fire({
706
885
  type: "mcp:client:connect",
707
- displayMessage: `Failed to establish connection to server ${serverId} with url ${url}`,
886
+ displayMessage: `establishConnection skipped for ${serverId}, already in ${conn.connectionState} state`,
708
887
  payload: {
709
- url,
710
- transport: conn.options.transport.type ?? "auto",
711
- state: conn.connectionState,
712
- error: toErrorMessage(error)
888
+ url: conn.url.toString(),
889
+ transport: conn.options.transport.type || "unknown",
890
+ state: conn.connectionState
713
891
  },
714
892
  timestamp: Date.now(),
715
893
  id: nanoid()
716
894
  });
717
- this._onServerStateChanged.fire();
895
+ return;
718
896
  }
897
+ const connectResult = await this.connectToServer(serverId);
898
+ this._onServerStateChanged.fire();
899
+ if (connectResult.state === MCPConnectionState.CONNECTED) await this.discoverIfConnected(serverId);
900
+ this._onObservabilityEvent.fire({
901
+ type: "mcp:client:connect",
902
+ displayMessage: `establishConnection completed for ${serverId}, final state: ${conn.connectionState}`,
903
+ payload: {
904
+ url: conn.url.toString(),
905
+ transport: conn.options.transport.type || "unknown",
906
+ state: conn.connectionState
907
+ },
908
+ timestamp: Date.now(),
909
+ id: nanoid()
910
+ });
719
911
  }
720
912
  /**
721
913
  * Configure OAuth callback handling
@@ -759,7 +951,7 @@ var MCPClientManager = class {
759
951
  */
760
952
  getAITools() {
761
953
  if (!this.jsonSchema) throw new Error("jsonSchema not initialized.");
762
- 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.`);
954
+ 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.`);
763
955
  return Object.fromEntries(getNamespacedData(this.mcpConnections, "tools").map((tool) => {
764
956
  return [`tool_${tool.serverId.replace(/-/g, "")}_${tool.name}`, {
765
957
  description: tool.description,
@@ -789,10 +981,18 @@ var MCPClientManager = class {
789
981
  return this.getAITools();
790
982
  }
791
983
  /**
792
- * Closes all connections to MCP servers
984
+ * Closes all active in-memory connections to MCP servers.
985
+ *
986
+ * Note: This only closes the transport connections - it does NOT remove
987
+ * servers from storage. Servers will still be listed and their callback
988
+ * URLs will still match incoming OAuth requests.
989
+ *
990
+ * Use removeServer() instead if you want to fully clean up a server
991
+ * (closes connection AND removes from storage).
793
992
  */
794
993
  async closeAllConnections() {
795
994
  const ids = Object.keys(this.mcpConnections);
995
+ for (const id of ids) this.mcpConnections[id].cancelDiscovery();
796
996
  await Promise.all(ids.map(async (id) => {
797
997
  await this.mcpConnections[id].client.close();
798
998
  }));
@@ -809,6 +1009,7 @@ var MCPClientManager = class {
809
1009
  */
810
1010
  async closeConnection(id) {
811
1011
  if (!this.mcpConnections[id]) throw new Error(`Connection with id "${id}" does not exist.`);
1012
+ this.mcpConnections[id].cancelDiscovery();
812
1013
  await this.mcpConnections[id].client.close();
813
1014
  delete this.mcpConnections[id];
814
1015
  const store = this._connectionDisposables.get(id);
@@ -816,17 +1017,20 @@ var MCPClientManager = class {
816
1017
  this._connectionDisposables.delete(id);
817
1018
  }
818
1019
  /**
819
- * Remove an MCP server from storage
1020
+ * Remove an MCP server - closes connection if active and removes from storage.
820
1021
  */
821
1022
  async removeServer(serverId) {
822
- await this._storage.removeServer(serverId);
1023
+ if (this.mcpConnections[serverId]) try {
1024
+ await this.closeConnection(serverId);
1025
+ } catch (_e) {}
1026
+ this.removeServerFromStorage(serverId);
823
1027
  this._onServerStateChanged.fire();
824
1028
  }
825
1029
  /**
826
1030
  * List all MCP servers from storage
827
1031
  */
828
- async listServers() {
829
- return await this._storage.listServers();
1032
+ listServers() {
1033
+ return this.getServersFromStorage();
830
1034
  }
831
1035
  /**
832
1036
  * Dispose the manager and all resources.
@@ -897,5 +1101,5 @@ function getNamespacedData(mcpClients, type) {
897
1101
  }
898
1102
 
899
1103
  //#endregion
900
- export { getNamespacedData as n, DisposableStore as r, MCPClientManager as t };
901
- //# sourceMappingURL=client-DpkZyXgJ.js.map
1104
+ export { DisposableStore as i, getNamespacedData as n, MCPConnectionState as r, MCPClientManager as t };
1105
+ //# sourceMappingURL=client-QZa2Rq0l.js.map