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 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-9Ld2_lnt.js
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 StreamableHTTPEdgeClientTransport(this.url, this.options.transport);
406
- case "sse": return new SSEEdgeClientTransport(this.url, this.options.transport);
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 auth Auth paramters if being used to create a DurableObjectOAuthClientProvider
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._onConnected = new Emitter();
497
- this.onConnected = this._onConnected.event;
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
- * @param transportConfig Transport config
503
- * @param clientConfig Client config
504
- * @param capabilities Client capabilities (i.e. if the client supports roots/sampling)
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
- if (!this.jsonSchema) {
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
- this._callbackUrls.push(options.transport.authProvider.redirectUrl.toString());
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: options.transport?.authProvider?.clientId,
568
- id
743
+ clientId
569
744
  };
570
745
  }
571
- return { id };
746
+ if (conn.connectionState === "ready") this._onServerStateChanged.fire();
747
+ return { state: "ready" };
572
748
  }
573
- isCallbackRequest(req) {
574
- return req.method === "GET" && !!this._callbackUrls.find((url) => {
575
- return req.url.startsWith(url);
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 urlMatch = this._callbackUrls.find((url$1) => {
581
- return req.url.startsWith(url$1);
756
+ const matchingServer = (await this._storage.listServers()).find((server) => {
757
+ return server.callback_url && req.url.startsWith(server.callback_url);
582
758
  });
583
- if (!urlMatch) throw new Error(`No callback URI match found for the request url: ${req.url}. Was the request matched with \`isCallbackRequest()\`?`);
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 urlParams = urlMatch.split("/");
587
- const serverId = urlParams[urlParams.length - 1];
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 !== "authenticating") throw new Error("Failed to authenticate: the client isn't in the `authenticating` state");
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: error instanceof Error ? error.message : String(error)
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._onConnected.fire(serverId);
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._onConnected.dispose();
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/do-oauth-client-provider-CswoD5Lu.js
810
- var DurableObjectOAuthClientProvider = class {
811
- constructor(storage, clientName, baseRedirectUrl) {
812
- this.storage = storage;
813
- this.clientName = clientName;
814
- this.baseRedirectUrl = baseRedirectUrl;
815
- }
816
- get clientMetadata() {
817
- return {
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 tokens() {
864
- if (!this._clientId_) return;
865
- return await this.storage.get(this.tokenKey(this.clientId)) ?? void 0;
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 saveTokens(tokens) {
868
- await this.storage.put(this.tokenKey(this.clientId), tokens);
1050
+ async removeServer(serverId) {
1051
+ this.sql`
1052
+ DELETE FROM cf_agents_mcp_servers WHERE id = ${serverId}
1053
+ `;
869
1054
  }
870
- get authUrl() {
871
- return this._authUrl_;
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
- * Because this operates on the server side (but we need browser auth), we send this url back to the user
875
- * and require user interact to initiate the redirect flow
876
- */
877
- async redirectToAuthorization(authUrl) {
878
- const stateToken = nanoid();
879
- authUrl.searchParams.set("state", stateToken);
880
- this._authUrl_ = authUrl.toString();
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
- codeVerifierKey(clientId) {
883
- return `${this.keyPrefix(clientId)}/code_verifier`;
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 saveCodeVerifier(verifier) {
886
- const key = this.codeVerifierKey(this.clientId);
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 codeVerifier() {
891
- const codeVerifier = await this.storage.get(this.codeVerifierKey(this.clientId));
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._mcpStateRestored = false;
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 this.sql`
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.onConnected(async () => {
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 cf_agents_mcp_servers (
1111
- id TEXT PRIMARY KEY NOT NULL,
1112
- name TEXT NOT NULL,
1113
- server_url TEXT NOT NULL,
1114
- callback_url TEXT NOT NULL,
1115
- client_id TEXT,
1116
- auth_url TEXT,
1117
- server_options TEXT
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._ensureMcpStateRestored();
1129
- if (this.mcp.isCallbackRequest(request)) {
1130
- const result = await this.mcp.handleCallbackRequest(request);
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._ensureMcpStateRestored();
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 > ${Math.floor(Date.now() / 1e3)}
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.ctx.abort("destroyed");
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
- const result = await this._connectToMcpServerInternal(serverName, url, callbackUrl, options);
1744
- this.sql`
1745
- INSERT
1746
- OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
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
- const { id, authUrl, clientId } = await this.mcp.connect(url, {
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
- authUrl,
1786
- clientId,
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.unregisterCallbackUrl(id);
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.sql`
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 ?? "authenticating"
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
- if (config?.successRedirect && result.authSuccess) return Response.redirect(config.successRedirect);
1837
- if (config?.errorRedirect && !result.authSuccess) return Response.redirect(`${config.errorRedirect}?error=${encodeURIComponent(result.authError || "Unknown error")}`);
1838
- const baseUrl = new URL(request.url).origin;
1839
- return Response.redirect(baseUrl);
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") response = new Response(response.body, { headers: {
1866
- ...response.headers,
1867
- ...corsHeaders
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
  /**