agents 0.0.0-bcae0ba → 0.0.0-c3e8618

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.
@@ -0,0 +1,41 @@
1
+ import { DurableObject } from 'cloudflare:workers';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { Connection } from 'partyserver';
4
+
5
+ interface CORSOptions {
6
+ origin?: string;
7
+ methods?: string;
8
+ headers?: string;
9
+ maxAge?: number;
10
+ }
11
+ declare abstract class McpAgent<Env = unknown, State = unknown, Props extends Record<string, unknown> = Record<string, unknown>> extends DurableObject<Env> {
12
+ #private;
13
+ protected constructor(ctx: DurableObjectState, env: Env);
14
+ /**
15
+ * Agents API allowlist
16
+ */
17
+ initialState: State;
18
+ get state(): State;
19
+ sql<T = Record<string, string | number | boolean | null>>(strings: TemplateStringsArray, ...values: (string | number | boolean | null)[]): T[];
20
+ setState(state: State): void;
21
+ onStateUpdate(state: State | undefined, source: Connection | "server"): void;
22
+ /**
23
+ * McpAgent API
24
+ */
25
+ abstract server: McpServer;
26
+ private transport;
27
+ props: Props;
28
+ initRun: boolean;
29
+ abstract init(): Promise<void>;
30
+ _init(props: Props): Promise<void>;
31
+ onSSE(path: string): Promise<Response>;
32
+ onMCPMessage(request: Request): Promise<Response>;
33
+ static mount(path: string, { binding, corsOptions, }?: {
34
+ binding?: string;
35
+ corsOptions?: CORSOptions;
36
+ }): {
37
+ fetch: (request: Request, env: Record<string, DurableObjectNamespace<McpAgent>>, ctx: ExecutionContext) => Promise<Response>;
38
+ };
39
+ }
40
+
41
+ export { McpAgent };
package/dist/react.d.ts CHANGED
@@ -1,21 +1,18 @@
1
- import { PartySocket } from "partysocket";
2
- import { usePartySocket } from "partysocket/react";
3
- import { StreamOptions } from "./client.js";
1
+ import { PartySocket } from 'partysocket';
2
+ import { usePartySocket } from 'partysocket/react';
3
+ import { StreamOptions } from './client.js';
4
4
 
5
5
  /**
6
6
  * Options for the useAgent hook
7
7
  * @template State Type of the Agent's state
8
8
  */
