agents 0.0.0-9dbe072 → 0.0.0-9e2f4e7

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.
Files changed (40) hide show
  1. package/dist/ai-chat-agent.d.ts +28 -3
  2. package/dist/ai-chat-agent.js +99 -103
  3. package/dist/ai-chat-agent.js.map +1 -1
  4. package/dist/ai-react.d.ts +13 -0
  5. package/dist/ai-react.js +3 -3
  6. package/dist/ai-react.js.map +1 -1
  7. package/dist/chunk-BZXOAZUX.js +106 -0
  8. package/dist/chunk-BZXOAZUX.js.map +1 -0
  9. package/dist/{chunk-AV3OMRR4.js → chunk-MXJNY43J.js} +328 -140
  10. package/dist/chunk-MXJNY43J.js.map +1 -0
  11. package/dist/{chunk-YZNSS675.js → chunk-OYJXQRRH.js} +56 -26
  12. package/dist/chunk-OYJXQRRH.js.map +1 -0
  13. package/dist/chunk-VCSB47AK.js +116 -0
  14. package/dist/chunk-VCSB47AK.js.map +1 -0
  15. package/dist/client.d.ts +15 -1
  16. package/dist/client.js +6 -126
  17. package/dist/client.js.map +1 -1
  18. package/dist/index.d.ts +111 -13
  19. package/dist/index.js +8 -7
  20. package/dist/mcp/client.d.ts +30 -15
  21. package/dist/mcp/client.js +1 -2
  22. package/dist/mcp/do-oauth-client-provider.d.ts +3 -3
  23. package/dist/mcp/do-oauth-client-provider.js +3 -103
  24. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  25. package/dist/mcp/index.d.ts +16 -5
  26. package/dist/mcp/index.js +135 -139
  27. package/dist/mcp/index.js.map +1 -1
  28. package/dist/react.d.ts +85 -5
  29. package/dist/react.js +14 -2
  30. package/dist/react.js.map +1 -1
  31. package/dist/schedule.js +0 -2
  32. package/dist/schedule.js.map +1 -1
  33. package/dist/serializable.d.ts +32 -0
  34. package/dist/serializable.js +1 -0
  35. package/package.json +10 -7
  36. package/src/index.ts +392 -56
  37. package/dist/chunk-AV3OMRR4.js.map +0 -1
  38. package/dist/chunk-HMLY7DHA.js +0 -16
  39. package/dist/chunk-YZNSS675.js.map +0 -1
  40. /package/dist/{chunk-HMLY7DHA.js.map → serializable.js.map} +0 -0
package/src/index.ts CHANGED
@@ -1,20 +1,33 @@
1
1
  import {
2
2
  Server,
3
- routePartykitRequest,
4
- type PartyServerOptions,
5
3
  getServerByName,
4
+ routePartykitRequest,
6
5
  type Connection,
7
6
  type ConnectionContext,
7
+ type PartyServerOptions,
8
8
  type WSMessage,
9
9
  } from "partyserver";
10
10
 
11
11
  import { parseCronExpression } from "cron-schedule";
12
12
  import { nanoid } from "nanoid";
13
13
 
14
+ import type {
15
+ Prompt,
16
+ Resource,
17
+ ServerCapabilities,
18
+ Tool,
19
+ } from "@modelcontextprotocol/sdk/types.js";
14
20
  import { AsyncLocalStorage } from "node:async_hooks";
15
21
  import { MCPClientManager } from "./mcp/client";
22
+ import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
16
23
 
17
- export type { Connection, WSMessage, ConnectionContext } from "partyserver";
24
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
25
+ import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
26
+
27
+ import { camelCaseToKebabCase } from "./client";
28
+ import type { MCPClientConnection } from "./mcp/client-connection";
29
+
30
+ export type { Connection, ConnectionContext, WSMessage } from "partyserver";
18
31
 
