agents 0.0.0-c3e8618 → 0.0.0-c4c9271

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 (43) hide show
  1. package/dist/ai-chat-agent.d.ts +73 -23
  2. package/dist/ai-chat-agent.js +230 -0
  3. package/dist/ai-chat-agent.js.map +1 -0
  4. package/dist/ai-react.d.ts +85 -44
  5. package/dist/ai-react.js +203 -0
  6. package/dist/ai-react.js.map +1 -0
  7. package/dist/ai-types.d.ts +65 -40
  8. package/dist/ai-types.js +1 -0
  9. package/dist/ai-types.js.map +1 -0
  10. package/dist/chunk-6RPGDIE2.js +786 -0
  11. package/dist/chunk-6RPGDIE2.js.map +1 -0
  12. package/dist/chunk-BZXOAZUX.js +106 -0
  13. package/dist/chunk-BZXOAZUX.js.map +1 -0
  14. package/dist/chunk-OYJXQRRH.js +465 -0
  15. package/dist/chunk-OYJXQRRH.js.map +1 -0
  16. package/dist/chunk-VCSB47AK.js +116 -0
  17. package/dist/chunk-VCSB47AK.js.map +1 -0
  18. package/dist/client.d.ts +71 -37
  19. package/dist/client.js +11 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/index.d.ts +331 -179
  22. package/dist/index.js +22 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/mcp/client.d.ts +142 -34
  25. package/dist/mcp/client.js +9 -0
  26. package/dist/mcp/client.js.map +1 -0
  27. package/dist/mcp/do-oauth-client-provider.d.ts +41 -0
  28. package/dist/mcp/do-oauth-client-provider.js +7 -0
  29. package/dist/mcp/do-oauth-client-provider.js.map +1 -0
  30. package/dist/mcp/index.d.ts +50 -7
  31. package/dist/mcp/index.js +782 -0
  32. package/dist/mcp/index.js.map +1 -0
  33. package/dist/react.d.ts +104 -15
  34. package/dist/react.js +116 -0
  35. package/dist/react.js.map +1 -0
  36. package/dist/schedule.d.ts +30 -20
  37. package/dist/schedule.js +71 -0
  38. package/dist/schedule.js.map +1 -0
  39. package/dist/serializable.d.ts +32 -0
  40. package/dist/serializable.js +1 -0
  41. package/dist/serializable.js.map +1 -0
  42. package/package.json +28 -5
  43. package/src/index.ts +396 -60
package/src/index.ts CHANGED
@@ -1,21 +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";
21
+ import { MCPClientManager } from "./mcp/client";
22
+ import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
15
23
 
16
- 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";
17
26
 
18
- import { WorkflowEntrypoint as CFWorkflowEntrypoint } from "cloudflare:workers";
27
+ import { camelCaseToKebabCase } from "./client";
28
+ import type { MCPClientConnection } from "./mcp/client-connection";
29
+
30
+ export type { Connection, ConnectionContext, WSMessage } from "partyserver";
19
31
 
20
32
  /**
21
33
  * RPC request message from client
@@ -99,7 +111,6 @@ export type CallableMetadata = {
99
111
  streaming?: boolean;
100
112
  };
101
113
 
102
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
103
114
  const callableMetadata = new Map<Function, CallableMetadata>();
104
115
 
105
116
  /**
@@ -119,11 +130,6 @@ export function unstable_callable(metadata: CallableMetadata = {}) {
119
130
  };
120
131
  }
121
132
 
122
- /**
123
- * A class for creating workflow entry points that can be used with Cloudflare Workers
124
- */
125
- export class WorkflowEntrypoint extends CFWorkflowEntrypoint {}
126
-
127
133
  /**
128
134
  * Represents a scheduled task within an Agent
129
135
  * @template T Type of the payload data
@@ -165,24 +171,95 @@ function getNextCronTime(cron: string) {
165
171
  return interval.getNextDate();
166
172
  }
167
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
+
168
216
  const STATE_ROW_ID = "cf_state_row_id";
169
217
  const STATE_WAS_CHANGED = "cf_state_was_changed";
170
218
 
171
219
  const DEFAULT_STATE = {} as unknown;
172
220
 
173
- export const unstable_context = new AsyncLocalStorage<{
221
+ const agentContext = new AsyncLocalStorage<{
174
222
  agent: Agent<unknown>;
175
223
  connection: Connection | undefined;
176
224
  request: Request | undefined;
177
225
  }>();
178
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
+
179
251
  /**
180
252
  * Base class for creating Agent implementations
181
253
  * @template Env Environment type containing bindings
182
254
  * @template State State type to store within the Agent
183
255
  */