9
- type UseAgentOptions<State = unknown> = Omit<
10
- Parameters<typeof usePartySocket>[0],
11
- "party" | "room"
12
- > & {
13
- /** Name of the agent to connect to */
14
- agent: string;
15
- /** Name of the specific Agent instance */
16
- name?: string;
17
- /** Called when the Agent's state is updated */
18
- onStateUpdate?: (state: State, source: "server" | "client") => void;
9
+ type UseAgentOptions<State = unknown> = Omit<Parameters<typeof usePartySocket>[0], "party" | "room"> & {
10
+ /** Name of the agent to connect to */
11
+ agent: string;
12
+ /** Name of the specific Agent instance */
13
+ name?: string;
14
+ /** Called when the Agent's state is updated */
15
+ onStateUpdate?: (state: State, source: "server" | "client") => void;
19
16
  };
20
17
  /**
21
18
  * React hook for connecting to an Agent
@@ -23,17 +20,11 @@ type UseAgentOptions<State = unknown> = Omit<
23
20
  * @param options Connection options
24
21
  * @returns WebSocket connection with setState and call methods
25
22
  */
26
- declare function useAgent<State = unknown>(
27
- options: UseAgentOptions<State>
28
- ): PartySocket & {
29
- agent: string;
30
- name: string;
31
- setState: (state: State) => void;
32
- call: <T = unknown>(
33
- method: string,
34
- args?: unknown[],
35
- streamOptions?: StreamOptions
36
- ) => Promise<T>;
23
+ declare function useAgent<State = unknown>(options: UseAgentOptions<State>): PartySocket & {
24
+ agent: string;
25
+ name: string;
26
+ setState: (state: State) => void;
27
+ call: <T = unknown>(method: string, args?: unknown[], streamOptions?: StreamOptions) => Promise<T>;
37
28
  };
38
29
 
39
30
  export { type UseAgentOptions, useAgent };
@@ -1,53 +1,43 @@
1
- import { z } from "zod";
1
+ import { z } from 'zod';
2
2
 
3
3
  type Schedule = z.infer<typeof unstable_scheduleSchema>;
4
- declare function unstable_getSchedulePrompt(event: { date: Date }): string;
5
- declare const unstable_scheduleSchema: z.ZodObject<
6
- {
4
+ declare function unstable_getSchedulePrompt(event: {
5
+ date: Date;
6
+ }): string;
7
+ declare const unstable_scheduleSchema: z.ZodObject<{
7
8
  description: z.ZodString;
8
- when: z.ZodObject<
9
- {
9
+ when: z.ZodObject<{
10
10
  type: z.ZodEnum<["scheduled", "delayed", "cron", "no-schedule"]>;
11
11
  date: z.ZodOptional<z.ZodDate>;
12
12
  delayInSeconds: z.ZodOptional<z.ZodNumber>;
13
13
  cron: z.ZodOptional<z.ZodString>;
14
- },
15
- "strip",
16
- z.ZodTypeAny,
17
- {
14
+ }, "strip", z.ZodTypeAny, {
18
15
  type: "scheduled" | "delayed" | "cron" | "no-schedule";
19
16
  cron?: string | undefined;
20
17
  delayInSeconds?: number | undefined;
21
18
  date?: Date | undefined;
22
- },
23
- {
19
+ }, {
24
20
  type: "scheduled" | "delayed" | "cron" | "no-schedule";
25
21
  cron?: string | undefined;
26
22
  delayInSeconds?: number | undefined;
27
23
  date?: Date | undefined;
28
- }
29
- >;
30
- },
31
- "strip",
32
- z.ZodTypeAny,
33
- {
24
+ }>;
25
+ }, "strip", z.ZodTypeAny, {
34
26
  description: string;
35
27
  when: {
36
- type: "scheduled" | "delayed" | "cron" | "no-schedule";
37
- cron?: string | undefined;
38
- delayInSeconds?: number | undefined;
39
- date?: Date | undefined;
28
+ type: "scheduled" | "delayed" | "cron" | "no-schedule";
29
+ cron?: string | undefined;
30
+ delayInSeconds?: number | undefined;
31
+ date?: Date | undefined;
40
32
  };
41
- },
42
- {
33
+ }, {
43
34
  description: string;
44
35
  when: {
45
- type: "scheduled" | "delayed" | "cron" | "no-schedule";
46
- cron?: string | undefined;
47
- delayInSeconds?: number | undefined;
48
- date?: Date | undefined;
36
+ type: "scheduled" | "delayed" | "cron" | "no-schedule";
37
+ cron?: string | undefined;
38
+ delayInSeconds?: number | undefined;
39
+ date?: Date | undefined;
49
40
  };
50
- }
51
- >;
41
+ }>;
52
42
 
53
43
  export { type Schedule, unstable_getSchedulePrompt, unstable_scheduleSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents",
3
- "version": "0.0.0-bcae0ba",
3
+ "version": "0.0.0-c3e8618",
4
4
  "main": "src/index.ts",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -42,6 +42,16 @@
42
42
  "types": "./dist/schedule.d.ts",
43
43
  "require": "./dist/schedule.js",
44
44
  "import": "./dist/schedule.js"
45
+ },
46
+ "./mcp": {
47
+ "types": "./dist/mcp/index.d.ts",
48
+ "require": "./dist/mcp/index.js",
49
+ "import": "./dist/mcp/index.js"
50
+ },
51
+ "./mcp/client": {
52
+ "types": "./dist/mcp/client.d.ts",
53
+ "require": "./dist/mcp/client.js",
54
+ "import": "./dist/mcp/client.js"
45
55
  }
46
56
  },
47
57
  "keywords": [],
@@ -58,8 +68,11 @@
58
68
  "description": "A home for your AI agents",
59
69
  "dependencies": {
60
70
  "cron-schedule": "^5.0.4",
61
- "nanoid": "^5.1.3",
62
- "partyserver": "^0.0.65",
63
- "partysocket": "0.0.0-548c226"
71
+ "nanoid": "^5.1.5",
72
+ "partyserver": "^0.0.66",
73
+ "partysocket": "1.1.3"
74
+ },
75
+ "devDependencies": {
76
+ "@modelcontextprotocol/sdk": "^1.8.0"
64
77
  }
65
78
  }
