agents 0.17.3 → 0.17.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@ import { tryN } from "./retries.js";
2
2
  import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider.js";
3
3
  import { nanoid } from "nanoid";
4
4
  import { getServerByName } from "partyserver";
5
- import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker-provider.js";
6
5
  import { z } from "zod";
7
6
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker-provider.js";
8
8
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
9
9
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
10
10
  import { ElicitRequestSchema, JSONRPCMessageSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, isJSONRPCErrorResponse, isJSONRPCResultResponse } from "@modelcontextprotocol/sdk/types.js";
@@ -282,6 +282,7 @@ var RPCServerTransport = class {
282
282
  };
283
283
  //#endregion
284
284
  //#region src/mcp/client-connection.ts
285
+ const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
285
286
  /**
286
287
  * Connection state machine for MCP client connections.
287
288
  *
@@ -304,12 +305,21 @@ const MCPConnectionState = {
304
305
  /** Connection failed at some point */
305
306
  FAILED: "failed"
306
307
  };
308
+ /** Derive the elicitation capability to advertise from the handler keys. */
309
+ function elicitationCapabilitiesFromHandlers(handlers) {
310
+ if (!handlers) return void 0;
311
+ const elicitation = {};
312
+ if (handlers.form) elicitation.form = {};
313
+ if (handlers.url) elicitation.url = {};
314
+ return elicitation.form || elicitation.url ? elicitation : void 0;
315
+ }
307
316
  var MCPClientConnection = class {
308
- constructor(url, info, options = {
317
+ constructor(url, _info, options = {
309
318
  client: {},
310
319
  transport: {}
311
320
  }) {
312
321
  this.url = url;
322
+ this._info = _info;
313
323
  this.options = options;
314
324
  this.connectionState = MCPConnectionState.CONNECTING;
315
325
  this.connectionError = null;
@@ -320,14 +330,44 @@ var MCPClientConnection = class {
320
330
  this._probingCapabilities = false;
321
331
  this._onObservabilityEvent = new Emitter();
322
332
  this.onObservabilityEvent = this._onObservabilityEvent.event;
333
+ this._elicitationEnabled = false;
334
+ this.options = {
335
+ ...options,
336
+ client: {
337
+ ...defaultClientOptions,
338
+ ...options.client
339
+ }
340
+ };
341
+ this.client = this.createClient();
342
+ }
343
+ createClient() {
344
+ const seed = this.options.capabilitySeed;
345
+ const elicitation = this.options.client?.capabilities?.elicitation ?? elicitationCapabilitiesFromHandlers(this.options.elicitationHandlers) ?? seed?.elicitation;
346
+ this._elicitationEnabled = elicitation !== void 0;
323
347
  const clientOptions = {
324
- ...options.client,
348
+ ...this.options.client,
325
349
  capabilities: {
326
- ...options.client?.capabilities,
327
- elicitation: {}
350
+ ...seed,
351
+ ...this.options.client?.capabilities,
352
+ ...elicitation ? { elicitation } : {}
328
353
  }
329
354
  };
330
- this.client = new Client(info, clientOptions);
355
+ return new Client(this._info, clientOptions);
356
+ }
357
+ /**
358
+ * Configure the handler used for server-initiated elicitation requests.
359
+ *
360
+ * If the connection has not been initialized yet, rebuild the SDK client so
361
+ * handler-driven elicitation capabilities are reflected in the initial
362
+ * handshake. A rebuild (rather than `Client.registerCapabilities`) is
363
+ * required because SDK capability registration is merge-only — it cannot
364
+ * un-advertise a mode when handlers are cleared before connecting. Active
365
+ * connections keep their negotiated capabilities until they reconnect.
366
+ */
367
+ configureElicitationHandlers(handlers) {
368
+ this.options.elicitationHandlers = handlers;
369
+ this.options.capabilitySeed = void 0;
370
+ if (!this.client.transport) this.client = this.createClient();
331
371
  }
332
372
  /**
333
373
  * Initialize a client connection, if authentication is required, the connection will be in the AUTHENTICATING state
@@ -343,11 +383,12 @@ var MCPClientConnection = class {
343
383
  try {
344
384
  await this.client.close();
345
385
  } catch {}
386
+ this.client = this.createClient();
346
387
  }
347
388
  const res = await this.tryConnect(transportType);
348
389
  this.connectionState = res.state;
349
390
  if (res.state === MCPConnectionState.CONNECTED && res.transport) {
350
- this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
391
+ if (this._elicitationEnabled) this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
351
392
  return await this.handleElicitationRequest(request);
352
393
  });
353
394
  this.lastConnectedTransport = res.transport;
@@ -634,11 +675,19 @@ var MCPClientConnection = class {
634
675
  return templatesAgg;
635
676
  }
636
677
  /**
637
- * Handle elicitation request from server
638
- * Automatically uses the Agent's built-in elicitation handling if available
678
+ * Handle elicitation request from server.
679
+ *
680
+ * Delegates to the `elicitationHandlers` connection option when provided.
681
+ *
682
+ * @deprecated Overriding or instance-patching this method directly is
683
+ * deprecated — pass the `elicitationHandlers` connection option instead.
639
684
  */
640
- async handleElicitationRequest(_request) {
641
- throw new Error("Elicitation handler must be implemented for your platform. Override handleElicitationRequest method.");
685
+ async handleElicitationRequest(request) {
686
+ const mode = request.params.mode === "url" ? "url" : "form";
687
+ const handler = this.options.elicitationHandlers?.[mode];
688
+ if (handler) return handler(request);
689
+ if (this.options.elicitationHandlers) throw new Error(`No MCP ${mode}-mode elicitation handler configured for this connection.`);
690
+ throw new Error("Elicitation handler must be implemented for your platform. Provide the MCPClientConnection elicitationHandlers option, or register handlers through the MCP client manager before connecting.");
642
691
  }
643
692
  isResumedStreamableHttpSession() {
644
693
  return this._transport instanceof StreamableHTTPClientTransport && typeof this._transport.sessionId === "string";
@@ -764,7 +813,6 @@ var MCPClientConnection = class {
764
813
  };
765
814
  //#endregion
766
815
  //#region src/mcp/client.ts
767
- const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
768
816
  /** Maximum length of a normalized MCP server id. */
769
817
  const MCP_SERVER_ID_MAX_LENGTH = 64;
770
818
  /**
@@ -912,6 +960,21 @@ var MCPClientManager = class {
912
960
  this._storage = options.storage;
913
961
  this._createAuthProviderFn = options.createAuthProvider;
914
962
  }
963
+ /**
964
+ * Scope the manager-level elicitation handler to a single connection.
965
+ * Returns undefined when no handler is configured so the connection keeps
966
+ * its default throwing behavior.
967
+ */
968
+ scopedElicitationHandlers(serverId) {
969
+ const handlers = this._elicitationHandlers;
970
+ if (!handlers) return void 0;
971
+ const form = handlers.form;
972
+ const url = handlers.url;
973
+ return {
974
+ form: form ? (request) => form(request, serverId) : void 0,
975
+ url: url ? (request) => url(request, serverId) : void 0
976
+ };
977
+ }
915
978
  sql(query, ...bindings) {
916
979
  return [...this._storage.sql.exec(query, ...bindings)];
917
980
  }
@@ -979,6 +1042,8 @@ var MCPClientManager = class {
979
1042
  delete this.mcpConnections[oldId];
980
1043
  const authProvider = conn.options.transport.authProvider;
981
1044
  if (authProvider) authProvider.serverId = newId;
1045
+ const scoped = this.scopedElicitationHandlers(newId);
1046
+ if (scoped) conn.configureElicitationHandlers(scoped);
982
1047
  }
983
1048
  const disposables = this._connectionDisposables.get(oldId);
984
1049
  if (disposables) {
@@ -1007,12 +1072,37 @@ var MCPClientManager = class {
1007
1072
  }));
1008
1073
  }
1009
1074
  /**
1010
- * Get the retry options for a server from stored server_options
1075
+ * Get the parsed server_options for a stored server, if any.
1011
1076
  */
1012
- getServerRetryOptions(serverId) {
1077
+ getStoredServerOptions(serverId) {
1013
1078
  const rows = this.sql("SELECT server_options FROM cf_agents_mcp_servers WHERE id = ?", serverId);
1014
1079
  if (!rows.length || !rows[0].server_options) return void 0;
1015
- return JSON.parse(rows[0].server_options).retry;
1080
+ return JSON.parse(rows[0].server_options);
1081
+ }
1082
+ /**
1083
+ * Clear the capabilities persisted on a stored server row. Called once a
1084
+ * seeded connection's handshake completes (see `createConnection`): the
1085
+ * stamp is valid for one successful restore — sessions that configure
1086
+ * handlers re-stamp every row, so a deploy that stops configuring them
1087
+ * stops advertising stale modes after its first connected wake instead of
1088
+ * forever, while wakes that never handshake don't burn the stamp.
1089
+ */
1090
+ clearStoredCapabilities(serverId) {
1091
+ const row = this.sql("SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers WHERE id = ?", serverId)[0];
1092
+ if (!row?.server_options) return;
1093
+ const options = JSON.parse(row.server_options);
1094
+ if (!options.capabilities) return;
1095
+ options.capabilities = void 0;
1096
+ this.saveServerToStorage({
1097
+ ...row,
1098
+ server_options: JSON.stringify(options)
1099
+ });
1100
+ }
1101
+ /**
1102
+ * Get the retry options for a server from stored server_options
1103
+ */
1104
+ getServerRetryOptions(serverId) {
1105
+ return this.getStoredServerOptions(serverId)?.retry;
1016
1106
  }
1017
1107
  clearServerAuthUrl(serverId) {
1018
1108
  this.sql("UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?", serverId);
@@ -1129,7 +1219,8 @@ var MCPClientManager = class {
1129
1219
  callback_url: "",
1130
1220
  server_options: JSON.stringify({
1131
1221
  bindingName,
1132
- props
1222
+ props,
1223
+ capabilities: this.advertisedHandlerCapabilities()
1133
1224
  })
1134
1225
  });
1135
1226
  }
@@ -1166,6 +1257,7 @@ var MCPClientManager = class {
1166
1257
  }
1167
1258
  }
1168
1259
  const parsedOptions = server.server_options ? JSON.parse(server.server_options) : null;
1260
+ if (parsedOptions?.client) delete parsedOptions.client.jsonSchemaValidator;
1169
1261
  let authProvider;
1170
1262
  if (server.callback_url) {
1171
1263
  authProvider = this._createAuthProviderFn ? this._createAuthProviderFn(server.callback_url) : this.createAuthProvider(server.id, server.callback_url, clientName, server.client_id ?? void 0);
@@ -1257,24 +1349,11 @@ var MCPClientManager = class {
1257
1349
  }
1258
1350
  if (isBlockedUrl(url)) throw new Error(`Blocked URL: ${url} — MCP client connections to private/internal addresses are not allowed`);
1259
1351
  if (!options.reconnect?.oauthCode || !this.mcpConnections[id]) {
1260
- const normalizedTransport = {
1261
- ...options.transport,
1262
- type: options.transport?.type ?? "auto"
1263
- };
1264
- this.mcpConnections[id] = new MCPClientConnection(new URL(url), {
1265
- name: this._name,
1266
- version: this._version
1267
- }, {
1268
- client: options.client ?? {},
1269
- transport: normalizedTransport
1352
+ delete this.mcpConnections[id];
1353
+ this.createConnection(id, url, {
1354
+ client: options.client,
1355
+ transport: options.transport ?? {}
1270
1356
  });
1271
- const store = new DisposableStore();
1272
- const existing = this._connectionDisposables.get(id);
1273
- if (existing) existing.dispose();
1274
- this._connectionDisposables.set(id, store);
1275
- store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
1276
- this._onObservabilityEvent.fire(event);
1277
- }));
1278
1357
  }
1279
1358
  await this.mcpConnections[id].init();
1280
1359
  if (options.reconnect?.oauthCode) try {
@@ -1326,15 +1405,15 @@ var MCPClientManager = class {
1326
1405
  ...options.transport,
1327
1406
  type: options.transport?.type ?? "auto"
1328
1407
  };
1408
+ const capabilitySeed = this.getStoredServerOptions(id)?.capabilities;
1329
1409
  this.mcpConnections[id] = new MCPClientConnection(new URL(url), {
1330
1410
  name: this._name,
1331
1411
  version: this._version
1332
1412
  }, {
1333
- client: {
1334
- ...defaultClientOptions,
1335
- ...options.client
1336
- },
1337
- transport: normalizedTransport
1413
+ client: options.client ?? {},
1414
+ transport: normalizedTransport,
1415
+ elicitationHandlers: this.scopedElicitationHandlers(id),
1416
+ capabilitySeed
1338
1417
  });
1339
1418
  const store = new DisposableStore();
1340
1419
  const existing = this._connectionDisposables.get(id);
@@ -1343,6 +1422,17 @@ var MCPClientManager = class {
1343
1422
  store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
1344
1423
  this._onObservabilityEvent.fire(event);
1345
1424
  }));
1425
+ if (capabilitySeed) {
1426
+ const conn = this.mcpConnections[id];
1427
+ const seedClear = conn.onObservabilityEvent((event) => {
1428
+ if (event.type !== "mcp:client:connect" || event.payload.state !== MCPConnectionState.CONNECTED) return;
1429
+ seedClear.dispose();
1430
+ if (this._elicitationHandlers || !conn.options.capabilitySeed) return;
1431
+ const currentId = Object.keys(this.mcpConnections).find((key) => this.mcpConnections[key] === conn);
1432
+ if (currentId) this.clearStoredCapabilities(currentId);
1433
+ });
1434
+ store.add(seedClear);
1435
+ }
1346
1436
  return this.mcpConnections[id];
1347
1437
  }
1348
1438
  /**
@@ -1363,6 +1453,7 @@ var MCPClientManager = class {
1363
1453
  }
1364
1454
  });
1365
1455
  const { authProvider: _, ...transportWithoutAuth } = options.transport ?? {};
1456
+ const { jsonSchemaValidator: _validator, ...serializableClient } = options.client ?? {};
1366
1457
  this.saveServerToStorage({
1367
1458
  id,
1368
1459
  name: options.name,
@@ -1371,9 +1462,10 @@ var MCPClientManager = class {
1371
1462
  client_id: options.clientId ?? null,
1372
1463
  auth_url: options.authUrl ?? null,
1373
1464
  server_options: JSON.stringify({
1374
- client: options.client,
1465
+ client: serializableClient,
1375
1466
  transport: transportWithoutAuth,
1376
- retry: options.retry
1467
+ retry: options.retry,
1468
+ capabilities: this.advertisedHandlerCapabilities()
1377
1469
  })
1378
1470
  });
1379
1471
  this._onServerStateChanged.fire();
@@ -1649,6 +1741,51 @@ var MCPClientManager = class {
1649
1741
  this._oauthCallbackConfig = config;
1650
1742
  }
1651
1743
  /**
1744
+ * Configure handling for server-initiated `elicitation/create` requests.
1745
+ *
1746
+ * The handler is held in memory only and applied to every MCP connection
1747
+ * created or restored by this manager. Call this before registering
1748
+ * connections when you want the initial MCP handshake to advertise
1749
+ * handler-driven form- and url-mode elicitation. Existing active connections
1750
+ * keep their negotiated capabilities until they reconnect.
1751
+ *
1752
+ * The advertised modes are persisted with each stored server, so
1753
+ * connections restored after hibernation re-advertise them at the handshake
1754
+ * even when this is called later in the wake-up (e.g. from onStart) — the
1755
+ * handlers attach to the live connections as soon as this runs.
1756
+ *
1757
+ * Pass undefined to clear the handler.
1758
+ *
1759
+ * @param handlers Elicitation handlers keyed by mode, each scoped with the server id that sent the request
1760
+ */
1761
+ configureElicitationHandlers(handlers) {
1762
+ this._elicitationHandlers = handlers && (handlers.form || handlers.url) ? handlers : void 0;
1763
+ this.persistAdvertisedCapabilities();
1764
+ for (const [id, connection] of Object.entries(this.mcpConnections)) connection.configureElicitationHandlers(this.scopedElicitationHandlers(id));
1765
+ }
1766
+ /** Client capabilities advertised from the currently configured handlers. */
1767
+ advertisedHandlerCapabilities() {
1768
+ const elicitation = elicitationCapabilitiesFromHandlers(this._elicitationHandlers);
1769
+ return elicitation ? { elicitation } : void 0;
1770
+ }
1771
+ /**
1772
+ * Record the handler-derived capabilities on every stored server row so a
1773
+ * restore after hibernation re-advertises them before the handlers
1774
+ * themselves are reconfigured.
1775
+ */
1776
+ persistAdvertisedCapabilities() {
1777
+ const capabilities = this.advertisedHandlerCapabilities();
1778
+ for (const server of this.getServersFromStorage()) {
1779
+ const options = server.server_options ? JSON.parse(server.server_options) : {};
1780
+ if (JSON.stringify(options.capabilities) === JSON.stringify(capabilities)) continue;
1781
+ options.capabilities = capabilities;
1782
+ this.saveServerToStorage({
1783
+ ...server,
1784
+ server_options: JSON.stringify(options)
1785
+ });
1786
+ }
1787
+ }
1788
+ /**
1652
1789
  * Get the current OAuth callback configuration
1653
1790
  * @returns The current OAuth callback configuration
1654
1791
  */
@@ -1845,4 +1982,4 @@ function getNamespacedData(mcpClients, type) {
1845
1982
  //#endregion
1846
1983
  export { MCPConnectionState as a, RPC_DO_PREFIX as c, normalizeServerId as i, DisposableStore as l, MCP_SERVER_ID_MAX_LENGTH as n, RPCClientTransport as o, getNamespacedData as r, RPCServerTransport as s, MCPClientManager as t };
1847
1984
 
1848
- //# sourceMappingURL=client-BZ-B3NhC.js.map
1985
+ //# sourceMappingURL=client-C7F0MaVz.js.map