hono-agents 0.0.0-4dafe91 → 0.0.0-4e285b2
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 +493 -333
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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
|
-
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 { ElicitRequestSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
9
|
import { parseCronExpression } from "cron-schedule";
|
|
9
10
|
import "cloudflare:email";
|
|
@@ -43,7 +44,96 @@ function camelCaseToKebabCase(str) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
//#endregion
|
|
46
|
-
//#region ../agents/dist/client-
|
|
47
|
+
//#region ../agents/dist/do-oauth-client-provider-D2P1lSft.js
|
|
48
|
+
var DurableObjectOAuthClientProvider = class {
|
|
49
|
+
constructor(storage, clientName, baseRedirectUrl) {
|
|
50
|
+
this.storage = storage;
|
|
51
|
+
this.clientName = clientName;
|
|
52
|
+
this.baseRedirectUrl = baseRedirectUrl;
|
|
53
|
+
}
|
|
54
|
+
get clientMetadata() {
|
|
55
|
+
return {
|
|
56
|
+
client_name: this.clientName,
|
|
57
|
+
client_uri: this.clientUri,
|
|
58
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
59
|
+
redirect_uris: [this.redirectUrl],
|
|
60
|
+
response_types: ["code"],
|
|
61
|
+
token_endpoint_auth_method: "none"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
get clientUri() {
|
|
65
|
+
return new URL(this.redirectUrl).origin;
|
|
66
|
+
}
|
|
67
|
+
get redirectUrl() {
|
|
68
|
+
return `${this.baseRedirectUrl}/${this.serverId}`;
|
|
69
|
+
}
|
|
70
|
+
get clientId() {
|
|
71
|
+
if (!this._clientId_) throw new Error("Trying to access clientId before it was set");
|
|
72
|
+
return this._clientId_;
|
|
73
|
+
}
|
|
74
|
+
set clientId(clientId_) {
|
|
75
|
+
this._clientId_ = clientId_;
|
|
76
|
+
}
|
|
77
|
+
get serverId() {
|
|
78
|
+
if (!this._serverId_) throw new Error("Trying to access serverId before it was set");
|
|
79
|
+
return this._serverId_;
|
|
80
|
+
}
|
|
81
|
+
set serverId(serverId_) {
|
|
82
|
+
this._serverId_ = serverId_;
|
|
83
|
+
}
|
|
84
|
+
keyPrefix(clientId) {
|
|
85
|
+
return `/${this.clientName}/${this.serverId}/${clientId}`;
|
|
86
|
+
}
|
|
87
|
+
clientInfoKey(clientId) {
|
|
88
|
+
return `${this.keyPrefix(clientId)}/client_info/`;
|
|
89
|
+
}
|
|
90
|
+
async clientInformation() {
|
|
91
|
+
if (!this._clientId_) return;
|
|
92
|
+
return await this.storage.get(this.clientInfoKey(this.clientId)) ?? void 0;
|
|
93
|
+
}
|
|
94
|
+
async saveClientInformation(clientInformation) {
|
|
95
|
+
await this.storage.put(this.clientInfoKey(clientInformation.client_id), clientInformation);
|
|
96
|
+
this.clientId = clientInformation.client_id;
|
|
97
|
+
}
|
|
98
|
+
tokenKey(clientId) {
|
|
99
|
+
return `${this.keyPrefix(clientId)}/token`;
|
|
100
|
+
}
|
|
101
|
+
async tokens() {
|
|
102
|
+
if (!this._clientId_) return;
|
|
103
|
+
return await this.storage.get(this.tokenKey(this.clientId)) ?? void 0;
|
|
104
|
+
}
|
|
105
|
+
async saveTokens(tokens) {
|
|
106
|
+
await this.storage.put(this.tokenKey(this.clientId), tokens);
|
|
107
|
+
}
|
|
108
|
+
get authUrl() {
|
|
109
|
+
return this._authUrl_;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Because this operates on the server side (but we need browser auth), we send this url back to the user
|
|
113
|
+
* and require user interact to initiate the redirect flow
|
|
114
|
+
*/
|
|
115
|
+
redirectToAuthorization(authUrl) {
|
|
116
|
+
const stateToken = nanoid();
|
|
117
|
+
authUrl.searchParams.set("state", stateToken);
|
|
118
|
+
this._authUrl_ = authUrl.toString();
|
|
119
|
+
}
|
|
120
|
+
codeVerifierKey(clientId) {
|
|
121
|
+
return `${this.keyPrefix(clientId)}/code_verifier`;
|
|
122
|
+
}
|
|
123
|
+
async saveCodeVerifier(verifier) {
|
|
124
|
+
const key = this.codeVerifierKey(this.clientId);
|
|
125
|
+
if (await this.storage.get(key)) return;
|
|
126
|
+
await this.storage.put(key, verifier);
|
|
127
|
+
}
|
|
128
|
+
async codeVerifier() {
|
|
129
|
+
const codeVerifier = await this.storage.get(this.codeVerifierKey(this.clientId));
|
|
130
|
+
if (!codeVerifier) throw new Error("No code verifier found");
|
|
131
|
+
return codeVerifier;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region ../agents/dist/client-DpkZyXgJ.js
|
|
47
137
|
function toDisposable(fn) {
|
|
48
138
|
return { dispose: fn };
|
|
49
139
|
}
|
|
@@ -91,74 +181,6 @@ function isTransportNotImplemented(error) {
|
|
|
91
181
|
const msg = toErrorMessage(error);
|
|
92
182
|
return msg.includes("404") || msg.includes("405") || msg.includes("Not Implemented") || msg.includes("not implemented");
|
|
93
183
|
}
|
|
94
|
-
var SSEEdgeClientTransport = class extends SSEClientTransport {
|
|
95
|
-
/**
|
|
96
|
-
* Creates a new EdgeSSEClientTransport, which overrides fetch to be compatible with the CF workers environment
|
|
97
|
-
*/
|
|
98
|
-
constructor(url, options) {
|
|
99
|
-
const fetchOverride = async (fetchUrl, fetchInit = {}) => {
|
|
100
|
-
const headers = await this.authHeaders();
|
|
101
|
-
const workerOptions = {
|
|
102
|
-
...fetchInit,
|
|
103
|
-
headers: {
|
|
104
|
-
...options.requestInit?.headers,
|
|
105
|
-
...fetchInit?.headers,
|
|
106
|
-
...headers
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
delete workerOptions.mode;
|
|
110
|
-
return options.eventSourceInit?.fetch?.(fetchUrl, workerOptions) || fetch(fetchUrl, workerOptions);
|
|
111
|
-
};
|
|
112
|
-
super(url, {
|
|
113
|
-
...options,
|
|
114
|
-
eventSourceInit: {
|
|
115
|
-
...options.eventSourceInit,
|
|
116
|
-
fetch: fetchOverride
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
this.authProvider = options.authProvider;
|
|
120
|
-
}
|
|
121
|
-
async authHeaders() {
|
|
122
|
-
if (this.authProvider) {
|
|
123
|
-
const tokens = await this.authProvider.tokens();
|
|
124
|
-
if (tokens) return { Authorization: `Bearer ${tokens.access_token}` };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
var StreamableHTTPEdgeClientTransport = class extends StreamableHTTPClientTransport {
|
|
129
|
-
/**
|
|
130
|
-
* Creates a new StreamableHTTPEdgeClientTransport, which overrides fetch to be compatible with the CF workers environment
|
|
131
|
-
*/
|
|
132
|
-
constructor(url, options) {
|
|
133
|
-
const fetchOverride = async (fetchUrl, fetchInit = {}) => {
|
|
134
|
-
const headers = await this.authHeaders();
|
|
135
|
-
const workerOptions = {
|
|
136
|
-
...fetchInit,
|
|
137
|
-
headers: {
|
|
138
|
-
...options.requestInit?.headers,
|
|
139
|
-
...fetchInit?.headers,
|
|
140
|
-
...headers
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
delete workerOptions.mode;
|
|
144
|
-
return options.requestInit?.fetch?.(fetchUrl, workerOptions) || fetch(fetchUrl, workerOptions);
|
|
145
|
-
};
|
|
146
|
-
super(url, {
|
|
147
|
-
...options,
|
|
148
|
-
requestInit: {
|
|
149
|
-
...options.requestInit,
|
|
150
|
-
fetch: fetchOverride
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
this.authProvider = options.authProvider;
|
|
154
|
-
}
|
|
155
|
-
async authHeaders() {
|
|
156
|
-
if (this.authProvider) {
|
|
157
|
-
const tokens = await this.authProvider.tokens();
|
|
158
|
-
if (tokens) return { Authorization: `Bearer ${tokens.access_token}` };
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
184
|
var MCPClientConnection = class {
|
|
163
185
|
constructor(url, info, options = {
|
|
164
186
|
client: {},
|
|
@@ -402,8 +424,8 @@ var MCPClientConnection = class {
|
|
|
402
424
|
*/
|
|
403
425
|
getTransport(transportType) {
|
|
404
426
|
switch (transportType) {
|
|
405
|
-
case "streamable-http": return new
|
|
406
|
-
case "sse": return new
|
|
427
|
+
case "streamable-http": return new StreamableHTTPClientTransport(this.url, this.options.transport);
|
|
428
|
+
case "sse": return new SSEClientTransport(this.url, this.options.transport);
|
|
407
429
|
default: throw new Error(`Unsupported transport type: ${transportType}`);
|
|
408
430
|
}
|
|
409
431
|
}
|
|
@@ -475,6 +497,7 @@ var MCPClientConnection = class {
|
|
|
475
497
|
};
|
|
476
498
|
}
|
|
477
499
|
};
|
|
500
|
+
const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
|
|
478
501
|
/**
|
|
479
502
|
* Utility class that aggregates multiple MCP clients into one
|
|
480
503
|
*/
|
|
@@ -482,26 +505,88 @@ var MCPClientManager = class {
|
|
|
482
505
|
/**
|
|
483
506
|
* @param _name Name of the MCP client
|
|
484
507
|
* @param _version Version of the MCP Client
|
|
485
|
-
* @param
|
|
508
|
+
* @param options Storage adapter for persisting MCP server state
|
|
486
509
|
*/
|
|
487
|
-
constructor(_name, _version) {
|
|
510
|
+
constructor(_name, _version, options) {
|
|
488
511
|
this._name = _name;
|
|
489
512
|
this._version = _version;
|
|
490
513
|
this.mcpConnections = {};
|
|
491
|
-
this._callbackUrls = [];
|
|
492
514
|
this._didWarnAboutUnstableGetAITools = false;
|
|
493
515
|
this._connectionDisposables = /* @__PURE__ */ new Map();
|
|
516
|
+
this._isRestored = false;
|
|
494
517
|
this._onObservabilityEvent = new Emitter();
|
|
495
518
|
this.onObservabilityEvent = this._onObservabilityEvent.event;
|
|
496
|
-
this.
|
|
497
|
-
this.
|
|
519
|
+
this._onServerStateChanged = new Emitter();
|
|
520
|
+
this.onServerStateChanged = this._onServerStateChanged.event;
|
|
521
|
+
this._storage = options.storage;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Create an auth provider for a server
|
|
525
|
+
* @internal
|
|
526
|
+
*/
|
|
527
|
+
createAuthProvider(serverId, callbackUrl, clientName, clientId) {
|
|
528
|
+
const authProvider = new DurableObjectOAuthClientProvider(this._storage, clientName, callbackUrl);
|
|
529
|
+
authProvider.serverId = serverId;
|
|
530
|
+
if (clientId) authProvider.clientId = clientId;
|
|
531
|
+
return authProvider;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Restore MCP server connections from storage
|
|
535
|
+
* This method is called on Agent initialization to restore previously connected servers
|
|
536
|
+
*
|
|
537
|
+
* @param clientName Name to use for OAuth client (typically the agent instance name)
|
|
538
|
+
*/
|
|
539
|
+
async restoreConnectionsFromStorage(clientName) {
|
|
540
|
+
if (this._isRestored) return;
|
|
541
|
+
const servers = await this._storage.listServers();
|
|
542
|
+
if (!servers || servers.length === 0) {
|
|
543
|
+
this._isRestored = true;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
for (const server of servers) {
|
|
547
|
+
const existingConn = this.mcpConnections[server.id];
|
|
548
|
+
if (existingConn) {
|
|
549
|
+
if (existingConn.connectionState === "ready") {
|
|
550
|
+
console.warn(`[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (existingConn.connectionState === "authenticating" || existingConn.connectionState === "connecting" || existingConn.connectionState === "discovering") continue;
|
|
554
|
+
if (existingConn.connectionState === "failed") {
|
|
555
|
+
try {
|
|
556
|
+
await existingConn.client.close();
|
|
557
|
+
} catch (error) {
|
|
558
|
+
console.warn(`[MCPClientManager] Error closing failed connection ${server.id}:`, error);
|
|
559
|
+
}
|
|
560
|
+
delete this.mcpConnections[server.id];
|
|
561
|
+
this._connectionDisposables.get(server.id)?.dispose();
|
|
562
|
+
this._connectionDisposables.delete(server.id);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const parsedOptions = server.server_options ? JSON.parse(server.server_options) : null;
|
|
566
|
+
const authProvider = this.createAuthProvider(server.id, server.callback_url, clientName, server.client_id ?? void 0);
|
|
567
|
+
this.createConnection(server.id, server.server_url, {
|
|
568
|
+
client: parsedOptions?.client ?? {},
|
|
569
|
+
transport: {
|
|
570
|
+
...parsedOptions?.transport ?? {},
|
|
571
|
+
type: parsedOptions?.transport?.type ?? "auto",
|
|
572
|
+
authProvider
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
await this.connectToServer(server.id).catch((error) => {
|
|
576
|
+
console.error(`Error restoring ${server.id}:`, error);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
this._isRestored = true;
|
|
498
580
|
}
|
|
499
581
|
/**
|
|
500
582
|
* Connect to and register an MCP server
|
|
501
583
|
*
|
|
502
|
-
* @
|
|
503
|
-
*
|
|
504
|
-
*
|
|
584
|
+
* @deprecated This method is maintained for backward compatibility.
|
|
585
|
+
* For new code, use registerServer() and connectToServer() separately.
|
|
586
|
+
*
|
|
587
|
+
* @param url Server URL
|
|
588
|
+
* @param options Connection options
|
|
589
|
+
* @returns Object with server ID, auth URL (if OAuth), and client ID (if OAuth)
|
|
505
590
|
*/
|
|
506
591
|
async connect(url, options = {}) {
|
|
507
592
|
/**
|
|
@@ -511,10 +596,7 @@ var MCPClientManager = class {
|
|
|
511
596
|
* .connect() is called on at least one server.
|
|
512
597
|
* So it's safe to delay loading it until .connect() is called.
|
|
513
598
|
*/
|
|
514
|
-
|
|
515
|
-
const { jsonSchema } = await import("ai");
|
|
516
|
-
this.jsonSchema = jsonSchema;
|
|
517
|
-
}
|
|
599
|
+
await this.ensureJsonSchema();
|
|
518
600
|
const id = options.reconnect?.id ?? nanoid(8);
|
|
519
601
|
if (options.transport?.authProvider) {
|
|
520
602
|
options.transport.authProvider.serverId = id;
|
|
@@ -560,35 +642,139 @@ var MCPClientManager = class {
|
|
|
560
642
|
throw error;
|
|
561
643
|
}
|
|
562
644
|
const authUrl = options.transport?.authProvider?.authUrl;
|
|
563
|
-
if (this.mcpConnections[id].connectionState === "authenticating" && authUrl && options.transport?.authProvider?.redirectUrl) {
|
|
564
|
-
|
|
645
|
+
if (this.mcpConnections[id].connectionState === "authenticating" && authUrl && options.transport?.authProvider?.redirectUrl) return {
|
|
646
|
+
authUrl,
|
|
647
|
+
clientId: options.transport?.authProvider?.clientId,
|
|
648
|
+
id
|
|
649
|
+
};
|
|
650
|
+
return { id };
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Create an in-memory connection object and set up observability
|
|
654
|
+
* Does NOT save to storage - use registerServer() for that
|
|
655
|
+
*/
|
|
656
|
+
createConnection(id, url, options) {
|
|
657
|
+
if (this.mcpConnections[id]) return;
|
|
658
|
+
const normalizedTransport = {
|
|
659
|
+
...options.transport,
|
|
660
|
+
type: options.transport?.type ?? "auto"
|
|
661
|
+
};
|
|
662
|
+
this.mcpConnections[id] = new MCPClientConnection(new URL(url), {
|
|
663
|
+
name: this._name,
|
|
664
|
+
version: this._version
|
|
665
|
+
}, {
|
|
666
|
+
client: {
|
|
667
|
+
...defaultClientOptions,
|
|
668
|
+
...options.client
|
|
669
|
+
},
|
|
670
|
+
transport: normalizedTransport
|
|
671
|
+
});
|
|
672
|
+
const store = new DisposableStore();
|
|
673
|
+
const existing = this._connectionDisposables.get(id);
|
|
674
|
+
if (existing) existing.dispose();
|
|
675
|
+
this._connectionDisposables.set(id, store);
|
|
676
|
+
store.add(this.mcpConnections[id].onObservabilityEvent((event) => {
|
|
677
|
+
this._onObservabilityEvent.fire(event);
|
|
678
|
+
}));
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Register an MCP server connection without connecting
|
|
682
|
+
* Creates the connection object, sets up observability, and saves to storage
|
|
683
|
+
*
|
|
684
|
+
* @param id Server ID
|
|
685
|
+
* @param options Registration options including URL, name, callback URL, and connection config
|
|
686
|
+
* @returns Server ID
|
|
687
|
+
*/
|
|
688
|
+
async registerServer(id, options) {
|
|
689
|
+
this.createConnection(id, options.url, {
|
|
690
|
+
client: options.client,
|
|
691
|
+
transport: {
|
|
692
|
+
...options.transport,
|
|
693
|
+
type: options.transport?.type ?? "auto"
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
await this._storage.saveServer({
|
|
697
|
+
id,
|
|
698
|
+
name: options.name,
|
|
699
|
+
server_url: options.url,
|
|
700
|
+
callback_url: options.callbackUrl,
|
|
701
|
+
client_id: options.clientId ?? null,
|
|
702
|
+
auth_url: options.authUrl ?? null,
|
|
703
|
+
server_options: JSON.stringify({
|
|
704
|
+
client: options.client,
|
|
705
|
+
transport: options.transport
|
|
706
|
+
})
|
|
707
|
+
});
|
|
708
|
+
this._onServerStateChanged.fire();
|
|
709
|
+
return id;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Connect to an already registered MCP server and initialize the connection.
|
|
713
|
+
*
|
|
714
|
+
* For OAuth servers, this returns `{ state: "authenticating", authUrl, clientId? }`
|
|
715
|
+
* without establishing the connection. The user must complete the OAuth flow via
|
|
716
|
+
* the authUrl, which will trigger a callback handled by `handleCallbackRequest()`.
|
|
717
|
+
*
|
|
718
|
+
* For non-OAuth servers, this establishes the connection immediately and returns
|
|
719
|
+
* `{ state: "ready" }`.
|
|
720
|
+
*
|
|
721
|
+
* Updates storage with auth URL and client ID after connection.
|
|
722
|
+
*
|
|
723
|
+
* @param id Server ID (must be registered first via registerServer())
|
|
724
|
+
* @returns Connection result with current state and OAuth info (if applicable)
|
|
725
|
+
*/
|
|
726
|
+
async connectToServer(id) {
|
|
727
|
+
const conn = this.mcpConnections[id];
|
|
728
|
+
if (!conn) throw new Error(`Server ${id} is not registered. Call registerServer() first.`);
|
|
729
|
+
await conn.init();
|
|
730
|
+
const authUrl = conn.options.transport.authProvider?.authUrl;
|
|
731
|
+
if (conn.connectionState === "authenticating" && authUrl && conn.options.transport.authProvider?.redirectUrl) {
|
|
732
|
+
const clientId = conn.options.transport.authProvider?.clientId;
|
|
733
|
+
const serverRow = (await this._storage.listServers()).find((s) => s.id === id);
|
|
734
|
+
if (serverRow) await this._storage.saveServer({
|
|
735
|
+
...serverRow,
|
|
736
|
+
auth_url: authUrl,
|
|
737
|
+
client_id: clientId ?? null
|
|
738
|
+
});
|
|
739
|
+
this._onServerStateChanged.fire();
|
|
565
740
|
return {
|
|
741
|
+
state: "authenticating",
|
|
566
742
|
authUrl,
|
|
567
|
-
clientId
|
|
568
|
-
id
|
|
743
|
+
clientId
|
|
569
744
|
};
|
|
570
745
|
}
|
|
571
|
-
|
|
746
|
+
if (conn.connectionState === "ready") this._onServerStateChanged.fire();
|
|
747
|
+
return { state: "ready" };
|
|
572
748
|
}
|
|
573
|
-
isCallbackRequest(req) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
749
|
+
async isCallbackRequest(req) {
|
|
750
|
+
if (req.method !== "GET") return false;
|
|
751
|
+
if (!req.url.includes("/callback")) return false;
|
|
752
|
+
return (await this._storage.listServers()).some((server) => server.callback_url && req.url.startsWith(server.callback_url));
|
|
577
753
|
}
|
|
578
754
|
async handleCallbackRequest(req) {
|
|
579
755
|
const url = new URL(req.url);
|
|
580
|
-
const
|
|
581
|
-
return req.url.startsWith(
|
|
756
|
+
const matchingServer = (await this._storage.listServers()).find((server) => {
|
|
757
|
+
return server.callback_url && req.url.startsWith(server.callback_url);
|
|
582
758
|
});
|
|
583
|
-
if (!
|
|
759
|
+
if (!matchingServer) throw new Error(`No callback URI match found for the request url: ${req.url}. Was the request matched with \`isCallbackRequest()\`?`);
|
|
760
|
+
const serverId = matchingServer.id;
|
|
584
761
|
const code = url.searchParams.get("code");
|
|
585
762
|
const state = url.searchParams.get("state");
|
|
586
|
-
const
|
|
587
|
-
const
|
|
763
|
+
const error = url.searchParams.get("error");
|
|
764
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
765
|
+
if (error) return {
|
|
766
|
+
serverId,
|
|
767
|
+
authSuccess: false,
|
|
768
|
+
authError: errorDescription || error
|
|
769
|
+
};
|
|
588
770
|
if (!code) throw new Error("Unauthorized: no code provided");
|
|
589
771
|
if (!state) throw new Error("Unauthorized: no state provided");
|
|
590
772
|
if (this.mcpConnections[serverId] === void 0) throw new Error(`Could not find serverId: ${serverId}`);
|
|
591
|
-
if (this.mcpConnections[serverId].connectionState
|
|
773
|
+
if (this.mcpConnections[serverId].connectionState === "ready") return {
|
|
774
|
+
serverId,
|
|
775
|
+
authSuccess: true
|
|
776
|
+
};
|
|
777
|
+
if (this.mcpConnections[serverId].connectionState !== "authenticating") throw new Error(`Failed to authenticate: the client is in "${this.mcpConnections[serverId].connectionState}" state, expected "authenticating"`);
|
|
592
778
|
const conn = this.mcpConnections[serverId];
|
|
593
779
|
if (!conn.options.transport.authProvider) throw new Error("Trying to finalize authentication for a server connection without an authProvider");
|
|
594
780
|
const clientId = conn.options.transport.authProvider.clientId || state;
|
|
@@ -596,15 +782,19 @@ var MCPClientManager = class {
|
|
|
596
782
|
conn.options.transport.authProvider.serverId = serverId;
|
|
597
783
|
try {
|
|
598
784
|
await conn.completeAuthorization(code);
|
|
785
|
+
await this._storage.clearAuthUrl(serverId);
|
|
786
|
+
this._onServerStateChanged.fire();
|
|
599
787
|
return {
|
|
600
788
|
serverId,
|
|
601
789
|
authSuccess: true
|
|
602
790
|
};
|
|
603
|
-
} catch (error) {
|
|
791
|
+
} catch (error$1) {
|
|
792
|
+
const errorMessage = error$1 instanceof Error ? error$1.message : String(error$1);
|
|
793
|
+
this._onServerStateChanged.fire();
|
|
604
794
|
return {
|
|
605
795
|
serverId,
|
|
606
796
|
authSuccess: false,
|
|
607
|
-
authError:
|
|
797
|
+
authError: errorMessage
|
|
608
798
|
};
|
|
609
799
|
}
|
|
610
800
|
}
|
|
@@ -627,7 +817,7 @@ var MCPClientManager = class {
|
|
|
627
817
|
}
|
|
628
818
|
try {
|
|
629
819
|
await conn.establishConnection();
|
|
630
|
-
this.
|
|
820
|
+
this._onServerStateChanged.fire();
|
|
631
821
|
} catch (error) {
|
|
632
822
|
const url = conn.url.toString();
|
|
633
823
|
this._onObservabilityEvent.fire({
|
|
@@ -642,23 +832,10 @@ var MCPClientManager = class {
|
|
|
642
832
|
timestamp: Date.now(),
|
|
643
833
|
id: nanoid()
|
|
644
834
|
});
|
|
835
|
+
this._onServerStateChanged.fire();
|
|
645
836
|
}
|
|
646
837
|
}
|
|
647
838
|
/**
|
|
648
|
-
* Register a callback URL for OAuth handling
|
|
649
|
-
* @param url The callback URL to register
|
|
650
|
-
*/
|
|
651
|
-
registerCallbackUrl(url) {
|
|
652
|
-
if (!this._callbackUrls.includes(url)) this._callbackUrls.push(url);
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Unregister a callback URL
|
|
656
|
-
* @param serverId The server ID whose callback URL should be removed
|
|
657
|
-
*/
|
|
658
|
-
unregisterCallbackUrl(serverId) {
|
|
659
|
-
this._callbackUrls = this._callbackUrls.filter((url) => !url.endsWith(`/${serverId}`));
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
839
|
* Configure OAuth callback handling
|
|
663
840
|
* @param config OAuth callback configuration
|
|
664
841
|
*/
|
|
@@ -679,9 +856,28 @@ var MCPClientManager = class {
|
|
|
679
856
|
return getNamespacedData(this.mcpConnections, "tools");
|
|
680
857
|
}
|
|
681
858
|
/**
|
|
859
|
+
* Lazy-loads the jsonSchema function from the AI SDK.
|
|
860
|
+
*
|
|
861
|
+
* This defers importing the "ai" package until it's actually needed, which helps reduce
|
|
862
|
+
* initial bundle size and startup time. The jsonSchema function is required for converting
|
|
863
|
+
* MCP tools into AI SDK tool definitions via getAITools().
|
|
864
|
+
*
|
|
865
|
+
* @internal This method is for internal use only. It's automatically called before operations
|
|
866
|
+
* that need jsonSchema (like getAITools() or OAuth flows). External consumers should not need
|
|
867
|
+
* to call this directly.
|
|
868
|
+
*/
|
|
869
|
+
async ensureJsonSchema() {
|
|
870
|
+
if (!this.jsonSchema) {
|
|
871
|
+
const { jsonSchema } = await import("ai");
|
|
872
|
+
this.jsonSchema = jsonSchema;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
682
876
|
* @returns a set of tools that you can use with the AI SDK
|
|
683
877
|
*/
|
|
684
878
|
getAITools() {
|
|
879
|
+
if (!this.jsonSchema) throw new Error("jsonSchema not initialized.");
|
|
880
|
+
for (const [id, conn] of Object.entries(this.mcpConnections)) if (conn.connectionState !== "ready" && conn.connectionState !== "authenticating") console.warn(`[getAITools] WARNING: Reading tools from connection ${id} in state "${conn.connectionState}". Tools may not be loaded yet.`);
|
|
685
881
|
return Object.fromEntries(getNamespacedData(this.mcpConnections, "tools").map((tool) => {
|
|
686
882
|
return [`tool_${tool.serverId.replace(/-/g, "")}_${tool.name}`, {
|
|
687
883
|
description: tool.description,
|
|
@@ -738,13 +934,26 @@ var MCPClientManager = class {
|
|
|
738
934
|
this._connectionDisposables.delete(id);
|
|
739
935
|
}
|
|
740
936
|
/**
|
|
937
|
+
* Remove an MCP server from storage
|
|
938
|
+
*/
|
|
939
|
+
async removeServer(serverId) {
|
|
940
|
+
await this._storage.removeServer(serverId);
|
|
941
|
+
this._onServerStateChanged.fire();
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* List all MCP servers from storage
|
|
945
|
+
*/
|
|
946
|
+
async listServers() {
|
|
947
|
+
return await this._storage.listServers();
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
741
950
|
* Dispose the manager and all resources.
|
|
742
951
|
*/
|
|
743
952
|
async dispose() {
|
|
744
953
|
try {
|
|
745
954
|
await this.closeAllConnections();
|
|
746
955
|
} finally {
|
|
747
|
-
this.
|
|
956
|
+
this._onServerStateChanged.dispose();
|
|
748
957
|
this._onObservabilityEvent.dispose();
|
|
749
958
|
}
|
|
750
959
|
}
|
|
@@ -806,96 +1015,72 @@ function getNamespacedData(mcpClients, type) {
|
|
|
806
1015
|
}
|
|
807
1016
|
|
|
808
1017
|
//#endregion
|
|
809
|
-
//#region ../agents/dist/
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
client_name: this.clientName,
|
|
819
|
-
client_uri: this.clientUri,
|
|
820
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
821
|
-
redirect_uris: [this.redirectUrl],
|
|
822
|
-
response_types: ["code"],
|
|
823
|
-
token_endpoint_auth_method: "none"
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
get clientUri() {
|
|
827
|
-
return new URL(this.redirectUrl).origin;
|
|
828
|
-
}
|
|
829
|
-
get redirectUrl() {
|
|
830
|
-
return `${this.baseRedirectUrl}/${this.serverId}`;
|
|
831
|
-
}
|
|
832
|
-
get clientId() {
|
|
833
|
-
if (!this._clientId_) throw new Error("Trying to access clientId before it was set");
|
|
834
|
-
return this._clientId_;
|
|
835
|
-
}
|
|
836
|
-
set clientId(clientId_) {
|
|
837
|
-
this._clientId_ = clientId_;
|
|
838
|
-
}
|
|
839
|
-
get serverId() {
|
|
840
|
-
if (!this._serverId_) throw new Error("Trying to access serverId before it was set");
|
|
841
|
-
return this._serverId_;
|
|
842
|
-
}
|
|
843
|
-
set serverId(serverId_) {
|
|
844
|
-
this._serverId_ = serverId_;
|
|
845
|
-
}
|
|
846
|
-
keyPrefix(clientId) {
|
|
847
|
-
return `/${this.clientName}/${this.serverId}/${clientId}`;
|
|
848
|
-
}
|
|
849
|
-
clientInfoKey(clientId) {
|
|
850
|
-
return `${this.keyPrefix(clientId)}/client_info/`;
|
|
851
|
-
}
|
|
852
|
-
async clientInformation() {
|
|
853
|
-
if (!this._clientId_) return;
|
|
854
|
-
return await this.storage.get(this.clientInfoKey(this.clientId)) ?? void 0;
|
|
855
|
-
}
|
|
856
|
-
async saveClientInformation(clientInformation) {
|
|
857
|
-
await this.storage.put(this.clientInfoKey(clientInformation.client_id), clientInformation);
|
|
858
|
-
this.clientId = clientInformation.client_id;
|
|
859
|
-
}
|
|
860
|
-
tokenKey(clientId) {
|
|
861
|
-
return `${this.keyPrefix(clientId)}/token`;
|
|
1018
|
+
//#region ../agents/dist/src-Dk8lwxHf.js
|
|
1019
|
+
/**
|
|
1020
|
+
* SQL-based storage adapter that wraps SQL operations
|
|
1021
|
+
* Used by Agent class to provide SQL access to MCPClientManager
|
|
1022
|
+
*/
|
|
1023
|
+
var AgentMCPClientStorage = class {
|
|
1024
|
+
constructor(sql, kv) {
|
|
1025
|
+
this.sql = sql;
|
|
1026
|
+
this.kv = kv;
|
|
862
1027
|
}
|
|
863
|
-
async
|
|
864
|
-
|
|
865
|
-
|
|
1028
|
+
async saveServer(server) {
|
|
1029
|
+
this.sql`
|
|
1030
|
+
INSERT OR REPLACE INTO cf_agents_mcp_servers (
|
|
1031
|
+
id,
|
|
1032
|
+
name,
|
|
1033
|
+
server_url,
|
|
1034
|
+
client_id,
|
|
1035
|
+
auth_url,
|
|
1036
|
+
callback_url,
|
|
1037
|
+
server_options
|
|
1038
|
+
)
|
|
1039
|
+
VALUES (
|
|
1040
|
+
${server.id},
|
|
1041
|
+
${server.name},
|
|
1042
|
+
${server.server_url},
|
|
1043
|
+
${server.client_id ?? null},
|
|
1044
|
+
${server.auth_url ?? null},
|
|
1045
|
+
${server.callback_url},
|
|
1046
|
+
${server.server_options ?? null}
|
|
1047
|
+
)
|
|
1048
|
+
`;
|
|
866
1049
|
}
|
|
867
|
-
async
|
|
868
|
-
|
|
1050
|
+
async removeServer(serverId) {
|
|
1051
|
+
this.sql`
|
|
1052
|
+
DELETE FROM cf_agents_mcp_servers WHERE id = ${serverId}
|
|
1053
|
+
`;
|
|
869
1054
|
}
|
|
870
|
-
|
|
871
|
-
return this.
|
|
1055
|
+
async listServers() {
|
|
1056
|
+
return this.sql`
|
|
1057
|
+
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options
|
|
1058
|
+
FROM cf_agents_mcp_servers
|
|
1059
|
+
`;
|
|
872
1060
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1061
|
+
async getServerByCallbackUrl(callbackUrl) {
|
|
1062
|
+
const results = this.sql`
|
|
1063
|
+
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options
|
|
1064
|
+
FROM cf_agents_mcp_servers
|
|
1065
|
+
WHERE callback_url = ${callbackUrl}
|
|
1066
|
+
LIMIT 1
|
|
1067
|
+
`;
|
|
1068
|
+
return results.length > 0 ? results[0] : null;
|
|
881
1069
|
}
|
|
882
|
-
|
|
883
|
-
|
|
1070
|
+
async clearAuthUrl(serverId) {
|
|
1071
|
+
this.sql`
|
|
1072
|
+
UPDATE cf_agents_mcp_servers
|
|
1073
|
+
SET auth_url = NULL
|
|
1074
|
+
WHERE id = ${serverId}
|
|
1075
|
+
`;
|
|
884
1076
|
}
|
|
885
|
-
async
|
|
886
|
-
|
|
887
|
-
if (await this.storage.get(key)) return;
|
|
888
|
-
await this.storage.put(key, verifier);
|
|
1077
|
+
async get(key) {
|
|
1078
|
+
return this.kv.get(key);
|
|
889
1079
|
}
|
|
890
|
-
async
|
|
891
|
-
|
|
892
|
-
if (!codeVerifier) throw new Error("No code verifier found");
|
|
893
|
-
return codeVerifier;
|
|
1080
|
+
async put(key, value) {
|
|
1081
|
+
return this.kv.put(key, value);
|
|
894
1082
|
}
|
|
895
1083
|
};
|
|
896
|
-
|
|
897
|
-
//#endregion
|
|
898
|
-
//#region ../agents/dist/src-Dz0H9hSU.js
|
|
899
1084
|
/**
|
|
900
1085
|
* A generic observability implementation that logs events to the console.
|
|
901
1086
|
*/
|
|
@@ -1014,9 +1199,8 @@ var Agent = class Agent$1 extends Server {
|
|
|
1014
1199
|
super(ctx, env$1);
|
|
1015
1200
|
this._state = DEFAULT_STATE;
|
|
1016
1201
|
this._disposables = new DisposableStore();
|
|
1017
|
-
this.
|
|
1202
|
+
this._destroyed = false;
|
|
1018
1203
|
this._ParentClass = Object.getPrototypeOf(this).constructor;
|
|
1019
|
-
this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1");
|
|
1020
1204
|
this.initialState = DEFAULT_STATE;
|
|
1021
1205
|
this.observability = genericObservability;
|
|
1022
1206
|
this._flushingQueue = false;
|
|
@@ -1054,28 +1238,45 @@ var Agent = class Agent$1 extends Server {
|
|
|
1054
1238
|
}
|
|
1055
1239
|
});
|
|
1056
1240
|
if (row.type === "cron") {
|
|
1241
|
+
if (this._destroyed) return;
|
|
1057
1242
|
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1058
1243
|
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1e3);
|
|
1059
1244
|
this.sql`
|
|
1060
1245
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
1061
1246
|
`;
|
|
1062
|
-
} else
|
|
1247
|
+
} else {
|
|
1248
|
+
if (this._destroyed) return;
|
|
1249
|
+
this.sql`
|
|
1063
1250
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
1064
1251
|
`;
|
|
1252
|
+
}
|
|
1065
1253
|
}
|
|
1254
|
+
if (this._destroyed) return;
|
|
1066
1255
|
await this._scheduleNextAlarm();
|
|
1067
1256
|
};
|
|
1257
|
+
this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", { storage: new AgentMCPClientStorage(this.sql.bind(this), this.ctx.storage.kv) });
|
|
1068
1258
|
if (!wrappedClasses.has(this.constructor)) {
|
|
1069
1259
|
this._autoWrapCustomMethods();
|
|
1070
1260
|
wrappedClasses.add(this.constructor);
|
|
1071
1261
|
}
|
|
1072
|
-
this._disposables.add(this.mcp.
|
|
1073
|
-
this.broadcastMcpServers();
|
|
1262
|
+
this._disposables.add(this.mcp.onServerStateChanged(async () => {
|
|
1263
|
+
await this.broadcastMcpServers();
|
|
1074
1264
|
}));
|
|
1075
1265
|
this._disposables.add(this.mcp.onObservabilityEvent((event) => {
|
|
1076
1266
|
this.observability?.emit(event);
|
|
1077
1267
|
}));
|
|
1078
1268
|
this.sql`
|
|
1269
|
+
CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
|
|
1270
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
1271
|
+
name TEXT NOT NULL,
|
|
1272
|
+
server_url TEXT NOT NULL,
|
|
1273
|
+
callback_url TEXT NOT NULL,
|
|
1274
|
+
client_id TEXT,
|
|
1275
|
+
auth_url TEXT,
|
|
1276
|
+
server_options TEXT
|
|
1277
|
+
)
|
|
1278
|
+
`;
|
|
1279
|
+
this.sql`
|
|
1079
1280
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
1080
1281
|
id TEXT PRIMARY KEY NOT NULL,
|
|
1081
1282
|
state TEXT
|
|
@@ -1089,32 +1290,16 @@ var Agent = class Agent$1 extends Server {
|
|
|
1089
1290
|
created_at INTEGER DEFAULT (unixepoch())
|
|
1090
1291
|
)
|
|
1091
1292
|
`;
|
|
1092
|
-
this.ctx.blockConcurrencyWhile(async () => {
|
|
1093
|
-
return this._tryCatch(async () => {
|
|
1094
|
-
this.sql`
|
|
1095
|
-
CREATE TABLE IF NOT EXISTS cf_agents_schedules (
|
|
1096
|
-
id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
|
|
1097
|
-
callback TEXT,
|
|
1098
|
-
payload TEXT,
|
|
1099
|
-
type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron')),
|
|
1100
|
-
time INTEGER,
|
|
1101
|
-
delayInSeconds INTEGER,
|
|
1102
|
-
cron TEXT,
|
|
1103
|
-
created_at INTEGER DEFAULT (unixepoch())
|
|
1104
|
-
)
|
|
1105
|
-
`;
|
|
1106
|
-
await this.alarm();
|
|
1107
|
-
});
|
|
1108
|
-
});
|
|
1109
1293
|
this.sql`
|
|
1110
|
-
CREATE TABLE IF NOT EXISTS
|
|
1111
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1294
|
+
CREATE TABLE IF NOT EXISTS cf_agents_schedules (
|
|
1295
|
+
id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
|
|
1296
|
+
callback TEXT,
|
|
1297
|
+
payload TEXT,
|
|
1298
|
+
type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron')),
|
|
1299
|
+
time INTEGER,
|
|
1300
|
+
delayInSeconds INTEGER,
|
|
1301
|
+
cron TEXT,
|
|
1302
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
1118
1303
|
)
|
|
1119
1304
|
`;
|
|
1120
1305
|
const _onRequest = this.onRequest.bind(this);
|
|
@@ -1125,17 +1310,9 @@ var Agent = class Agent$1 extends Server {
|
|
|
1125
1310
|
request,
|
|
1126
1311
|
email: void 0
|
|
1127
1312
|
}, async () => {
|
|
1128
|
-
await this.
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
this.broadcastMcpServers();
|
|
1132
|
-
if (result.authSuccess) this.mcp.establishConnection(result.serverId).catch((error) => {
|
|
1133
|
-
console.error("Background connection failed:", error);
|
|
1134
|
-
}).finally(() => {
|
|
1135
|
-
this.broadcastMcpServers();
|
|
1136
|
-
});
|
|
1137
|
-
return this.handleOAuthCallbackResponse(result, request);
|
|
1138
|
-
}
|
|
1313
|
+
await this.mcp.ensureJsonSchema();
|
|
1314
|
+
const oauthResponse = await this.handleMcpOAuthCallback(request);
|
|
1315
|
+
if (oauthResponse) return oauthResponse;
|
|
1139
1316
|
return this._tryCatch(() => _onRequest(request));
|
|
1140
1317
|
});
|
|
1141
1318
|
};
|
|
@@ -1147,6 +1324,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1147
1324
|
request: void 0,
|
|
1148
1325
|
email: void 0
|
|
1149
1326
|
}, async () => {
|
|
1327
|
+
await this.mcp.ensureJsonSchema();
|
|
1150
1328
|
if (typeof message !== "string") return this._tryCatch(() => _onMessage(connection, message));
|
|
1151
1329
|
let parsed;
|
|
1152
1330
|
try {
|
|
@@ -1211,13 +1389,13 @@ var Agent = class Agent$1 extends Server {
|
|
|
1211
1389
|
connection,
|
|
1212
1390
|
request: ctx$1.request,
|
|
1213
1391
|
email: void 0
|
|
1214
|
-
}, () => {
|
|
1392
|
+
}, async () => {
|
|
1215
1393
|
if (this.state) connection.send(JSON.stringify({
|
|
1216
1394
|
state: this.state,
|
|
1217
1395
|
type: MessageType.CF_AGENT_STATE
|
|
1218
1396
|
}));
|
|
1219
1397
|
connection.send(JSON.stringify({
|
|
1220
|
-
mcp: this.getMcpServers(),
|
|
1398
|
+
mcp: await this.getMcpServers(),
|
|
1221
1399
|
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
1222
1400
|
}));
|
|
1223
1401
|
this.observability?.emit({
|
|
@@ -1239,8 +1417,8 @@ var Agent = class Agent$1 extends Server {
|
|
|
1239
1417
|
email: void 0
|
|
1240
1418
|
}, async () => {
|
|
1241
1419
|
await this._tryCatch(async () => {
|
|
1242
|
-
await this.
|
|
1243
|
-
this.broadcastMcpServers();
|
|
1420
|
+
await this.mcp.restoreConnectionsFromStorage(this.name);
|
|
1421
|
+
await this.broadcastMcpServers();
|
|
1244
1422
|
return _onStart(props);
|
|
1245
1423
|
});
|
|
1246
1424
|
});
|
|
@@ -1646,7 +1824,7 @@ var Agent = class Agent$1 extends Server {
|
|
|
1646
1824
|
async _scheduleNextAlarm() {
|
|
1647
1825
|
const result = this.sql`
|
|
1648
1826
|
SELECT time FROM cf_agents_schedules
|
|
1649
|
-
WHERE time
|
|
1827
|
+
WHERE time >= ${Math.floor(Date.now() / 1e3)}
|
|
1650
1828
|
ORDER BY time ASC
|
|
1651
1829
|
LIMIT 1
|
|
1652
1830
|
`;
|
|
@@ -1660,15 +1838,18 @@ var Agent = class Agent$1 extends Server {
|
|
|
1660
1838
|
* Destroy the Agent, removing all state and scheduled tasks
|
|
1661
1839
|
*/
|
|
1662
1840
|
async destroy() {
|
|
1841
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1663
1842
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
1664
1843
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
1665
|
-
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1666
1844
|
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
1667
1845
|
await this.ctx.storage.deleteAlarm();
|
|
1668
1846
|
await this.ctx.storage.deleteAll();
|
|
1669
1847
|
this._disposables.dispose();
|
|
1670
|
-
await this.mcp.dispose
|
|
1671
|
-
this.
|
|
1848
|
+
await this.mcp.dispose();
|
|
1849
|
+
this._destroyed = true;
|
|
1850
|
+
setTimeout(() => {
|
|
1851
|
+
this.ctx.abort("destroyed");
|
|
1852
|
+
}, 0);
|
|
1672
1853
|
this.observability?.emit({
|
|
1673
1854
|
displayMessage: "Agent destroyed",
|
|
1674
1855
|
id: nanoid(),
|
|
@@ -1684,43 +1865,6 @@ var Agent = class Agent$1 extends Server {
|
|
|
1684
1865
|
_isCallable(method) {
|
|
1685
1866
|
return callableMetadata.has(this[method]);
|
|
1686
1867
|
}
|
|
1687
|
-
async _ensureMcpStateRestored() {
|
|
1688
|
-
if (this._mcpStateRestored) return;
|
|
1689
|
-
this._mcpStateRestored = true;
|
|
1690
|
-
const servers = this.sql`
|
|
1691
|
-
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options
|
|
1692
|
-
FROM cf_agents_mcp_servers
|
|
1693
|
-
`;
|
|
1694
|
-
if (!servers || !Array.isArray(servers) || servers.length === 0) return;
|
|
1695
|
-
for (const server of servers) if (server.callback_url) this.mcp.registerCallbackUrl(`${server.callback_url}/${server.id}`);
|
|
1696
|
-
for (const server of servers) if (!!server.auth_url) {
|
|
1697
|
-
const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage, this.name, server.callback_url);
|
|
1698
|
-
authProvider.serverId = server.id;
|
|
1699
|
-
if (server.client_id) authProvider.clientId = server.client_id;
|
|
1700
|
-
const parsedOptions = server.server_options ? JSON.parse(server.server_options) : void 0;
|
|
1701
|
-
const conn = new MCPClientConnection(new URL(server.server_url), {
|
|
1702
|
-
name: this.name,
|
|
1703
|
-
version: "1.0.0"
|
|
1704
|
-
}, {
|
|
1705
|
-
client: parsedOptions?.client ?? {},
|
|
1706
|
-
transport: {
|
|
1707
|
-
...parsedOptions?.transport ?? {},
|
|
1708
|
-
type: parsedOptions?.transport?.type ?? "auto",
|
|
1709
|
-
authProvider
|
|
1710
|
-
}
|
|
1711
|
-
});
|
|
1712
|
-
conn.connectionState = "authenticating";
|
|
1713
|
-
this.mcp.mcpConnections[server.id] = conn;
|
|
1714
|
-
} else {
|
|
1715
|
-
const parsedOptions = server.server_options ? JSON.parse(server.server_options) : void 0;
|
|
1716
|
-
this._connectToMcpServerInternal(server.name, server.server_url, server.callback_url, parsedOptions, {
|
|
1717
|
-
id: server.id,
|
|
1718
|
-
oauthClientId: server.client_id ?? void 0
|
|
1719
|
-
}).catch((error) => {
|
|
1720
|
-
console.error(`Error restoring ${server.id}:`, error);
|
|
1721
|
-
});
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
1868
|
/**
|
|
1725
1869
|
* Connect to a new MCP Server
|
|
1726
1870
|
*
|
|
@@ -1740,29 +1884,10 @@ var Agent = class Agent$1 extends Server {
|
|
|
1740
1884
|
resolvedCallbackHost = `${requestUrl.protocol}//${requestUrl.host}`;
|
|
1741
1885
|
}
|
|
1742
1886
|
const callbackUrl = `${resolvedCallbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
VALUES (
|
|
1748
|
-
${result.id},
|
|
1749
|
-
${serverName},
|
|
1750
|
-
${url},
|
|
1751
|
-
${result.clientId ?? null},
|
|
1752
|
-
${result.authUrl ?? null},
|
|
1753
|
-
${callbackUrl},
|
|
1754
|
-
${options ? JSON.stringify(options) : null}
|
|
1755
|
-
);
|
|
1756
|
-
`;
|
|
1757
|
-
this.broadcastMcpServers();
|
|
1758
|
-
return result;
|
|
1759
|
-
}
|
|
1760
|
-
async _connectToMcpServerInternal(_serverName, url, callbackUrl, options, reconnect) {
|
|
1761
|
-
const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage, this.name, callbackUrl);
|
|
1762
|
-
if (reconnect) {
|
|
1763
|
-
authProvider.serverId = reconnect.id;
|
|
1764
|
-
if (reconnect.oauthClientId) authProvider.clientId = reconnect.oauthClientId;
|
|
1765
|
-
}
|
|
1887
|
+
await this.mcp.ensureJsonSchema();
|
|
1888
|
+
const id = nanoid(8);
|
|
1889
|
+
const authProvider = new DurableObjectOAuthClientProvider(this.ctx.storage.kv, this.name, callbackUrl);
|
|
1890
|
+
authProvider.serverId = id;
|
|
1766
1891
|
const transportType = options?.transport?.type ?? "auto";
|
|
1767
1892
|
let headerTransportOpts = {};
|
|
1768
1893
|
if (options?.transport?.headers) headerTransportOpts = {
|
|
@@ -1772,59 +1897,78 @@ var Agent = class Agent$1 extends Server {
|
|
|
1772
1897
|
}) },
|
|
1773
1898
|
requestInit: { headers: options?.transport?.headers }
|
|
1774
1899
|
};
|
|
1775
|
-
|
|
1900
|
+
await this.mcp.registerServer(id, {
|
|
1901
|
+
url,
|
|
1902
|
+
name: serverName,
|
|
1903
|
+
callbackUrl,
|
|
1776
1904
|
client: options?.client,
|
|
1777
|
-
reconnect,
|
|
1778
1905
|
transport: {
|
|
1779
1906
|
...headerTransportOpts,
|
|
1780
1907
|
authProvider,
|
|
1781
1908
|
type: transportType
|
|
1782
1909
|
}
|
|
1783
1910
|
});
|
|
1911
|
+
const result = await this.mcp.connectToServer(id);
|
|
1784
1912
|
return {
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
id
|
|
1913
|
+
id,
|
|
1914
|
+
authUrl: result.state === "authenticating" ? result.authUrl : void 0
|
|
1788
1915
|
};
|
|
1789
1916
|
}
|
|
1790
1917
|
async removeMcpServer(id) {
|
|
1791
|
-
this.mcp.closeConnection(id);
|
|
1792
|
-
this.mcp.
|
|
1793
|
-
this.sql`
|
|
1794
|
-
DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
|
|
1795
|
-
`;
|
|
1796
|
-
this.broadcastMcpServers();
|
|
1918
|
+
if (this.mcp.mcpConnections[id]) await this.mcp.closeConnection(id);
|
|
1919
|
+
await this.mcp.removeServer(id);
|
|
1797
1920
|
}
|
|
1798
|
-
getMcpServers() {
|
|
1921
|
+
async getMcpServers() {
|
|
1799
1922
|
const mcpState = {
|
|
1800
1923
|
prompts: this.mcp.listPrompts(),
|
|
1801
1924
|
resources: this.mcp.listResources(),
|
|
1802
1925
|
servers: {},
|
|
1803
1926
|
tools: this.mcp.listTools()
|
|
1804
1927
|
};
|
|
1805
|
-
const servers = this.
|
|
1806
|
-
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1807
|
-
`;
|
|
1928
|
+
const servers = await this.mcp.listServers();
|
|
1808
1929
|
if (servers && Array.isArray(servers) && servers.length > 0) for (const server of servers) {
|
|
1809
1930
|
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1931
|
+
let defaultState = "not-connected";
|
|
1932
|
+
if (!serverConn && server.auth_url) defaultState = "authenticating";
|
|
1810
1933
|
mcpState.servers[server.id] = {
|
|
1811
1934
|
auth_url: server.auth_url,
|
|
1812
1935
|
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1813
1936
|
instructions: serverConn?.instructions ?? null,
|
|
1814
1937
|
name: server.name,
|
|
1815
1938
|
server_url: server.server_url,
|
|
1816
|
-
state: serverConn?.connectionState ??
|
|
1939
|
+
state: serverConn?.connectionState ?? defaultState
|
|
1817
1940
|
};
|
|
1818
1941
|
}
|
|
1819
1942
|
return mcpState;
|
|
1820
1943
|
}
|
|
1821
|
-
broadcastMcpServers() {
|
|
1944
|
+
async broadcastMcpServers() {
|
|
1822
1945
|
this.broadcast(JSON.stringify({
|
|
1823
|
-
mcp: this.getMcpServers(),
|
|
1946
|
+
mcp: await this.getMcpServers(),
|
|
1824
1947
|
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
1825
1948
|
}));
|
|
1826
1949
|
}
|
|
1827
1950
|
/**
|
|
1951
|
+
* Handle MCP OAuth callback request if it's an OAuth callback.
|
|
1952
|
+
*
|
|
1953
|
+
* This method encapsulates the entire OAuth callback flow:
|
|
1954
|
+
* 1. Checks if the request is an MCP OAuth callback
|
|
1955
|
+
* 2. Processes the OAuth code exchange
|
|
1956
|
+
* 3. Establishes the connection if successful
|
|
1957
|
+
* 4. Broadcasts MCP server state updates
|
|
1958
|
+
* 5. Returns the appropriate HTTP response
|
|
1959
|
+
*
|
|
1960
|
+
* @param request The incoming HTTP request
|
|
1961
|
+
* @returns Response if this was an OAuth callback, null otherwise
|
|
1962
|
+
*/
|
|
1963
|
+
async handleMcpOAuthCallback(request) {
|
|
1964
|
+
if (!await this.mcp.isCallbackRequest(request)) return null;
|
|
1965
|
+
const result = await this.mcp.handleCallbackRequest(request);
|
|
1966
|
+
if (result.authSuccess) this.mcp.establishConnection(result.serverId).catch((error) => {
|
|
1967
|
+
console.error("[Agent handleMcpOAuthCallback] Background connection failed:", error);
|
|
1968
|
+
});
|
|
1969
|
+
return this.handleOAuthCallbackResponse(result, request);
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1828
1972
|
* Handle OAuth callback response using MCPClientManager configuration
|
|
1829
1973
|
* @param result OAuth callback result
|
|
1830
1974
|
* @param request The original request (needed for base URL)
|
|
@@ -1833,10 +1977,21 @@ var Agent = class Agent$1 extends Server {
|
|
|
1833
1977
|
handleOAuthCallbackResponse(result, request) {
|
|
1834
1978
|
const config = this.mcp.getOAuthCallbackConfig();
|
|
1835
1979
|
if (config?.customHandler) return config.customHandler(result);
|
|
1836
|
-
|
|
1837
|
-
if (config?.
|
|
1838
|
-
|
|
1839
|
-
|
|
1980
|
+
const baseOrigin = new URL(request.url).origin;
|
|
1981
|
+
if (config?.successRedirect && result.authSuccess) try {
|
|
1982
|
+
return Response.redirect(new URL(config.successRedirect, baseOrigin).href);
|
|
1983
|
+
} catch (e) {
|
|
1984
|
+
console.error("Invalid successRedirect URL:", config.successRedirect, e);
|
|
1985
|
+
return Response.redirect(baseOrigin);
|
|
1986
|
+
}
|
|
1987
|
+
if (config?.errorRedirect && !result.authSuccess) try {
|
|
1988
|
+
const errorUrl = `${config.errorRedirect}?error=${encodeURIComponent(result.authError || "Unknown error")}`;
|
|
1989
|
+
return Response.redirect(new URL(errorUrl, baseOrigin).href);
|
|
1990
|
+
} catch (e) {
|
|
1991
|
+
console.error("Invalid errorRedirect URL:", config.errorRedirect, e);
|
|
1992
|
+
return Response.redirect(baseOrigin);
|
|
1993
|
+
}
|
|
1994
|
+
return Response.redirect(baseOrigin);
|
|
1840
1995
|
}
|
|
1841
1996
|
};
|
|
1842
1997
|
const wrappedClasses = /* @__PURE__ */ new Set();
|
|
@@ -1862,10 +2017,15 @@ async function routeAgentRequest(request, env$1, options) {
|
|
|
1862
2017
|
prefix: "agents",
|
|
1863
2018
|
...options
|
|
1864
2019
|
});
|
|
1865
|
-
if (response && corsHeaders && request.headers.get("upgrade")?.toLowerCase() !== "websocket" && request.headers.get("Upgrade")?.toLowerCase() !== "websocket")
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2020
|
+
if (response && corsHeaders && request.headers.get("upgrade")?.toLowerCase() !== "websocket" && request.headers.get("Upgrade")?.toLowerCase() !== "websocket") {
|
|
2021
|
+
const newHeaders = new Headers(response.headers);
|
|
2022
|
+
for (const [key, value] of Object.entries(corsHeaders)) newHeaders.set(key, value);
|
|
2023
|
+
response = new Response(response.body, {
|
|
2024
|
+
status: response.status,
|
|
2025
|
+
statusText: response.statusText,
|
|
2026
|
+
headers: newHeaders
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
1869
2029
|
return response;
|
|
1870
2030
|
}
|
|
1871
2031
|
/**
|