package/src/index.ts CHANGED
@@ -11,6 +11,8 @@ import {
11
11
  import { parseCronExpression } from "cron-schedule";
12
12
  import { nanoid } from "nanoid";
13
13
 
14
+ import { AsyncLocalStorage } from "node:async_hooks";
15
+
14
16
  export type { Connection, WSMessage, ConnectionContext } from "partyserver";
15
17
 
16
18
  import { WorkflowEntrypoint as CFWorkflowEntrypoint } from "cloudflare:workers";
@@ -168,6 +170,12 @@ const STATE_WAS_CHANGED = "cf_state_was_changed";
168
170
 
169
171
  const DEFAULT_STATE = {} as unknown;
170
172
 
173
+ export const unstable_context = new AsyncLocalStorage<{
174
+ agent: Agent<unknown>;
175
+ connection: Connection | undefined;
176
+ request: Request | undefined;
177
+ }>();
178
+
171
179
  /**
172
180
  * Base class for creating Agent implementations
173
181
  * @template Env Environment type containing bindings
@@ -256,7 +264,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
256
264
  return [...this.ctx.storage.sql.exec(query, ...values)] as T[];
257
265
  } catch (e) {
258
266
  console.error(`failed to execute sql query: ${query}`, e);
259
- throw e;
267
+ throw this.onError(e);
260
268
  }
261
269
  }
262
270
  constructor(ctx: AgentContext, env: Env) {
@@ -270,7 +278,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
270
278
  `;
271
279
 
272
280
  void this.ctx.blockConcurrencyWhile(async () => {
273
- try {
281
+ return this.#tryCatch(async () => {
274
282
  // Create alarms table if it doesn't exist
275
283
  this.sql`
276
284
  CREATE TABLE IF NOT EXISTS cf_agents_schedules (
@@ -287,97 +295,105 @@ export class Agent<Env, State = unknown> extends Server<Env> {
287
295
 
288
296
  // execute any pending alarms and schedule the next alarm
289
297
  await this.alarm();
290
- } catch (e) {
291
- console.error(e);
292
- throw e;
293
- }
298
+ });
294
299
  });
295
300
 
296
301
  const _onMessage = this.onMessage.bind(this);
297
302
  this.onMessage = async (connection: Connection, message: WSMessage) => {
298
- if (typeof message !== "string") {
299
- return _onMessage(connection, message);
300
- }
301
-
302
- let parsed: unknown;
303
- try {
304
- parsed = JSON.parse(message);
305
- } catch (e) {
306
- // silently fail and let the onMessage handler handle it
307
- return _onMessage(connection, message);
308
- }
309
-
310
- if (isStateUpdateMessage(parsed)) {
311
- this.#setStateInternal(parsed.state as State, connection);
312
- return;
313
- }
314
-
315
- if (isRPCRequest(parsed)) {
316
- try {
317
- const { id, method, args } = parsed;
318
-
319
- // Check if method exists and is callable
320
- const methodFn = this[method as keyof this];
321
- if (typeof methodFn !== "function") {
322
- throw new Error(`Method ${method} does not exist`);
303
+ return unstable_context.run(
304
+ { agent: this, connection, request: undefined },
305
+ async () => {
306
+ if (typeof message !== "string") {
307
+ return this.#tryCatch(() => _onMessage(connection, message));
323
308
  }
324
309
 
325
- if (!this.isCallable(method)) {
326
- throw new Error(`Method ${method} is not callable`);
310
+ let parsed: unknown;
311
+ try {
312
+ parsed = JSON.parse(message);
313
+ } catch (e) {
314
+ // silently fail and let the onMessage handler handle it
315
+ return this.#tryCatch(() => _onMessage(connection, message));
327
316
  }
328
317
 
329
- // biome-ignore lint/complexity/noBannedTypes: <explanation>
330
- const metadata = callableMetadata.get(methodFn as Function);
318
+ if (isStateUpdateMessage(parsed)) {
319
+ this.#setStateInternal(parsed.state as State, connection);
320
+ return;
321
+ }
331
322
 
332
- // For streaming methods, pass a StreamingResponse object
333
- if (metadata?.streaming) {
334
- const stream = new StreamingResponse(connection, id);
335
- await methodFn.apply(this, [stream, ...args]);
323
+ if (isRPCRequest(parsed)) {
324
+ try {
325
+ const { id, method, args } = parsed;
326
+
327
+ // Check if method exists and is callable
328
+ const methodFn = this[method as keyof this];
329
+ if (typeof methodFn !== "function") {
330
+ throw new Error(`Method ${method} does not exist`);
331
+ }
332
+
333
+ if (!this.#isCallable(method)) {
334
+ throw new Error(`Method ${method} is not callable`);
335
+ }
336
+
337
+ // biome-ignore lint/complexity/noBannedTypes: <explanation>
338
+ const metadata = callableMetadata.get(methodFn as Function);
339
+
340
+ // For streaming methods, pass a StreamingResponse object
341
+ if (metadata?.streaming) {
342
+ const stream = new StreamingResponse(connection, id);
343
+ await methodFn.apply(this, [stream, ...args]);
344
+ return;
345
+ }
346
+
347
+ // For regular methods, execute and send response
348
+ const result = await methodFn.apply(this, args);
349
+ const response: RPCResponse = {
350
+ type: "rpc",
351
+ id,
352
+ success: true,
353
+ result,
354
+ done: true,
355
+ };
356
+ connection.send(JSON.stringify(response));
357
+ } catch (e) {
358
+ // Send error response
359
+ const response: RPCResponse = {
360
+ type: "rpc",
361
+ id: parsed.id,
362
+ success: false,
363
+ error:
364
+ e instanceof Error ? e.message : "Unknown error occurred",
365
+ };
366
+ connection.send(JSON.stringify(response));
367
+ console.error("RPC error:", e);
368
+ }
336
369
  return;
337
370
  }
338
371
 
339
- // For regular methods, execute and send response
340
- const result = await methodFn.apply(this, args);
341
- const response: RPCResponse = {
342
- type: "rpc",
343
- id,
344
- success: true,
345
- result,
346
- done: true,
347
- };
348
- connection.send(JSON.stringify(response));
349
- } catch (e) {
350
- // Send error response
351
- const response: RPCResponse = {
352
- type: "rpc",
353
- id: parsed.id,
354
- success: false,
355
- error: e instanceof Error ? e.message : "Unknown error occurred",
356
- };
357
- connection.send(JSON.stringify(response));
358
- console.error("RPC error:", e);
372
+ return this.#tryCatch(() => _onMessage(connection, message));
359
373
  }
360
- return;
361
- }
362
-
363
- return _onMessage(connection, message);
374
+ );
364
375
  };
365
376
 
366
377
  const _onConnect = this.onConnect.bind(this);
367
378
  this.onConnect = (connection: Connection, ctx: ConnectionContext) => {
368
379
  // TODO: This is a hack to ensure the state is sent after the connection is established
369
380
  // must fix this
370
- setTimeout(() => {
371
- if (this.state) {
372
- connection.send(
373
- JSON.stringify({
374
- type: "cf_agent_state",
375
- state: this.state,
376
- })
377
- );
381
+ return unstable_context.run(
382
+ { agent: this, connection, request: ctx.request },
383
+ async () => {
384
+ setTimeout(() => {
385
+ if (this.state) {
386
+ connection.send(
387
+ JSON.stringify({
388
+ type: "cf_agent_state",
389
+ state: this.state,
390
+ })
391
+ );
392
+ }
393
+ return this.#tryCatch(() => _onConnect(connection, ctx));
394
+ }, 20);
378
395
  }
379
- _onConnect(connection, ctx);
380
- }, 20);
396
+ );
381
397
  };
382
398
  }
383
399
 
@@ -398,7 +414,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
398
414
  }),
399
415
  source !== "server" ? [source.id] : []
400
416
  );
401
- this.onStateUpdate(state, source);
417
+ return this.#tryCatch(() => {
418
+ const { connection, request } = unstable_context.getStore() || {};
419
+ return unstable_context.run(
420
+ { agent: this, connection, request },
421
+ async () => {
422
+ return this.onStateUpdate(state, source);
423
+ }
424
+ );
425
+ });
402
426
  }
403
427
 
404
428
  /**
@@ -423,7 +447,47 @@ export class Agent<Env, State = unknown> extends Server<Env> {
423
447
  * @param email Email message to process
424
448
  */
425
449
  onEmail(email: ForwardableEmailMessage) {
426
- throw new Error("Not implemented");
450
+ return unstable_context.run(
451
+ { agent: this, connection: undefined, request: undefined },
452
+ async () => {
453
+ console.error("onEmail not implemented");
454
+ }
455
+ );
456
+ }
457
+
458
+ async #tryCatch<T>(fn: () => T | Promise<T>) {
459
+ try {
460
+ return await fn();
461
+ } catch (e) {
462
+ throw this.onError(e);
463
+ }
464
+ }
465
+
466
+ override onError(
467
+ connection: Connection,
468
+ error: unknown
469
+ ): void | Promise<void>;
470
+ override onError(error: unknown): void | Promise<void>;
471
+ override onError(connectionOrError: Connection | unknown, error?: unknown) {
472
+ let theError: unknown;
473
+ if (connectionOrError && error) {
474
+ theError = error;
475
+ // this is a websocket connection error
476
+ console.error(
477
+ "Error on websocket connection:",
478
+ (connectionOrError as Connection).id,
479
+ theError
480
+ );
481
+ console.error(
482
+ "Override onError(connection, error) to handle websocket connection errors"
483
+ );
484
+ } else {
485
+ theError = connectionOrError;
486
+ // this is a server error
487
+ console.error("Error on server:", theError);
488
+ console.error("Override onError(error) to handle server errors");
489
+ }
490
+ throw theError;
427
491
  }