184
256
  export class Agent<Env, State = unknown> extends Server<Env> {
185
- #state = DEFAULT_STATE as State;
257
+ private _state = DEFAULT_STATE as State;
258
+
259
+ private _ParentClass: typeof Agent<Env, State> =
260
+ Object.getPrototypeOf(this).constructor;
261
+
262
+ mcp: MCPClientManager = new MCPClientManager(this._ParentClass.name, "0.0.1");
186
263
 
187
264
  /**
188
265
  * Initial state for the Agent
@@ -194,9 +271,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
194
271
  * Current state of the Agent
195
272
  */
196
273
  get state(): State {
197
- if (this.#state !== DEFAULT_STATE) {
274
+ if (this._state !== DEFAULT_STATE) {
198
275
  // state was previously set, and populated internal state
199
- return this.#state;
276
+ return this._state;
200
277
  }
201
278
  // looks like this is the first time the state is being accessed
202
279
  // check if the state was set in a previous life
@@ -216,8 +293,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
216
293
  ) {
217
294
  const state = result[0]?.state as string; // could be null?
218
295
 
219
- this.#state = JSON.parse(state);
220
- return this.#state;
296
+ this._state = JSON.parse(state);
297
+ return this._state;
221
298
  }
222
299
 
223
300
  // ok, this is the first time the state is being accessed
@@ -278,7 +355,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
278
355
  `;
279
356
 
280
357
  void this.ctx.blockConcurrencyWhile(async () => {
281
- return this.#tryCatch(async () => {
358
+ return this._tryCatch(async () => {
282
359
  // Create alarms table if it doesn't exist
283
360
  this.sql`
284
361
  CREATE TABLE IF NOT EXISTS cf_agents_schedules (
@@ -298,13 +375,53 @@ export class Agent<Env, State = unknown> extends Server<Env> {
298
375
  });
299
376
  });
300
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
+
301
418
  const _onMessage = this.onMessage.bind(this);
302
419
  this.onMessage = async (connection: Connection, message: WSMessage) => {
303
- return unstable_context.run(
420
+ return agentContext.run(
304
421
  { agent: this, connection, request: undefined },
305
422
  async () => {
306
423
  if (typeof message !== "string") {
307
- return this.#tryCatch(() => _onMessage(connection, message));
424
+ return this._tryCatch(() => _onMessage(connection, message));
308
425
  }
309
426
 
310
427
  let parsed: unknown;
@@ -312,11 +429,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
312
429
  parsed = JSON.parse(message);
313
430
  } catch (e) {
314
431
  // silently fail and let the onMessage handler handle it
315
- return this.#tryCatch(() => _onMessage(connection, message));
432
+ return this._tryCatch(() => _onMessage(connection, message));
316
433
  }
317
434
 
318
435
  if (isStateUpdateMessage(parsed)) {
319
- this.#setStateInternal(parsed.state as State, connection);
436
+ this._setStateInternal(parsed.state as State, connection);
320
437
  return;
321
438
  }
322
439
 
@@ -330,11 +447,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
330
447
  throw new Error(`Method ${method} does not exist`);
331
448
  }
332
449
 
333
- if (!this.#isCallable(method)) {
450
+ if (!this._isCallable(method)) {
334
451
  throw new Error(`Method ${method} is not callable`);
335
452
  }
336
453
 
337
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
338
454
  const metadata = callableMetadata.get(methodFn as Function);
339
455
 
340
456
  // For streaming methods, pass a StreamingResponse object
@@ -369,7 +485,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
369
485
  return;
370
486
  }
371
487
 
372
- return this.#tryCatch(() => _onMessage(connection, message));
488
+ return this._tryCatch(() => _onMessage(connection, message));
373
489
  }
374
490
  );
375
491
  };
@@ -378,7 +494,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
378
494
  this.onConnect = (connection: Connection, ctx: ConnectionContext) => {
379
495
  // TODO: This is a hack to ensure the state is sent after the connection is established
380
496
  // must fix this
381
- return unstable_context.run(
497
+ return agentContext.run(
382
498
  { agent: this, connection, request: ctx.request },
383
499
  async () => {
384
500
  setTimeout(() => {
@@ -390,15 +506,67 @@ export class Agent<Env, State = unknown> extends Server<Env> {
390
506
  })
391
507
  );
392
508
  }
393
- 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));
394
518
  }, 20);
