hono-agents 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +811 -488
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "partysocket";
|
|
2
2
|
import { nanoid } from "nanoid";
|
|
3
|
+
import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker-provider.js";
|
|
3
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
|
-
import { ElicitRequestSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
6
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
-
import "
|
|
7
|
+
import { ElicitRequestSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
9
|
import { parseCronExpression } from "cron-schedule";
|
|
10
10
|
import "cloudflare:email";
|
|
@@ -12,8 +12,133 @@ import { Server, routePartykitRequest } from "partyserver";
|
|
|
12
12
|
import { env } from "hono/adapter";
|
|
13
13
|
import { createMiddleware } from "hono/factory";
|
|
14
14
|
|
|
15
|
-
//#region ../agents/dist/
|
|
16
|
-
|
|
15
|
+
//#region ../agents/dist/ai-types-CrMqkwc_.js
|
|
16
|
+
/**
|
|
17
|
+
* Enum for message types to improve type safety and maintainability
|
|
18
|
+
*/
|
|
19
|
+
let MessageType = /* @__PURE__ */ function(MessageType$1) {
|
|
20
|
+
MessageType$1["CF_AGENT_CHAT_MESSAGES"] = "cf_agent_chat_messages";
|
|
21
|
+
MessageType$1["CF_AGENT_USE_CHAT_REQUEST"] = "cf_agent_use_chat_request";
|
|
22
|
+
MessageType$1["CF_AGENT_USE_CHAT_RESPONSE"] = "cf_agent_use_chat_response";
|
|
23
|
+
MessageType$1["CF_AGENT_CHAT_CLEAR"] = "cf_agent_chat_clear";
|
|
24
|
+
MessageType$1["CF_AGENT_CHAT_REQUEST_CANCEL"] = "cf_agent_chat_request_cancel";
|
|
25
|
+
/** Sent by server when client connects and there's an active stream to resume */
|
|
26
|
+
MessageType$1["CF_AGENT_STREAM_RESUMING"] = "cf_agent_stream_resuming";
|
|
27
|
+
/** Sent by client to acknowledge stream resuming notification and request chunks */
|
|
28
|
+
MessageType$1["CF_AGENT_STREAM_RESUME_ACK"] = "cf_agent_stream_resume_ack";
|
|
29
|
+
MessageType$1["CF_AGENT_MCP_SERVERS"] = "cf_agent_mcp_servers";
|
|
30
|
+
MessageType$1["CF_MCP_AGENT_EVENT"] = "cf_mcp_agent_event";
|
|
31
|
+
MessageType$1["CF_AGENT_STATE"] = "cf_agent_state";
|
|
32
|
+
MessageType$1["RPC"] = "rpc";
|
|
33
|
+
return MessageType$1;
|
|
34
|
+
}({});
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region ../agents/dist/client-B3SR12TQ.js
|
|
38
|
+
/**
|
|
39
|
+
* Convert a camelCase string to a kebab-case string
|
|
40
|
+
* @param str The string to convert
|
|
41
|
+
* @returns The kebab-case string
|
|
42
|
+
*/
|
|
43
|
+
function camelCaseToKebabCase(str) {
|
|
44
|
+
if (str === str.toUpperCase() && str !== str.toLowerCase()) return str.toLowerCase().replace(/_/g, "-");
|
|
45
|
+
let kebabified = str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
46
|
+
kebabified = kebabified.startsWith("-") ? kebabified.slice(1) : kebabified;
|
|
47
|
+
return kebabified.replace(/_/g, "-").replace(/-$/, "");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region ../agents/dist/do-oauth-client-provider-CwqK5SXm.js
|
|
52
|
+
var DurableObjectOAuthClientProvider = class {
|
|
53
|
+
constructor(storage, clientName, baseRedirectUrl) {
|
|
54
|
+
this.storage = storage;
|
|
55
|
+
this.clientName = clientName;
|
|
56
|
+
this.baseRedirectUrl = baseRedirectUrl;
|
|
57
|
+
if (!storage) throw new Error("DurableObjectOAuthClientProvider requires a valid DurableObjectStorage instance");
|
|
58
|
+
}
|
|
59
|
+
get clientMetadata() {
|
|
60
|
+
return {
|
|
61
|
+
client_name: this.clientName,
|
|
62
|
+
client_uri: this.clientUri,
|
|
63
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
64
|
+
redirect_uris: [this.redirectUrl],
|
|
65
|
+
response_types: ["code"],
|
|
66
|
+
token_endpoint_auth_method: "none"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
get clientUri() {
|
|
70
|
+
return new URL(this.redirectUrl).origin;
|
|
71
|
+
}
|
|
72
|
+
get redirectUrl() {
|
|
73
|
+
return `${this.baseRedirectUrl}/${this.serverId}`;
|
|
74
|
+
}
|
|
75
|
+
get clientId() {
|
|
76
|
+
if (!this._clientId_) throw new Error("Trying to access clientId before it was set");
|
|
77
|
+
return this._clientId_;
|
|
78
|
+
}
|
|
79
|
+
set clientId(clientId_) {
|
|
80
|
+
this._clientId_ = clientId_;
|
|
81
|
+
}
|
|
82
|
+
get serverId() {
|
|
83
|
+
if (!this._serverId_) throw new Error("Trying to access serverId before it was set");
|
|
84
|
+
return this._serverId_;
|
|
85
|
+
}
|
|
86
|
+
set serverId(serverId_) {
|
|
87
|
+
this._serverId_ = serverId_;
|
|
88
|
+
}
|
|
89
|
+
keyPrefix(clientId) {
|
|
90
|
+
return `/${this.clientName}/${this.serverId}/${clientId}`;
|
|
91
|
+
}
|
|
92
|
+
clientInfoKey(clientId) {
|
|
93
|
+
return `${this.keyPrefix(clientId)}/client_info/`;
|
|
94
|
+
}
|
|
95
|
+
async clientInformation() {
|
|
96
|
+
if (!this._clientId_) return;
|
|
97
|
+
return await this.storage.get(this.clientInfoKey(this.clientId)) ?? void 0;
|
|
98
|
+
}
|
|
99
|
+
async saveClientInformation(clientInformation) {
|
|
100
|
+
await this.storage.put(this.clientInfoKey(clientInformation.client_id), clientInformation);
|
|
101
|
+
this.clientId = clientInformation.client_id;
|
|
102
|
+
}
|
|
103
|
+
tokenKey(clientId) {
|
|
104
|
+
return `${this.keyPrefix(clientId)}/token`;
|
|
105
|
+
}
|
|
106
|
+
async tokens() {
|
|
107
|
+
if (!this._clientId_) return;
|
|
108
|
+
return await this.storage.get(this.tokenKey(this.clientId)) ?? void 0;
|
|
109
|
+
}
|
|
110
|
+
async saveTokens(tokens) {
|
|
111
|
+
await this.storage.put(this.tokenKey(this.clientId), tokens);
|
|
112
|
+
}
|
|
113
|
+
get authUrl() {
|
|
114
|
+
return this._authUrl_;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Because this operates on the server side (but we need browser auth), we send this url back to the user
|
|
118
|
+
* and require user interact to initiate the redirect flow
|
|
119
|
+
*/
|
|
120
|
+
async redirectToAuthorization(authUrl) {
|
|
121
|
+
const stateToken = nanoid();
|
|
122
|
+
authUrl.searchParams.set("state", stateToken);
|
|
123
|
+
this._authUrl_ = authUrl.toString();
|
|
124
|
+
}
|
|
125
|
+
codeVerifierKey(clientId) {
|
|
126
|
+
return `${this.keyPrefix(clientId)}/code_verifier`;
|
|
127
|
+
}
|
|
128
|
+
async saveCodeVerifier(verifier) {
|
|
129
|
+
const key = this.codeVerifierKey(this.clientId);
|
|
130
|
+
if (await this.storage.get(key)) return;
|
|
131
|
+
await this.storage.put(key, verifier);
|
|
132
|
+
}
|
|
133
|
+
async codeVerifier() {
|
|
134
|
+
const codeVerifier = await this.storage.get(this.codeVerifierKey(this.clientId));
|
|
135
|
+
if (!codeVerifier) throw new Error("No code verifier found");
|
|
136
|
+
return codeVerifier;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region ../agents/dist/client-BZq9qau2.js
|
|
17
142
|
function toDisposable(fn) {
|
|
18
143
|
return { dispose: fn };
|
|
19
144
|
}
|
|
@@ -61,73 +186,21 @@ function isTransportNotImplemented(error) {
|
|
|
61
186
|
const msg = toErrorMessage(error);
|
|
62
187
|
return msg.includes("404") || msg.includes("405") || msg.includes("Not Implemented") || msg.includes("not implemented");
|
|
63
188
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
delete workerOptions.mode;
|
|
80
|
-
return options.eventSourceInit?.fetch?.(fetchUrl, workerOptions) || fetch(fetchUrl, workerOptions);
|
|
81
|
-
};
|
|
82
|
-
super(url, {
|
|
83
|
-
...options,
|
|
84
|
-
eventSourceInit: {
|
|
85
|
-
...options.eventSourceInit,
|
|
86
|
-
fetch: fetchOverride
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
this.authProvider = options.authProvider;
|
|
90
|
-
}
|
|
91
|
-
async authHeaders() {
|
|
92
|
-
if (this.authProvider) {
|
|
93
|
-
const tokens = await this.authProvider.tokens();
|
|
94
|
-
if (tokens) return { Authorization: `Bearer ${tokens.access_token}` };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
var StreamableHTTPEdgeClientTransport = class extends StreamableHTTPClientTransport {
|
|
99
|
-
/**
|
|
100
|
-
* Creates a new StreamableHTTPEdgeClientTransport, which overrides fetch to be compatible with the CF workers environment
|
|
101
|
-
*/
|
|
102
|
-
constructor(url, options) {
|
|
103
|
-
const fetchOverride = async (fetchUrl, fetchInit = {}) => {
|
|
104
|
-
const headers = await this.authHeaders();
|
|
105
|
-
const workerOptions = {
|
|
106
|
-
...fetchInit,
|
|
107
|
-
headers: {
|
|
108
|
-
...options.requestInit?.headers,
|
|
109
|
-
...fetchInit?.headers,
|
|
110
|
-
...headers
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
delete workerOptions.mode;
|
|
114
|
-
return options.requestInit?.fetch?.(fetchUrl, workerOptions) || fetch(fetchUrl, workerOptions);
|
|
115
|
-
};
|
|
116
|
-
super(url, {
|
|
117
|
-
...options,
|
|
118
|
-
requestInit: {
|
|
119
|
-
...options.requestInit,
|
|
120
|
-
fetch: fetchOverride
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
this.authProvider = options.authProvider;
|
|
124
|
-
}
|
|
125
|
-
async authHeaders() {
|
|
126
|
-
if (this.authProvider) {
|
|
127
|
-
const tokens = await this.authProvider.tokens();
|
|
128
|
-
if (tokens) return { Authorization: `Bearer ${tokens.access_token}` };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
189
|
+
/**
|
|
190
|
+
* Connection state machine for MCP client connections.
|
|
191
|
+
*
|
|
192
|
+
* State transitions:
|
|
193
|
+
* - Non-OAuth: init() → CONNECTING → DISCOVERING → READY
|
|
194
|
+
* - OAuth: init() → AUTHENTICATING → (callback) → CONNECTING → DISCOVERING → READY
|
|
195
|
+
* - Any state can transition to FAILED on error
|
|
196
|
+
*/
|
|
197
|
+
const MCPConnectionState = {
|
|
198
|
+
AUTHENTICATING: "authenticating",
|
|
199
|
+
CONNECTING: "connecting",
|
|
200
|
+
CONNECTED: "connected",
|
|
201
|
+
DISCOVERING: "discovering",
|
|
202
|
+
READY: "ready",
|
|
203
|
+
FAILED: "failed"
|
|
131
204
|
};
|
|
132
205
|
var MCPClientConnection = class {
|
|
133
206
|
constructor(url, info, options = {
|
|
@@ -136,7 +209,7 @@ var MCPClientConnection = class {
|
|
|
136
209
|
}) {
|
|
137
210
|
this.url = url;
|
|
138
211
|
this.options = options;
|
|
139
|
-
this.connectionState =
|
|
212
|
+
this.connectionState = MCPConnectionState.CONNECTING;
|
|
140
213
|
this.tools = [];
|
|
141
214
|
this.prompts = [];
|
|
142
215
|
this.resources = [];
|
|
@@ -152,36 +225,49 @@ var MCPClientConnection = class {
|
|
|
152
225
|
});
|
|
153
226
|
}
|
|
154
227
|
/**
|
|
155
|
-
* Initialize a client connection
|
|
228
|
+
* Initialize a client connection, if authentication is required, the connection will be in the AUTHENTICATING state
|
|
229
|
+
* Sets connection state based on the result and emits observability events
|
|
156
230
|
*
|
|
157
|
-
* @returns
|
|
231
|
+
* @returns Error message if connection failed, undefined otherwise
|
|
158
232
|
*/
|
|
159
233
|
async init() {
|
|
160
234
|
const transportType = this.options.transport.type;
|
|
161
235
|
if (!transportType) throw new Error("Transport type must be specified");
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
this.
|
|
167
|
-
|
|
168
|
-
|
|
236
|
+
const res = await this.tryConnect(transportType);
|
|
237
|
+
this.connectionState = res.state;
|
|
238
|
+
if (res.state === MCPConnectionState.CONNECTED && res.transport) {
|
|
239
|
+
this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
240
|
+
return await this.handleElicitationRequest(request);
|
|
241
|
+
});
|
|
242
|
+
this.lastConnectedTransport = res.transport;
|
|
169
243
|
this._onObservabilityEvent.fire({
|
|
170
244
|
type: "mcp:client:connect",
|
|
171
|
-
displayMessage: `
|
|
245
|
+
displayMessage: `Connected successfully using ${res.transport} transport for ${this.url.toString()}`,
|
|
246
|
+
payload: {
|
|
247
|
+
url: this.url.toString(),
|
|
248
|
+
transport: res.transport,
|
|
249
|
+
state: this.connectionState
|
|
250
|
+
},
|
|
251
|
+
timestamp: Date.now(),
|
|
252
|
+
id: nanoid()
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
} else if (res.state === MCPConnectionState.FAILED && res.error) {
|
|
256
|
+
const errorMessage = toErrorMessage(res.error);
|
|
257
|
+
this._onObservabilityEvent.fire({
|
|
258
|
+
type: "mcp:client:connect",
|
|
259
|
+
displayMessage: `Failed to connect to ${this.url.toString()}: ${errorMessage}`,
|
|
172
260
|
payload: {
|
|
173
261
|
url: this.url.toString(),
|
|
174
262
|
transport: transportType,
|
|
175
263
|
state: this.connectionState,
|
|
176
|
-
error:
|
|
264
|
+
error: errorMessage
|
|
177
265
|
},
|
|
178
266
|
timestamp: Date.now(),
|
|
179
267
|
id: nanoid()
|
|
180
268
|
});
|
|
181
|
-
|
|
182
|
-
return;
|
|
269
|
+
return errorMessage;
|
|
183
270
|
}
|
|
184
|
-
await this.discoverAndRegister();
|
|
185
271
|
}
|
|
186
272
|
/**
|
|
187
273
|
* Finish OAuth by probing transports based on configured type.
|
|
@@ -213,113 +299,189 @@ var MCPClientConnection = class {
|
|
|
213
299
|
* Complete OAuth authorization
|
|
214
300
|
*/
|
|
215
301
|
async completeAuthorization(code) {
|
|
216
|
-
if (this.connectionState !==
|
|
302
|
+
if (this.connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error("Connection must be in authenticating state to complete authorization");
|
|
217
303
|
try {
|
|
218
304
|
await this.finishAuthProbe(code);
|
|
219
|
-
this.connectionState =
|
|
305
|
+
this.connectionState = MCPConnectionState.CONNECTING;
|
|
220
306
|
} catch (error) {
|
|
221
|
-
this.connectionState =
|
|
307
|
+
this.connectionState = MCPConnectionState.FAILED;
|
|
222
308
|
throw error;
|
|
223
309
|
}
|
|
224
310
|
}
|
|
225
311
|
/**
|
|
226
|
-
*
|
|
312
|
+
* Discover server capabilities and register tools, resources, prompts, and templates.
|
|
313
|
+
* This method does the work but does not manage connection state - that's handled by discover().
|
|
227
314
|
*/
|
|
228
|
-
async
|
|
229
|
-
|
|
315
|
+
async discoverAndRegister() {
|
|
316
|
+
this.serverCapabilities = this.client.getServerCapabilities();
|
|
317
|
+
if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
|
|
318
|
+
const operations = [];
|
|
319
|
+
const operationNames = [];
|
|
320
|
+
operations.push(Promise.resolve(this.client.getInstructions()));
|
|
321
|
+
operationNames.push("instructions");
|
|
322
|
+
if (this.serverCapabilities.tools) {
|
|
323
|
+
operations.push(this.registerTools());
|
|
324
|
+
operationNames.push("tools");
|
|
325
|
+
}
|
|
326
|
+
if (this.serverCapabilities.resources) {
|
|
327
|
+
operations.push(this.registerResources());
|
|
328
|
+
operationNames.push("resources");
|
|
329
|
+
}
|
|
330
|
+
if (this.serverCapabilities.prompts) {
|
|
331
|
+
operations.push(this.registerPrompts());
|
|
332
|
+
operationNames.push("prompts");
|
|
333
|
+
}
|
|
334
|
+
if (this.serverCapabilities.resources) {
|
|
335
|
+
operations.push(this.registerResourceTemplates());
|
|
336
|
+
operationNames.push("resource templates");
|
|
337
|
+
}
|
|
230
338
|
try {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
339
|
+
const results = await Promise.all(operations);
|
|
340
|
+
for (let i = 0; i < results.length; i++) {
|
|
341
|
+
const result = results[i];
|
|
342
|
+
switch (operationNames[i]) {
|
|
343
|
+
case "instructions":
|
|
344
|
+
this.instructions = result;
|
|
345
|
+
break;
|
|
346
|
+
case "tools":
|
|
347
|
+
this.tools = result;
|
|
348
|
+
break;
|
|
349
|
+
case "resources":
|
|
350
|
+
this.resources = result;
|
|
351
|
+
break;
|
|
352
|
+
case "prompts":
|
|
353
|
+
this.prompts = result;
|
|
354
|
+
break;
|
|
355
|
+
case "resource templates":
|
|
356
|
+
this.resourceTemplates = result;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
235
360
|
} catch (error) {
|
|
236
|
-
this.
|
|
361
|
+
this._onObservabilityEvent.fire({
|
|
362
|
+
type: "mcp:client:discover",
|
|
363
|
+
displayMessage: `Failed to discover capabilities for ${this.url.toString()}: ${toErrorMessage(error)}`,
|
|
364
|
+
payload: {
|
|
365
|
+
url: this.url.toString(),
|
|
366
|
+
error: toErrorMessage(error)
|
|
367
|
+
},
|
|
368
|
+
timestamp: Date.now(),
|
|
369
|
+
id: nanoid()
|
|
370
|
+
});
|
|
237
371
|
throw error;
|
|
238
372
|
}
|
|
239
373
|
}
|
|
240
374
|
/**
|
|
241
|
-
* Discover server capabilities
|
|
375
|
+
* Discover server capabilities with timeout and cancellation support.
|
|
376
|
+
* If called while a previous discovery is in-flight, the previous discovery will be aborted.
|
|
377
|
+
*
|
|
378
|
+
* @param options Optional configuration
|
|
379
|
+
* @param options.timeoutMs Timeout in milliseconds (default: 15000)
|
|
380
|
+
* @returns Result indicating success/failure with optional error message
|
|
242
381
|
*/
|
|
243
|
-
async
|
|
244
|
-
|
|
245
|
-
this.
|
|
246
|
-
if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
|
|
247
|
-
const [instructionsResult, toolsResult, resourcesResult, promptsResult, resourceTemplatesResult] = await Promise.allSettled([
|
|
248
|
-
this.client.getInstructions(),
|
|
249
|
-
this.registerTools(),
|
|
250
|
-
this.registerResources(),
|
|
251
|
-
this.registerPrompts(),
|
|
252
|
-
this.registerResourceTemplates()
|
|
253
|
-
]);
|
|
254
|
-
const operations = [
|
|
255
|
-
{
|
|
256
|
-
name: "instructions",
|
|
257
|
-
result: instructionsResult
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
name: "tools",
|
|
261
|
-
result: toolsResult
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
name: "resources",
|
|
265
|
-
result: resourcesResult
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
name: "prompts",
|
|
269
|
-
result: promptsResult
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
name: "resource templates",
|
|
273
|
-
result: resourceTemplatesResult
|
|
274
|
-
}
|
|
275
|
-
];
|
|
276
|
-
for (const { name, result } of operations) if (result.status === "rejected") {
|
|
277
|
-
const url = this.url.toString();
|
|
382
|
+
async discover(options = {}) {
|
|
383
|
+
const { timeoutMs = 15e3 } = options;
|
|
384
|
+
if (this.connectionState !== MCPConnectionState.CONNECTED && this.connectionState !== MCPConnectionState.READY) {
|
|
278
385
|
this._onObservabilityEvent.fire({
|
|
279
386
|
type: "mcp:client:discover",
|
|
280
|
-
displayMessage: `
|
|
387
|
+
displayMessage: `Discovery skipped for ${this.url.toString()}, state is ${this.connectionState}`,
|
|
281
388
|
payload: {
|
|
282
|
-
url,
|
|
283
|
-
|
|
284
|
-
error: result.reason
|
|
389
|
+
url: this.url.toString(),
|
|
390
|
+
state: this.connectionState
|
|
285
391
|
},
|
|
286
392
|
timestamp: Date.now(),
|
|
287
393
|
id: nanoid()
|
|
288
394
|
});
|
|
395
|
+
return {
|
|
396
|
+
success: false,
|
|
397
|
+
error: `Discovery skipped - connection in ${this.connectionState} state`
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (this._discoveryAbortController) {
|
|
401
|
+
this._discoveryAbortController.abort();
|
|
402
|
+
this._discoveryAbortController = void 0;
|
|
403
|
+
}
|
|
404
|
+
const abortController = new AbortController();
|
|
405
|
+
this._discoveryAbortController = abortController;
|
|
406
|
+
this.connectionState = MCPConnectionState.DISCOVERING;
|
|
407
|
+
let timeoutId;
|
|
408
|
+
try {
|
|
409
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
410
|
+
timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error(`Discovery timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
411
|
+
});
|
|
412
|
+
if (abortController.signal.aborted) throw new Error("Discovery was cancelled");
|
|
413
|
+
const abortPromise = new Promise((_, reject) => {
|
|
414
|
+
abortController.signal.addEventListener("abort", () => {
|
|
415
|
+
reject(/* @__PURE__ */ new Error("Discovery was cancelled"));
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
await Promise.race([
|
|
419
|
+
this.discoverAndRegister(),
|
|
420
|
+
timeoutPromise,
|
|
421
|
+
abortPromise
|
|
422
|
+
]);
|
|
423
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
424
|
+
this.connectionState = MCPConnectionState.READY;
|
|
425
|
+
this._onObservabilityEvent.fire({
|
|
426
|
+
type: "mcp:client:discover",
|
|
427
|
+
displayMessage: `Discovery completed for ${this.url.toString()}`,
|
|
428
|
+
payload: { url: this.url.toString() },
|
|
429
|
+
timestamp: Date.now(),
|
|
430
|
+
id: nanoid()
|
|
431
|
+
});
|
|
432
|
+
return { success: true };
|
|
433
|
+
} catch (e) {
|
|
434
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
435
|
+
this.connectionState = MCPConnectionState.CONNECTED;
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
error: e instanceof Error ? e.message : String(e)
|
|
439
|
+
};
|
|
440
|
+
} finally {
|
|
441
|
+
this._discoveryAbortController = void 0;
|
|
289
442
|
}
|
|
290
|
-
this.instructions = instructionsResult.status === "fulfilled" ? instructionsResult.value : void 0;
|
|
291
|
-
this.tools = toolsResult.status === "fulfilled" ? toolsResult.value : [];
|
|
292
|
-
this.resources = resourcesResult.status === "fulfilled" ? resourcesResult.value : [];
|
|
293
|
-
this.prompts = promptsResult.status === "fulfilled" ? promptsResult.value : [];
|
|
294
|
-
this.resourceTemplates = resourceTemplatesResult.status === "fulfilled" ? resourceTemplatesResult.value : [];
|
|
295
|
-
this.connectionState = "ready";
|
|
296
443
|
}
|
|
297
444
|
/**
|
|
298
|
-
*
|
|
445
|
+
* Cancel any in-flight discovery operation.
|
|
446
|
+
* Called when closing the connection.
|
|
447
|
+
*/
|
|
448
|
+
cancelDiscovery() {
|
|
449
|
+
if (this._discoveryAbortController) {
|
|
450
|
+
this._discoveryAbortController.abort();
|
|
451
|
+
this._discoveryAbortController = void 0;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Notification handler registration for tools
|
|
456
|
+
* Should only be called if serverCapabilities.tools exists
|
|
299
457
|
*/
|
|
300
458
|
async registerTools() {
|
|
301
|
-
if (
|
|
302
|
-
if (this.serverCapabilities.tools.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
|
|
459
|
+
if (this.serverCapabilities?.tools?.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
|
|
303
460
|
this.tools = await this.fetchTools();
|
|
304
461
|
});
|
|
305
462
|
return this.fetchTools();
|
|
306
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Notification handler registration for resources
|
|
466
|
+
* Should only be called if serverCapabilities.resources exists
|
|
467
|
+
*/
|
|
307
468
|
async registerResources() {
|
|
308
|
-
if (
|
|
309
|
-
if (this.serverCapabilities.resources.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
|
|
469
|
+
if (this.serverCapabilities?.resources?.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
|
|
310
470
|
this.resources = await this.fetchResources();
|
|
311
471
|
});
|
|
312
472
|
return this.fetchResources();
|
|
313
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Notification handler registration for prompts
|
|
476
|
+
* Should only be called if serverCapabilities.prompts exists
|
|
477
|
+
*/
|
|
314
478
|
async registerPrompts() {
|
|
315
|
-
if (
|
|
316
|
-
if (this.serverCapabilities.prompts.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
|
|
479
|
+
if (this.serverCapabilities?.prompts?.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
|
|
317
480
|
this.prompts = await this.fetchPrompts();
|
|
318
481
|
});
|
|
319
482
|
return this.fetchPrompts();
|
|
320
483
|
}
|
|
321
484
|
async registerResourceTemplates() {
|
|
322
|
-
if (!this.serverCapabilities || !this.serverCapabilities.resources) return [];
|
|
323
485
|
return this.fetchResourceTemplates();
|
|
324
486
|
}
|
|
325
487
|
async fetchTools() {
|
|
@@ -372,8 +534,8 @@ var MCPClientConnection = class {
|
|
|
372
534
|
*/
|
|
373
535
|
getTransport(transportType) {
|
|
374
536
|
switch (transportType) {
|
|
375
|
-
case "streamable-http": return new
|
|
376
|
-
case "sse": return new
|
|
537
|
+
case "streamable-http": return new StreamableHTTPClientTransport(this.url, this.options.transport);
|
|
538
|
+
case "sse": return new SSEClientTransport(this.url, this.options.transport);
|
|
377
539
|
default: throw new Error(`Unsupported transport type: ${transportType}`);
|
|
378
540
|
}
|
|
379
541
|
}
|
|
@@ -385,44 +547,24 @@ var MCPClientConnection = class {
|
|
|
385
547
|
const transport = this.getTransport(currentTransportType);
|
|
386
548
|
try {
|
|
387
549
|
await this.client.connect(transport);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
displayMessage: `Connected successfully using ${currentTransportType} transport for ${url}`,
|
|
393
|
-
payload: {
|
|
394
|
-
url,
|
|
395
|
-
transport: currentTransportType,
|
|
396
|
-
state: this.connectionState
|
|
397
|
-
},
|
|
398
|
-
timestamp: Date.now(),
|
|
399
|
-
id: nanoid()
|
|
400
|
-
});
|
|
401
|
-
break;
|
|
550
|
+
return {
|
|
551
|
+
state: MCPConnectionState.CONNECTED,
|
|
552
|
+
transport: currentTransportType
|
|
553
|
+
};
|
|
402
554
|
} catch (e) {
|
|
403
555
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
404
|
-
if (isUnauthorized(error))
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
payload: {
|
|
411
|
-
url,
|
|
412
|
-
transport: currentTransportType,
|
|
413
|
-
state: this.connectionState
|
|
414
|
-
},
|
|
415
|
-
timestamp: Date.now(),
|
|
416
|
-
id: nanoid()
|
|
417
|
-
});
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
throw e;
|
|
556
|
+
if (isUnauthorized(error)) return { state: MCPConnectionState.AUTHENTICATING };
|
|
557
|
+
if (isTransportNotImplemented(error) && hasFallback) continue;
|
|
558
|
+
return {
|
|
559
|
+
state: MCPConnectionState.FAILED,
|
|
560
|
+
error
|
|
561
|
+
};
|
|
421
562
|
}
|
|
422
563
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
564
|
+
return {
|
|
565
|
+
state: MCPConnectionState.FAILED,
|
|
566
|
+
error: /* @__PURE__ */ new Error("No transports available")
|
|
567
|
+
};
|
|
426
568
|
}
|
|
427
569
|
_capabilityErrorHandler(empty, method) {
|
|
428
570
|
return (e) => {
|
|
@@ -445,14 +587,7 @@ var MCPClientConnection = class {
|
|
|
445
587
|
};
|
|
446
588
|
}
|
|
447
589
|
};
|
|
448
|
-
|
|
449
|
-
function getJsonSchema() {
|
|
450
|
-
if (!jsonSchemaFn) {
|
|
451
|
-
const { jsonSchema } = __require("ai");
|
|
452
|
-
jsonSchemaFn = jsonSchema;
|
|
453
|
-
}
|
|
454
|
-
return jsonSchemaFn;
|
|
455
|
-
}
|
|
590
|
+
const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
|
|
456
591
|
/**
|
|
457
592
|
* Utility class that aggregates multiple MCP clients into one
|
|
458
593
|
*/
|
|
@@ -460,28 +595,131 @@ var MCPClientManager = class {
|
|
|
460
595
|
/**
|
|
461
596
|
* @param _name Name of the MCP client
|
|
462
597
|
* @param _version Version of the MCP Client
|
|
463
|
-
* @param
|
|
598
|
+
* @param options Storage adapter for persisting MCP server state
|
|
464
599
|
*/
|
|
465
|
-
constructor(_name, _version) {
|
|
600
|
+
constructor(_name, _version, options) {
|
|
466
601
|
this._name = _name;
|
|
467
602
|
this._version = _version;
|
|
468
603
|
this.mcpConnections = {};
|
|
469
|
-
this._callbackUrls = [];
|
|
470
604
|
this._didWarnAboutUnstableGetAITools = false;
|
|
471
605
|
this._connectionDisposables = /* @__PURE__ */ new Map();
|
|
606
|
+
this._isRestored = false;
|
|
472
607
|
this._onObservabilityEvent = new Emitter();
|
|
473
608
|
this.onObservabilityEvent = this._onObservabilityEvent.event;
|
|
474
|
-
this.
|
|
475
|
-
this.
|
|
609
|
+
this._onServerStateChanged = new Emitter();
|
|
610
|
+
this.onServerStateChanged = this._onServerStateChanged.event;
|
|
611
|
+
if (!options.storage) throw new Error("MCPClientManager requires a valid DurableObjectStorage instance");
|
|
612
|
+
this._storage = options.storage;
|
|
613
|
+
}
|
|
614
|
+
sql(query, ...bindings) {
|
|
615
|
+
return [...this._storage.sql.exec(query, ...bindings)];
|
|
616
|
+
}
|
|
617
|
+
saveServerToStorage(server) {
|
|
618
|
+
this.sql(`INSERT OR REPLACE INTO cf_agents_mcp_servers (
|
|
619
|
+
id, name, server_url, client_id, auth_url, callback_url, server_options
|
|
620
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)`, server.id, server.name, server.server_url, server.client_id ?? null, server.auth_url ?? null, server.callback_url, server.server_options ?? null);
|
|
621
|
+
}
|
|
622
|
+
removeServerFromStorage(serverId) {
|
|
623
|
+
this.sql("DELETE FROM cf_agents_mcp_servers WHERE id = ?", serverId);
|
|
624
|
+
}
|
|
625
|
+
getServersFromStorage() {
|
|
626
|
+
return this.sql("SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers");
|
|
627
|
+
}
|
|
628
|
+
clearServerAuthUrl(serverId) {
|
|
629
|
+
this.sql("UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?", serverId);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Create an auth provider for a server
|
|
633
|
+
* @internal
|
|
634
|
+
*/
|
|
635
|
+
createAuthProvider(serverId, callbackUrl, clientName, clientId) {
|
|
636
|
+
if (!this._storage) throw new Error("Cannot create auth provider: storage is not initialized");
|
|
637
|
+
const authProvider = new DurableObjectOAuthClientProvider(this._storage, clientName, callbackUrl);
|
|
638
|
+
authProvider.serverId = serverId;
|
|
639
|
+
if (clientId) authProvider.clientId = clientId;
|
|
640
|
+
return authProvider;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Restore MCP server connections from storage
|
|
644
|
+
* This method is called on Agent initialization to restore previously connected servers
|
|
645
|
+
*
|
|
646
|
+
* @param clientName Name to use for OAuth client (typically the agent instance name)
|
|
647
|
+
*/
|
|
648
|
+
async restoreConnectionsFromStorage(clientName) {
|
|
649
|
+
if (this._isRestored) return;
|
|
650
|
+
const servers = this.getServersFromStorage();
|
|
651
|
+
if (!servers || servers.length === 0) {
|
|
652
|
+
this._isRestored = true;
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
for (const server of servers) {
|
|
656
|
+
const existingConn = this.mcpConnections[server.id];
|
|
657
|
+
if (existingConn) {
|
|
658
|
+
if (existingConn.connectionState === MCPConnectionState.READY) {
|
|
659
|
+
console.warn(`[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (existingConn.connectionState === MCPConnectionState.AUTHENTICATING || existingConn.connectionState === MCPConnectionState.CONNECTING || existingConn.connectionState === MCPConnectionState.DISCOVERING) continue;
|
|
663
|
+
if (existingConn.connectionState === MCPConnectionState.FAILED) {
|
|
664
|
+
try {
|
|
665
|
+
await existingConn.client.close();
|
|
666
|
+
} catch (error) {
|
|
667
|
+
console.warn(`[MCPClientManager] Error closing failed connection ${server.id}:`, error);
|
|
668
|
+
}
|
|
669
|
+
delete this.mcpConnections[server.id];
|
|
670
|
+
this._connectionDisposables.get(server.id)?.dispose();
|
|
671
|
+
this._connectionDisposables.delete(server.id);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const parsedOptions = server.server_options ? JSON.parse(server.server_options) : null;
|
|
675
|
+
const authProvider = this.createAuthProvider(server.id, server.callback_url, clientName, server.client_id ?? void 0);
|
|
676
|
+
const conn = this.createConnection(server.id, server.server_url, {
|
|
677
|
+
client: parsedOptions?.client ?? {},
|
|
678
|
+
transport: {
|
|
679
|
+
...parsedOptions?.transport ?? {},
|
|
680
|
+
type: parsedOptions?.transport?.type ?? "auto",
|
|
681
|
+
authProvider
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
if (server.auth_url) {
|
|
685
|
+
conn.connectionState = MCPConnectionState.AUTHENTICATING;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
this._restoreServer(server.id);
|
|
689
|
+
}
|
|
690
|
+
this._isRestored = true;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Internal method to restore a single server connection and discovery
|
|
694
|
+
*/
|
|
695
|
+
async _restoreServer(serverId) {
|
|
696
|
+
if ((await this.connectToServer(serverId).catch((error) => {
|
|
697
|
+
console.error(`Error connecting to ${serverId}:`, error);
|
|
698
|
+
return null;
|
|
699
|
+
}))?.state === MCPConnectionState.CONNECTED) {
|
|
700
|
+
const discoverResult = await this.discoverIfConnected(serverId);
|
|
701
|
+
if (discoverResult && !discoverResult.success) console.error(`Error discovering ${serverId}:`, discoverResult.error);
|
|
702
|
+
}
|
|
476
703
|
}
|
|
477
704
|
/**
|
|
478
705
|
* Connect to and register an MCP server
|
|
479
706
|
*
|
|
480
|
-
* @
|
|
481
|
-
*
|
|
482
|
-
*
|
|
707
|
+
* @deprecated This method is maintained for backward compatibility.
|
|
708
|
+
* For new code, use registerServer() and connectToServer() separately.
|
|
709
|
+
*
|
|
710
|
+
* @param url Server URL
|
|
711
|
+
* @param options Connection options
|
|
712
|
+
* @returns Object with server ID, auth URL (if OAuth), and client ID (if OAuth)
|
|
483
713
|
*/
|
|
484
714
|
async connect(url, options = {}) {
|
|
715
|
+
/**
|
|
716
|
+
* We need to delay loading ai sdk, because putting it in module scope is
|
|
717
|
+
* causing issues with startup time.
|
|
718
|
+
* The only place it's used is in getAITools, which only matters after
|
|
719
|
+
* .connect() is called on at least one server.
|
|
720
|
+
* So it's safe to delay loading it until .connect() is called.
|
|
721
|
+
*/
|
|
722
|
+
await this.ensureJsonSchema();
|
|
485
723
|
const id = options.reconnect?.id ?? nanoid(8);
|
|
486
724
|
if (options.transport?.authProvider) {
|
|
487
725
|
options.transport.authProvider.serverId = id;
|
|
@@ -510,7 +748,7 @@ var MCPClientManager = class {
|
|
|
510
748
|
await this.mcpConnections[id].init();
|
|
511
749
|
if (options.reconnect?.oauthCode) try {
|
|
512
750
|
await this.mcpConnections[id].completeAuthorization(options.reconnect.oauthCode);
|
|
513
|
-
await this.mcpConnections[id].
|
|
751
|
+
await this.mcpConnections[id].init();
|
|
514
752
|
} catch (error) {
|
|
515
753
|
this._onObservabilityEvent.fire({
|
|
516
754
|
type: "mcp:client:connect",
|
|
@@ -527,35 +765,160 @@ var MCPClientManager = class {
|
|
|
527
765
|
throw error;
|
|
528
766
|
}
|
|
529
767
|
const authUrl = options.transport?.authProvider?.authUrl;
|
|
530
|
-
if (this.mcpConnections[id].connectionState ===
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
768
|
+
if (this.mcpConnections[id].connectionState === MCPConnectionState.AUTHENTICATING && authUrl && options.transport?.authProvider?.redirectUrl) return {
|
|
769
|
+
authUrl,
|
|
770
|
+
clientId: options.transport?.authProvider?.clientId,
|
|
771
|
+
id
|
|
772
|
+
};
|
|
773
|
+
const discoverResult = await this.discoverIfConnected(id);
|
|
774
|
+
if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover server capabilities: ${discoverResult.error}`);
|
|
775
|
+
return { id };
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Create an in-memory connection object and set up observability
|
|
779
|
+
* Does NOT save to storage - use registerServer() for that
|
|
780
|
+
* @returns The connection object (existing or newly created)
|
|
781
|
+
*/
|
|
782
|
+
createConnection(id, url, options) {
|
|
783
|
+
if (this.mcpConnections[id]) return this.mcpConnections[id];
|
|
784
|
+
const normalizedTransport = {
|
|
785
|
+
...options.transport,
|
|
786
|
+
type: options.transport?.type ?? "auto"
|
|
787
|
+
};
|
|
788
|
+
this.mcpConnections[id] = new MCPClientConnection(new URL(url), {
|
|
789
|
+
name: this._name,
|
|
790
|
+
version: this._version
|
|
791
|
+
}, {
|
|
792
|
+
client: {
|
|
793
|
+
...defaultClientOptions,
|
|
794
|
+
...options.client
|
|
795
|
+
},
|
|
796
|
+
transport: normalizedTransport
|
|
797
|
+
});
|
|
798
|
+
const store = new DisposableStore();
|
|
799
|
+
const existing = this._connectionDisposables.get(id);
|
|
800
|
+
if (existing) existing.dispose();
|
|
801
|
+
this._connectionDisposables.set(id, store);
|
|
802
|
+
store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
|
|
803
|
+
this._onObservabilityEvent.fire(event);
|
|
804
|
+
}));
|
|
805
|
+
return this.mcpConnections[id];
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Register an MCP server connection without connecting
|
|
809
|
+
* Creates the connection object, sets up observability, and saves to storage
|
|
810
|
+
*
|
|
811
|
+
* @param id Server ID
|
|
812
|
+
* @param options Registration options including URL, name, callback URL, and connection config
|
|
813
|
+
* @returns Server ID
|
|
814
|
+
*/
|
|
815
|
+
async registerServer(id, options) {
|
|
816
|
+
this.createConnection(id, options.url, {
|
|
817
|
+
client: options.client,
|
|
818
|
+
transport: {
|
|
819
|
+
...options.transport,
|
|
820
|
+
type: options.transport?.type ?? "auto"
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
const { authProvider: _, ...transportWithoutAuth } = options.transport ?? {};
|
|
824
|
+
this.saveServerToStorage({
|
|
825
|
+
id,
|
|
826
|
+
name: options.name,
|
|
827
|
+
server_url: options.url,
|
|
828
|
+
callback_url: options.callbackUrl,
|
|
829
|
+
client_id: options.clientId ?? null,
|
|
830
|
+
auth_url: options.authUrl ?? null,
|
|
831
|
+
server_options: JSON.stringify({
|
|
832
|
+
client: options.client,
|
|
833
|
+
transport: transportWithoutAuth
|
|
834
|
+
})
|
|
835
|
+
});
|
|
836
|
+
this._onServerStateChanged.fire();
|
|
837
|
+
return id;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Connect to an already registered MCP server and initialize the connection.
|
|
841
|
+
*
|
|
842
|
+
* For OAuth servers, returns `{ state: "authenticating", authUrl, clientId? }`.
|
|
843
|
+
* The user must complete the OAuth flow via the authUrl, which triggers a
|
|
844
|
+
* callback handled by `handleCallbackRequest()`.
|
|
845
|
+
*
|
|
846
|
+
* For non-OAuth servers, establishes the transport connection and returns
|
|
847
|
+
* `{ state: "connected" }`. Call `discoverIfConnected()` afterwards to
|
|
848
|
+
* discover capabilities and transition to "ready" state.
|
|
849
|
+
*
|
|
850
|
+
* @param id Server ID (must be registered first via registerServer())
|
|
851
|
+
* @returns Connection result with current state and OAuth info (if applicable)
|
|
852
|
+
*/
|
|
853
|
+
async connectToServer(id) {
|
|
854
|
+
const conn = this.mcpConnections[id];
|
|
855
|
+
if (!conn) throw new Error(`Server ${id} is not registered. Call registerServer() first.`);
|
|
856
|
+
const error = await conn.init();
|
|
857
|
+
this._onServerStateChanged.fire();
|
|
858
|
+
switch (conn.connectionState) {
|
|
859
|
+
case MCPConnectionState.FAILED: return {
|
|
860
|
+
state: conn.connectionState,
|
|
861
|
+
error: error ?? "Unknown connection error"
|
|
862
|
+
};
|
|
863
|
+
case MCPConnectionState.AUTHENTICATING: {
|
|
864
|
+
const authUrl = conn.options.transport.authProvider?.authUrl;
|
|
865
|
+
const redirectUrl = conn.options.transport.authProvider?.redirectUrl;
|
|
866
|
+
if (!authUrl || !redirectUrl) return {
|
|
867
|
+
state: MCPConnectionState.FAILED,
|
|
868
|
+
error: `OAuth configuration incomplete: missing ${!authUrl ? "authUrl" : "redirectUrl"}`
|
|
869
|
+
};
|
|
870
|
+
const clientId = conn.options.transport.authProvider?.clientId;
|
|
871
|
+
const serverRow = this.getServersFromStorage().find((s) => s.id === id);
|
|
872
|
+
if (serverRow) this.saveServerToStorage({
|
|
873
|
+
...serverRow,
|
|
874
|
+
auth_url: authUrl,
|
|
875
|
+
client_id: clientId ?? null
|
|
876
|
+
});
|
|
877
|
+
return {
|
|
878
|
+
state: conn.connectionState,
|
|
879
|
+
authUrl,
|
|
880
|
+
clientId
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
case MCPConnectionState.CONNECTED: return { state: conn.connectionState };
|
|
884
|
+
default: return {
|
|
885
|
+
state: MCPConnectionState.FAILED,
|
|
886
|
+
error: `Unexpected connection state after init: ${conn.connectionState}`
|
|
536
887
|
};
|
|
537
888
|
}
|
|
538
|
-
return { id };
|
|
539
889
|
}
|
|
540
890
|
isCallbackRequest(req) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
891
|
+
if (req.method !== "GET") return false;
|
|
892
|
+
if (!req.url.includes("/callback")) return false;
|
|
893
|
+
return this.getServersFromStorage().some((server) => server.callback_url && req.url.startsWith(server.callback_url));
|
|
544
894
|
}
|
|
545
895
|
async handleCallbackRequest(req) {
|
|
546
896
|
const url = new URL(req.url);
|
|
547
|
-
const
|
|
548
|
-
return req.url.startsWith(
|
|
897
|
+
const matchingServer = this.getServersFromStorage().find((server) => {
|
|
898
|
+
return server.callback_url && req.url.startsWith(server.callback_url);
|
|
549
899
|
});
|
|
550
|
-
if (!
|
|
900
|
+
if (!matchingServer) throw new Error(`No callback URI match found for the request url: ${req.url}. Was the request matched with \`isCallbackRequest()\`?`);
|
|
901
|
+
const serverId = matchingServer.id;
|
|
551
902
|
const code = url.searchParams.get("code");
|
|
552
903
|
const state = url.searchParams.get("state");
|
|
553
|
-
const
|
|
554
|
-
const
|
|
904
|
+
const error = url.searchParams.get("error");
|
|
905
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
906
|
+
if (error) return {
|
|
907
|
+
serverId,
|
|
908
|
+
authSuccess: false,
|
|
909
|
+
authError: errorDescription || error
|
|
910
|
+
};
|
|
555
911
|
if (!code) throw new Error("Unauthorized: no code provided");
|
|
556
912
|
if (!state) throw new Error("Unauthorized: no state provided");
|
|
557
913
|
if (this.mcpConnections[serverId] === void 0) throw new Error(`Could not find serverId: ${serverId}`);
|
|
558
|
-
if (this.mcpConnections[serverId].connectionState
|
|
914
|
+
if (this.mcpConnections[serverId].connectionState === MCPConnectionState.READY || this.mcpConnections[serverId].connectionState === MCPConnectionState.CONNECTED) {
|
|
915
|
+
this.clearServerAuthUrl(serverId);
|
|
916
|
+
return {
|
|
917
|
+
serverId,
|
|
918
|
+
authSuccess: true
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
if (this.mcpConnections[serverId].connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error(`Failed to authenticate: the client is in "${this.mcpConnections[serverId].connectionState}" state, expected "authenticating"`);
|
|
559
922
|
const conn = this.mcpConnections[serverId];
|
|
560
923
|
if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
|
|
561
924
|
const clientId = conn.options.transport.authProvider.clientId || state;
|
|
@@ -563,21 +926,57 @@ var MCPClientManager = class {
|
|
|
563
926
|
conn.options.transport.authProvider.serverId = serverId;
|
|
564
927
|
try {
|
|
565
928
|
await conn.completeAuthorization(code);
|
|
929
|
+
this.clearServerAuthUrl(serverId);
|
|
930
|
+
this._onServerStateChanged.fire();
|
|
566
931
|
return {
|
|
567
932
|
serverId,
|
|
568
933
|
authSuccess: true
|
|
569
934
|
};
|
|
570
|
-
} catch (error) {
|
|
935
|
+
} catch (error$1) {
|
|
936
|
+
const errorMessage = error$1 instanceof Error ? error$1.message : String(error$1);
|
|
937
|
+
this._onServerStateChanged.fire();
|
|
571
938
|
return {
|
|
572
939
|
serverId,
|
|
573
940
|
authSuccess: false,
|
|
574
|
-
authError:
|
|
941
|
+
authError: errorMessage
|
|
575
942
|
};
|
|
576
943
|
}
|
|
577
944
|
}
|
|
578
945
|
/**
|
|
946
|
+
* Discover server capabilities if connection is in CONNECTED or READY state.
|
|
947
|
+
* Transitions to DISCOVERING then READY (or CONNECTED on error).
|
|
948
|
+
* Can be called to refresh server capabilities (e.g., from a UI refresh button).
|
|
949
|
+
*
|
|
950
|
+
* If called while a previous discovery is in-flight for the same server,
|
|
951
|
+
* the previous discovery will be aborted.
|
|
952
|
+
*
|
|
953
|
+
* @param serverId The server ID to discover
|
|
954
|
+
* @param options Optional configuration
|
|
955
|
+
* @param options.timeoutMs Timeout in milliseconds (default: 30000)
|
|
956
|
+
* @returns Result with current state and optional error, or undefined if connection not found
|
|
957
|
+
*/
|
|
958
|
+
async discoverIfConnected(serverId, options = {}) {
|
|
959
|
+
const conn = this.mcpConnections[serverId];
|
|
960
|
+
if (!conn) {
|
|
961
|
+
this._onObservabilityEvent.fire({
|
|
962
|
+
type: "mcp:client:discover",
|
|
963
|
+
displayMessage: `Connection not found for ${serverId}`,
|
|
964
|
+
payload: {},
|
|
965
|
+
timestamp: Date.now(),
|
|
966
|
+
id: nanoid()
|
|
967
|
+
});
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const result = await conn.discover(options);
|
|
971
|
+
this._onServerStateChanged.fire();
|
|
972
|
+
return {
|
|
973
|
+
...result,
|
|
974
|
+
state: conn.connectionState
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
579
978
|
* Establish connection in the background after OAuth completion
|
|
580
|
-
* This method
|
|
979
|
+
* This method connects to the server and discovers its capabilities
|
|
581
980
|
* @param serverId The server ID to establish connection for
|
|
582
981
|
*/
|
|
583
982
|
async establishConnection(serverId) {
|
|
@@ -592,38 +991,34 @@ var MCPClientManager = class {
|
|
|
592
991
|
});
|
|
593
992
|
return;
|
|
594
993
|
}
|
|
595
|
-
|
|
596
|
-
await conn.establishConnection();
|
|
597
|
-
this._onConnected.fire(serverId);
|
|
598
|
-
} catch (error) {
|
|
599
|
-
const url = conn.url.toString();
|
|
994
|
+
if (conn.connectionState === MCPConnectionState.DISCOVERING || conn.connectionState === MCPConnectionState.READY) {
|
|
600
995
|
this._onObservabilityEvent.fire({
|
|
601
996
|
type: "mcp:client:connect",
|
|
602
|
-
displayMessage: `
|
|
997
|
+
displayMessage: `establishConnection skipped for ${serverId}, already in ${conn.connectionState} state`,
|
|
603
998
|
payload: {
|
|
604
|
-
url,
|
|
605
|
-
transport: conn.options.transport.type
|
|
606
|
-
state: conn.connectionState
|
|
607
|
-
error: toErrorMessage(error)
|
|
999
|
+
url: conn.url.toString(),
|
|
1000
|
+
transport: conn.options.transport.type || "unknown",
|
|
1001
|
+
state: conn.connectionState
|
|
608
1002
|
},
|
|
609
1003
|
timestamp: Date.now(),
|
|
610
1004
|
id: nanoid()
|
|
611
1005
|
});
|
|
1006
|
+
return;
|
|
612
1007
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1008
|
+
const connectResult = await this.connectToServer(serverId);
|
|
1009
|
+
this._onServerStateChanged.fire();
|
|
1010
|
+
if (connectResult.state === MCPConnectionState.CONNECTED) await this.discoverIfConnected(serverId);
|
|
1011
|
+
this._onObservabilityEvent.fire({
|
|
1012
|
+
type: "mcp:client:connect",
|
|
1013
|
+
displayMessage: `establishConnection completed for ${serverId}, final state: ${conn.connectionState}`,
|
|
1014
|
+
payload: {
|
|
1015
|
+
url: conn.url.toString(),
|
|
1016
|
+
transport: conn.options.transport.type || "unknown",
|
|
1017
|
+
state: conn.connectionState
|
|
1018
|
+
},
|
|
1019
|
+
timestamp: Date.now(),
|
|
1020
|
+
id: nanoid()
|
|
1021
|
+
});
|
|
627
1022
|
}
|
|
628
1023
|
/**
|
|
629
1024
|
* Configure OAuth callback handling
|
|
@@ -646,9 +1041,28 @@ var MCPClientManager = class {
|
|
|
646
1041
|
return getNamespacedData(this.mcpConnections, "tools");
|
|
647
1042
|
}
|
|
648
1043
|
/**
|
|
1044
|
+
* Lazy-loads the jsonSchema function from the AI SDK.
|
|
1045
|
+
*
|
|
1046
|
+
* This defers importing the "ai" package until it's actually needed, which helps reduce
|
|
1047
|
+
* initial bundle size and startup time. The jsonSchema function is required for converting
|
|
1048
|
+
* MCP tools into AI SDK tool definitions via getAITools().
|
|
1049
|
+
*
|
|
1050
|
+
* @internal This method is for internal use only. It's automatically called before operations
|
|
1051
|
+
* that need jsonSchema (like getAITools() or OAuth flows). External consumers should not need
|
|
1052
|
+
* to call this directly.
|
|
1053
|
+
*/
|
|
1054
|
+
async ensureJsonSchema() {
|
|
1055
|
+
if (!this.jsonSchema) {
|
|
1056
|
+
const { jsonSchema } = await import("ai");
|
|
1057
|
+
this.jsonSchema = jsonSchema;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
649
1061
|
* @returns a set of tools that you can use with the AI SDK
|
|
650
1062
|
*/
|
|
651
1063
|
getAITools() {
|
|
1064
|
+
if (!this.jsonSchema) throw new Error("jsonSchema not initialized.");
|
|
1065
|
+
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.`);
|
|
652
1066
|
return Object.fromEntries(getNamespacedData(this.mcpConnections, "tools").map((tool) => {
|
|
653
1067
|
return [`tool_${tool.serverId.replace(/-/g, "")}_${tool.name}`, {
|
|
654
1068
|
description: tool.description,
|
|
@@ -661,8 +1075,8 @@ var MCPClientManager = class {
|
|
|
661
1075
|
if (result.isError) throw new Error(result.content[0].text);
|
|
662
1076
|
return result;
|
|
663
1077
|
},
|
|
664
|
-
inputSchema:
|
|
665
|
-
outputSchema: tool.outputSchema ?
|
|
1078
|
+
inputSchema: this.jsonSchema(tool.inputSchema),
|
|
1079
|
+
outputSchema: tool.outputSchema ? this.jsonSchema(tool.outputSchema) : void 0
|
|
666
1080
|
}];
|
|
667
1081
|
}));
|
|
668
1082
|
}
|
|
@@ -678,10 +1092,18 @@ var MCPClientManager = class {
|
|
|
678
1092
|
return this.getAITools();
|
|
679
1093
|
}
|
|
680
1094
|
/**
|
|
681
|
-
* Closes all connections to MCP servers
|
|
1095
|
+
* Closes all active in-memory connections to MCP servers.
|
|
1096
|
+
*
|
|
1097
|
+
* Note: This only closes the transport connections - it does NOT remove
|
|
1098
|
+
* servers from storage. Servers will still be listed and their callback
|
|
1099
|
+
* URLs will still match incoming OAuth requests.
|
|
1100
|
+
*
|
|
1101
|
+
* Use removeServer() instead if you want to fully clean up a server
|
|
1102
|
+
* (closes connection AND removes from storage).
|
|
682
1103
|
*/
|
|
683
1104
|
async closeAllConnections() {
|
|
684
1105
|
const ids = Object.keys(this.mcpConnections);
|
|
1106
|
+
for (const id of ids) this.mcpConnections[id].cancelDiscovery();
|
|
685
1107
|
await Promise.all(ids.map(async (id) => {
|
|
686
1108
|
await this.mcpConnections[id].client.close();
|
|
687
1109
|
}));
|
|
@@ -698,6 +1120,7 @@ var MCPClientManager = class {
|
|
|
698
1120
|
*/
|
|
699
1121
|
async closeConnection(id) {
|
|
700
1122
|
if (!this.mcpConnections[id]) throw new Error(`Connection with id "${id}" does not exist.`);
|
|
1123
|
+
this.mcpConnections[id].cancelDiscovery();
|
|
701
1124
|
await this.mcpConnections[id].client.close();
|
|
702
1125
|
delete this.mcpConnections[id];
|
|
703
1126
|
const store = this._connectionDisposables.get(id);
|
|
@@ -705,13 +1128,29 @@ var MCPClientManager = class {
|
|
|
705
1128
|
this._connectionDisposables.delete(id);
|
|
706
1129
|
}
|
|
707
1130
|
/**
|
|
1131
|
+
* Remove an MCP server - closes connection if active and removes from storage.
|
|
1132
|
+
*/
|
|
1133
|
+
async removeServer(serverId) {
|
|
1134
|
+
if (this.mcpConnections[serverId]) try {
|
|
1135
|
+
await this.closeConnection(serverId);
|
|
1136
|
+
} catch (e) {}
|
|
1137
|
+
this.removeServerFromStorage(serverId);
|
|
1138
|
+
this._onServerStateChanged.fire();
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* List all MCP servers from storage
|
|
1142
|
+
*/
|
|
1143
|
+
listServers() {
|
|
1144
|
+
return this.getServersFromStorage();
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
708
1147
|
* Dispose the manager and all resources.
|
|
709
1148
|
*/
|
|
710
1149
|
async dispose() {
|
|
711
1150
|
try {
|
|
712
1151
|
await this.closeAllConnections();
|
|
713
1152
|
} finally {
|
|
714
|
-
this.
|
|
1153
|
+
this._onServerStateChanged.dispose();
|
|
715
1154
|
this._onObservabilityEvent.dispose();
|
|
716
1155
|
}
|
|
717
1156
|
}
|
|
@@ -773,128 +1212,7 @@ function getNamespacedData(mcpClients, type) {
|
|
|
773
1212
|
}
|
|
774
1213
|
|
|
775
1214
|
//#endregion
|
|
776
|
-
//#region ../agents/dist/
|
|
777
|
-
/**
|
|
778
|
-
* Enum for message types to improve type safety and maintainability
|
|
779
|
-
*/
|
|
780
|
-
let MessageType = /* @__PURE__ */ function(MessageType$1) {
|
|
781
|
-
MessageType$1["CF_AGENT_CHAT_MESSAGES"] = "cf_agent_chat_messages";
|
|
782
|
-
MessageType$1["CF_AGENT_USE_CHAT_REQUEST"] = "cf_agent_use_chat_request";
|
|
783
|
-
MessageType$1["CF_AGENT_USE_CHAT_RESPONSE"] = "cf_agent_use_chat_response";
|
|
784
|
-
MessageType$1["CF_AGENT_CHAT_CLEAR"] = "cf_agent_chat_clear";
|
|
785
|
-
MessageType$1["CF_AGENT_CHAT_REQUEST_CANCEL"] = "cf_agent_chat_request_cancel";
|
|
786
|
-
MessageType$1["CF_AGENT_MCP_SERVERS"] = "cf_agent_mcp_servers";
|
|
787
|
-
MessageType$1["CF_MCP_AGENT_EVENT"] = "cf_mcp_agent_event";
|
|
788
|
-
MessageType$1["CF_AGENT_STATE"] = "cf_agent_state";
|
|
789
|
-
MessageType$1["RPC"] = "rpc";
|
|
790
|
-
return MessageType$1;
|
|
791
|
-
}({});
|
|
792
|
-
|
|
793
|
-
//#endregion
|
|
794
|
-
//#region ../agents/dist/client-zS-OCVJA.js
|
|
795
|
-
/**
|
|
796
|
-
* Convert a camelCase string to a kebab-case string
|
|
797
|
-
* @param str The string to convert
|
|
798
|
-
* @returns The kebab-case string
|
|
799
|
-
*/
|
|
800
|
-
function camelCaseToKebabCase(str) {
|
|
801
|
-
if (str === str.toUpperCase() && str !== str.toLowerCase()) return str.toLowerCase().replace(/_/g, "-");
|
|
802
|
-
let kebabified = str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
803
|
-
kebabified = kebabified.startsWith("-") ? kebabified.slice(1) : kebabified;
|
|
804
|
-
return kebabified.replace(/_/g, "-").replace(/-$/, "");
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
//#endregion
|
|
808
|
-
//#region ../agents/dist/do-oauth-client-provider-B2jr6UNq.js
|
|
809
|
-
var DurableObjectOAuthClientProvider = class {
|
|
810
|
-
constructor(storage, clientName, baseRedirectUrl) {
|
|
811
|
-
this.storage = storage;
|
|
812
|
-
this.clientName = clientName;
|
|
813
|
-
this.baseRedirectUrl = baseRedirectUrl;
|
|
814
|
-
}
|
|
815
|
-
get clientMetadata() {
|
|
816
|
-
return {
|
|
817
|
-
client_name: this.clientName,
|
|
818
|
-
client_uri: this.clientUri,
|
|
819
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
820
|
-
redirect_uris: [this.redirectUrl],
|
|
821
|
-
response_types: ["code"],
|
|
822
|
-
token_endpoint_auth_method: "none"
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
get clientUri() {
|
|
826
|
-
return new URL(this.redirectUrl).origin;
|
|
827
|
-
}
|
|
828
|
-
get redirectUrl() {
|
|
829
|
-
return `${this.baseRedirectUrl}/${this.serverId}`;
|
|
830
|
-
}
|
|
831
|
-
get clientId() {
|
|
832
|
-
if (!this._clientId_) throw new Error("Trying to access clientId before it was set");
|
|
833
|
-
return this._clientId_;
|
|
834
|
-
}
|
|
835
|
-
set clientId(clientId_) {
|
|
836
|
-
this._clientId_ = clientId_;
|
|
837
|
-
}
|
|
838
|
-
get serverId() {
|
|
839
|
-
if (!this._serverId_) throw new Error("Trying to access serverId before it was set");
|
|
840
|
-
return this._serverId_;
|
|
841
|
-
}
|
|
842
|
-
set serverId(serverId_) {
|
|
843
|
-
this._serverId_ = serverId_;
|
|
844
|
-
}
|
|
845
|
-
keyPrefix(clientId) {
|
|
846
|
-
return `/${this.clientName}/${this.serverId}/${clientId}`;
|
|
847
|
-
}
|
|
848
|
-
clientInfoKey(clientId) {
|
|
849
|
-
return `${this.keyPrefix(clientId)}/client_info/`;
|
|
850
|
-
}
|
|
851
|
-
async clientInformation() {
|
|
852
|
-
if (!this._clientId_) return;
|
|
853
|
-
return await this.storage.get(this.clientInfoKey(this.clientId)) ?? void 0;
|
|
854
|
-
}
|
|
855
|
-
async saveClientInformation(clientInformation) {
|
|
856
|
-
await this.storage.put(this.clientInfoKey(clientInformation.client_id), clientInformation);
|
|
857
|
-
this.clientId = clientInformation.client_id;
|
|
858
|
-
}
|
|
859
|
-
tokenKey(clientId) {
|
|
860
|
-
return `${this.keyPrefix(clientId)}/token`;
|
|
861
|
-
}
|
|
862
|
-
async tokens() {
|
|
863
|
-
if (!this._clientId_) return;
|
|
864
|
-
return await this.storage.get(this.tokenKey(this.clientId)) ?? void 0;
|
|
865
|
-
}
|
|
866
|
-
async saveTokens(tokens) {
|
|
867
|
-
await this.storage.put(this.tokenKey(this.clientId), tokens);
|
|
868
|
-
}
|
|
869
|
-
get authUrl() {
|
|
870
|
-
return this._authUrl_;
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* Because this operates on the server side (but we need browser auth), we send this url back to the user
|
|
874
|
-
* and require user interact to initiate the redirect flow
|
|
875
|
-
*/
|
|
876
|
-
async redirectToAuthorization(authUrl) {
|
|
877
|
-
const stateToken = nanoid();
|
|
878
|
-
authUrl.searchParams.set("state", stateToken);
|
|
879
|
-
this._authUrl_ = authUrl.toString();
|
|
880
|
-
}
|
|
881
|
-
codeVerifierKey(clientId) {
|
|
882
|
-
return `${this.keyPrefix(clientId)}/code_verifier`;
|
|
883
|
-
}
|
|
884
|
-
async saveCodeVerifier(verifier) {
|
|
885
|
-
const key = this.codeVerifierKey(this.clientId);
|
|
886
|
-
if (await this.storage.get(key)) return;
|
|
887
|
-
await this.storage.put(key, verifier);
|
|
888
|
-
}
|
|
889
|
-
async codeVerifier() {
|
|
890
|
-
const codeVerifier = await this.storage.get(this.codeVerifierKey(this.clientId));
|
|
891
|
-
if (!codeVerifier) throw new Error("No code verifier found");
|
|
892
|
-
return codeVerifier;
|
|
893
|
-
}
|
|
894
|
-
};
|
|
895
|
-
|
|
896
|
-
//#endregion
|
|
897
|
-
//#region ../agents/dist/src-C9xZ0CrH.js
|
|
1215
|
+
//#region ../agents/dist/src-D_KKH_4c.js
|
|
898
1216
|
/**
|
|
899
1217
|
* A generic observability implementation that logs events to the console.
|
|
900
1218
|
*/
|
|
@@ -1013,8 +1331,8 @@ var Agent = class Agent$1 extends Server {
|
|
|
1013
1331
|
super(ctx, env$1);
|
|
1014
1332
|
this._state = DEFAULT_STATE;
|
|
1015
1333
|
this._disposables = new DisposableStore();
|
|
1334
|
+
this._destroyed = false;
|
|
1016
1335
|
this._ParentClass = Object.getPrototypeOf(this).constructor;
|
|
1017
|
-
this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1");
|
|
1018
1336
|
this.initialState = DEFAULT_STATE;
|
|
1019
1337
|
this.observability = genericObservability;
|
|
1020
1338
|
this._flushingQueue = false;
|
|
@@ -1052,27 +1370,37 @@ var Agent = class Agent$1 extends Server {
|
|
|
1052
1370
|
}
|
|
1053
1371
|
});
|
|
1054
1372
|
if (row.type === "cron") {
|
|
1373
|
+
if (this._destroyed) return;
|
|
1055
1374
|
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1056
1375
|
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1e3);
|
|
1057
1376
|
this.sql`
|
|
1058
1377
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
1059
1378
|
`;
|
|
1060
|
-
} else
|
|
1379
|
+
} else {
|
|
1380
|
+
if (this._destroyed) return;
|
|
1381
|
+
this.sql`
|
|
1061
1382
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
1062
1383
|
`;
|
|
1384
|
+
}
|
|
1063
1385
|
}
|
|
1386
|
+
if (this._destroyed) return;
|
|
1064
1387
|
await this._scheduleNextAlarm();
|
|
1065
1388
|
};
|
|
1066
1389
|
if (!wrappedClasses.has(this.constructor)) {
|
|
1067
1390
|
this._autoWrapCustomMethods();
|
|
1068
1391
|
wrappedClasses.add(this.constructor);
|
|
1069
1392
|
}
|
|
1070
|
-
this.
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1393
|
+
this.sql`
|
|
1394
|
+
CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
|
|
1395
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
1396
|
+
name TEXT NOT NULL,
|
|
1397
|
+
server_url TEXT NOT NULL,
|
|
1398
|
+
callback_url TEXT NOT NULL,
|
|
1399
|
+
client_id TEXT,
|
|
1400
|
+
auth_url TEXT,
|
|
1401
|
+
server_options TEXT
|
|
1402
|
+
)
|
|
1403
|
+
`;
|
|
1076
1404
|
this.sql`
|
|
1077
1405
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
1078
1406
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -1087,34 +1415,25 @@ var Agent = class Agent$1 extends Server {
|
|
|
1087
1415
|
created_at INTEGER DEFAULT (unixepoch())
|
|
1088
1416
|
)
|
|
1089
1417
|
`;
|
|
1090
|
-
this.ctx.blockConcurrencyWhile(async () => {
|
|
1091
|
-
return this._tryCatch(async () => {
|
|
1092
|
-
this.sql`
|
|
1093
|
-
CREATE TABLE IF NOT EXISTS cf_agents_schedules (
|
|
1094
|
-
id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
|
|
1095
|
-
callback TEXT,
|
|
1096
|
-
payload TEXT,
|
|
1097
|
-
type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron')),
|
|
1098
|
-
time INTEGER,
|
|
1099
|
-
delayInSeconds INTEGER,
|
|
1100
|
-
cron TEXT,
|
|
1101
|
-
created_at INTEGER DEFAULT (unixepoch())
|
|
1102
|
-
)
|
|
1103
|
-
`;
|
|
1104
|
-
await this.alarm();
|
|
1105
|
-
});
|
|
1106
|
-
});
|
|
1107
1418
|
this.sql`
|
|
1108
|
-
CREATE TABLE IF NOT EXISTS
|
|
1109
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1419
|
+
CREATE TABLE IF NOT EXISTS cf_agents_schedules (
|
|
1420
|
+
id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
|
|
1421
|
+
callback TEXT,
|
|
1422
|
+
payload TEXT,
|
|
1423
|
+
type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron')),
|
|
1424
|
+
time INTEGER,
|
|
1425
|
+
delayInSeconds INTEGER,
|
|
1426
|
+
cron TEXT,
|
|
1427
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
1116
1428
|
)
|
|
1117
1429
|
`;
|
|
1430
|
+
this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", { storage: this.ctx.storage });
|
|
1431
|
+
this._disposables.add(this.mcp.onServerStateChanged(async () => {
|
|
1432
|
+
this.broadcastMcpServers();
|
|
1433
|
+
}));
|
|
1434
|
+
this._disposables.add(this.mcp.onObservabilityEvent((event) => {
|
|
1435
|
+
this.observability?.emit(event);
|
|
1436
|
+
}));
|
|
1118
1437
|
const _onRequest = this.onRequest.bind(this);
|
|
1119
1438
|
this.onRequest = (request) => {
|
|
1120
1439
|
return agentContext.run({
|
|
@@ -1123,16 +1442,9 @@ var Agent = class Agent$1 extends Server {
|
|
|
1123
1442
|
request,
|
|
1124
1443
|
email: void 0
|
|
1125
1444
|
}, async () => {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
if (result.authSuccess) this.mcp.establishConnection(result.serverId).catch((error) => {
|
|
1130
|
-
console.error("Background connection failed:", error);
|
|
1131
|
-
}).finally(() => {
|
|
1132
|
-
this.broadcastMcpServers();
|
|
1133
|
-
});
|
|
1134
|
-
return this.handleOAuthCallbackResponse(result, request);
|
|
1135
|
-
}
|
|
1445
|
+
await this.mcp.ensureJsonSchema();
|
|
1446
|
+
const oauthResponse = await this.handleMcpOAuthCallback(request);
|
|
1447
|
+
if (oauthResponse) return oauthResponse;
|
|
1136
1448
|
return this._tryCatch(() => _onRequest(request));
|
|
1137
1449
|
});
|
|
1138
1450
|
};
|
|
@@ -1144,6 +1456,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1144
1456
|
request: void 0,
|
|
1145
1457
|
email: void 0
|
|
1146
1458
|
}, async () => {
|
|
1459
|
+
await this.mcp.ensureJsonSchema();
|
|
1147
1460
|
if (typeof message !== "string") return this._tryCatch(() => _onMessage(connection, message));
|
|
1148
1461
|
let parsed;
|
|
1149
1462
|
try {
|
|
@@ -1208,7 +1521,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1208
1521
|
connection,
|
|
1209
1522
|
request: ctx$1.request,
|
|
1210
1523
|
email: void 0
|
|
1211
|
-
}, () => {
|
|
1524
|
+
}, async () => {
|
|
1212
1525
|
if (this.state) connection.send(JSON.stringify({
|
|
1213
1526
|
state: this.state,
|
|
1214
1527
|
type: MessageType.CF_AGENT_STATE
|
|
@@ -1235,27 +1548,9 @@ var Agent = class Agent$1 extends Server {
|
|
|
1235
1548
|
request: void 0,
|
|
1236
1549
|
email: void 0
|
|
1237
1550
|
}, async () => {
|
|
1238
|
-
await this._tryCatch(() => {
|
|
1239
|
-
|
|
1240
|
-
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1241
|
-
`;
|
|
1551
|
+
await this._tryCatch(async () => {
|
|
1552
|
+
await this.mcp.restoreConnectionsFromStorage(this.name);
|
|
1242
1553
|
this.broadcastMcpServers();
|
|
1243
|
-
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
1244
|
-
servers.forEach((server) => {
|
|
1245
|
-
if (server.callback_url) this.mcp.registerCallbackUrl(`${server.callback_url}/${server.id}`);
|
|
1246
|
-
});
|
|
1247
|
-
servers.forEach((server) => {
|
|
1248
|
-
this._connectToMcpServerInternal(server.name, server.server_url, server.callback_url, server.server_options ? JSON.parse(server.server_options) : void 0, {
|
|
1249
|
-
id: server.id,
|
|
1250
|
-
oauthClientId: server.client_id ?? void 0
|
|
1251
|
-
}).then(() => {
|
|
1252
|
-
this.broadcastMcpServers();
|
|
1253
|
-
}).catch((error) => {
|
|
1254
|
-
console.error(`Error connecting to MCP server: ${server.name} (${server.server_url})`, error);
|
|
1255
|
-
this.broadcastMcpServers();
|
|
1256
|
-
});
|
|
1257
|
-
});
|
|
1258
|
-
}
|
|
1259
1554
|
return _onStart(props);
|
|
1260
1555
|
});
|
|
1261
1556
|
});
|
|
@@ -1661,7 +1956,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1661
1956
|
async _scheduleNextAlarm() {
|
|
1662
1957
|
const result = this.sql`
|
|
1663
1958
|
SELECT time FROM cf_agents_schedules
|
|
1664
|
-
WHERE time
|
|
1959
|
+
WHERE time >= ${Math.floor(Date.now() / 1e3)}
|
|
1665
1960
|
ORDER BY time ASC
|
|
1666
1961
|
LIMIT 1
|
|
1667
1962
|
`;
|
|
@@ -1675,15 +1970,18 @@ var Agent = class Agent$1 extends Server {
|
|
|
1675
1970
|
* Destroy the Agent, removing all state and scheduled tasks
|
|
1676
1971
|
*/
|
|
1677
1972
|
async destroy() {
|
|
1973
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1678
1974
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
1679
1975
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
1680
|
-
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1681
1976
|
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
1682
1977
|
await this.ctx.storage.deleteAlarm();
|
|
1683
1978
|
await this.ctx.storage.deleteAll();
|
|
1684
1979
|
this._disposables.dispose();
|
|
1685
|
-
await this.mcp.dispose
|
|
1686
|
-
this.
|
|
1980
|
+
await this.mcp.dispose();
|
|
1981
|
+
this._destroyed = true;
|
|
1982
|
+
setTimeout(() => {
|
|
1983
|
+
this.ctx.abort("destroyed");
|
|
1984
|
+
}, 0);
|
|
1687
1985
|
this.observability?.emit({
|
|
1688
1986
|
displayMessage: "Agent destroyed",
|
|
1689
1987
|
id: nanoid(),
|
|
@@ -1707,7 +2005,8 @@ var Agent = class Agent$1 extends Server {
|
|
|
1707
2005
|
* @param callbackHost Base host for the agent, used for the redirect URI. If not provided, will be derived from the current request.
|
|
1708
2006
|
* @param agentsPrefix agents routing prefix if not using `agents`
|
|
1709
2007
|
* @param options MCP client and transport options
|
|
1710
|
-
* @returns authUrl
|
|
2008
|
+
* @returns Server id and state - either "authenticating" with authUrl, or "ready"
|
|
2009
|
+
* @throws If connection or discovery fails
|
|
1711
2010
|
*/
|
|
1712
2011
|
async addMcpServer(serverName, url, callbackHost, agentsPrefix = "agents", options) {
|
|
1713
2012
|
let resolvedCallbackHost = callbackHost;
|
|
@@ -1718,29 +2017,10 @@ var Agent = class Agent$1 extends Server {
|
|
|
1718
2017
|
resolvedCallbackHost = `${requestUrl.protocol}//${requestUrl.host}`;
|
|
1719
2018
|
}
|
|
1720
2019
|
const callbackUrl = `${resolvedCallbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
INSERT
|
|
1724
|
-
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1725
|
-
VALUES (
|
|
1726
|
-
${result.id},
|
|
1727
|
-
${serverName},
|
|
1728
|
-
${url},
|
|
1729
|
-
${result.clientId ?? null},
|
|
1730
|
-
${result.authUrl ?? null},
|
|
1731
|
-
${callbackUrl},
|
|
1732
|
-
${options ? JSON.stringify(options) : null}
|
|
1733
|
-
);
|
|
1734
|
-
`;
|
|
1735
|
-
this.broadcastMcpServers();
|
|
1736
|
-
return result;
|
|
1737
|
-
}
|
|
1738
|
-
async _connectToMcpServerInternal(_serverName, url, callbackUrl, options, reconnect) {
|
|
2020
|
+
await this.mcp.ensureJsonSchema();
|
|
2021
|
+
const id = nanoid(8);
|
|
1739
2022
|
const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage, this.name, callbackUrl);
|
|
1740
|
-
|
|
1741
|
-
authProvider.serverId = reconnect.id;
|
|
1742
|
-
if (reconnect.oauthClientId) authProvider.clientId = reconnect.oauthClientId;
|
|
1743
|
-
}
|
|
2023
|
+
authProvider.serverId = id;
|
|
1744
2024
|
const transportType = options?.transport?.type ?? "auto";
|
|
1745
2025
|
let headerTransportOpts = {};
|
|
1746
2026
|
if (options?.transport?.headers) headerTransportOpts = {
|
|
@@ -1750,28 +2030,33 @@ var Agent = class Agent$1 extends Server {
|
|
|
1750
2030
|
}) },
|
|
1751
2031
|
requestInit: { headers: options?.transport?.headers }
|
|
1752
2032
|
};
|
|
1753
|
-
|
|
2033
|
+
await this.mcp.registerServer(id, {
|
|
2034
|
+
url,
|
|
2035
|
+
name: serverName,
|
|
2036
|
+
callbackUrl,
|
|
1754
2037
|
client: options?.client,
|
|
1755
|
-
reconnect,
|
|
1756
2038
|
transport: {
|
|
1757
2039
|
...headerTransportOpts,
|
|
1758
2040
|
authProvider,
|
|
1759
2041
|
type: transportType
|
|
1760
2042
|
}
|
|
1761
2043
|
});
|
|
2044
|
+
const result = await this.mcp.connectToServer(id);
|
|
2045
|
+
if (result.state === MCPConnectionState.FAILED) throw new Error(`Failed to connect to MCP server at ${url}: ${result.error}`);
|
|
2046
|
+
if (result.state === MCPConnectionState.AUTHENTICATING) return {
|
|
2047
|
+
id,
|
|
2048
|
+
state: result.state,
|
|
2049
|
+
authUrl: result.authUrl
|
|
2050
|
+
};
|
|
2051
|
+
const discoverResult = await this.mcp.discoverIfConnected(id);
|
|
2052
|
+
if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover MCP server capabilities: ${discoverResult.error}`);
|
|
1762
2053
|
return {
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
id
|
|
2054
|
+
id,
|
|
2055
|
+
state: MCPConnectionState.READY
|
|
1766
2056
|
};
|
|
1767
2057
|
}
|
|
1768
2058
|
async removeMcpServer(id) {
|
|
1769
|
-
this.mcp.
|
|
1770
|
-
this.mcp.unregisterCallbackUrl(id);
|
|
1771
|
-
this.sql`
|
|
1772
|
-
DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
|
|
1773
|
-
`;
|
|
1774
|
-
this.broadcastMcpServers();
|
|
2059
|
+
await this.mcp.removeServer(id);
|
|
1775
2060
|
}
|
|
1776
2061
|
getMcpServers() {
|
|
1777
2062
|
const mcpState = {
|
|
@@ -1780,18 +2065,18 @@ var Agent = class Agent$1 extends Server {
|
|
|
1780
2065
|
servers: {},
|
|
1781
2066
|
tools: this.mcp.listTools()
|
|
1782
2067
|
};
|
|
1783
|
-
const servers = this.
|
|
1784
|
-
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1785
|
-
`;
|
|
2068
|
+
const servers = this.mcp.listServers();
|
|
1786
2069
|
if (servers && Array.isArray(servers) && servers.length > 0) for (const server of servers) {
|
|
1787
2070
|
const serverConn = this.mcp.mcpConnections[server.id];
|
|
2071
|
+
let defaultState = "not-connected";
|
|
2072
|
+
if (!serverConn && server.auth_url) defaultState = "authenticating";
|
|
1788
2073
|
mcpState.servers[server.id] = {
|
|
1789
2074
|
auth_url: server.auth_url,
|
|
1790
2075
|
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1791
2076
|
instructions: serverConn?.instructions ?? null,
|
|
1792
2077
|
name: server.name,
|
|
1793
2078
|
server_url: server.server_url,
|
|
1794
|
-
state: serverConn?.connectionState ??
|
|
2079
|
+
state: serverConn?.connectionState ?? defaultState
|
|
1795
2080
|
};
|
|
1796
2081
|
}
|
|
1797
2082
|
return mcpState;
|
|
@@ -1803,6 +2088,28 @@ var Agent = class Agent$1 extends Server {
|
|
|
1803
2088
|
}));
|
|
1804
2089
|
}
|
|
1805
2090
|
/**
|
|
2091
|
+
* Handle MCP OAuth callback request if it's an OAuth callback.
|
|
2092
|
+
*
|
|
2093
|
+
* This method encapsulates the entire OAuth callback flow:
|
|
2094
|
+
* 1. Checks if the request is an MCP OAuth callback
|
|
2095
|
+
* 2. Processes the OAuth code exchange
|
|
2096
|
+
* 3. Establishes the connection if successful
|
|
2097
|
+
* 4. Broadcasts MCP server state updates
|
|
2098
|
+
* 5. Returns the appropriate HTTP response
|
|
2099
|
+
*
|
|
2100
|
+
* @param request The incoming HTTP request
|
|
2101
|
+
* @returns Response if this was an OAuth callback, null otherwise
|
|
2102
|
+
*/
|
|
2103
|
+
async handleMcpOAuthCallback(request) {
|
|
2104
|
+
if (!this.mcp.isCallbackRequest(request)) return null;
|
|
2105
|
+
const result = await this.mcp.handleCallbackRequest(request);
|
|
2106
|
+
if (result.authSuccess) this.mcp.establishConnection(result.serverId).catch((error) => {
|
|
2107
|
+
console.error("[Agent handleMcpOAuthCallback] Connection establishment failed:", error);
|
|
2108
|
+
});
|
|
2109
|
+
this.broadcastMcpServers();
|
|
2110
|
+
return this.handleOAuthCallbackResponse(result, request);
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
1806
2113
|
* Handle OAuth callback response using MCPClientManager configuration
|
|
1807
2114
|
* @param result OAuth callback result
|
|
1808
2115
|
* @param request The original request (needed for base URL)
|
|
@@ -1811,10 +2118,21 @@ var Agent = class Agent$1 extends Server {
|
|
|
1811
2118
|
handleOAuthCallbackResponse(result, request) {
|
|
1812
2119
|
const config = this.mcp.getOAuthCallbackConfig();
|
|
1813
2120
|
if (config?.customHandler) return config.customHandler(result);
|
|
1814
|
-
|
|
1815
|
-
if (config?.
|
|
1816
|
-
|
|
1817
|
-
|
|
2121
|
+
const baseOrigin = new URL(request.url).origin;
|
|
2122
|
+
if (config?.successRedirect && result.authSuccess) try {
|
|
2123
|
+
return Response.redirect(new URL(config.successRedirect, baseOrigin).href);
|
|
2124
|
+
} catch (e) {
|
|
2125
|
+
console.error("Invalid successRedirect URL:", config.successRedirect, e);
|
|
2126
|
+
return Response.redirect(baseOrigin);
|
|
2127
|
+
}
|
|
2128
|
+
if (config?.errorRedirect && !result.authSuccess) try {
|
|
2129
|
+
const errorUrl = `${config.errorRedirect}?error=${encodeURIComponent(result.authError || "Unknown error")}`;
|
|
2130
|
+
return Response.redirect(new URL(errorUrl, baseOrigin).href);
|
|
2131
|
+
} catch (e) {
|
|
2132
|
+
console.error("Invalid errorRedirect URL:", config.errorRedirect, e);
|
|
2133
|
+
return Response.redirect(baseOrigin);
|
|
2134
|
+
}
|
|
2135
|
+
return Response.redirect(baseOrigin);
|
|
1818
2136
|
}
|
|
1819
2137
|
};
|
|
1820
2138
|
const wrappedClasses = /* @__PURE__ */ new Set();
|
|
@@ -1840,10 +2158,15 @@ async function routeAgentRequest(request, env$1, options) {
|
|
|
1840
2158
|
prefix: "agents",
|
|
1841
2159
|
...options
|
|
1842
2160
|
});
|
|
1843
|
-
if (response && corsHeaders && request.headers.get("upgrade")?.toLowerCase() !== "websocket" && request.headers.get("Upgrade")?.toLowerCase() !== "websocket")
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2161
|
+
if (response && corsHeaders && request.headers.get("upgrade")?.toLowerCase() !== "websocket" && request.headers.get("Upgrade")?.toLowerCase() !== "websocket") {
|
|
2162
|
+
const newHeaders = new Headers(response.headers);
|
|
2163
|
+
for (const [key, value] of Object.entries(corsHeaders)) newHeaders.set(key, value);
|
|
2164
|
+
response = new Response(response.body, {
|
|
2165
|
+
status: response.status,
|
|
2166
|
+
statusText: response.statusText,
|
|
2167
|
+
headers: newHeaders
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
1847
2170
|
return response;
|
|
1848
2171
|
}
|
|
1849
2172
|
/**
|