428
492
 
429
493
  /**
@@ -465,7 +529,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
465
529
  )}, 'scheduled', ${timestamp})
466
530
  `;
467
531
 
468
- await this.scheduleNextAlarm();
532
+ await this.#scheduleNextAlarm();
469
533
 
470
534
  return {
471
535
  id,
@@ -486,7 +550,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
486
550
  )}, 'delayed', ${when}, ${timestamp})
487
551
  `;
488
552
 
489
- await this.scheduleNextAlarm();
553
+ await this.#scheduleNextAlarm();
490
554
 
491
555
  return {
492
556
  id,
@@ -508,7 +572,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
508
572
  )}, 'cron', ${when}, ${timestamp})
509
573
  `;
510
574
 
511
- await this.scheduleNextAlarm();
575
+ await this.#scheduleNextAlarm();
512
576
 
513
577
  return {
514
578
  id,
@@ -532,7 +596,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
532
596
  const result = this.sql<Schedule<string>>`
533
597
  SELECT * FROM cf_agents_schedules WHERE id = ${id}
534
598
  `;
535
- if (!result) return undefined;
599
+ if (!result) {
600
+ console.error(`schedule ${id} not found`);
601
+ return undefined;
602
+ }
536
603
 
537
604
  return { ...result[0], payload: JSON.parse(result[0].payload) as T };
538
605
  }
@@ -545,7 +612,6 @@ export class Agent<Env, State = unknown> extends Server<Env> {
545
612
  */
