agents 0.0.0-ca44ae8 → 0.0.0-ca5cdf0

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 (41) hide show
  1. package/dist/ai-chat-agent.d.ts +31 -5
  2. package/dist/ai-chat-agent.js +119 -115
  3. package/dist/ai-chat-agent.js.map +1 -1
  4. package/dist/ai-react.d.ts +17 -4
  5. package/dist/ai-react.js +27 -29
  6. package/dist/ai-react.js.map +1 -1
  7. package/dist/{chunk-MDL3W36D.js → chunk-4CIGD73X.js} +295 -125
  8. package/dist/chunk-4CIGD73X.js.map +1 -0
  9. package/dist/chunk-767EASBA.js +106 -0
  10. package/dist/chunk-767EASBA.js.map +1 -0
  11. package/dist/{chunk-Q5ZBHY4Z.js → chunk-E3LCYPCB.js} +49 -36
  12. package/dist/chunk-E3LCYPCB.js.map +1 -0
  13. package/dist/chunk-NKZZ66QY.js +116 -0
  14. package/dist/chunk-NKZZ66QY.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 +103 -9
  19. package/dist/index.js +4 -3
  20. package/dist/mcp/client.d.ts +301 -23
  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 +17 -7
  26. package/dist/mcp/index.js +146 -173
  27. package/dist/mcp/index.js.map +1 -1
  28. package/dist/react.d.ts +85 -5
  29. package/dist/react.js +20 -8
  30. package/dist/react.js.map +1 -1
  31. package/dist/schedule.d.ts +2 -2
  32. package/dist/schedule.js +4 -6
  33. package/dist/schedule.js.map +1 -1
  34. package/dist/serializable.d.ts +32 -0
  35. package/dist/serializable.js +1 -0
  36. package/package.json +70 -69
  37. package/src/index.ts +378 -77
  38. package/dist/chunk-HMLY7DHA.js +0 -16
  39. package/dist/chunk-MDL3W36D.js.map +0 -1
  40. package/dist/chunk-Q5ZBHY4Z.js.map +0 -1
  41. /package/dist/{chunk-HMLY7DHA.js.map → serializable.js.map} +0 -0
package/src/index.ts CHANGED
@@ -1,20 +1,30 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
4
+
5
+ import type {
6
+ Prompt,
7
+ Resource,
8
+ ServerCapabilities,
9
+ Tool,
10
+ } from "@modelcontextprotocol/sdk/types.js";
11
+ import { parseCronExpression } from "cron-schedule";
12
+ import { nanoid } from "nanoid";
1
13
  import {
2
- Server,
3
- routePartykitRequest,
4
- type PartyServerOptions,
5
- getServerByName,
6
14
  type Connection,
7
15
  type ConnectionContext,
16
+ getServerByName,
17
+ type PartyServerOptions,
18
+ routePartykitRequest,
19
+ Server,
8
20
  type WSMessage,
9
21
  } from "partyserver";
10
-
11
- import { parseCronExpression } from "cron-schedule";
12
- import { nanoid } from "nanoid";
13
-
14
- import { AsyncLocalStorage } from "node:async_hooks";
22
+ import { camelCaseToKebabCase } from "./client";
15
23
  import { MCPClientManager } from "./mcp/client";
24
+ // import type { MCPClientConnection } from "./mcp/client-connection";
25
+ import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
16
26
 
17
- export type { Connection, WSMessage, ConnectionContext } from "partyserver";
27
+ export type { Connection, ConnectionContext, WSMessage } from "partyserver";
18
28
 