19
32
  /**
20
33
  * RPC request message from client
@@ -98,7 +111,6 @@ export type CallableMetadata = {
98
111
  streaming?: boolean;
99
112
  };
100
113
 
101
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
102
114
  const callableMetadata = new Map<Function, CallableMetadata>();
103
115
 
104
116
  /**
@@ -159,29 +171,95 @@ function getNextCronTime(cron: string) {
159
171
  return interval.getNextDate();
160
172
  }
161
173
 
174
+ /**
175
+ * MCP Server state update message from server -> Client
176
+ */
177
+ export type MCPServerMessage = {
178
+ type: "cf_agent_mcp_servers";
179
+ mcp: MCPServersState;
180
+ };
181
+
182
+ export type MCPServersState = {
183
+ servers: {
184
+ [id: string]: MCPServer;
185
+ };
186
+ tools: Tool[];
187
+ prompts: Prompt[];
188
+ resources: Resource[];
189
+ };
190
+
191
+ export type MCPServer = {
192
+ name: string;
193
+ server_url: string;
194
+ auth_url: string | null;
195
+ // This state is specifically about the temporary process of getting a token (if needed).
196
+ // Scope outside of that can't be relied upon because when the DO sleeps, there's no way
197
+ // to communicate a change to a non-ready state.
198
+ state: "authenticating" | "connecting" | "ready" | "discovering" | "failed";
199
+ instructions: string | null;
200
+ capabilities: ServerCapabilities | null;
201
+ };
202
+
203
+ /**
204
+ * MCP Server data stored in DO SQL for resuming MCP Server connections
205
+ */
206
+ type MCPServerRow = {
207
+ id: string;
208
+ name: string;
209
+ server_url: string;
210
+ client_id: string | null;
211
+ auth_url: string | null;
212
+ callback_url: string;
213
+ server_options: string;
214
+ };
215
+
162
216
  const STATE_ROW_ID = "cf_state_row_id";
163
217
  const STATE_WAS_CHANGED = "cf_state_was_changed";
164
218
 
165
219
  const DEFAULT_STATE = {} as unknown;
166
220
 