546
613
  getSchedules<T = string>(
547
614
  criteria: {
548
- description?: string;
549
615
  id?: string;
550
616
  type?: "scheduled" | "delayed" | "cron";
551
617
  timeRange?: { start?: Date; end?: Date };
@@ -559,11 +625,6 @@ export class Agent<Env, State = unknown> extends Server<Env> {
559
625
  params.push(criteria.id);
560
626
  }
561
627
 
562
- if (criteria.description) {
563
- query += " AND description = ?";
564
- params.push(criteria.description);
565
- }
566
-
567
628
  if (criteria.type) {
568
629
  query += " AND type = ?";
569
630
  params.push(criteria.type);
@@ -598,11 +659,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
598
659
  async cancelSchedule(id: string): Promise<boolean> {
599
660
  this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
600
661
 
601
- await this.scheduleNextAlarm();
662
+ await this.#scheduleNextAlarm();
602
663
  return true;
603
664
  }
604
665
 
605
- private async scheduleNextAlarm() {
666
+ async #scheduleNextAlarm() {
606
667
  // Find the next schedule that needs to be executed
607
668
  const result = this.sql`
608
669
  SELECT time FROM cf_agents_schedules
@@ -636,16 +697,21 @@ export class Agent<Env, State = unknown> extends Server<Env> {
636
697
  console.error(`callback ${row.callback} not found`);
637
698
  continue;
638
699
  }
639
- try {
640
- (
641
- callback as (
642
- payload: unknown,
643
- schedule: Schedule<unknown>
644
- ) => Promise<void>
645
- ).bind(this)(JSON.parse(row.payload as string), row);
646
- } catch (e) {
647
- console.error(`error executing callback ${row.callback}`, e);
648
- }
700
+ await unstable_context.run(
701
+ { agent: this, connection: undefined, request: undefined },
702
+ async () => {
703
+ try {
704
+ await (
705
+ callback as (
706
+ payload: unknown,
707
+ schedule: Schedule<unknown>
708
+ ) => Promise<void>
709
+ ).bind(this)(JSON.parse(row.payload as string), row);
710
+ } catch (e) {
711
+ console.error(`error executing callback "${row.callback}"`, e);
712
+ }
713
+ }
714
+ );
649
715
  if (row.type === "cron") {
650
716
  // Update next execution time for cron schedules
651
717
  const nextExecutionTime = getNextCronTime(row.cron);
@@ -663,7 +729,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
663
729
  }
664
730
 
665
731
  // Schedule the next alarm
666
- await this.scheduleNextAlarm();
732
+ await this.#scheduleNextAlarm();
667
733
  }
668
734
 
669
735
  /**
@@ -683,7 +749,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
683
749
  * Get all methods marked as callable on this Agent
684
750
  * @returns A map of method names to their metadata
685
751
  */
686
- private isCallable(method: string): boolean {
752
+ #isCallable(method: string): boolean {
687
753
  // biome-ignore lint/complexity/noBannedTypes: <explanation>
688
754
  return callableMetadata.has(this[method as keyof this] as Function);
689
755
  }
@@ -756,7 +822,8 @@ export async function routeAgentRequest<Env>(
756
822
  if (
757
823
  response &&
758
824
  corsHeaders &&
759
- request.headers.get("upgrade") !== "websocket"
825
+ request.headers.get("upgrade")?.toLowerCase() !== "websocket" &&
826
+ request.headers.get("Upgrade")?.toLowerCase() !== "websocket"
760
827
  ) {
761
828
  response = new Response(response.body, {
762
829
  headers: {