19
29
  /**
20
30
  * RPC request message from client
@@ -98,7 +108,6 @@ export type CallableMetadata = {
98
108
  streaming?: boolean;
99
109
  };
100
110
 
101
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
102
111
  const callableMetadata = new Map<Function, CallableMetadata>();
103
112
 
104
113
  /**
@@ -108,6 +117,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
108
117
  export function unstable_callable(metadata: CallableMetadata = {}) {
109
118
  return function callableDecorator<This, Args extends unknown[], Return>(
110
119
  target: (this: This, ...args: Args) => Return,
120
+ // biome-ignore lint/correctness/noUnusedFunctionParameters: later
111
121
  context: ClassMethodDecoratorContext
112
122
  ) {
113
123
  if (!callableMetadata.has(target)) {
@@ -159,6 +169,48 @@ function getNextCronTime(cron: string) {
159
169
  return interval.getNextDate();
160
170
  }
161
171
 
172
+ /**
173
+ * MCP Server state update message from server -> Client
174
+ */
175
+ export type MCPServerMessage = {
176
+ type: "cf_agent_mcp_servers";
177
+ mcp: MCPServersState;
178
+ };
179
+
180
+ export type MCPServersState = {
181
+ servers: {
182
+ [id: string]: MCPServer;
183
+ };
184
+ tools: Tool[];
185
+ prompts: Prompt[];
186
+ resources: Resource[];
187
+ };
188
+
189
+ export type MCPServer = {
190
+ name: string;
191
+ server_url: string;
192
+ auth_url: string | null;
193
+ // This state is specifically about the temporary process of getting a token (if needed).
194
+ // Scope outside of that can't be relied upon because when the DO sleeps, there's no way
195
+ // to communicate a change to a non-ready state.
196
+ state: "authenticating" | "connecting" | "ready" | "discovering" | "failed";
197
+ instructions: string | null;
198
+ capabilities: ServerCapabilities | null;
199
+ };
200
+
201
+ /**
202
+ * MCP Server data stored in DO SQL for resuming MCP Server connections
203
+ */
204
+ type MCPServerRow = {
205
+ id: string;
206
+ name: string;
207
+ server_url: string;
208
+ client_id: string | null;
209
+ auth_url: string | null;
210
+ callback_url: string;
211
+ server_options: string;
212
+ };
213
+
162
214
  const STATE_ROW_ID = "cf_state_row_id";
163
215
  const STATE_WAS_CHANGED = "cf_state_was_changed";
164
216
 
@@ -200,12 +252,12 @@ export function getCurrentAgent<
200
252
  * @template State State type to store within the Agent
201
253
  */