395
519
  }
396
520
  );
397
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
+ };
398
563
  }
399
564
 
400
- #setStateInternal(state: State, source: Connection | "server" = "server") {
401
- this.#state = state;
565
+ private _setStateInternal(
566
+ state: State,
567
+ source: Connection | "server" = "server"
568
+ ) {
569
+ this._state = state;
402
570
  this.sql`
403
571
  INSERT OR REPLACE INTO cf_agents_state (id, state)
404
572
  VALUES (${STATE_ROW_ID}, ${JSON.stringify(state)})
@@ -414,9 +582,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
414
582
  }),
415
583
  source !== "server" ? [source.id] : []
416
584
  );
417
- return this.#tryCatch(() => {
418
- const { connection, request } = unstable_context.getStore() || {};
419
- return unstable_context.run(
585
+ return this._tryCatch(() => {
586
+ const { connection, request } = agentContext.getStore() || {};
587
+ return agentContext.run(
420
588
  { agent: this, connection, request },
421
589
  async () => {
422
590
  return this.onStateUpdate(state, source);
@@ -430,7 +598,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
430
598
  * @param state New state to set
431
599
  */
432
600
  setState(state: State) {
433
- this.#setStateInternal(state, "server");
601
+ this._setStateInternal(state, "server");
434
602
  }
435
603
 
436
604
  /**
@@ -447,7 +615,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
447
615
  * @param email Email message to process
448
616
  */
449
617
  onEmail(email: ForwardableEmailMessage) {
450
- return unstable_context.run(
618
+ return agentContext.run(
451
619
  { agent: this, connection: undefined, request: undefined },
452
620
  async () => {
453
621
  console.error("onEmail not implemented");
@@ -455,7 +623,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
455
623
  );
456
624
  }
457
625
 
458
- async #tryCatch<T>(fn: () => T | Promise<T>) {
626
+ private async _tryCatch<T>(fn: () => T | Promise<T>) {
459
627
  try {
460
628
  return await fn();
461
629
  } catch (e) {
@@ -529,7 +697,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
529
697
  )}, 'scheduled', ${timestamp})
530
698
  `;
531
699
 
532
- await this.#scheduleNextAlarm();
700
+ await this._scheduleNextAlarm();
533
701
 
534
702
  return {
535
703
  id,
@@ -550,7 +718,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
550
718
  )}, 'delayed', ${when}, ${timestamp})
551
719
  `;
552
720
 
553
- await this.#scheduleNextAlarm();
721
+ await this._scheduleNextAlarm();
554
722
 
555
723
  return {
556
724
  id,
@@ -572,7 +740,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
572
740
  )}, 'cron', ${when}, ${timestamp})
573
741
  `;
574
742
 
575
- await this.#scheduleNextAlarm();
743
+ await this._scheduleNextAlarm();
576
744
 