167
- export const unstable_context = new AsyncLocalStorage<{
221
+ const agentContext = new AsyncLocalStorage<{
168
222
  agent: Agent<unknown>;
169
223
  connection: Connection | undefined;
170
224
  request: Request | undefined;
171
225
  }>();
172
226
 
227
+ export function getCurrentAgent<
228
+ T extends Agent<unknown, unknown> = Agent<unknown, unknown>,
229
+ >(): {
230
+ agent: T | undefined;
231
+ connection: Connection | undefined;
232
+ request: Request<unknown, CfProperties<unknown>> | undefined;
233
+ } {
234
+ const store = agentContext.getStore() as
235
+ | {
236
+ agent: T;
237
+ connection: Connection | undefined;
238
+ request: Request<unknown, CfProperties<unknown>> | undefined;
239
+ }
240
+ | undefined;
241
+ if (!store) {
242
+ return {
243
+ agent: undefined,
244
+ connection: undefined,
245
+ request: undefined,
246
+ };
247
+ }
248
+ return store;
249
+ }
250
+
173
251
  /**
174
252
  * Base class for creating Agent implementations
175
253
  * @template Env Environment type containing bindings
176
254
  * @template State State type to store within the Agent
177
255
  */
178
256
  export class Agent<Env, State = unknown> extends Server<Env> {
179
- #state = DEFAULT_STATE as State;
257
+ private _state = DEFAULT_STATE as State;
180
258
 
181
- #ParentClass: typeof Agent<Env, State> =
259
+ private _ParentClass: typeof Agent<Env, State> =
182
260
  Object.getPrototypeOf(this).constructor;
183
261
 
184
- mcp: MCPClientManager = new MCPClientManager(this.#ParentClass.name, "0.0.1");
262
+ mcp: MCPClientManager = new MCPClientManager(this._ParentClass.name, "0.0.1");
185
263
 
186
264
  /**
187
265
  * Initial state for the Agent
@@ -193,9 +271,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
193
271
  * Current state of the Agent
194
272
  */
195
273
  get state(): State {
196
- if (this.#state !== DEFAULT_STATE) {
274
+ if (this._state !== DEFAULT_STATE) {
197
275
  // state was previously set, and populated internal state
198
- return this.#state;
276
+ return this._state;
199
277
  }
200
278
  // looks like this is the first time the state is being accessed
201
279
  // check if the state was set in a previous life
@@ -215,8 +293,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
215
293
  ) {
216
294
  const state = result[0]?.state as string; // could be null?
217
295
 
218
- this.#state = JSON.parse(state);
219
- return this.#state;
296
+ this._state = JSON.parse(state);
297
+ return this._state;
220
298
  }
221
299
 
222
300
  // ok, this is the first time the state is being accessed
@@ -277,7 +355,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
277
355
  `;
278
356
 
279
357
  void this.ctx.blockConcurrencyWhile(async () => {
280
- return this.#tryCatch(async () => {
358
+ return this._tryCatch(async () => {
281
359
  // Create alarms table if it doesn't exist
282
360
  this.sql`
283
361
  CREATE TABLE IF NOT EXISTS cf_agents_schedules (
@@ -297,13 +375,53 @@ export class Agent<Env, State = unknown> extends Server<Env> {
297
375
  });
298
376
  });
299
377
 
378
+ this.sql`
379
+ CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
380
+ id TEXT PRIMARY KEY NOT NULL,
381
+ name TEXT NOT NULL,
382
+ server_url TEXT NOT NULL,
383
+ callback_url TEXT NOT NULL,
384
+ client_id TEXT,
385
+ auth_url TEXT,
386
+ server_options TEXT
387
+ )
388
+ `;
389
+
390
+ const _onRequest = this.onRequest.bind(this);
391
+ this.onRequest = (request: Request) => {
392
+ return agentContext.run(
393
+ { agent: this, connection: undefined, request },
394
+ async () => {
395
+ if (this.mcp.isCallbackRequest(request)) {
396
+ await this.mcp.handleCallbackRequest(request);
397
+
398
+ // after the MCP connection handshake, we can send updated mcp state
399
+ this.broadcast(
400
+ JSON.stringify({
401
+ type: "cf_agent_mcp_servers",
402
+ mcp: this.getMcpServers(),
403
+ })
404
+ );
405
+
406
+ // We probably should let the user configure this response/redirect, but this is fine for now.
407
+ return new Response("<script>window.close();</script>", {
408
+ status: 200,
409
+ headers: { "content-type": "text/html" },
410
+ });
411
+ }
412
+
413
+ return this._tryCatch(() => _onRequest(request));
414
+ }
415
+ );
416
+ };
417
+
300
418
  const _onMessage = this.onMessage.bind(this);
301
419
  this.onMessage = async (connection: Connection, message: WSMessage) => {
302
- return unstable_context.run(
420
+ return agentContext.run(
303
421
  { agent: this, connection, request: undefined },
304
422
  async () => {
305
423
  if (typeof message !== "string") {
306
- return this.#tryCatch(() => _onMessage(connection, message));
424
+ return this._tryCatch(() => _onMessage(connection, message));
307
425
  }
308
426
 
309
427
  let parsed: unknown;
@@ -311,11 +429,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
311
429
  parsed = JSON.parse(message);
312
430
  } catch (e) {
313
431
  // silently fail and let the onMessage handler handle it
314
- return this.#tryCatch(() => _onMessage(connection, message));
432
+ return this._tryCatch(() => _onMessage(connection, message));
315
433
  }
316
434
 
317
435
  if (isStateUpdateMessage(parsed)) {
318
- this.#setStateInternal(parsed.state as State, connection);
436
+ this._setStateInternal(parsed.state as State, connection);
319
437
  return;
320
438
  }
321
439
 
@@ -329,11 +447,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
329
447
  throw new Error(`Method ${method} does not exist`);
330
448
  }
331
449
 
332
- if (!this.#isCallable(method)) {
450
+ if (!this._isCallable(method)) {
333
451
  throw new Error(`Method ${method} is not callable`);
334
452
  }
335
453
 
336
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
337
454
  const metadata = callableMetadata.get(methodFn as Function);
338
455
 
339
456
  // For streaming methods, pass a StreamingResponse object
@@ -368,7 +485,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
368
485
  return;
369
486
  }
370
487
 
371
- return this.#tryCatch(() => _onMessage(connection, message));
488
+ return this._tryCatch(() => _onMessage(connection, message));
372
489
  }
373
490
  );
374
491
  };
@@ -377,7 +494,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
377
494
  this.onConnect = (connection: Connection, ctx: ConnectionContext) => {
378
495
  // TODO: This is a hack to ensure the state is sent after the connection is established
379
496
  // must fix this
380
- return unstable_context.run(
497
+ return agentContext.run(
381
498
  { agent: this, connection, request: ctx.request },
382
499
  async () => {
383
500
  setTimeout(() => {
@@ -389,15 +506,67 @@ export class Agent<Env, State = unknown> extends Server<Env> {
389
506
  })
390
507
  );
391
508
  }
392
- return this.#tryCatch(() => _onConnect(connection, ctx));
509
+
510
+ connection.send(
511
+ JSON.stringify({
512
+ type: "cf_agent_mcp_servers",
513
+ mcp: this.getMcpServers(),
514
+ })
515
+ );
516
+
517
+ return this._tryCatch(() => _onConnect(connection, ctx));
393
518
  }, 20);
394
519
  }
395
520
  );