202
254
  export class Agent<Env, State = unknown> extends Server<Env> {
203
- #state = DEFAULT_STATE as State;
255
+ private _state = DEFAULT_STATE as State;
204
256
 
205
- #ParentClass: typeof Agent<Env, State> =
257
+ private _ParentClass: typeof Agent<Env, State> =
206
258
  Object.getPrototypeOf(this).constructor;
207
259
 
208
- mcp: MCPClientManager = new MCPClientManager(this.#ParentClass.name, "0.0.1");
260
+ mcp: MCPClientManager = new MCPClientManager(this._ParentClass.name, "0.0.1");
209
261
 
210
262
  /**
211
263
  * Initial state for the Agent
@@ -217,9 +269,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
217
269
  * Current state of the Agent
218
270
  */
219
271
  get state(): State {
220
- if (this.#state !== DEFAULT_STATE) {
272
+ if (this._state !== DEFAULT_STATE) {
221
273
  // state was previously set, and populated internal state
222
- return this.#state;
274
+ return this._state;
223
275
  }
224
276
  // looks like this is the first time the state is being accessed
225
277
  // check if the state was set in a previous life
@@ -239,8 +291,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
239
291
  ) {
240
292
  const state = result[0]?.state as string; // could be null?
241
293
 
242
- this.#state = JSON.parse(state);
243
- return this.#state;
294
+ this._state = JSON.parse(state);
295
+ return this._state;
244
296
  }
245
297
 
246
298
  // ok, this is the first time the state is being accessed
@@ -301,7 +353,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
301
353
  `;
302
354
 
303
355
  void this.ctx.blockConcurrencyWhile(async () => {
304
- return this.#tryCatch(async () => {
356
+ return this._tryCatch(async () => {
305
357
  // Create alarms table if it doesn't exist
306
358
  this.sql`
307
359
  CREATE TABLE IF NOT EXISTS cf_agents_schedules (
@@ -321,12 +373,42 @@ export class Agent<Env, State = unknown> extends Server<Env> {
321
373
  });
322
374
  });
323
375
 
376
+ this.sql`
377
+ CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
378
+ id TEXT PRIMARY KEY NOT NULL,
379
+ name TEXT NOT NULL,
380
+ server_url TEXT NOT NULL,
381
+ callback_url TEXT NOT NULL,
382
+ client_id TEXT,
383
+ auth_url TEXT,
384
+ server_options TEXT
385
+ )
386
+ `;
387
+
324
388
  const _onRequest = this.onRequest.bind(this);
325
389
  this.onRequest = (request: Request) => {
326
390
  return agentContext.run(
327
391
  { agent: this, connection: undefined, request },
328
392
  async () => {
329
- return this.#tryCatch(() => _onRequest(request));
393
+ if (this.mcp.isCallbackRequest(request)) {
394
+ await this.mcp.handleCallbackRequest(request);
395
+
396
+ // after the MCP connection handshake, we can send updated mcp state
397
+ this.broadcast(
398
+ JSON.stringify({
399
+ mcp: this.getMcpServers(),
400
+ type: "cf_agent_mcp_servers",
401
+ })
402
+ );
403
+
404
+ // We probably should let the user configure this response/redirect, but this is fine for now.
405
+ return new Response("<script>window.close();</script>", {
406
+ headers: { "content-type": "text/html" },
407
+ status: 200,
408
+ });
409
+ }
410
+
411
+ return this._tryCatch(() => _onRequest(request));
330
412
  }
331
413
  );
332
414
  };
@@ -337,19 +419,19 @@ export class Agent<Env, State = unknown> extends Server<Env> {
337
419
  { agent: this, connection, request: undefined },
338
420
  async () => {
339
421
  if (typeof message !== "string") {
340
- return this.#tryCatch(() => _onMessage(connection, message));
422
+ return this._tryCatch(() => _onMessage(connection, message));
341
423
  }
342
424
 
343
425
  let parsed: unknown;
344
426
  try {
345
427
  parsed = JSON.parse(message);
346
- } catch (e) {
428
+ } catch (_e) {
347
429
  // silently fail and let the onMessage handler handle it
348
- return this.#tryCatch(() => _onMessage(connection, message));
430
+ return this._tryCatch(() => _onMessage(connection, message));
349
431
  }
350
432
 
351
433
  if (isStateUpdateMessage(parsed)) {
352
- this.#setStateInternal(parsed.state as State, connection);
434
+ this._setStateInternal(parsed.state as State, connection);
353
435
  return;
354
436
  }
355
437
 
@@ -363,11 +445,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
363
445
  throw new Error(`Method ${method} does not exist`);
364
446
  }
365
447
 
366
- if (!this.#isCallable(method)) {
448
+ if (!this._isCallable(method)) {
367
449
  throw new Error(`Method ${method} is not callable`);
368
450
  }
369
451
 
370
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
371
452
  const metadata = callableMetadata.get(methodFn as Function);
372
453
 
373
454
  // For streaming methods, pass a StreamingResponse object
@@ -380,21 +461,21 @@ export class Agent<Env, State = unknown> extends Server<Env> {
380
461
  // For regular methods, execute and send response
381
462
  const result = await methodFn.apply(this, args);
382
463
  const response: RPCResponse = {
383
- type: "rpc",
464
+ done: true,
384
465
  id,
385
- success: true,
386
466
  result,
387
- done: true,
467
+ success: true,
468
+ type: "rpc",
388
469
  };
389
470
  connection.send(JSON.stringify(response));
390
471
  } catch (e) {
391
472
  // Send error response
392
473
  const response: RPCResponse = {
393
- type: "rpc",
394
- id: parsed.id,
395
- success: false,
396
474
  error:
397
475
  e instanceof Error ? e.message : "Unknown error occurred",
476
+ id: parsed.id,
477
+ success: false,
478
+ type: "rpc",
398
479
  };
399
480
  connection.send(JSON.stringify(response));
400
481
  console.error("RPC error:", e);
@@ -402,7 +483,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
402
483
  return;
403
484
  }
404
485
 
405
- return this.#tryCatch(() => _onMessage(connection, message));
486
+ return this._tryCatch(() => _onMessage(connection, message));
406
487
  }
407
488
  );
408
489
  };
