agents 0.17.3 → 0.18.0
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/{agent-tool-types-CNyE1iz_.d.ts → agent-tool-types-BNUGGBzQ.d.ts} +191 -61
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-BeOheFBK.d.ts → agent-tools-BFbzVLFc.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/chat/index.d.ts +29 -9
- package/dist/chat/index.js +31 -41
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +25 -1
- package/dist/chat/react.js +249 -93
- package/dist/chat/react.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/{client-BZ-B3NhC.js → client-CcjiFpTf.js} +352 -81
- package/dist/client-CcjiFpTf.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/cloudflare-BldFV0Pa.js +117 -0
- package/dist/cloudflare-BldFV0Pa.js.map +1 -0
- package/dist/index.d.ts +13 -11
- package/dist/index.js +153 -48
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +18 -14
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/index.d.ts +38 -30
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/ai/index.d.ts +155 -0
- package/dist/observability/ai/index.js +1845 -0
- package/dist/observability/ai/index.js.map +1 -0
- package/dist/react.d.ts +1 -1
- package/dist/{retries-CvHJwSuh.d.ts → retries-CAvxtG9d.d.ts} +16 -7
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +20 -2
- package/dist/retries.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/vite.d.ts +4 -4
- package/dist/vite.js +4 -2
- package/dist/vite.js.map +1 -1
- package/dist/{wire-types-nflOzNuU.js → wire-types-CU9rLoeS.js} +33 -2
- package/dist/wire-types-CU9rLoeS.js.map +1 -0
- package/dist/workflows.d.ts +1 -1
- package/docs/agent-class.md +9 -1
- package/docs/configuration.md +20 -0
- package/docs/mcp-client.md +52 -4
- package/docs/observability.md +282 -0
- package/package.json +7 -2
- package/dist/client-BZ-B3NhC.js.map +0 -1
- package/dist/wire-types-nflOzNuU.js.map +0 -1
|
@@ -2,11 +2,11 @@ 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
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
9
|
+
import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
10
10
|
import { ElicitRequestSchema, JSONRPCMessageSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, isJSONRPCErrorResponse, isJSONRPCResultResponse } from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
//#region src/core/events.ts
|
|
12
12
|
function toDisposable(fn) {
|
|
@@ -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,
|
|
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
|
-
...
|
|
327
|
-
|
|
350
|
+
...seed,
|
|
351
|
+
...this.options.client?.capabilities,
|
|
352
|
+
...elicitation ? { elicitation } : {}
|
|
328
353
|
}
|
|
329
354
|
};
|
|
330
|
-
|
|
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;
|
|
@@ -509,6 +550,7 @@ var MCPClientConnection = class {
|
|
|
509
550
|
});
|
|
510
551
|
return {
|
|
511
552
|
success: false,
|
|
553
|
+
reason: "error",
|
|
512
554
|
error: `Discovery skipped - connection in ${this.connectionState} state`
|
|
513
555
|
};
|
|
514
556
|
}
|
|
@@ -546,9 +588,11 @@ var MCPClientConnection = class {
|
|
|
546
588
|
} catch (e) {
|
|
547
589
|
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
548
590
|
this.connectionState = MCPConnectionState.CONNECTED;
|
|
591
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
549
592
|
return {
|
|
550
593
|
success: false,
|
|
551
|
-
|
|
594
|
+
reason: this._probingCapabilities && e instanceof StreamableHTTPError && e.code === 404 ? "stale-session" : "error",
|
|
595
|
+
error
|
|
552
596
|
};
|
|
553
597
|
} finally {
|
|
554
598
|
this._discoveryAbortController = void 0;
|
|
@@ -634,11 +678,19 @@ var MCPClientConnection = class {
|
|
|
634
678
|
return templatesAgg;
|
|
635
679
|
}
|
|
636
680
|
/**
|
|
637
|
-
* Handle elicitation request from server
|
|
638
|
-
*
|
|
681
|
+
* Handle elicitation request from server.
|
|
682
|
+
*
|
|
683
|
+
* Delegates to the `elicitationHandlers` connection option when provided.
|
|
684
|
+
*
|
|
685
|
+
* @deprecated Overriding or instance-patching this method directly is
|
|
686
|
+
* deprecated — pass the `elicitationHandlers` connection option instead.
|
|
639
687
|
*/
|
|
640
|
-
async handleElicitationRequest(
|
|
641
|
-
|
|
688
|
+
async handleElicitationRequest(request) {
|
|
689
|
+
const mode = request.params.mode === "url" ? "url" : "form";
|
|
690
|
+
const handler = this.options.elicitationHandlers?.[mode];
|
|
691
|
+
if (handler) return handler(request);
|
|
692
|
+
if (this.options.elicitationHandlers) throw new Error(`No MCP ${mode}-mode elicitation handler configured for this connection.`);
|
|
693
|
+
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
694
|
}
|
|
643
695
|
isResumedStreamableHttpSession() {
|
|
644
696
|
return this._transport instanceof StreamableHTTPClientTransport && typeof this._transport.sessionId === "string";
|
|
@@ -646,6 +698,10 @@ var MCPClientConnection = class {
|
|
|
646
698
|
get sessionId() {
|
|
647
699
|
if (this._transport instanceof StreamableHTTPClientTransport) return this._transport.sessionId;
|
|
648
700
|
}
|
|
701
|
+
/** @internal Clear a restored session before reconnecting. */
|
|
702
|
+
clearResumedSession() {
|
|
703
|
+
if ("sessionId" in this.options.transport) delete this.options.transport.sessionId;
|
|
704
|
+
}
|
|
649
705
|
getTransportName(transport) {
|
|
650
706
|
if (transport instanceof StreamableHTTPClientTransport) return "streamable-http";
|
|
651
707
|
if (transport instanceof SSEClientTransport) return "sse";
|
|
@@ -764,7 +820,6 @@ var MCPClientConnection = class {
|
|
|
764
820
|
};
|
|
765
821
|
//#endregion
|
|
766
822
|
//#region src/mcp/client.ts
|
|
767
|
-
const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
|
|
768
823
|
/** Maximum length of a normalized MCP server id. */
|
|
769
824
|
const MCP_SERVER_ID_MAX_LENGTH = 64;
|
|
770
825
|
/**
|
|
@@ -887,6 +942,13 @@ function isBlockedUrl(url) {
|
|
|
887
942
|
}
|
|
888
943
|
return false;
|
|
889
944
|
}
|
|
945
|
+
/** Converts a resolved connection failure into a retry signal. */
|
|
946
|
+
var ConnectRetryError = class extends Error {
|
|
947
|
+
constructor(result) {
|
|
948
|
+
super("MCP connection attempt failed");
|
|
949
|
+
this.result = result;
|
|
950
|
+
}
|
|
951
|
+
};
|
|
890
952
|
/**
|
|
891
953
|
* Utility class that aggregates multiple MCP clients into one
|
|
892
954
|
*/
|
|
@@ -900,6 +962,7 @@ var MCPClientManager = class {
|
|
|
900
962
|
this._name = _name;
|
|
901
963
|
this._version = _version;
|
|
902
964
|
this.mcpConnections = {};
|
|
965
|
+
this._aiToolSchemas = /* @__PURE__ */ new WeakMap();
|
|
903
966
|
this._didWarnAboutUnstableGetAITools = false;
|
|
904
967
|
this._connectionDisposables = /* @__PURE__ */ new Map();
|
|
905
968
|
this._isRestored = false;
|
|
@@ -912,6 +975,21 @@ var MCPClientManager = class {
|
|
|
912
975
|
this._storage = options.storage;
|
|
913
976
|
this._createAuthProviderFn = options.createAuthProvider;
|
|
914
977
|
}
|
|
978
|
+
/**
|
|
979
|
+
* Scope the manager-level elicitation handler to a single connection.
|
|
980
|
+
* Returns undefined when no handler is configured so the connection keeps
|
|
981
|
+
* its default throwing behavior.
|
|
982
|
+
*/
|
|
983
|
+
scopedElicitationHandlers(serverId) {
|
|
984
|
+
const handlers = this._elicitationHandlers;
|
|
985
|
+
if (!handlers) return void 0;
|
|
986
|
+
const form = handlers.form;
|
|
987
|
+
const url = handlers.url;
|
|
988
|
+
return {
|
|
989
|
+
form: form ? (request) => form(request, serverId) : void 0,
|
|
990
|
+
url: url ? (request) => url(request, serverId) : void 0
|
|
991
|
+
};
|
|
992
|
+
}
|
|
915
993
|
sql(query, ...bindings) {
|
|
916
994
|
return [...this._storage.sql.exec(query, ...bindings)];
|
|
917
995
|
}
|
|
@@ -944,6 +1022,11 @@ var MCPClientManager = class {
|
|
|
944
1022
|
*/
|
|
945
1023
|
async migrateServerId(oldId, newId, clientName) {
|
|
946
1024
|
if (oldId === newId) return;
|
|
1025
|
+
while (true) {
|
|
1026
|
+
const pending = this._pendingConnections.get(oldId);
|
|
1027
|
+
if (!pending) break;
|
|
1028
|
+
await pending.catch(() => {});
|
|
1029
|
+
}
|
|
947
1030
|
if (this.sql("SELECT id FROM cf_agents_mcp_servers WHERE id = ?", oldId).length === 0) {
|
|
948
1031
|
this._renameInMemoryConnection(oldId, newId);
|
|
949
1032
|
return;
|
|
@@ -979,6 +1062,8 @@ var MCPClientManager = class {
|
|
|
979
1062
|
delete this.mcpConnections[oldId];
|
|
980
1063
|
const authProvider = conn.options.transport.authProvider;
|
|
981
1064
|
if (authProvider) authProvider.serverId = newId;
|
|
1065
|
+
const scoped = this.scopedElicitationHandlers(newId);
|
|
1066
|
+
if (scoped) conn.configureElicitationHandlers(scoped);
|
|
982
1067
|
}
|
|
983
1068
|
const disposables = this._connectionDisposables.get(oldId);
|
|
984
1069
|
if (disposables) {
|
|
@@ -1007,12 +1092,37 @@ var MCPClientManager = class {
|
|
|
1007
1092
|
}));
|
|
1008
1093
|
}
|
|
1009
1094
|
/**
|
|
1010
|
-
* Get the
|
|
1095
|
+
* Get the parsed server_options for a stored server, if any.
|
|
1011
1096
|
*/
|
|
1012
|
-
|
|
1097
|
+
getStoredServerOptions(serverId) {
|
|
1013
1098
|
const rows = this.sql("SELECT server_options FROM cf_agents_mcp_servers WHERE id = ?", serverId);
|
|
1014
1099
|
if (!rows.length || !rows[0].server_options) return void 0;
|
|
1015
|
-
return JSON.parse(rows[0].server_options)
|
|
1100
|
+
return JSON.parse(rows[0].server_options);
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Clear the capabilities persisted on a stored server row. Called once a
|
|
1104
|
+
* seeded connection's handshake completes (see `createConnection`): the
|
|
1105
|
+
* stamp is valid for one successful restore — sessions that configure
|
|
1106
|
+
* handlers re-stamp every row, so a deploy that stops configuring them
|
|
1107
|
+
* stops advertising stale modes after its first connected wake instead of
|
|
1108
|
+
* forever, while wakes that never handshake don't burn the stamp.
|
|
1109
|
+
*/
|
|
1110
|
+
clearStoredCapabilities(serverId) {
|
|
1111
|
+
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];
|
|
1112
|
+
if (!row?.server_options) return;
|
|
1113
|
+
const options = JSON.parse(row.server_options);
|
|
1114
|
+
if (!options.capabilities) return;
|
|
1115
|
+
options.capabilities = void 0;
|
|
1116
|
+
this.saveServerToStorage({
|
|
1117
|
+
...row,
|
|
1118
|
+
server_options: JSON.stringify(options)
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Get the retry options for a server from stored server_options
|
|
1123
|
+
*/
|
|
1124
|
+
getServerRetryOptions(serverId) {
|
|
1125
|
+
return this.getStoredServerOptions(serverId)?.retry;
|
|
1016
1126
|
}
|
|
1017
1127
|
clearServerAuthUrl(serverId) {
|
|
1018
1128
|
this.sql("UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?", serverId);
|
|
@@ -1063,6 +1173,22 @@ var MCPClientManager = class {
|
|
|
1063
1173
|
if (authProvider.runWithCodeVerifierState) return authProvider.runWithCodeVerifierState(state, callback);
|
|
1064
1174
|
return callback();
|
|
1065
1175
|
}
|
|
1176
|
+
async hasRedeemableOAuthState(serverId, authProvider, state) {
|
|
1177
|
+
authProvider.serverId = serverId;
|
|
1178
|
+
try {
|
|
1179
|
+
return (await authProvider.checkState(state)).valid;
|
|
1180
|
+
} catch {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
ignoreUnverifiedCallback(serverId, error) {
|
|
1185
|
+
console.warn(`[MCPClientManager] Ignoring OAuth callback with unverified state for server "${serverId}": ${error}`);
|
|
1186
|
+
return {
|
|
1187
|
+
serverId,
|
|
1188
|
+
authSuccess: false,
|
|
1189
|
+
authError: error
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1066
1192
|
async consumeStaleOAuthState(serverId, authProvider, state) {
|
|
1067
1193
|
try {
|
|
1068
1194
|
const stateValidation = await authProvider.checkState(state);
|
|
@@ -1129,7 +1255,8 @@ var MCPClientManager = class {
|
|
|
1129
1255
|
callback_url: "",
|
|
1130
1256
|
server_options: JSON.stringify({
|
|
1131
1257
|
bindingName,
|
|
1132
|
-
props
|
|
1258
|
+
props,
|
|
1259
|
+
capabilities: this.advertisedHandlerCapabilities()
|
|
1133
1260
|
})
|
|
1134
1261
|
});
|
|
1135
1262
|
}
|
|
@@ -1166,6 +1293,7 @@ var MCPClientManager = class {
|
|
|
1166
1293
|
}
|
|
1167
1294
|
}
|
|
1168
1295
|
const parsedOptions = server.server_options ? JSON.parse(server.server_options) : null;
|
|
1296
|
+
if (parsedOptions?.client) delete parsedOptions.client.jsonSchemaValidator;
|
|
1169
1297
|
let authProvider;
|
|
1170
1298
|
if (server.callback_url) {
|
|
1171
1299
|
authProvider = this._createAuthProviderFn ? this._createAuthProviderFn(server.callback_url) : this.createAuthProvider(server.id, server.callback_url, clientName, server.client_id ?? void 0);
|
|
@@ -1223,16 +1351,30 @@ var MCPClientManager = class {
|
|
|
1223
1351
|
clearTimeout(timerId);
|
|
1224
1352
|
} else await settled;
|
|
1225
1353
|
}
|
|
1354
|
+
async _connectWithRetry(serverId, retry) {
|
|
1355
|
+
const maxAttempts = retry?.maxAttempts ?? 3;
|
|
1356
|
+
const baseDelayMs = retry?.baseDelayMs ?? 500;
|
|
1357
|
+
const maxDelayMs = retry?.maxDelayMs ?? 5e3;
|
|
1358
|
+
try {
|
|
1359
|
+
return await tryN(maxAttempts, async () => {
|
|
1360
|
+
const result = await this.connectToServer(serverId);
|
|
1361
|
+
if (result.state === MCPConnectionState.FAILED) throw new ConnectRetryError(result);
|
|
1362
|
+
return result;
|
|
1363
|
+
}, {
|
|
1364
|
+
baseDelayMs,
|
|
1365
|
+
maxDelayMs
|
|
1366
|
+
});
|
|
1367
|
+
} catch (error) {
|
|
1368
|
+
if (error instanceof ConnectRetryError) return error.result;
|
|
1369
|
+
throw error;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1226
1372
|
/**
|
|
1227
1373
|
* Internal method to restore a single server connection and discovery
|
|
1228
1374
|
*/
|
|
1229
1375
|
async _restoreServer(serverId, retry) {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
baseDelayMs: retry?.baseDelayMs ?? 500,
|
|
1233
|
-
maxDelayMs: retry?.maxDelayMs ?? 5e3
|
|
1234
|
-
}).catch((error) => {
|
|
1235
|
-
console.error(`Error connecting to ${serverId} after ${maxAttempts} attempts:`, error);
|
|
1376
|
+
if ((await this._connectWithRetry(serverId, retry).catch((error) => {
|
|
1377
|
+
console.error(`Error connecting to ${serverId}:`, error);
|
|
1236
1378
|
return null;
|
|
1237
1379
|
}))?.state === MCPConnectionState.CONNECTED) {
|
|
1238
1380
|
const discoverResult = await this.discoverIfConnected(serverId);
|
|
@@ -1257,24 +1399,16 @@ var MCPClientManager = class {
|
|
|
1257
1399
|
}
|
|
1258
1400
|
if (isBlockedUrl(url)) throw new Error(`Blocked URL: ${url} — MCP client connections to private/internal addresses are not allowed`);
|
|
1259
1401
|
if (!options.reconnect?.oauthCode || !this.mcpConnections[id]) {
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
transport: normalizedTransport
|
|
1402
|
+
const replaced = this.mcpConnections[id];
|
|
1403
|
+
if (replaced) {
|
|
1404
|
+
delete this.mcpConnections[id];
|
|
1405
|
+
await replaced.close().catch(() => {});
|
|
1406
|
+
this.updateStoredSessionId(id, void 0);
|
|
1407
|
+
}
|
|
1408
|
+
this.createConnection(id, url, {
|
|
1409
|
+
client: options.client,
|
|
1410
|
+
transport: options.transport ?? {}
|
|
1270
1411
|
});
|
|
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
1412
|
}
|
|
1279
1413
|
await this.mcpConnections[id].init();
|
|
1280
1414
|
if (options.reconnect?.oauthCode) try {
|
|
@@ -1326,15 +1460,15 @@ var MCPClientManager = class {
|
|
|
1326
1460
|
...options.transport,
|
|
1327
1461
|
type: options.transport?.type ?? "auto"
|
|
1328
1462
|
};
|
|
1463
|
+
const capabilitySeed = this.getStoredServerOptions(id)?.capabilities;
|
|
1329
1464
|
this.mcpConnections[id] = new MCPClientConnection(new URL(url), {
|
|
1330
1465
|
name: this._name,
|
|
1331
1466
|
version: this._version
|
|
1332
1467
|
}, {
|
|
1333
|
-
client: {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
transport: normalizedTransport
|
|
1468
|
+
client: options.client ?? {},
|
|
1469
|
+
transport: normalizedTransport,
|
|
1470
|
+
elicitationHandlers: this.scopedElicitationHandlers(id),
|
|
1471
|
+
capabilitySeed
|
|
1338
1472
|
});
|
|
1339
1473
|
const store = new DisposableStore();
|
|
1340
1474
|
const existing = this._connectionDisposables.get(id);
|
|
@@ -1343,6 +1477,17 @@ var MCPClientManager = class {
|
|
|
1343
1477
|
store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
|
|
1344
1478
|
this._onObservabilityEvent.fire(event);
|
|
1345
1479
|
}));
|
|
1480
|
+
if (capabilitySeed) {
|
|
1481
|
+
const conn = this.mcpConnections[id];
|
|
1482
|
+
const seedClear = conn.onObservabilityEvent((event) => {
|
|
1483
|
+
if (event.type !== "mcp:client:connect" || event.payload.state !== MCPConnectionState.CONNECTED) return;
|
|
1484
|
+
seedClear.dispose();
|
|
1485
|
+
if (this._elicitationHandlers || !conn.options.capabilitySeed) return;
|
|
1486
|
+
const currentId = Object.keys(this.mcpConnections).find((key) => this.mcpConnections[key] === conn);
|
|
1487
|
+
if (currentId) this.clearStoredCapabilities(currentId);
|
|
1488
|
+
});
|
|
1489
|
+
store.add(seedClear);
|
|
1490
|
+
}
|
|
1346
1491
|
return this.mcpConnections[id];
|
|
1347
1492
|
}
|
|
1348
1493
|
/**
|
|
@@ -1363,6 +1508,7 @@ var MCPClientManager = class {
|
|
|
1363
1508
|
}
|
|
1364
1509
|
});
|
|
1365
1510
|
const { authProvider: _, ...transportWithoutAuth } = options.transport ?? {};
|
|
1511
|
+
const { jsonSchemaValidator: _validator, ...serializableClient } = options.client ?? {};
|
|
1366
1512
|
this.saveServerToStorage({
|
|
1367
1513
|
id,
|
|
1368
1514
|
name: options.name,
|
|
@@ -1371,9 +1517,10 @@ var MCPClientManager = class {
|
|
|
1371
1517
|
client_id: options.clientId ?? null,
|
|
1372
1518
|
auth_url: options.authUrl ?? null,
|
|
1373
1519
|
server_options: JSON.stringify({
|
|
1374
|
-
client:
|
|
1520
|
+
client: serializableClient,
|
|
1375
1521
|
transport: transportWithoutAuth,
|
|
1376
|
-
retry: options.retry
|
|
1522
|
+
retry: options.retry,
|
|
1523
|
+
capabilities: this.advertisedHandlerCapabilities()
|
|
1377
1524
|
})
|
|
1378
1525
|
});
|
|
1379
1526
|
this._onServerStateChanged.fire();
|
|
@@ -1521,6 +1668,8 @@ var MCPClientManager = class {
|
|
|
1521
1668
|
}
|
|
1522
1669
|
return this.oauthCallbackSuccess(validation.serverId, conn);
|
|
1523
1670
|
}
|
|
1671
|
+
const authProvider = conn.options.transport.authProvider;
|
|
1672
|
+
if (validation.state && authProvider && !await this.hasRedeemableOAuthState(validation.serverId, authProvider, validation.state)) return this.ignoreUnverifiedCallback(validation.serverId, validation.error);
|
|
1524
1673
|
return this.failConnection(validation.serverId, validation.error);
|
|
1525
1674
|
}
|
|
1526
1675
|
return {
|
|
@@ -1541,13 +1690,13 @@ var MCPClientManager = class {
|
|
|
1541
1690
|
await this.consumeStaleOAuthState(serverId, authProvider, state);
|
|
1542
1691
|
return this.oauthCallbackSuccess(serverId, conn);
|
|
1543
1692
|
}
|
|
1544
|
-
|
|
1693
|
+
return this.ignoreUnverifiedCallback(serverId, stateValidation.error || "Invalid state");
|
|
1545
1694
|
}
|
|
1546
1695
|
if (this.isAuthAcceptedConnection(conn)) {
|
|
1547
1696
|
await this.consumeStaleOAuthState(serverId, authProvider, state);
|
|
1548
1697
|
return this.oauthCallbackSuccess(serverId, conn);
|
|
1549
1698
|
}
|
|
1550
|
-
if (conn.connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error(`Failed to authenticate
|
|
1699
|
+
if (conn.connectionState !== MCPConnectionState.AUTHENTICATING && conn.connectionState !== MCPConnectionState.FAILED) throw new Error(`Failed to authenticate from "${conn.connectionState}" state`);
|
|
1551
1700
|
conn.connectionState = MCPConnectionState.CONNECTING;
|
|
1552
1701
|
await authProvider.consumeState(state);
|
|
1553
1702
|
await this.completeAuthorizationAndCleanupVerifier(serverId, conn, authProvider, state, code);
|
|
@@ -1584,11 +1733,41 @@ var MCPClientManager = class {
|
|
|
1584
1733
|
return;
|
|
1585
1734
|
}
|
|
1586
1735
|
const result = await conn.discover(options);
|
|
1736
|
+
if (!result.success && result.reason === "stale-session") return this._recoverStaleSession(conn, serverId, options);
|
|
1587
1737
|
this._onServerStateChanged.fire();
|
|
1588
|
-
return
|
|
1589
|
-
|
|
1738
|
+
return this._toDiscoverResult(conn, result);
|
|
1739
|
+
}
|
|
1740
|
+
_toDiscoverResult(conn, result) {
|
|
1741
|
+
return result.success ? {
|
|
1742
|
+
success: true,
|
|
1743
|
+
state: conn.connectionState
|
|
1744
|
+
} : {
|
|
1745
|
+
success: false,
|
|
1746
|
+
error: result.error,
|
|
1747
|
+
state: conn.connectionState
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
async _recoverStaleSession(conn, serverId, options) {
|
|
1751
|
+
conn.clearResumedSession();
|
|
1752
|
+
this.updateStoredSessionId(serverId, void 0);
|
|
1753
|
+
let connectResult;
|
|
1754
|
+
try {
|
|
1755
|
+
connectResult = await this.connectToServer(serverId);
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
return {
|
|
1758
|
+
success: false,
|
|
1759
|
+
error: toErrorMessage(error),
|
|
1760
|
+
state: conn.connectionState
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
if (connectResult.state !== MCPConnectionState.CONNECTED) return {
|
|
1764
|
+
success: false,
|
|
1765
|
+
error: connectResult.state === MCPConnectionState.FAILED ? connectResult.error : `Connection in ${connectResult.state} state after session re-initialization`,
|
|
1590
1766
|
state: conn.connectionState
|
|
1591
1767
|
};
|
|
1768
|
+
const result = await conn.discover(options);
|
|
1769
|
+
this._onServerStateChanged.fire();
|
|
1770
|
+
return this._toDiscoverResult(conn, result);
|
|
1592
1771
|
}
|
|
1593
1772
|
/**
|
|
1594
1773
|
* Establish connection in the background after OAuth completion.
|
|
@@ -1625,10 +1804,7 @@ var MCPClientManager = class {
|
|
|
1625
1804
|
return;
|
|
1626
1805
|
}
|
|
1627
1806
|
const retry = this.getServerRetryOptions(serverId);
|
|
1628
|
-
const connectResult = await
|
|
1629
|
-
baseDelayMs: retry?.baseDelayMs ?? 500,
|
|
1630
|
-
maxDelayMs: retry?.maxDelayMs ?? 5e3
|
|
1631
|
-
});
|
|
1807
|
+
const connectResult = await this._connectWithRetry(serverId, retry);
|
|
1632
1808
|
this._onServerStateChanged.fire();
|
|
1633
1809
|
if (connectResult.state === MCPConnectionState.CONNECTED) await this.discoverIfConnected(serverId);
|
|
1634
1810
|
this._onObservabilityEvent.fire({
|
|
@@ -1649,6 +1825,51 @@ var MCPClientManager = class {
|
|
|
1649
1825
|
this._oauthCallbackConfig = config;
|
|
1650
1826
|
}
|
|
1651
1827
|
/**
|
|
1828
|
+
* Configure handling for server-initiated `elicitation/create` requests.
|
|
1829
|
+
*
|
|
1830
|
+
* The handler is held in memory only and applied to every MCP connection
|
|
1831
|
+
* created or restored by this manager. Call this before registering
|
|
1832
|
+
* connections when you want the initial MCP handshake to advertise
|
|
1833
|
+
* handler-driven form- and url-mode elicitation. Existing active connections
|
|
1834
|
+
* keep their negotiated capabilities until they reconnect.
|
|
1835
|
+
*
|
|
1836
|
+
* The advertised modes are persisted with each stored server, so
|
|
1837
|
+
* connections restored after hibernation re-advertise them at the handshake
|
|
1838
|
+
* even when this is called later in the wake-up (e.g. from onStart) — the
|
|
1839
|
+
* handlers attach to the live connections as soon as this runs.
|
|
1840
|
+
*
|
|
1841
|
+
* Pass undefined to clear the handler.
|
|
1842
|
+
*
|
|
1843
|
+
* @param handlers Elicitation handlers keyed by mode, each scoped with the server id that sent the request
|
|
1844
|
+
*/
|
|
1845
|
+
configureElicitationHandlers(handlers) {
|
|
1846
|
+
this._elicitationHandlers = handlers && (handlers.form || handlers.url) ? handlers : void 0;
|
|
1847
|
+
this.persistAdvertisedCapabilities();
|
|
1848
|
+
for (const [id, connection] of Object.entries(this.mcpConnections)) connection.configureElicitationHandlers(this.scopedElicitationHandlers(id));
|
|
1849
|
+
}
|
|
1850
|
+
/** Client capabilities advertised from the currently configured handlers. */
|
|
1851
|
+
advertisedHandlerCapabilities() {
|
|
1852
|
+
const elicitation = elicitationCapabilitiesFromHandlers(this._elicitationHandlers);
|
|
1853
|
+
return elicitation ? { elicitation } : void 0;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Record the handler-derived capabilities on every stored server row so a
|
|
1857
|
+
* restore after hibernation re-advertises them before the handlers
|
|
1858
|
+
* themselves are reconfigured.
|
|
1859
|
+
*/
|
|
1860
|
+
persistAdvertisedCapabilities() {
|
|
1861
|
+
const capabilities = this.advertisedHandlerCapabilities();
|
|
1862
|
+
for (const server of this.getServersFromStorage()) {
|
|
1863
|
+
const options = server.server_options ? JSON.parse(server.server_options) : {};
|
|
1864
|
+
if (JSON.stringify(options.capabilities) === JSON.stringify(capabilities)) continue;
|
|
1865
|
+
options.capabilities = capabilities;
|
|
1866
|
+
this.saveServerToStorage({
|
|
1867
|
+
...server,
|
|
1868
|
+
server_options: JSON.stringify(options)
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1652
1873
|
* Get the current OAuth callback configuration
|
|
1653
1874
|
* @returns The current OAuth callback configuration
|
|
1654
1875
|
*/
|
|
@@ -1663,37 +1884,87 @@ var MCPClientManager = class {
|
|
|
1663
1884
|
return getNamespacedData(this.filterConnections(filter), "tools");
|
|
1664
1885
|
}
|
|
1665
1886
|
/**
|
|
1887
|
+
* Convert connected MCP tools for the AI SDK. Converted schemas are reused
|
|
1888
|
+
* while a live connection retains the same catalog array and schema-source
|
|
1889
|
+
* identities; tool records and execute closures are rebuilt on every call.
|
|
1890
|
+
*
|
|
1666
1891
|
* @param filter - Optional filter to scope results to specific servers
|
|
1667
1892
|
* @returns a set of tools that you can use with the AI SDK
|
|
1668
1893
|
*/
|
|
1669
1894
|
getAITools(filter) {
|
|
1670
1895
|
const connections = this.filterConnections(filter);
|
|
1671
|
-
for (const [id, conn] of Object.entries(connections)) 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.`);
|
|
1672
1896
|
const entries = [];
|
|
1673
|
-
for (const
|
|
1674
|
-
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1897
|
+
for (const [serverId, conn] of Object.entries(connections)) {
|
|
1898
|
+
if (conn.connectionState !== MCPConnectionState.READY && conn.connectionState !== MCPConnectionState.AUTHENTICATING) console.warn(`[getAITools] WARNING: Reading tools from connection ${serverId} in state "${conn.connectionState}". Tools may not be loaded yet.`);
|
|
1899
|
+
const catalog = conn.tools;
|
|
1900
|
+
let cache = this._aiToolSchemas.get(conn);
|
|
1901
|
+
if (!cache || cache.catalog !== catalog) {
|
|
1902
|
+
cache = {
|
|
1903
|
+
catalog,
|
|
1904
|
+
converted: []
|
|
1905
|
+
};
|
|
1906
|
+
this._aiToolSchemas.set(conn, cache);
|
|
1907
|
+
}
|
|
1908
|
+
for (const [index, tool] of catalog.entries()) {
|
|
1909
|
+
const toolName = tool.name;
|
|
1910
|
+
try {
|
|
1911
|
+
const sourceInputSchema = tool.inputSchema;
|
|
1912
|
+
const sourceOutputSchema = tool.outputSchema;
|
|
1913
|
+
let slot = cache.converted[index];
|
|
1914
|
+
if (!slot || slot.tool !== tool || slot.inputSchema !== sourceInputSchema || slot.outputSchema !== sourceOutputSchema) {
|
|
1915
|
+
try {
|
|
1916
|
+
slot = {
|
|
1917
|
+
status: "converted",
|
|
1918
|
+
tool,
|
|
1919
|
+
inputSchema: sourceInputSchema,
|
|
1920
|
+
outputSchema: sourceOutputSchema,
|
|
1921
|
+
converted: {
|
|
1922
|
+
inputSchema: sourceInputSchema ? z.fromJSONSchema(sourceInputSchema) : z.fromJSONSchema({ type: "object" }),
|
|
1923
|
+
outputSchema: sourceOutputSchema ? z.fromJSONSchema(sourceOutputSchema) : void 0
|
|
1924
|
+
}
|
|
1925
|
+
};
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
const errorText = String(error);
|
|
1928
|
+
cache.converted[index] = {
|
|
1929
|
+
status: "failed",
|
|
1930
|
+
tool,
|
|
1931
|
+
inputSchema: sourceInputSchema,
|
|
1932
|
+
outputSchema: sourceOutputSchema,
|
|
1933
|
+
error: errorText
|
|
1934
|
+
};
|
|
1935
|
+
console.warn(`[getAITools] Skipping tool "${toolName}" from "${serverId}": ${errorText}`);
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
cache.converted[index] = slot;
|
|
1689
1939
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1940
|
+
if (slot.status === "failed") continue;
|
|
1941
|
+
const toolKey = `tool_${serverId.replace(/-/g, "")}_${toolName}`;
|
|
1942
|
+
const title = tool.title ?? tool.annotations?.title;
|
|
1943
|
+
const description = tool.description;
|
|
1944
|
+
entries.push([toolKey, {
|
|
1945
|
+
description,
|
|
1946
|
+
title,
|
|
1947
|
+
execute: async (args) => {
|
|
1948
|
+
const result = await this.callTool({
|
|
1949
|
+
arguments: args,
|
|
1950
|
+
name: toolName,
|
|
1951
|
+
serverId
|
|
1952
|
+
});
|
|
1953
|
+
if (result.isError) {
|
|
1954
|
+
const textContent = result.content?.[0];
|
|
1955
|
+
const message = textContent?.type === "text" && textContent.text ? textContent.text : "Tool call failed";
|
|
1956
|
+
throw new Error(message);
|
|
1957
|
+
}
|
|
1958
|
+
return result;
|
|
1959
|
+
},
|
|
1960
|
+
inputSchema: slot.converted.inputSchema,
|
|
1961
|
+
outputSchema: slot.converted.outputSchema
|
|
1962
|
+
}]);
|
|
1963
|
+
} catch (error) {
|
|
1964
|
+
console.warn(`[getAITools] Skipping tool "${toolName}" from "${serverId}": ${error}`);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
cache.converted.length = catalog.length;
|
|
1697
1968
|
}
|
|
1698
1969
|
return Object.fromEntries(entries);
|
|
1699
1970
|
}
|
|
@@ -1845,4 +2116,4 @@ function getNamespacedData(mcpClients, type) {
|
|
|
1845
2116
|
//#endregion
|
|
1846
2117
|
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
2118
|
|
|
1848
|
-
//# sourceMappingURL=client-
|
|
2119
|
+
//# sourceMappingURL=client-CcjiFpTf.js.map
|