396
521
  };
522
+
523
+ const _onStart = this.onStart.bind(this);
524
+ this.onStart = async () => {
525
+ return agentContext.run(
526
+ { agent: this, connection: undefined, request: undefined },
527
+ async () => {
528
+ const servers = this.sql<MCPServerRow>`
529
+ SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
530
+ `;
531
+
532
+ // from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
533
+ await Promise.allSettled(
534
+ servers
535
+ .filter((server) => server.auth_url === null)
536
+ .map((server) => {
537
+ return this._connectToMcpServerInternal(
538
+ server.name,
539
+ server.server_url,
540
+ server.callback_url,
541
+ server.server_options
542
+ ? JSON.parse(server.server_options)
543
+ : undefined,
544
+ {
545
+ id: server.id,
546
+ oauthClientId: server.client_id ?? undefined,
547
+ }
548
+ );
549
+ })
550
+ );
551
+
552
+ this.broadcast(
553
+ JSON.stringify({
554
+ type: "cf_agent_mcp_servers",
555
+ mcp: this.getMcpServers(),
556
+ })
557
+ );
558
+
559
+ await this._tryCatch(() => _onStart());
560
+ }
561
+ );
562
+ };
397
563
  }
398
564
 
399
- #setStateInternal(state: State, source: Connection | "server" = "server") {
400
- this.#state = state;
565
+ private _setStateInternal(
566
+ state: State,
567
+ source: Connection | "server" = "server"
568
+ ) {
569
+ this._state = state;
401
570
  this.sql`
402
571
  INSERT OR REPLACE INTO cf_agents_state (id, state)
403
572
  VALUES (${STATE_ROW_ID}, ${JSON.stringify(state)})
@@ -413,9 +582,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
413
582
  }),
414
583
  source !== "server" ? [source.id] : []
415
584
  );
416
- return this.#tryCatch(() => {
417
- const { connection, request } = unstable_context.getStore() || {};
418
- return unstable_context.run(
585
+ return this._tryCatch(() => {
586
+ const { connection, request } = agentContext.getStore() || {};
587
+ return agentContext.run(
419
588
  { agent: this, connection, request },
420
589
  async () => {
421
590
  return this.onStateUpdate(state, source);
@@ -429,7 +598,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
429
598
  * @param state New state to set
430
599
  */
431
600
  setState(state: State) {
432
- this.#setStateInternal(state, "server");
601
+ this._setStateInternal(state, "server");
433
602
  }
434
603
 
435
604
  /**
@@ -446,7 +615,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
446
615
  * @param email Email message to process
447
616
  */
448
617
  onEmail(email: ForwardableEmailMessage) {
449
- return unstable_context.run(
618
+ return agentContext.run(
450
619
  { agent: this, connection: undefined, request: undefined },
451
620
  async () => {
452
621
  console.error("onEmail not implemented");
@@ -454,7 +623,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
454
623
  );
455
624
  }
456
625
 
457
- async #tryCatch<T>(fn: () => T | Promise<T>) {
626
+ private async _tryCatch<T>(fn: () => T | Promise<T>) {
458
627
  try {
459
628
  return await fn();
460
629
  } catch (e) {
@@ -528,7 +697,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
528
697
  )}, 'scheduled', ${timestamp})
529
698
  `;
530
699
 
531
- await this.#scheduleNextAlarm();
700
+ await this._scheduleNextAlarm();
532
701
 
533
702
  return {
534
703
  id,
@@ -549,7 +718,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
549
718
  )}, 'delayed', ${when}, ${timestamp})