@@ -418,20 +499,69 @@ export class Agent<Env, State = unknown> extends Server<Env> {
418
499
  if (this.state) {
419
500
  connection.send(
420
501
  JSON.stringify({
421
- type: "cf_agent_state",
422
502
  state: this.state,
503
+ type: "cf_agent_state",
423
504
  })
424
505
  );
425
506
  }
426
- return this.#tryCatch(() => _onConnect(connection, ctx));
507
+
508
+ connection.send(
509
+ JSON.stringify({
510
+ mcp: this.getMcpServers(),
511
+ type: "cf_agent_mcp_servers",
512
+ })
513
+ );
514
+
515
+ return this._tryCatch(() => _onConnect(connection, ctx));
427
516
  }, 20);
428
517
  }
429
518
  );
430
519
  };
520
+
521
+ const _onStart = this.onStart.bind(this);
522
+ this.onStart = async () => {
523
+ return agentContext.run(
524
+ { agent: this, connection: undefined, request: undefined },
525
+ async () => {
526
+ const servers = this.sql<MCPServerRow>`
527
+ SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
528
+ `;
529
+
530
+ // from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
531
+ Promise.allSettled(
532
+ servers.map((server) => {
533
+ return this._connectToMcpServerInternal(
534
+ server.name,
535
+ server.server_url,
536
+ server.callback_url,
537
+ server.server_options
538
+ ? JSON.parse(server.server_options)
539
+ : undefined,
540
+ {
541
+ id: server.id,
542
+ oauthClientId: server.client_id ?? undefined,
543
+ }
544
+ );
545
+ })
546
+ ).then((_results) => {
547
+ this.broadcast(
548
+ JSON.stringify({
549
+ mcp: this.getMcpServers(),
550
+ type: "cf_agent_mcp_servers",
551
+ })
552
+ );
553
+ });
554
+ await this._tryCatch(() => _onStart());
555
+ }
556
+ );
557
+ };
431
558
  }
432
559
 