577
745
  return {
578
746
  id,
@@ -659,11 +827,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
659
827
  async cancelSchedule(id: string): Promise<boolean> {
660
828
  this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
661
829
 
662
- await this.#scheduleNextAlarm();
830
+ await this._scheduleNextAlarm();
663
831
  return true;
664
832
  }
665
833
 
666
- async #scheduleNextAlarm() {
834
+ private async _scheduleNextAlarm() {
667
835
  // Find the next schedule that needs to be executed
668
836
  const result = this.sql`
669
837
  SELECT time FROM cf_agents_schedules
@@ -680,10 +848,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
680
848
  }
681
849
 
682
850
  /**
683
- * Method called when an alarm fires
684
- * 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/}
685
857
  */
686
- async alarm() {
858
+ public readonly alarm = async () => {
687
859
  const now = Math.floor(Date.now() / 1000);
688
860
 
689
861
  // Get all schedules that should be executed now
@@ -697,7 +869,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
697
869
  console.error(`callback ${row.callback} not found`);
698
870
  continue;
699
871
  }
700
- await unstable_context.run(
872
+ await agentContext.run(
701
873
  { agent: this, connection: undefined, request: undefined },
702
874
  async () => {
703
875
  try {
@@ -729,8 +901,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
729
901
  }
730
902
 
731
903
  // Schedule the next alarm
732
- await this.#scheduleNextAlarm();
733
- }
904
+ await this._scheduleNextAlarm();
905
+ };
734
906
 
735
907
  /**
736
908
  * Destroy the Agent, removing all state and scheduled tasks
@@ -739,20 +911,184 @@ export class Agent<Env, State = unknown> extends Server<Env> {
739
911
  // drop all tables
740
912
  this.sql`DROP TABLE IF EXISTS cf_agents_state`;
741
913
  this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
914
+ this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
742
915
 
743
916
  // delete all alarms
744
917
  await this.ctx.storage.deleteAlarm();
745
918
  await this.ctx.storage.deleteAll();
919
+ this.ctx.abort("destroyed"); // enforce that the agent is evicted
746
920
  }
747
921
 
748
922
  /**
749
923
  * Get all methods marked as callable on this Agent
750
924
  * @returns A map of method names to their metadata
751
925
  */
752
- #isCallable(method: string): boolean {
753
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
926
+ private _isCallable(method: string): boolean {
754
927
  return callableMetadata.has(this[method as keyof this] as Function);
755
928
  }
929
+
930
+ /**
931
+ * Connect to a new MCP Server
932
+ *
933
+ * @param url MCP Server SSE URL
934
+ * @param callbackHost Base host for the agent, used for the redirect URI.
935
+ * @param agentsPrefix agents routing prefix if not using `agents`
936
+ * @param options MCP client and transport (header) options
937
+ * @returns authUrl
938
+ */
939
+ async addMcpServer(
940
+ serverName: string,
941
+ url: string,
942
+ callbackHost: string,
943
+ agentsPrefix = "agents",
944
+ options?: {
945
+ client?: ConstructorParameters<typeof Client>[1];
946
+ transport?: {
947
+ headers: HeadersInit;
948
+ };
949
+ }
950
+ ): Promise<{ id: string; authUrl: string | undefined }> {
951
+ const callbackUrl = `${callbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
952
+
953
+ const result = await this._connectToMcpServerInternal(
954
+ serverName,
955
+ url,
956
+ callbackUrl,
957
+ options
958
+ );
959
+
960
+ this.broadcast(
961
+ JSON.stringify({
962
+ type: "cf_agent_mcp_servers",
963
+ mcp: this.getMcpServers(),
964
+ })
965
+ );
966
+
967
+ return result;
968
+ }
969
+
970
+ async _connectToMcpServerInternal(
971
+ serverName: string,
972
+ url: string,
973
+ callbackUrl: string,
974
+ // it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
975
+ options?: {
976
+ client?: ConstructorParameters<typeof Client>[1];
977
+ /**
978
+ * We don't expose the normal set of transport options because:
979
+ * 1) we can't serialize things like the auth provider or a fetch function into the DB for reconnection purposes
980
+ * 2) We probably want these options to be agnostic to the transport type (SSE vs Streamable)
981
+ *
982
+ * 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).
983
+ */
984
+ transport?: {
985
+ headers?: HeadersInit;
986
+ };
987
+ },
988
+ reconnect?: {
989
+ id: string;
990
+ oauthClientId?: string;
991
+ }
992
+ ): Promise<{ id: string; authUrl: string | undefined }> {
993
+ const authProvider = new DurableObjectOAuthClientProvider(
994
+ this.ctx.storage,
995
+ this.name,
996
+ callbackUrl
997
+ );
998
+
999
+ if (reconnect) {
1000
+ authProvider.serverId = reconnect.id;
1001
+ if (reconnect.oauthClientId) {
1002
+ authProvider.clientId = reconnect.oauthClientId;
1003
+ }
1004
+ }
1005
+
1006
+ // allows passing through transport headers if necessary
1007
+ // this handles some non-standard bearer auth setups (i.e. MCP server behind CF access instead of OAuth)
1008
+ let headerTransportOpts: SSEClientTransportOptions = {};
1009
+ if (options?.transport?.headers) {
1010
+ headerTransportOpts = {
1011
+ eventSourceInit: {
1012
+ fetch: (url, init) =>
1013
+ fetch(url, {
1014
+ ...init,
1015
+ headers: options?.transport?.headers,
1016
+ }),
1017
+ },
1018
+ requestInit: {
1019
+ headers: options?.transport?.headers,
1020
+ },
1021
+ };
1022
+ }
1023
+
1024
+ const { id, authUrl, clientId } = await this.mcp.connect(url, {
1025
+ reconnect,
1026
+ transport: {
1027
+ ...headerTransportOpts,
1028
+ authProvider,
1029
+ },
1030
+ client: options?.client,
1031
+ });
1032
+
1033
+ this.sql`
1034
+ INSERT OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
1035
+ VALUES (
1036
+ ${id},
1037
+ ${serverName},
1038
+ ${url},
1039
+ ${clientId ?? null},
1040
+ ${authUrl ?? null},
1041
+ ${callbackUrl},
1042
+ ${options ? JSON.stringify(options) : null}
1043
+ );
1044
+ `;
1045
+
1046
+ return {
1047
+ id,
1048
+ authUrl,
1049
+ };
1050
+ }
1051
+
1052
+ async removeMcpServer(id: string) {
1053
+ this.mcp.closeConnection(id);
1054
+ this.sql`
1055
+ DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
1056
+ `;
1057
+ this.broadcast(
1058
+ JSON.stringify({
1059
+ type: "cf_agent_mcp_servers",
1060
+ mcp: this.getMcpServers(),
1061
+ })
1062
+ );
1063
+ }
1064
+
1065
+ getMcpServers(): MCPServersState {
1066
+ const mcpState: MCPServersState = {
1067
+ servers: {},
1068
+ tools: this.mcp.listTools(),
1069
+ prompts: this.mcp.listPrompts(),
1070
+ resources: this.mcp.listResources(),
1071
+ };
1072
+
1073
+ const servers = this.sql<MCPServerRow>`
1074
+ SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
1075
+ `;
1076
+
1077
+ for (const server of servers) {
1078
+ const serverConn = this.mcp.mcpConnections[server.id];
1079
+ mcpState.servers[server.id] = {
1080
+ name: server.name,
1081
+ server_url: server.server_url,
1082
+ auth_url: server.auth_url,
1083
+ // mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
1084
+ state: serverConn?.connectionState ?? "authenticating",
1085
+ instructions: serverConn?.instructions ?? null,
1086
+ capabilities: serverConn?.serverCapabilities ?? null,
1087
+ };
1088
+ }
1089
+
1090
+ return mcpState;
1091
+ }
756
1092
  }
757
1093
 
758
1094
  /**
@@ -856,7 +1192,7 @@ export async function routeAgentEmail<Env>(
856
1192
  * @param options Options for Agent creation
857
1193
  * @returns Promise resolving to an Agent instance stub
858
1194
  */
859
- export function getAgentByName<Env, T extends Agent<Env>>(
1195
+ export async function getAgentByName<Env, T extends Agent<Env>>(
860
1196
  namespace: AgentNamespace<T>,
861
1197
  name: string,
862
1198
  options?: {
@@ -871,13 +1207,13 @@ export function getAgentByName<Env, T extends Agent<Env>>(
871
1207
  * A wrapper for streaming responses in callable methods
872
1208
  */
873
1209
  export class StreamingResponse {
874
- #connection: Connection;
875
- #id: string;
876
- #closed = false;
1210
+ private _connection: Connection;
1211
+ private _id: string;
1212
+ private _closed = false;
877
1213
 
878
1214
  constructor(connection: Connection, id: string) {
879
- this.#connection = connection;
880
- this.#id = id;
1215
+ this._connection = connection;
1216
+ this._id = id;
881
1217
  }
882
1218
 
883
1219
  /**
@@ -885,17 +1221,17 @@ export class StreamingResponse {
885
1221
  * @param chunk The data to send
886
1222
  */
887
1223
  send(chunk: unknown) {
888
- if (this.#closed) {
1224
+ if (this._closed) {
889
1225
  throw new Error("StreamingResponse is already closed");
890
1226
  }
891
1227
  const response: RPCResponse = {
892
1228
  type: "rpc",
893
- id: this.#id,
1229
+ id: this._id,
894
1230
  success: true,
895
1231
  result: chunk,
896
1232
  done: false,
897
1233
  };
898
- this.#connection.send(JSON.stringify(response));
1234
+ this._connection.send(JSON.stringify(response));
899
1235
  }
900
1236
 
901
1237
  /**
@@ -903,17 +1239,17 @@ export class StreamingResponse {
903
1239
  * @param finalChunk Optional final chunk of data to send
904
1240
  */
905
1241
  end(finalChunk?: unknown) {
906
- if (this.#closed) {
1242
+ if (this._closed) {
907
1243
  throw new Error("StreamingResponse is already closed");
908
1244
  }
909
- this.#closed = true;
1245
+ this._closed = true;
910
1246
  const response: RPCResponse = {
911
1247
  type: "rpc",
912
- id: this.#id,
1248
+ id: this._id,
913
1249
  success: true,
914
1250
  result: finalChunk,
915
1251
  done: true,
916
1252
  };
917
- this.#connection.send(JSON.stringify(response));
1253
+ this._connection.send(JSON.stringify(response));
918
1254
  }
919
1255
  }