hono-agents 0.0.0-e135cf5 → 0.0.0-e173b41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +736 -405
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
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
5
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
5
6
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -11,7 +12,7 @@ import { Server, routePartykitRequest } from "partyserver";
|
|
|
11
12
|
import { env } from "hono/adapter";
|
|
12
13
|
import { createMiddleware } from "hono/factory";
|
|
13
14
|
|
|
14
|
-
//#region ../agents/dist/ai-types-
|
|
15
|
+
//#region ../agents/dist/ai-types-CrMqkwc_.js
|
|
15
16
|
/**
|
|
16
17
|
* Enum for message types to improve type safety and maintainability
|
|
17
18
|
*/
|
|
@@ -21,6 +22,10 @@ let MessageType = /* @__PURE__ */ function(MessageType$1) {
|
|
|
21
22
|
MessageType$1["CF_AGENT_USE_CHAT_RESPONSE"] = "cf_agent_use_chat_response";
|
|
22
23
|
MessageType$1["CF_AGENT_CHAT_CLEAR"] = "cf_agent_chat_clear";
|
|
23
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";
|
|
24
29
|
MessageType$1["CF_AGENT_MCP_SERVERS"] = "cf_agent_mcp_servers";
|
|
25
30
|
MessageType$1["CF_MCP_AGENT_EVENT"] = "cf_mcp_agent_event";
|
|
26
31
|
MessageType$1["CF_AGENT_STATE"] = "cf_agent_state";
|
|
@@ -29,7 +34,7 @@ let MessageType = /* @__PURE__ */ function(MessageType$1) {
|
|
|
29
34
|
}({});
|
|
30
35
|
|
|
31
36
|
//#endregion
|
|
32
|
-
//#region ../agents/dist/client-
|
|
37
|
+
//#region ../agents/dist/client-B3SR12TQ.js
|
|
33
38
|
/**
|
|
34
39
|
* Convert a camelCase string to a kebab-case string
|
|
35
40
|
* @param str The string to convert
|
|
@@ -43,7 +48,97 @@ function camelCaseToKebabCase(str) {
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
//#endregion
|
|
46
|
-
//#region ../agents/dist/client-
|
|
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
|
|
47
142
|
function toDisposable(fn) {
|
|
48
143
|
return { dispose: fn };
|
|
49
144
|
}
|
|
@@ -91,6 +186,22 @@ function isTransportNotImplemented(error) {
|
|
|
91
186
|
const msg = toErrorMessage(error);
|
|
92
187
|
return msg.includes("404") || msg.includes("405") || msg.includes("Not Implemented") || msg.includes("not implemented");
|
|
93
188
|
}
|
|
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"
|
|
204
|
+
};
|
|
94
205
|
var MCPClientConnection = class {
|
|
95
206
|
constructor(url, info, options = {
|
|
96
207
|
client: {},
|
|
@@ -98,7 +209,7 @@ var MCPClientConnection = class {
|
|
|
98
209
|
}) {
|
|
99
210
|
this.url = url;
|
|
100
211
|
this.options = options;
|
|
101
|
-
this.connectionState =
|
|
212
|
+
this.connectionState = MCPConnectionState.CONNECTING;
|
|
102
213
|
this.tools = [];
|
|
103
214
|
this.prompts = [];
|
|
104
215
|
this.resources = [];
|
|
@@ -114,36 +225,49 @@ var MCPClientConnection = class {
|
|
|
114
225
|
});
|
|
115
226
|
}
|
|
116
227
|
/**
|
|
117
|
-
* 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
|
|
118
230
|
*
|
|
119
|
-
* @returns
|
|
231
|
+
* @returns Error message if connection failed, undefined otherwise
|
|
120
232
|
*/
|
|
121
233
|
async init() {
|
|
122
234
|
const transportType = this.options.transport.type;
|
|
123
235
|
if (!transportType) throw new Error("Transport type must be specified");
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
|
|
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;
|
|
243
|
+
this._onObservabilityEvent.fire({
|
|
244
|
+
type: "mcp:client:connect",
|
|
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);
|
|
131
257
|
this._onObservabilityEvent.fire({
|
|
132
258
|
type: "mcp:client:connect",
|
|
133
|
-
displayMessage: `
|
|
259
|
+
displayMessage: `Failed to connect to ${this.url.toString()}: ${errorMessage}`,
|
|
134
260
|
payload: {
|
|
135
261
|
url: this.url.toString(),
|
|
136
262
|
transport: transportType,
|
|
137
263
|
state: this.connectionState,
|
|
138
|
-
error:
|
|
264
|
+
error: errorMessage
|
|
139
265
|
},
|
|
140
266
|
timestamp: Date.now(),
|
|
141
267
|
id: nanoid()
|
|
142
268
|
});
|
|
143
|
-
|
|
144
|
-
return;
|
|
269
|
+
return errorMessage;
|
|
145
270
|
}
|
|
146
|
-
await this.discoverAndRegister();
|
|
147
271
|
}
|
|
148
272
|
/**
|
|
149
273
|
* Finish OAuth by probing transports based on configured type.
|
|
@@ -175,113 +299,189 @@ var MCPClientConnection = class {
|
|
|
175
299
|
* Complete OAuth authorization
|
|
176
300
|
*/
|
|
177
301
|
async completeAuthorization(code) {
|
|
178
|
-
if (this.connectionState !==
|
|
302
|
+
if (this.connectionState !== MCPConnectionState.AUTHENTICATING) throw new Error("Connection must be in authenticating state to complete authorization");
|
|
179
303
|
try {
|
|
180
304
|
await this.finishAuthProbe(code);
|
|
181
|
-
this.connectionState =
|
|
305
|
+
this.connectionState = MCPConnectionState.CONNECTING;
|
|
182
306
|
} catch (error) {
|
|
183
|
-
this.connectionState =
|
|
307
|
+
this.connectionState = MCPConnectionState.FAILED;
|
|
184
308
|
throw error;
|
|
185
309
|
}
|
|
186
310
|
}
|
|
187
311
|
/**
|
|
188
|
-
*
|
|
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().
|
|
189
314
|
*/
|
|
190
|
-
async
|
|
191
|
-
|
|
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
|
+
}
|
|
192
338
|
try {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
+
}
|
|
197
360
|
} catch (error) {
|
|
198
|
-
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
|
+
});
|
|
199
371
|
throw error;
|
|
200
372
|
}
|
|
201
373
|
}
|
|
202
374
|
/**
|
|
203
|
-
* 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
|
|
204
381
|
*/
|
|
205
|
-
async
|
|
206
|
-
|
|
207
|
-
this.
|
|
208
|
-
if (!this.serverCapabilities) throw new Error("The MCP Server failed to return server capabilities");
|
|
209
|
-
const [instructionsResult, toolsResult, resourcesResult, promptsResult, resourceTemplatesResult] = await Promise.allSettled([
|
|
210
|
-
this.client.getInstructions(),
|
|
211
|
-
this.registerTools(),
|
|
212
|
-
this.registerResources(),
|
|
213
|
-
this.registerPrompts(),
|
|
214
|
-
this.registerResourceTemplates()
|
|
215
|
-
]);
|
|
216
|
-
const operations = [
|
|
217
|
-
{
|
|
218
|
-
name: "instructions",
|
|
219
|
-
result: instructionsResult
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
name: "tools",
|
|
223
|
-
result: toolsResult
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
name: "resources",
|
|
227
|
-
result: resourcesResult
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
name: "prompts",
|
|
231
|
-
result: promptsResult
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: "resource templates",
|
|
235
|
-
result: resourceTemplatesResult
|
|
236
|
-
}
|
|
237
|
-
];
|
|
238
|
-
for (const { name, result } of operations) if (result.status === "rejected") {
|
|
239
|
-
const url = this.url.toString();
|
|
382
|
+
async discover(options = {}) {
|
|
383
|
+
const { timeoutMs = 15e3 } = options;
|
|
384
|
+
if (this.connectionState !== MCPConnectionState.CONNECTED && this.connectionState !== MCPConnectionState.READY) {
|
|
240
385
|
this._onObservabilityEvent.fire({
|
|
241
386
|
type: "mcp:client:discover",
|
|
242
|
-
displayMessage: `
|
|
387
|
+
displayMessage: `Discovery skipped for ${this.url.toString()}, state is ${this.connectionState}`,
|
|
243
388
|
payload: {
|
|
244
|
-
url,
|
|
245
|
-
|
|
246
|
-
error: result.reason
|
|
389
|
+
url: this.url.toString(),
|
|
390
|
+
state: this.connectionState
|
|
247
391
|
},
|
|
248
392
|
timestamp: Date.now(),
|
|
249
393
|
id: nanoid()
|
|
250
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;
|
|
251
442
|
}
|
|
252
|
-
this.instructions = instructionsResult.status === "fulfilled" ? instructionsResult.value : void 0;
|
|
253
|
-
this.tools = toolsResult.status === "fulfilled" ? toolsResult.value : [];
|
|
254
|
-
this.resources = resourcesResult.status === "fulfilled" ? resourcesResult.value : [];
|
|
255
|
-
this.prompts = promptsResult.status === "fulfilled" ? promptsResult.value : [];
|
|
256
|
-
this.resourceTemplates = resourceTemplatesResult.status === "fulfilled" ? resourceTemplatesResult.value : [];
|
|
257
|
-
this.connectionState = "ready";
|
|
258
443
|
}
|
|
259
444
|
/**
|
|
260
|
-
*
|
|
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
|
|
261
457
|
*/
|
|
262
458
|
async registerTools() {
|
|
263
|
-
if (
|
|
264
|
-
if (this.serverCapabilities.tools.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
|
|
459
|
+
if (this.serverCapabilities?.tools?.listChanged) this.client.setNotificationHandler(ToolListChangedNotificationSchema, async (_notification) => {
|
|
265
460
|
this.tools = await this.fetchTools();
|
|
266
461
|
});
|
|
267
462
|
return this.fetchTools();
|
|
268
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Notification handler registration for resources
|
|
466
|
+
* Should only be called if serverCapabilities.resources exists
|
|
467
|
+
*/
|
|
269
468
|
async registerResources() {
|
|
270
|
-
if (
|
|
271
|
-
if (this.serverCapabilities.resources.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
|
|
469
|
+
if (this.serverCapabilities?.resources?.listChanged) this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async (_notification) => {
|
|
272
470
|
this.resources = await this.fetchResources();
|
|
273
471
|
});
|
|
274
472
|
return this.fetchResources();
|
|
275
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Notification handler registration for prompts
|
|
476
|
+
* Should only be called if serverCapabilities.prompts exists
|
|
477
|
+
*/
|
|
276
478
|
async registerPrompts() {
|
|
277
|
-
if (
|
|
278
|
-
if (this.serverCapabilities.prompts.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
|
|
479
|
+
if (this.serverCapabilities?.prompts?.listChanged) this.client.setNotificationHandler(PromptListChangedNotificationSchema, async (_notification) => {
|
|
279
480
|
this.prompts = await this.fetchPrompts();
|
|
280
481
|
});
|
|
281
482
|
return this.fetchPrompts();
|
|
282
483
|
}
|
|
283
484
|
async registerResourceTemplates() {
|
|
284
|
-
if (!this.serverCapabilities || !this.serverCapabilities.resources) return [];
|
|
285
485
|
return this.fetchResourceTemplates();
|
|
286
486
|
}
|
|
287
487
|
async fetchTools() {
|
|
@@ -347,44 +547,24 @@ var MCPClientConnection = class {
|
|
|
347
547
|
const transport = this.getTransport(currentTransportType);
|
|
348
548
|
try {
|
|
349
549
|
await this.client.connect(transport);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
displayMessage: `Connected successfully using ${currentTransportType} transport for ${url}`,
|
|
355
|
-
payload: {
|
|
356
|
-
url,
|
|
357
|
-
transport: currentTransportType,
|
|
358
|
-
state: this.connectionState
|
|
359
|
-
},
|
|
360
|
-
timestamp: Date.now(),
|
|
361
|
-
id: nanoid()
|
|
362
|
-
});
|
|
363
|
-
break;
|
|
550
|
+
return {
|
|
551
|
+
state: MCPConnectionState.CONNECTED,
|
|
552
|
+
transport: currentTransportType
|
|
553
|
+
};
|
|
364
554
|
} catch (e) {
|
|
365
555
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
366
|
-
if (isUnauthorized(error))
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
payload: {
|
|
373
|
-
url,
|
|
374
|
-
transport: currentTransportType,
|
|
375
|
-
state: this.connectionState
|
|
376
|
-
},
|
|
377
|
-
timestamp: Date.now(),
|
|
378
|
-
id: nanoid()
|
|
379
|
-
});
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
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
|
+
};
|
|
383
562
|
}
|
|
384
563
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
564
|
+
return {
|
|
565
|
+
state: MCPConnectionState.FAILED,
|
|
566
|
+
error: /* @__PURE__ */ new Error("No transports available")
|
|
567
|
+
};
|
|
388
568
|
}
|
|
389
569
|
_capabilityErrorHandler(empty, method) {
|
|
390
570
|
return (e) => {
|
|
@@ -407,6 +587,7 @@ var MCPClientConnection = class {
|
|
|
407
587
|
};
|
|
408
588
|
}
|
|
409
589
|
};
|
|
590
|
+
const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
|
|
410
591
|
/**
|
|
411
592
|
* Utility class that aggregates multiple MCP clients into one
|
|
412
593
|
*/
|
|
@@ -414,26 +595,121 @@ var MCPClientManager = class {
|
|
|
414
595
|
/**
|
|
415
596
|
* @param _name Name of the MCP client
|
|
416
597
|
* @param _version Version of the MCP Client
|
|
417
|
-
* @param
|
|
598
|
+
* @param options Storage adapter for persisting MCP server state
|
|
418
599
|
*/
|
|
419
|
-
constructor(_name, _version) {
|
|
600
|
+
constructor(_name, _version, options) {
|
|
420
601
|
this._name = _name;
|
|
421
602
|
this._version = _version;
|
|
422
603
|
this.mcpConnections = {};
|
|
423
|
-
this._callbackUrls = [];
|
|
424
604
|
this._didWarnAboutUnstableGetAITools = false;
|
|
425
605
|
this._connectionDisposables = /* @__PURE__ */ new Map();
|
|
606
|
+
this._isRestored = false;
|
|
426
607
|
this._onObservabilityEvent = new Emitter();
|
|
427
608
|
this.onObservabilityEvent = this._onObservabilityEvent.event;
|
|
428
|
-
this.
|
|
429
|
-
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
|
+
}
|
|
430
703
|
}
|
|
431
704
|
/**
|
|
432
705
|
* Connect to and register an MCP server
|
|
433
706
|
*
|
|
434
|
-
* @
|
|
435
|
-
*
|
|
436
|
-
*
|
|
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)
|
|
437
713
|
*/
|
|
438
714
|
async connect(url, options = {}) {
|
|
439
715
|
/**
|
|
@@ -443,10 +719,7 @@ var MCPClientManager = class {
|
|
|
443
719
|
* .connect() is called on at least one server.
|
|
444
720
|
* So it's safe to delay loading it until .connect() is called.
|
|
445
721
|
*/
|
|
446
|
-
|
|
447
|
-
const { jsonSchema } = await import("ai");
|
|
448
|
-
this.jsonSchema = jsonSchema;
|
|
449
|
-
}
|
|
722
|
+
await this.ensureJsonSchema();
|
|
450
723
|
const id = options.reconnect?.id ?? nanoid(8);
|
|
451
724
|
if (options.transport?.authProvider) {
|
|
452
725
|
options.transport.authProvider.serverId = id;
|
|
@@ -475,7 +748,7 @@ var MCPClientManager = class {
|
|
|
475
748
|
await this.mcpConnections[id].init();
|
|
476
749
|
if (options.reconnect?.oauthCode) try {
|
|
477
750
|
await this.mcpConnections[id].completeAuthorization(options.reconnect.oauthCode);
|
|
478
|
-
await this.mcpConnections[id].
|
|
751
|
+
await this.mcpConnections[id].init();
|
|
479
752
|
} catch (error) {
|
|
480
753
|
this._onObservabilityEvent.fire({
|
|
481
754
|
type: "mcp:client:connect",
|
|
@@ -492,33 +765,144 @@ var MCPClientManager = class {
|
|
|
492
765
|
throw error;
|
|
493
766
|
}
|
|
494
767
|
const authUrl = options.transport?.authProvider?.authUrl;
|
|
495
|
-
if (this.mcpConnections[id].connectionState ===
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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}`
|
|
501
887
|
};
|
|
502
888
|
}
|
|
503
|
-
return { id };
|
|
504
889
|
}
|
|
505
890
|
isCallbackRequest(req) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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));
|
|
509
894
|
}
|
|
510
895
|
async handleCallbackRequest(req) {
|
|
511
896
|
const url = new URL(req.url);
|
|
512
|
-
const
|
|
513
|
-
return req.url.startsWith(
|
|
897
|
+
const matchingServer = this.getServersFromStorage().find((server) => {
|
|
898
|
+
return server.callback_url && req.url.startsWith(server.callback_url);
|
|
514
899
|
});
|
|
515
|
-
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;
|
|
516
902
|
const code = url.searchParams.get("code");
|
|
517
903
|
const state = url.searchParams.get("state");
|
|
518
904
|
const error = url.searchParams.get("error");
|
|
519
905
|
const errorDescription = url.searchParams.get("error_description");
|
|
520
|
-
const urlParams = urlMatch.split("/");
|
|
521
|
-
const serverId = urlParams[urlParams.length - 1];
|
|
522
906
|
if (error) return {
|
|
523
907
|
serverId,
|
|
524
908
|
authSuccess: false,
|
|
@@ -527,7 +911,14 @@ var MCPClientManager = class {
|
|
|
527
911
|
if (!code) throw new Error("Unauthorized: no code provided");
|
|
528
912
|
if (!state) throw new Error("Unauthorized: no state provided");
|
|
529
913
|
if (this.mcpConnections[serverId] === void 0) throw new Error(`Could not find serverId: ${serverId}`);
|
|
530
|
-
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"`);
|
|
531
922
|
const conn = this.mcpConnections[serverId];
|
|
532
923
|
if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
|
|
533
924
|
const clientId = conn.options.transport.authProvider.clientId || state;
|
|
@@ -535,21 +926,57 @@ var MCPClientManager = class {
|
|
|
535
926
|
conn.options.transport.authProvider.serverId = serverId;
|
|
536
927
|
try {
|
|
537
928
|
await conn.completeAuthorization(code);
|
|
929
|
+
this.clearServerAuthUrl(serverId);
|
|
930
|
+
this._onServerStateChanged.fire();
|
|
538
931
|
return {
|
|
539
932
|
serverId,
|
|
540
933
|
authSuccess: true
|
|
541
934
|
};
|
|
542
935
|
} catch (error$1) {
|
|
936
|
+
const errorMessage = error$1 instanceof Error ? error$1.message : String(error$1);
|
|
937
|
+
this._onServerStateChanged.fire();
|
|
543
938
|
return {
|
|
544
939
|
serverId,
|
|
545
940
|
authSuccess: false,
|
|
546
|
-
authError:
|
|
941
|
+
authError: errorMessage
|
|
547
942
|
};
|
|
548
943
|
}
|
|
549
944
|
}
|
|
550
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
|
+
/**
|
|
551
978
|
* Establish connection in the background after OAuth completion
|
|
552
|
-
* This method
|
|
979
|
+
* This method connects to the server and discovers its capabilities
|
|
553
980
|
* @param serverId The server ID to establish connection for
|
|
554
981
|
*/
|
|
555
982
|
async establishConnection(serverId) {
|
|
@@ -564,38 +991,34 @@ var MCPClientManager = class {
|
|
|
564
991
|
});
|
|
565
992
|
return;
|
|
566
993
|
}
|
|
567
|
-
|
|
568
|
-
await conn.establishConnection();
|
|
569
|
-
this._onConnected.fire(serverId);
|
|
570
|
-
} catch (error) {
|
|
571
|
-
const url = conn.url.toString();
|
|
994
|
+
if (conn.connectionState === MCPConnectionState.DISCOVERING || conn.connectionState === MCPConnectionState.READY) {
|
|
572
995
|
this._onObservabilityEvent.fire({
|
|
573
996
|
type: "mcp:client:connect",
|
|
574
|
-
displayMessage: `
|
|
997
|
+
displayMessage: `establishConnection skipped for ${serverId}, already in ${conn.connectionState} state`,
|
|
575
998
|
payload: {
|
|
576
|
-
url,
|
|
577
|
-
transport: conn.options.transport.type
|
|
578
|
-
state: conn.connectionState
|
|
579
|
-
error: toErrorMessage(error)
|
|
999
|
+
url: conn.url.toString(),
|
|
1000
|
+
transport: conn.options.transport.type || "unknown",
|
|
1001
|
+
state: conn.connectionState
|
|
580
1002
|
},
|
|
581
1003
|
timestamp: Date.now(),
|
|
582
1004
|
id: nanoid()
|
|
583
1005
|
});
|
|
1006
|
+
return;
|
|
584
1007
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
+
});
|
|
599
1022
|
}
|
|
600
1023
|
/**
|
|
601
1024
|
* Configure OAuth callback handling
|
|
@@ -618,9 +1041,28 @@ var MCPClientManager = class {
|
|
|
618
1041
|
return getNamespacedData(this.mcpConnections, "tools");
|
|
619
1042
|
}
|
|
620
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
|
+
/**
|
|
621
1061
|
* @returns a set of tools that you can use with the AI SDK
|
|
622
1062
|
*/
|
|
623
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.`);
|
|
624
1066
|
return Object.fromEntries(getNamespacedData(this.mcpConnections, "tools").map((tool) => {
|
|
625
1067
|
return [`tool_${tool.serverId.replace(/-/g, "")}_${tool.name}`, {
|
|
626
1068
|
description: tool.description,
|
|
@@ -650,10 +1092,18 @@ var MCPClientManager = class {
|
|
|
650
1092
|
return this.getAITools();
|
|
651
1093
|
}
|
|
652
1094
|
/**
|
|
653
|
-
* 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).
|
|
654
1103
|
*/
|
|
655
1104
|
async closeAllConnections() {
|
|
656
1105
|
const ids = Object.keys(this.mcpConnections);
|
|
1106
|
+
for (const id of ids) this.mcpConnections[id].cancelDiscovery();
|
|
657
1107
|
await Promise.all(ids.map(async (id) => {
|
|
658
1108
|
await this.mcpConnections[id].client.close();
|
|
659
1109
|
}));
|
|
@@ -670,6 +1120,7 @@ var MCPClientManager = class {
|
|
|
670
1120
|
*/
|
|
671
1121
|
async closeConnection(id) {
|
|
672
1122
|
if (!this.mcpConnections[id]) throw new Error(`Connection with id "${id}" does not exist.`);
|
|
1123
|
+
this.mcpConnections[id].cancelDiscovery();
|
|
673
1124
|
await this.mcpConnections[id].client.close();
|
|
674
1125
|
delete this.mcpConnections[id];
|
|
675
1126
|
const store = this._connectionDisposables.get(id);
|
|
@@ -677,13 +1128,29 @@ var MCPClientManager = class {
|
|
|
677
1128
|
this._connectionDisposables.delete(id);
|
|
678
1129
|
}
|
|
679
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
|
+
/**
|
|
680
1147
|
* Dispose the manager and all resources.
|
|
681
1148
|
*/
|
|
682
1149
|
async dispose() {
|
|
683
1150
|
try {
|
|
684
1151
|
await this.closeAllConnections();
|
|
685
1152
|
} finally {
|
|
686
|
-
this.
|
|
1153
|
+
this._onServerStateChanged.dispose();
|
|
687
1154
|
this._onObservabilityEvent.dispose();
|
|
688
1155
|
}
|
|
689
1156
|
}
|
|
@@ -745,96 +1212,7 @@ function getNamespacedData(mcpClients, type) {
|
|
|
745
1212
|
}
|
|
746
1213
|
|
|
747
1214
|
//#endregion
|
|
748
|
-
//#region ../agents/dist/
|
|
749
|
-
var DurableObjectOAuthClientProvider = class {
|
|
750
|
-
constructor(storage, clientName, baseRedirectUrl) {
|
|
751
|
-
this.storage = storage;
|
|
752
|
-
this.clientName = clientName;
|
|
753
|
-
this.baseRedirectUrl = baseRedirectUrl;
|
|
754
|
-
}
|
|
755
|
-
get clientMetadata() {
|
|
756
|
-
return {
|
|
757
|
-
client_name: this.clientName,
|
|
758
|
-
client_uri: this.clientUri,
|
|
759
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
760
|
-
redirect_uris: [this.redirectUrl],
|
|
761
|
-
response_types: ["code"],
|
|
762
|
-
token_endpoint_auth_method: "none"
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
get clientUri() {
|
|
766
|
-
return new URL(this.redirectUrl).origin;
|
|
767
|
-
}
|
|
768
|
-
get redirectUrl() {
|
|
769
|
-
return `${this.baseRedirectUrl}/${this.serverId}`;
|
|
770
|
-
}
|
|
771
|
-
get clientId() {
|
|
772
|
-
if (!this._clientId_) throw new Error("Trying to access clientId before it was set");
|
|
773
|
-
return this._clientId_;
|
|
774
|
-
}
|
|
775
|
-
set clientId(clientId_) {
|
|
776
|
-
this._clientId_ = clientId_;
|
|
777
|
-
}
|
|
778
|
-
get serverId() {
|
|
779
|
-
if (!this._serverId_) throw new Error("Trying to access serverId before it was set");
|
|
780
|
-
return this._serverId_;
|
|
781
|
-
}
|
|
782
|
-
set serverId(serverId_) {
|
|
783
|
-
this._serverId_ = serverId_;
|
|
784
|
-
}
|
|
785
|
-
keyPrefix(clientId) {
|
|
786
|
-
return `/${this.clientName}/${this.serverId}/${clientId}`;
|
|
787
|
-
}
|
|
788
|
-
clientInfoKey(clientId) {
|
|
789
|
-
return `${this.keyPrefix(clientId)}/client_info/`;
|
|
790
|
-
}
|
|
791
|
-
async clientInformation() {
|
|
792
|
-
if (!this._clientId_) return;
|
|
793
|
-
return await this.storage.get(this.clientInfoKey(this.clientId)) ?? void 0;
|
|
794
|
-
}
|
|
795
|
-
async saveClientInformation(clientInformation) {
|
|
796
|
-
await this.storage.put(this.clientInfoKey(clientInformation.client_id), clientInformation);
|
|
797
|
-
this.clientId = clientInformation.client_id;
|
|
798
|
-
}
|
|
799
|
-
tokenKey(clientId) {
|
|
800
|
-
return `${this.keyPrefix(clientId)}/token`;
|
|
801
|
-
}
|
|
802
|
-
async tokens() {
|
|
803
|
-
if (!this._clientId_) return;
|
|
804
|
-
return await this.storage.get(this.tokenKey(this.clientId)) ?? void 0;
|
|
805
|
-
}
|
|
806
|
-
async saveTokens(tokens) {
|
|
807
|
-
await this.storage.put(this.tokenKey(this.clientId), tokens);
|
|
808
|
-
}
|
|
809
|
-
get authUrl() {
|
|
810
|
-
return this._authUrl_;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Because this operates on the server side (but we need browser auth), we send this url back to the user
|
|
814
|
-
* and require user interact to initiate the redirect flow
|
|
815
|
-
*/
|
|
816
|
-
async redirectToAuthorization(authUrl) {
|
|
817
|
-
const stateToken = nanoid();
|
|
818
|
-
authUrl.searchParams.set("state", stateToken);
|
|
819
|
-
this._authUrl_ = authUrl.toString();
|
|
820
|
-
}
|
|
821
|
-
codeVerifierKey(clientId) {
|
|
822
|
-
return `${this.keyPrefix(clientId)}/code_verifier`;
|
|
823
|
-
}
|
|
824
|
-
async saveCodeVerifier(verifier) {
|
|
825
|
-
const key = this.codeVerifierKey(this.clientId);
|
|
826
|
-
if (await this.storage.get(key)) return;
|
|
827
|
-
await this.storage.put(key, verifier);
|
|
828
|
-
}
|
|
829
|
-
async codeVerifier() {
|
|
830
|
-
const codeVerifier = await this.storage.get(this.codeVerifierKey(this.clientId));
|
|
831
|
-
if (!codeVerifier) throw new Error("No code verifier found");
|
|
832
|
-
return codeVerifier;
|
|
833
|
-
}
|
|
834
|
-
};
|
|
835
|
-
|
|
836
|
-
//#endregion
|
|
837
|
-
//#region ../agents/dist/src-C8K3lu37.js
|
|
1215
|
+
//#region ../agents/dist/src-D_KKH_4c.js
|
|
838
1216
|
/**
|
|
839
1217
|
* A generic observability implementation that logs events to the console.
|
|
840
1218
|
*/
|
|
@@ -953,9 +1331,8 @@ var Agent = class Agent$1 extends Server {
|
|
|
953
1331
|
super(ctx, env$1);
|
|
954
1332
|
this._state = DEFAULT_STATE;
|
|
955
1333
|
this._disposables = new DisposableStore();
|
|
956
|
-
this.
|
|
1334
|
+
this._destroyed = false;
|
|
957
1335
|
this._ParentClass = Object.getPrototypeOf(this).constructor;
|
|
958
|
-
this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1");
|
|
959
1336
|
this.initialState = DEFAULT_STATE;
|
|
960
1337
|
this.observability = genericObservability;
|
|
961
1338
|
this._flushingQueue = false;
|
|
@@ -993,27 +1370,37 @@ var Agent = class Agent$1 extends Server {
|
|
|
993
1370
|
}
|
|
994
1371
|
});
|
|
995
1372
|
if (row.type === "cron") {
|
|
1373
|
+
if (this._destroyed) return;
|
|
996
1374
|
const nextExecutionTime = getNextCronTime(row.cron);
|
|
997
1375
|
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1e3);
|
|
998
1376
|
this.sql`
|
|
999
1377
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
1000
1378
|
`;
|
|
1001
|
-
} else
|
|
1379
|
+
} else {
|
|
1380
|
+
if (this._destroyed) return;
|
|
1381
|
+
this.sql`
|
|
1002
1382
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
1003
1383
|
`;
|
|
1384
|
+
}
|
|
1004
1385
|
}
|
|
1386
|
+
if (this._destroyed) return;
|
|
1005
1387
|
await this._scheduleNextAlarm();
|
|
1006
1388
|
};
|
|
1007
1389
|
if (!wrappedClasses.has(this.constructor)) {
|
|
1008
1390
|
this._autoWrapCustomMethods();
|
|
1009
1391
|
wrappedClasses.add(this.constructor);
|
|
1010
1392
|
}
|
|
1011
|
-
this.
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
+
`;
|
|
1017
1404
|
this.sql`
|
|
1018
1405
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
1019
1406
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -1028,34 +1415,25 @@ var Agent = class Agent$1 extends Server {
|
|
|
1028
1415
|
created_at INTEGER DEFAULT (unixepoch())
|
|
1029
1416
|
)
|
|
1030
1417
|
`;
|
|
1031
|
-
this.ctx.blockConcurrencyWhile(async () => {
|
|
1032
|
-
return this._tryCatch(async () => {
|
|
1033
|
-
this.sql`
|
|
1034
|
-
CREATE TABLE IF NOT EXISTS cf_agents_schedules (
|
|
1035
|
-
id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
|
|
1036
|
-
callback TEXT,
|
|
1037
|
-
payload TEXT,
|
|
1038
|
-
type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron')),
|
|
1039
|
-
time INTEGER,
|
|
1040
|
-
delayInSeconds INTEGER,
|
|
1041
|
-
cron TEXT,
|
|
1042
|
-
created_at INTEGER DEFAULT (unixepoch())
|
|
1043
|
-
)
|
|
1044
|
-
`;
|
|
1045
|
-
await this.alarm();
|
|
1046
|
-
});
|
|
1047
|
-
});
|
|
1048
1418
|
this.sql`
|
|
1049
|
-
CREATE TABLE IF NOT EXISTS
|
|
1050
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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())
|
|
1057
1428
|
)
|
|
1058
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
|
+
}));
|
|
1059
1437
|
const _onRequest = this.onRequest.bind(this);
|
|
1060
1438
|
this.onRequest = (request) => {
|
|
1061
1439
|
return agentContext.run({
|
|
@@ -1064,20 +1442,9 @@ var Agent = class Agent$1 extends Server {
|
|
|
1064
1442
|
request,
|
|
1065
1443
|
email: void 0
|
|
1066
1444
|
}, async () => {
|
|
1067
|
-
await this.
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
this.broadcastMcpServers();
|
|
1071
|
-
if (result.authSuccess) {
|
|
1072
|
-
this.clearMcpServerAuthUrl(result.serverId);
|
|
1073
|
-
this.mcp.establishConnection(result.serverId).catch((error) => {
|
|
1074
|
-
console.error("Background connection failed:", error);
|
|
1075
|
-
}).finally(() => {
|
|
1076
|
-
this.broadcastMcpServers();
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
|
-
return this.handleOAuthCallbackResponse(result, request);
|
|
1080
|
-
}
|
|
1445
|
+
await this.mcp.ensureJsonSchema();
|
|
1446
|
+
const oauthResponse = await this.handleMcpOAuthCallback(request);
|
|
1447
|
+
if (oauthResponse) return oauthResponse;
|
|
1081
1448
|
return this._tryCatch(() => _onRequest(request));
|
|
1082
1449
|
});
|
|
1083
1450
|
};
|
|
@@ -1089,6 +1456,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1089
1456
|
request: void 0,
|
|
1090
1457
|
email: void 0
|
|
1091
1458
|
}, async () => {
|
|
1459
|
+
await this.mcp.ensureJsonSchema();
|
|
1092
1460
|
if (typeof message !== "string") return this._tryCatch(() => _onMessage(connection, message));
|
|
1093
1461
|
let parsed;
|
|
1094
1462
|
try {
|
|
@@ -1153,7 +1521,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1153
1521
|
connection,
|
|
1154
1522
|
request: ctx$1.request,
|
|
1155
1523
|
email: void 0
|
|
1156
|
-
}, () => {
|
|
1524
|
+
}, async () => {
|
|
1157
1525
|
if (this.state) connection.send(JSON.stringify({
|
|
1158
1526
|
state: this.state,
|
|
1159
1527
|
type: MessageType.CF_AGENT_STATE
|
|
@@ -1181,7 +1549,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1181
1549
|
email: void 0
|
|
1182
1550
|
}, async () => {
|
|
1183
1551
|
await this._tryCatch(async () => {
|
|
1184
|
-
await this.
|
|
1552
|
+
await this.mcp.restoreConnectionsFromStorage(this.name);
|
|
1185
1553
|
this.broadcastMcpServers();
|
|
1186
1554
|
return _onStart(props);
|
|
1187
1555
|
});
|
|
@@ -1588,7 +1956,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1588
1956
|
async _scheduleNextAlarm() {
|
|
1589
1957
|
const result = this.sql`
|
|
1590
1958
|
SELECT time FROM cf_agents_schedules
|
|
1591
|
-
WHERE time
|
|
1959
|
+
WHERE time >= ${Math.floor(Date.now() / 1e3)}
|
|
1592
1960
|
ORDER BY time ASC
|
|
1593
1961
|
LIMIT 1
|
|
1594
1962
|
`;
|
|
@@ -1602,15 +1970,18 @@ var Agent = class Agent$1 extends Server {
|
|
|
1602
1970
|
* Destroy the Agent, removing all state and scheduled tasks
|
|
1603
1971
|
*/
|
|
1604
1972
|
async destroy() {
|
|
1973
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1605
1974
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
1606
1975
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
1607
|
-
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1608
1976
|
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
1609
1977
|
await this.ctx.storage.deleteAlarm();
|
|
1610
1978
|
await this.ctx.storage.deleteAll();
|
|
1611
1979
|
this._disposables.dispose();
|
|
1612
|
-
await this.mcp.dispose
|
|
1613
|
-
this.
|
|
1980
|
+
await this.mcp.dispose();
|
|
1981
|
+
this._destroyed = true;
|
|
1982
|
+
setTimeout(() => {
|
|
1983
|
+
this.ctx.abort("destroyed");
|
|
1984
|
+
}, 0);
|
|
1614
1985
|
this.observability?.emit({
|
|
1615
1986
|
displayMessage: "Agent destroyed",
|
|
1616
1987
|
id: nanoid(),
|
|
@@ -1626,43 +1997,6 @@ var Agent = class Agent$1 extends Server {
|
|
|
1626
1997
|
_isCallable(method) {
|
|
1627
1998
|
return callableMetadata.has(this[method]);
|
|
1628
1999
|
}
|
|
1629
|
-
async _ensureMcpStateRestored() {
|
|
1630
|
-
if (this._mcpStateRestored) return;
|
|
1631
|
-
this._mcpStateRestored = true;
|
|
1632
|
-
const servers = this.sql`
|
|
1633
|
-
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options
|
|
1634
|
-
FROM cf_agents_mcp_servers
|
|
1635
|
-
`;
|
|
1636
|
-
if (!servers || !Array.isArray(servers) || servers.length === 0) return;
|
|
1637
|
-
for (const server of servers) if (server.callback_url) this.mcp.registerCallbackUrl(`${server.callback_url}/${server.id}`);
|
|
1638
|
-
for (const server of servers) if (!!server.auth_url) {
|
|
1639
|
-
const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage, this.name, server.callback_url);
|
|
1640
|
-
authProvider.serverId = server.id;
|
|
1641
|
-
if (server.client_id) authProvider.clientId = server.client_id;
|
|
1642
|
-
const parsedOptions = server.server_options ? JSON.parse(server.server_options) : void 0;
|
|
1643
|
-
const conn = new MCPClientConnection(new URL(server.server_url), {
|
|
1644
|
-
name: this.name,
|
|
1645
|
-
version: "1.0.0"
|
|
1646
|
-
}, {
|
|
1647
|
-
client: parsedOptions?.client ?? {},
|
|
1648
|
-
transport: {
|
|
1649
|
-
...parsedOptions?.transport ?? {},
|
|
1650
|
-
type: parsedOptions?.transport?.type ?? "auto",
|
|
1651
|
-
authProvider
|
|
1652
|
-
}
|
|
1653
|
-
});
|
|
1654
|
-
conn.connectionState = "authenticating";
|
|
1655
|
-
this.mcp.mcpConnections[server.id] = conn;
|
|
1656
|
-
} else {
|
|
1657
|
-
const parsedOptions = server.server_options ? JSON.parse(server.server_options) : void 0;
|
|
1658
|
-
this._connectToMcpServerInternal(server.name, server.server_url, server.callback_url, parsedOptions, {
|
|
1659
|
-
id: server.id,
|
|
1660
|
-
oauthClientId: server.client_id ?? void 0
|
|
1661
|
-
}).catch((error) => {
|
|
1662
|
-
console.error(`Error restoring ${server.id}:`, error);
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
2000
|
/**
|
|
1667
2001
|
* Connect to a new MCP Server
|
|
1668
2002
|
*
|
|
@@ -1671,7 +2005,8 @@ var Agent = class Agent$1 extends Server {
|
|
|
1671
2005
|
* @param callbackHost Base host for the agent, used for the redirect URI. If not provided, will be derived from the current request.
|
|
1672
2006
|
* @param agentsPrefix agents routing prefix if not using `agents`
|
|
1673
2007
|
* @param options MCP client and transport options
|
|
1674
|
-
* @returns authUrl
|
|
2008
|
+
* @returns Server id and state - either "authenticating" with authUrl, or "ready"
|
|
2009
|
+
* @throws If connection or discovery fails
|
|
1675
2010
|
*/
|
|
1676
2011
|
async addMcpServer(serverName, url, callbackHost, agentsPrefix = "agents", options) {
|
|
1677
2012
|
let resolvedCallbackHost = callbackHost;
|
|
@@ -1682,29 +2017,10 @@ var Agent = class Agent$1 extends Server {
|
|
|
1682
2017
|
resolvedCallbackHost = `${requestUrl.protocol}//${requestUrl.host}`;
|
|
1683
2018
|
}
|
|
1684
2019
|
const callbackUrl = `${resolvedCallbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
INSERT
|
|
1688
|
-
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1689
|
-
VALUES (
|
|
1690
|
-
${result.id},
|
|
1691
|
-
${serverName},
|
|
1692
|
-
${url},
|
|
1693
|
-
${result.clientId ?? null},
|
|
1694
|
-
${result.authUrl ?? null},
|
|
1695
|
-
${callbackUrl},
|
|
1696
|
-
${options ? JSON.stringify(options) : null}
|
|
1697
|
-
);
|
|
1698
|
-
`;
|
|
1699
|
-
this.broadcastMcpServers();
|
|
1700
|
-
return result;
|
|
1701
|
-
}
|
|
1702
|
-
async _connectToMcpServerInternal(_serverName, url, callbackUrl, options, reconnect) {
|
|
2020
|
+
await this.mcp.ensureJsonSchema();
|
|
2021
|
+
const id = nanoid(8);
|
|
1703
2022
|
const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage, this.name, callbackUrl);
|
|
1704
|
-
|
|
1705
|
-
authProvider.serverId = reconnect.id;
|
|
1706
|
-
if (reconnect.oauthClientId) authProvider.clientId = reconnect.oauthClientId;
|
|
1707
|
-
}
|
|
2023
|
+
authProvider.serverId = id;
|
|
1708
2024
|
const transportType = options?.transport?.type ?? "auto";
|
|
1709
2025
|
let headerTransportOpts = {};
|
|
1710
2026
|
if (options?.transport?.headers) headerTransportOpts = {
|
|
@@ -1714,40 +2030,33 @@ var Agent = class Agent$1 extends Server {
|
|
|
1714
2030
|
}) },
|
|
1715
2031
|
requestInit: { headers: options?.transport?.headers }
|
|
1716
2032
|
};
|
|
1717
|
-
|
|
2033
|
+
await this.mcp.registerServer(id, {
|
|
2034
|
+
url,
|
|
2035
|
+
name: serverName,
|
|
2036
|
+
callbackUrl,
|
|
1718
2037
|
client: options?.client,
|
|
1719
|
-
reconnect,
|
|
1720
2038
|
transport: {
|
|
1721
2039
|
...headerTransportOpts,
|
|
1722
2040
|
authProvider,
|
|
1723
2041
|
type: transportType
|
|
1724
2042
|
}
|
|
1725
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}`);
|
|
1726
2053
|
return {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
id
|
|
2054
|
+
id,
|
|
2055
|
+
state: MCPConnectionState.READY
|
|
1730
2056
|
};
|
|
1731
2057
|
}
|
|
1732
2058
|
async removeMcpServer(id) {
|
|
1733
|
-
this.mcp.
|
|
1734
|
-
this.mcp.unregisterCallbackUrl(id);
|
|
1735
|
-
this.sql`
|
|
1736
|
-
DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
|
|
1737
|
-
`;
|
|
1738
|
-
this.broadcastMcpServers();
|
|
1739
|
-
}
|
|
1740
|
-
/**
|
|
1741
|
-
* Clear the auth_url for an MCP server after successful OAuth authentication
|
|
1742
|
-
* This prevents the agent from continuously asking for OAuth on reconnect
|
|
1743
|
-
* @param id The server ID to clear auth_url for
|
|
1744
|
-
*/
|
|
1745
|
-
clearMcpServerAuthUrl(id) {
|
|
1746
|
-
this.sql`
|
|
1747
|
-
UPDATE cf_agents_mcp_servers
|
|
1748
|
-
SET auth_url = NULL
|
|
1749
|
-
WHERE id = ${id}
|
|
1750
|
-
`;
|
|
2059
|
+
await this.mcp.removeServer(id);
|
|
1751
2060
|
}
|
|
1752
2061
|
getMcpServers() {
|
|
1753
2062
|
const mcpState = {
|
|
@@ -1756,18 +2065,18 @@ var Agent = class Agent$1 extends Server {
|
|
|
1756
2065
|
servers: {},
|
|
1757
2066
|
tools: this.mcp.listTools()
|
|
1758
2067
|
};
|
|
1759
|
-
const servers = this.
|
|
1760
|
-
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1761
|
-
`;
|
|
2068
|
+
const servers = this.mcp.listServers();
|
|
1762
2069
|
if (servers && Array.isArray(servers) && servers.length > 0) for (const server of servers) {
|
|
1763
2070
|
const serverConn = this.mcp.mcpConnections[server.id];
|
|
2071
|
+
let defaultState = "not-connected";
|
|
2072
|
+
if (!serverConn && server.auth_url) defaultState = "authenticating";
|
|
1764
2073
|
mcpState.servers[server.id] = {
|
|
1765
2074
|
auth_url: server.auth_url,
|
|
1766
2075
|
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1767
2076
|
instructions: serverConn?.instructions ?? null,
|
|
1768
2077
|
name: server.name,
|
|
1769
2078
|
server_url: server.server_url,
|
|
1770
|
-
state: serverConn?.connectionState ??
|
|
2079
|
+
state: serverConn?.connectionState ?? defaultState
|
|
1771
2080
|
};
|
|
1772
2081
|
}
|
|
1773
2082
|
return mcpState;
|
|
@@ -1779,6 +2088,28 @@ var Agent = class Agent$1 extends Server {
|
|
|
1779
2088
|
}));
|
|
1780
2089
|
}
|
|
1781
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
|
+
/**
|
|
1782
2113
|
* Handle OAuth callback response using MCPClientManager configuration
|
|
1783
2114
|
* @param result OAuth callback result
|
|
1784
2115
|
* @param request The original request (needed for base URL)
|