433
- #setStateInternal(state: State, source: Connection | "server" = "server") {
434
- this.#state = state;
560
+ private _setStateInternal(
561
+ state: State,
562
+ source: Connection | "server" = "server"
563
+ ) {
564
+ this._state = state;
435
565
  this.sql`
436
566
  INSERT OR REPLACE INTO cf_agents_state (id, state)
437
567
  VALUES (${STATE_ROW_ID}, ${JSON.stringify(state)})
@@ -442,12 +572,12 @@ export class Agent<Env, State = unknown> extends Server<Env> {
442
572
  `;
443
573
  this.broadcast(
444
574
  JSON.stringify({
445
- type: "cf_agent_state",
446
575
  state: state,
576
+ type: "cf_agent_state",
447
577
  }),
448
578
  source !== "server" ? [source.id] : []
449
579
  );
450
- return this.#tryCatch(() => {
580
+ return this._tryCatch(() => {
451
581
  const { connection, request } = agentContext.getStore() || {};
452
582
  return agentContext.run(
453
583
  { agent: this, connection, request },
@@ -463,7 +593,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
463
593
  * @param state New state to set
464
594
  */
465
595
  setState(state: State) {
466
- this.#setStateInternal(state, "server");
596
+ this._setStateInternal(state, "server");
467
597
  }
468
598
 
469
599
  /**
@@ -471,6 +601,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
471
601
  * @param state Updated state
472
602
  * @param source Source of the state update ("server" or a client connection)
473
603
  */
604
+ // biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later
474
605
  onStateUpdate(state: State | undefined, source: Connection | "server") {
475
606
  // override this to handle state updates
476
607
  }
@@ -479,6 +610,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
479
610
  * Called when the Agent receives an email
480
611
  * @param email Email message to process
481
612
  */
613
+ // biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later
482
614
  onEmail(email: ForwardableEmailMessage) {
483
615
  return agentContext.run(
484
616
  { agent: this, connection: undefined, request: undefined },
@@ -488,7 +620,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
488
620
  );
489
621
  }
490
622
 
491
- async #tryCatch<T>(fn: () => T | Promise<T>) {
623
+ private async _tryCatch<T>(fn: () => T | Promise<T>) {
492
624
  try {
493
625
  return await fn();
494
626
  } catch (e) {
@@ -562,11 +694,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
562
694
  )}, 'scheduled', ${timestamp})
563
695
  `;
564
696
 
565
- await this.#scheduleNextAlarm();
697
+ await this._scheduleNextAlarm();
566
698
 
567
699
  return {
568
- id,
569
700
  callback: callback,
701
+ id,
570
702
  payload: payload as T,
571
703
  time: timestamp,
572
704
  type: "scheduled",
@@ -583,13 +715,13 @@ export class Agent<Env, State = unknown> extends Server<Env> {
583
715
  )}, 'delayed', ${when}, ${timestamp})
584
716
  `;
585
717
 
586
- await this.#scheduleNextAlarm();
718
+ await this._scheduleNextAlarm();
587
719
 
588
720
  return {
589
- id,
590
721
  callback: callback,
591
- payload: payload as T,
592
722
  delayInSeconds: when,
723
+ id,
724
+ payload: payload as T,
593
725
  time: timestamp,
594
726
  type: "delayed",
595
727
  };
@@ -605,13 +737,13 @@ export class Agent<Env, State = unknown> extends Server<Env> {
605
737
  )}, 'cron', ${when}, ${timestamp})
606
738
  `;
607
739
 
608
- await this.#scheduleNextAlarm();
740
+ await this._scheduleNextAlarm();
609
741
 
610
742
  return {
611
- id,
612
743
  callback: callback,
613
- payload: payload as T,
614
744
  cron: when,
745
+ id,
746
+ payload: payload as T,
615
747
  time: timestamp,
616
748
  type: "cron",
617
749
  };
@@ -692,11 +824,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
692
824
  async cancelSchedule(id: string): Promise<boolean> {
693
825
  this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
694
826
 
695
- await this.#scheduleNextAlarm();
827
+ await this._scheduleNextAlarm();
696
828
  return true;
697
829
  }
698
830
 
699
- async #scheduleNextAlarm() {
831
+ private async _scheduleNextAlarm() {
700
832
  // Find the next schedule that needs to be executed
701
833
  const result = this.sql`
702
834
  SELECT time FROM cf_agents_schedules
@@ -766,7 +898,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
766
898
  }
767
899
 
768
900
  // Schedule the next alarm
769
- await this.#scheduleNextAlarm();
901
+ await this._scheduleNextAlarm();
770
902
  };
771
903
 
772
904
  /**
@@ -776,20 +908,189 @@ export class Agent<Env, State = unknown> extends Server<Env> {
776
908
  // drop all tables
777
909
  this.sql`DROP TABLE IF EXISTS cf_agents_state`;
778
910
  this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
911
+ this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
779
912
 
780
913
  // delete all alarms
781
914
  await this.ctx.storage.deleteAlarm();
782
915
  await this.ctx.storage.deleteAll();
916
+ this.ctx.abort("destroyed"); // enforce that the agent is evicted
783
917
  }
784
918
 
785
919
  /**
786
920
  * Get all methods marked as callable on this Agent
787
921
  * @returns A map of method names to their metadata
788
922
  */
789
- #isCallable(method: string): boolean {
790
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
923
+ private _isCallable(method: string): boolean {
791
924
  return callableMetadata.has(this[method as keyof this] as Function);
792
925
  }