550
719
  `;
551
720
 
552
- await this.#scheduleNextAlarm();
721
+ await this._scheduleNextAlarm();
553
722
 
554
723
  return {
555
724
  id,
@@ -571,7 +740,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
571
740
  )}, 'cron', ${when}, ${timestamp})
572
741
  `;
573
742
 
574
- await this.#scheduleNextAlarm();
743
+ await this._scheduleNextAlarm();
575
744
 
576
745
  return {
577
746
  id,
@@ -658,11 +827,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
658
827
  async cancelSchedule(id: string): Promise<boolean> {
659
828
  this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
660
829
 
661
- await this.#scheduleNextAlarm();
830
+ await this._scheduleNextAlarm();
662
831
  return true;
663
832
  }
664
833
 
665
- async #scheduleNextAlarm() {
834
+ private async _scheduleNextAlarm() {
666
835
  // Find the next schedule that needs to be executed
667
836
  const result = this.sql`
668
837
  SELECT time FROM cf_agents_schedules
@@ -679,10 +848,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
679
848
  }
680
849
 
681
850
  /**
682
- * Method called when an alarm fires
683
- * Executes any scheduled tasks that are due
851
+ * Method called when an alarm fires.
852
+ * Executes any scheduled tasks that are due.
853
+ *
854
+ * @remarks
855
+ * To schedule a task, please use the `this.schedule` method instead.
856
+ * See {@link https://developers.cloudflare.com/agents/api-reference/schedule-tasks/}
684
857
  */
685
- async alarm() {
858
+ public readonly alarm = async () => {
686
859
  const now = Math.floor(Date.now() / 1000);
687
860
 
688
861
  // Get all schedules that should be executed now
@@ -696,7 +869,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
696
869
  console.error(`callback ${row.callback} not found`);
697
870
  continue;
698
871
  }
699
- await unstable_context.run(
872
+ await agentContext.run(
700
873
  { agent: this, connection: undefined, request: undefined },
701
874
  async () => {
702
875
  try {
@@ -728,8 +901,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
728
901
  }
729
902
 
730
903
  // Schedule the next alarm
731
- await this.#scheduleNextAlarm();
732
- }
904
+ await this._scheduleNextAlarm();
905
+ };
733
906
 
734
907
  /**
735
908
  * Destroy the Agent, removing all state and scheduled tasks
@@ -738,6 +911,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
738
911
  // drop all tables
739
912
  this.sql`DROP TABLE IF EXISTS cf_agents_state`;
740
913
  this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
914
+ this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
741
915
 
742
916
  // delete all alarms
743
917
  await this.ctx.storage.deleteAlarm();
@@ -748,10 +922,172 @@ export class Agent<Env, State = unknown> extends Server<Env> {
748
922
  * Get all methods marked as callable on this Agent
749
923
  * @returns A map of method names to their metadata
750
924
  */
751
- #isCallable(method: string): boolean {
752
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
925
+ private _isCallable(method: string): boolean {
753
926
  return callableMetadata.has(this[method as keyof this] as Function);
754
927
  }
928
+
929
+ /**
930
+ * Connect to a new MCP Server
931
+ *
932
+ * @param url MCP Server SSE URL
933
+ * @param callbackHost Base host for the agent, used for the redirect URI.
934
+ * @param agentsPrefix agents routing prefix if not using `agents`
935
+ * @param options MCP client and transport (header) options
936
+ * @returns authUrl
937
+ */
938
+ async addMcpServer(
939
+ serverName: string,
940
+ url: string,
941
+ callbackHost: string,
942
+ agentsPrefix = "agents",
943
+ options?: {
944
+ client?: ConstructorParameters<typeof Client>[1];
945
+ transport?: {
946
+ headers: HeadersInit;
947
+ };
948
+ }
949
+ ): Promise<{ id: string; authUrl: string | undefined }> {
950
+ const callbackUrl = `${callbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
951
+
952
+ const result = await this._connectToMcpServerInternal(
953
+ serverName,
954
+ url,
955
+ callbackUrl,
956
+ options
957
+ );
958
+
959
+ this.broadcast(
960
+ JSON.stringify({
961
+ type: "cf_agent_mcp_servers",
962
+ mcp: this.getMcpServers(),
963
+ })
964
+ );
965
+
966
+ return result;
967
+ }
968
+
969
+ async _connectToMcpServerInternal(
970
+ serverName: string,
971
+ url: string,
972
+ callbackUrl: string,
973
+ // it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
974
+ options?: {
975
+ client?: ConstructorParameters<typeof Client>[1];
976
+ /**
977
+ * We don't expose the normal set of transport options because:
978
+ * 1) we can't serialize things like the auth provider or a fetch function into the DB for reconnection purposes
979
+ * 2) We probably want these options to be agnostic to the transport type (SSE vs Streamable)
980
+ *
981
+ * This has the limitation that you can't override fetch, but I think headers should handle nearly all cases needed (i.e. non-standard bearer auth).
982
+ */
983
+ transport?: {
984
+ headers?: HeadersInit;
985
+ };
986
+ },
987
+ reconnect?: {
988
+ id: string;
989
+ oauthClientId?: string;
990
+ }
991
+ ): Promise<{ id: string; authUrl: string | undefined }> {
992
+ const authProvider = new DurableObjectOAuthClientProvider(
993
+ this.ctx.storage,
994
+ this.name,
995
+ callbackUrl
996
+ );
997
+
998
+ if (reconnect) {
999
+ authProvider.serverId = reconnect.id;
1000
+ if (reconnect.oauthClientId) {
1001
+ authProvider.clientId = reconnect.oauthClientId;
1002
+ }
1003
+ }
1004
+
1005
+ // allows passing through transport headers if necessary
1006
+ // this handles some non-standard bearer auth setups (i.e. MCP server behind CF access instead of OAuth)
1007
+ let headerTransportOpts: SSEClientTransportOptions = {};
1008
+ if (options?.transport?.headers) {
1009
+ headerTransportOpts = {
1010
+ eventSourceInit: {
1011
+ fetch: (url, init) =>
1012
+ fetch(url, {
1013
+ ...init,
1014
+ headers: options?.transport?.headers,
1015
+ }),
1016
+ },
1017
+ requestInit: {
1018
+ headers: options?.transport?.headers,
1019
+ },
1020
+ };
1021
+ }
1022
+
1023
+ const { id, authUrl, clientId } = await this.mcp.connect(url, {
1024
+ reconnect,
1025
+ transport: {
1026
+ ...headerTransportOpts,
1027
+ authProvider,
1028
+ },
1029
+ client: options?.client,
1030
+ });
1031
+
1032
+ this.sql`
1033
+ INSERT OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
1034
+ VALUES (
1035
+ ${id},
1036
+ ${serverName},
1037
+ ${url},
1038
+ ${clientId ?? null},
1039
+ ${authUrl ?? null},
1040
+ ${callbackUrl},
1041
+ ${options ? JSON.stringify(options) : null}
1042
+ );
1043
+ `;
1044
+
1045
+ return {
1046
+ id,
1047
+ authUrl,
1048
+ };
1049
+ }
1050
+
1051
+ async removeMcpServer(id: string) {
1052
+ this.mcp.closeConnection(id);
1053
+ this.sql`
1054
+ DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
1055
+ `;
1056
+ this.broadcast(
1057
+ JSON.stringify({
1058
+ type: "cf_agent_mcp_servers",
1059
+ mcp: this.getMcpServers(),
1060
+ })
1061
+ );
1062
+ }
1063
+
1064
+ getMcpServers(): MCPServersState {
1065
+ const mcpState: MCPServersState = {
1066
+ servers: {},
1067
+ tools: this.mcp.listTools(),
1068
+ prompts: this.mcp.listPrompts(),
1069
+ resources: this.mcp.listResources(),
1070
+ };
1071
+
1072
+ const servers = this.sql<MCPServerRow>`
1073
+ SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
1074
+ `;
1075
+
1076
+ for (const server of servers) {
1077
+ const serverConn = this.mcp.mcpConnections[server.id];
1078
+ mcpState.servers[server.id] = {
1079
+ name: server.name,
1080
+ server_url: server.server_url,
1081
+ auth_url: server.auth_url,
1082
+ // mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
1083
+ state: serverConn?.connectionState ?? "authenticating",
1084
+ instructions: serverConn?.instructions ?? null,
1085
+ capabilities: serverConn?.serverCapabilities ?? null,
1086
+ };
1087
+ }
1088
+
1089
+ return mcpState;
1090
+ }
755
1091
  }
756
1092
 
757
1093
  /**
@@ -855,7 +1191,7 @@ export async function routeAgentEmail<Env>(
855
1191
  * @param options Options for Agent creation
856
1192
  * @returns Promise resolving to an Agent instance stub
857
1193
  */
858
- export function getAgentByName<Env, T extends Agent<Env>>(
1194
+ export async function getAgentByName<Env, T extends Agent<Env>>(
859
1195
  namespace: AgentNamespace<T>,
860
1196
  name: string,
861
1197
  options?: {
@@ -870,13 +1206,13 @@ export function getAgentByName<Env, T extends Agent<Env>>(
870
1206
  * A wrapper for streaming responses in callable methods
871
1207
  */
872
1208
  export class StreamingResponse {
873
- #connection: Connection;
874
- #id: string;
875
- #closed = false;
1209
+ private _connection: Connection;
1210
+ private _id: string;
1211
+ private _closed = false;
876
1212
 
877
1213
  constructor(connection: Connection, id: string) {
878
- this.#connection = connection;
879
- this.#id = id;
1214
+ this._connection = connection;
1215
+ this._id = id;
880
1216
  }
881
1217
 
882
1218
  /**
@@ -884,17 +1220,17 @@ export class StreamingResponse {
884
1220
  * @param chunk The data to send
885
1221
  */
886
1222
  send(chunk: unknown) {
887
- if (this.#closed) {
1223
+ if (this._closed) {
888
1224
  throw new Error("StreamingResponse is already closed");
889
1225
  }
890
1226
  const response: RPCResponse = {
891
1227
  type: "rpc",
892
- id: this.#id,
1228
+ id: this._id,
893
1229
  success: true,
894
1230
  result: chunk,
895
1231
  done: false,
896
1232
  };
897
- this.#connection.send(JSON.stringify(response));
1233
+ this._connection.send(JSON.stringify(response));
898
1234
  }
899
1235
 
900
1236
  /**
@@ -902,17 +1238,17 @@ export class StreamingResponse {
902
1238
  * @param finalChunk Optional final chunk of data to send
903
1239
  */
904
1240
  end(finalChunk?: unknown) {
905
- if (this.#closed) {
1241
+ if (this._closed) {
906
1242
  throw new Error("StreamingResponse is already closed");
907
1243
  }
908
- this.#closed = true;
1244
+ this._closed = true;
909
1245
  const response: RPCResponse = {
910
1246
  type: "rpc",
911
- id: this.#id,
1247
+ id: this._id,
912
1248
  success: true,
913
1249
  result: finalChunk,
914
1250
  done: true,
915
1251
  };
916
- this.#connection.send(JSON.stringify(response));
1252
+ this._connection.send(JSON.stringify(response));
917
1253
  }
918
1254
  }