926
+
927
+ /**
928
+ * Connect to a new MCP Server
929
+ *
930
+ * @param url MCP Server SSE URL
931
+ * @param callbackHost Base host for the agent, used for the redirect URI.
932
+ * @param agentsPrefix agents routing prefix if not using `agents`
933
+ * @param options MCP client and transport (header) options
934
+ * @returns authUrl
935
+ */
936
+ async addMcpServer(
937
+ serverName: string,
938
+ url: string,
939
+ callbackHost: string,
940
+ agentsPrefix = "agents",
941
+ options?: {
942
+ client?: ConstructorParameters<typeof Client>[1];
943
+ transport?: {
944
+ headers: HeadersInit;
945
+ };
946
+ }
947
+ ): Promise<{ id: string; authUrl: string | undefined }> {
948
+ const callbackUrl = `${callbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
949
+
950
+ const result = await this._connectToMcpServerInternal(
951
+ serverName,
952
+ url,
953
+ callbackUrl,
954
+ options
955
+ );
956
+ this.sql`
957
+ INSERT
958
+ OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
959
+ VALUES (
960
+ ${result.id},
961
+ ${serverName},
962
+ ${url},
963
+ ${result.clientId ?? null},
964
+ ${result.authUrl ?? null},
965
+ ${callbackUrl},
966
+ ${options ? JSON.stringify(options) : null}
967
+ );
968
+ `;
969
+
970
+ this.broadcast(
971
+ JSON.stringify({
972
+ mcp: this.getMcpServers(),
973
+ type: "cf_agent_mcp_servers",
974
+ })
975
+ );
976
+
977
+ return result;
978
+ }
979
+
980
+ async _connectToMcpServerInternal(
981
+ _serverName: string,
982
+ url: string,
983
+ callbackUrl: string,
984
+ // it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
985
+ options?: {
986
+ client?: ConstructorParameters<typeof Client>[1];
987
+ /**
988
+ * We don't expose the normal set of transport options because:
989
+ * 1) we can't serialize things like the auth provider or a fetch function into the DB for reconnection purposes
990
+ * 2) We probably want these options to be agnostic to the transport type (SSE vs Streamable)
991
+ *
992
+ * 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).
993
+ */
994
+ transport?: {
995
+ headers?: HeadersInit;
996
+ };
997
+ },
998
+ reconnect?: {
999
+ id: string;
1000
+ oauthClientId?: string;
1001
+ }
1002
+ ): Promise<{
1003
+ id: string;
1004
+ authUrl: string | undefined;
1005
+ clientId: string | undefined;
1006
+ }> {
1007
+ const authProvider = new DurableObjectOAuthClientProvider(
1008
+ this.ctx.storage,
1009
+ this.name,
1010
+ callbackUrl
1011
+ );
1012
+
1013
+ if (reconnect) {
1014
+ authProvider.serverId = reconnect.id;
1015
+ if (reconnect.oauthClientId) {
1016
+ authProvider.clientId = reconnect.oauthClientId;
1017
+ }
1018
+ }
1019
+
1020
+ // allows passing through transport headers if necessary
1021
+ // this handles some non-standard bearer auth setups (i.e. MCP server behind CF access instead of OAuth)
1022
+ let headerTransportOpts: SSEClientTransportOptions = {};
1023
+ if (options?.transport?.headers) {
1024
+ headerTransportOpts = {
1025
+ eventSourceInit: {
1026
+ fetch: (url, init) =>
1027
+ fetch(url, {
1028
+ ...init,
1029
+ headers: options?.transport?.headers,
1030
+ }),
1031
+ },
1032
+ requestInit: {
1033
+ headers: options?.transport?.headers,
1034
+ },
1035
+ };
1036
+ }
1037
+
1038
+ const { id, authUrl, clientId } = await this.mcp.connect(url, {
1039
+ client: options?.client,
1040
+ reconnect,
1041
+ transport: {
1042
+ ...headerTransportOpts,
1043
+ authProvider,
1044
+ },
1045
+ });
1046
+
1047
+ return {
1048
+ authUrl,
1049
+ clientId,
1050
+ id,
1051
+ };
1052
+ }
1053
+
1054
+ async removeMcpServer(id: string) {
1055
+ this.mcp.closeConnection(id);
1056
+ this.sql`
1057
+ DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
1058
+ `;
1059
+ this.broadcast(
1060
+ JSON.stringify({
1061
+ mcp: this.getMcpServers(),
1062
+ type: "cf_agent_mcp_servers",
1063
+ })
1064
+ );
1065
+ }
1066
+
1067
+ getMcpServers(): MCPServersState {
1068
+ const mcpState: MCPServersState = {
1069
+ prompts: this.mcp.listPrompts(),
1070
+ resources: this.mcp.listResources(),
1071
+ servers: {},
1072
+ tools: this.mcp.listTools(),
1073
+ };
1074
+
1075
+ const servers = this.sql<MCPServerRow>`
1076
+ SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
1077
+ `;
1078
+
1079
+ for (const server of servers) {
1080
+ const serverConn = this.mcp.mcpConnections[server.id];
1081
+ mcpState.servers[server.id] = {
1082
+ auth_url: server.auth_url,
1083
+ capabilities: serverConn?.serverCapabilities ?? null,
1084
+ instructions: serverConn?.instructions ?? null,
1085
+ name: server.name,
1086
+ server_url: server.server_url,
1087
+ // mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
1088
+ state: serverConn?.connectionState ?? "authenticating",
1089
+ };
1090
+ }
1091
+
1092
+ return mcpState;
1093
+ }
793
1094
  }
794
1095
 
795
1096
  /**
@@ -829,9 +1130,9 @@ export async function routeAgentRequest<Env>(
829
1130
  const corsHeaders =
830
1131
  options?.cors === true
831
1132
  ? {
832
- "Access-Control-Allow-Origin": "*",
833
- "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
834
1133
  "Access-Control-Allow-Credentials": "true",
1134
+ "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
1135
+ "Access-Control-Allow-Origin": "*",
835
1136
  "Access-Control-Max-Age": "86400",
836
1137
  }
837
1138
  : options?.cors;
@@ -879,9 +1180,9 @@ export async function routeAgentRequest<Env>(
879
1180
  * @param options Routing options
880
1181
  */
881
1182
  export async function routeAgentEmail<Env>(
882
- email: ForwardableEmailMessage,
883
- env: Env,
884
- options?: AgentOptions<Env>
1183
+ _email: ForwardableEmailMessage,
1184
+ _env: Env,
1185
+ _options?: AgentOptions<Env>
885
1186
  ): Promise<void> {}
886
1187
 
887
1188
  /**
@@ -908,13 +1209,13 @@ export async function getAgentByName<Env, T extends Agent<Env>>(
908
1209
  * A wrapper for streaming responses in callable methods
909
1210
  */
910
1211
  export class StreamingResponse {
911
- #connection: Connection;
912
- #id: string;
913
- #closed = false;
1212
+ private _connection: Connection;
1213
+ private _id: string;
1214
+ private _closed = false;
914
1215
 
915
1216
  constructor(connection: Connection, id: string) {
916
- this.#connection = connection;
917
- this.#id = id;
1217
+ this._connection = connection;
1218
+ this._id = id;
918
1219
  }
919
1220
 
920
1221
  /**
@@ -922,17 +1223,17 @@ export class StreamingResponse {
922
1223
  * @param chunk The data to send
923
1224
  */
924
1225
  send(chunk: unknown) {
925
- if (this.#closed) {
1226
+ if (this._closed) {
926
1227
  throw new Error("StreamingResponse is already closed");
927
1228
  }
928
1229
  const response: RPCResponse = {
929
- type: "rpc",
930
- id: this.#id,
931
- success: true,
932
- result: chunk,
933
1230
  done: false,
1231
+ id: this._id,
1232
+ result: chunk,
1233
+ success: true,
1234
+ type: "rpc",
934
1235
  };
935
- this.#connection.send(JSON.stringify(response));
1236
+ this._connection.send(JSON.stringify(response));
936
1237
  }
937
1238
 
938
1239
  /**
@@ -940,17 +1241,17 @@ export class StreamingResponse {
940
1241
  * @param finalChunk Optional final chunk of data to send
941
1242
  */
942
1243
  end(finalChunk?: unknown) {
943
- if (this.#closed) {
1244
+ if (this._closed) {
944
1245
  throw new Error("StreamingResponse is already closed");
945
1246
  }
946
- this.#closed = true;
1247
+ this._closed = true;
947
1248
  const response: RPCResponse = {
948
- type: "rpc",
949
- id: this.#id,
950
- success: true,
951
- result: finalChunk,
952
1249
  done: true,
1250
+ id: this._id,
1251
+ result: finalChunk,
1252
+ success: true,
1253
+ type: "rpc",
953
1254
  };
954
- this.#connection.send(JSON.stringify(response));
1255
+ this._connection.send(JSON.stringify(response));
955
1256
  }
956
1257
  }