sello 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/mcp.md ADDED
@@ -0,0 +1,149 @@
1
+ # Add Sello to an MCP Server
2
+
3
+ Sello belongs around the tool callback: after your MCP server has identified the tool, before your business logic runs.
4
+
5
+ ```ts
6
+ import { sello } from "sello";
7
+
8
+ const receipts = sello.service();
9
+
10
+ server.registerTool(
11
+ "calendar.create_event",
12
+ { inputSchema: createEventInputSchema },
13
+ receipts.mcpTool("calendar.create_event", async (args) => {
14
+ const event = await calendar.events.create(args);
15
+ return {
16
+ content: [{ type: "text", text: event.id }],
17
+ };
18
+ }),
19
+ );
20
+ ```
21
+
22
+ Some MCP SDK versions call this registration method `tool` instead of `registerTool`. Use the same callback slot either way.
23
+
24
+ ## What It Does
25
+
26
+ `receipts.mcpTool(...)` returns a normal MCP tool callback. When the agent calls the tool, Sello:
27
+
28
+ 1. Reads the agent authorization token from the MCP context.
29
+ 2. Verifies the token before your callback runs.
30
+ 3. Runs your callback unchanged.
31
+ 4. Emits a `success`, `error`, or `denied` receipt.
32
+ 5. Preserves the callback return value and rethrows callback errors.
33
+
34
+ The default receipt action type is:
35
+
36
+ ```text
37
+ mcp.tools/call.<tool-name>
38
+ ```
39
+
40
+ For `calendar.create_event`, the action type is:
41
+
42
+ ```text
43
+ mcp.tools/call.calendar.create_event
44
+ ```
45
+
46
+ ## Token Source
47
+
48
+ By default, Sello looks for `Authorization: Bearer ...` in common MCP context shapes, including:
49
+
50
+ ```ts
51
+ context.headers.authorization
52
+ context.requestInfo.headers.authorization
53
+ context.request.headers.authorization
54
+ ```
55
+
56
+ Fetch-style `Headers` objects are supported too.
57
+
58
+ If your transport stores the token somewhere else, pass an extractor:
59
+
60
+ ```ts
61
+ receipts.mcpTool("calendar.create_event", handler, {
62
+ authorizationToken: ({ context }) => context.session.token,
63
+ });
64
+ ```
65
+
66
+ The token should be the same authorization token the agent used for the tool call. Sello hashes the exact token bytes into `sello_token_ref`; do not parse and reserialize it first.
67
+
68
+ ## What Gets Hashed
69
+
70
+ The default MCP input hash covers only the stable MCP action shape:
71
+
72
+ ```json
73
+ {
74
+ "method": "tools/call",
75
+ "params": {
76
+ "name": "calendar.create_event",
77
+ "arguments": {}
78
+ }
79
+ }
80
+ ```
81
+
82
+ Sello does not hash:
83
+
84
+ - The bearer token.
85
+ - Transport headers.
86
+ - Connection/session objects.
87
+ - Other context that may vary across runtimes.
88
+
89
+ That keeps receipts stable without leaking secrets or transport details.
90
+
91
+ If you need a different hash boundary, pass `canonicalizeInput`.
92
+
93
+ ## Unknown Tools
94
+
95
+ Only wrap tools you actually execute. If a request names an unknown tool, return your normal MCP `method not found` or `tool not found` error without emitting a Sello receipt.
96
+
97
+ The minimal example does this:
98
+
99
+ ```ts
100
+ if (request.body.params.name !== "calendar.create_event") {
101
+ return {
102
+ jsonrpc: "2.0",
103
+ id: request.body.id,
104
+ error: { code: -32601, message: "tool not found" },
105
+ };
106
+ }
107
+ ```
108
+
109
+ ## View Actions
110
+
111
+ In local development:
112
+
113
+ ```bash
114
+ npx sello dev
115
+ ```
116
+
117
+ Run your MCP tool call, then view verified actions:
118
+
119
+ ```bash
120
+ npx sello actions
121
+ ```
122
+
123
+ Or open:
124
+
125
+ ```text
126
+ http://localhost:8787/actions
127
+ ```
128
+
129
+ For a specific agent token:
130
+
131
+ ```bash
132
+ npx sello actions --token <agent-token>
133
+ ```
134
+
135
+ ## Runnable Demo
136
+
137
+ To generate a tiny MCP-shaped demo in your project:
138
+
139
+ ```bash
140
+ npx --yes sello init-mcp-demo
141
+ node sello-mcp-demo.mjs
142
+ npx --yes sello actions
143
+ ```
144
+
145
+ Keep `npx sello dev` running in another terminal while you run the generated file.
146
+
147
+ ## Example
148
+
149
+ See [`sdks/typescript/examples/mcp-minimal-server.ts`](../sdks/typescript/examples/mcp-minimal-server.ts) for a dependency-free MCP-shaped example. It wraps one `tools/call` handler and leaves unknown tools unreceipted.
@@ -16,6 +16,9 @@ Use this checklist before publishing a Sello release.
16
16
 
17
17
  ```bash
18
18
  node --run test
19
+ node --run package:test
20
+ node --run package:a2a-test
21
+ node --run package:mcp-test
19
22
  npm pack --dry-run
20
23
  ```
21
24
 
@@ -28,6 +31,8 @@ cd "$tmpdir/sello"
28
31
  node -v # must be v22.7.0 or newer
29
32
  node --run test
30
33
  node --run package:test
34
+ node --run package:a2a-test
35
+ node --run package:mcp-test
31
36
  npm pack --dry-run
32
37
  node --experimental-strip-types sdks/typescript/src/cli/sello.ts --help
33
38
  node --experimental-strip-types sdks/typescript/src/cli/sello.ts dev --dry-run
@@ -67,6 +72,8 @@ git push origin main --tags
67
72
  After publishing:
68
73
 
69
74
  - Confirm `npm view sello version` shows the new version.
75
+ - Confirm `SELLO_PACKAGE_SPEC=sello@$(node -p "require('./package.json').version") node --run package:a2a-test` passes against the published npm package.
76
+ - Confirm `SELLO_PACKAGE_SPEC=sello@$(node -p "require('./package.json').version") node --run package:mcp-test` passes against the published npm package.
70
77
  - Publish a GitHub Release for the pushed tag so PyPI trusted publishing runs.
71
78
  - Confirm `python -m pip index versions sello` shows the new version after PyPI publishing.
72
79
  - Confirm `npx sello --help` works from a clean temp directory.
@@ -72,18 +72,43 @@ npx --yes sello call-http-demo
72
72
  npx --yes sello actions
73
73
  ```
74
74
 
75
+ To try the same local loop around MCP or A2A-shaped handlers:
76
+
77
+ ```bash
78
+ npx --yes sello init-mcp-demo
79
+ node sello-mcp-demo.mjs
80
+ npx --yes sello actions
81
+ ```
82
+
83
+ ```bash
84
+ npx --yes sello init-a2a-demo
85
+ node sello-a2a-demo.mjs
86
+ npx --yes sello actions
87
+ ```
88
+
75
89
  ## What Just Happened?
76
90
 
77
- `sello dev` created local development keys, a demo authorization token, a service registry, and a local transparency log. The wrapped tool or route verified the token before running your handler. After the handler returned, Sello signed an encrypted receipt for the observed action and submitted it to the local log. The log stored encrypted receipt data, not plaintext action details. `sello actions` used the owner key from local dev state to fetch, verify, decrypt, and print the action.
91
+ `sello dev` created local development keys, a demo authorization token, a service registry, and a local transparency log. The wrapped function, route, MCP tool, or A2A message callback verified the token before running your code. After your code returned, Sello signed an encrypted receipt for the observed action and submitted it to the local log. The log stored encrypted receipt data, not plaintext action details. `sello actions` used the owner key from local dev state to fetch, verify, decrypt, and print the action.
78
92
 
79
93
  Local dev state lives under `.sello/`. The encrypted dev log is stored in `.sello/dev-log.jsonl`, so receipts survive restarting `sello dev` without being committed to git.
80
94
 
95
+ ## Which Wrapper?
96
+
97
+ | Existing boundary | Use |
98
+ | --- | --- |
99
+ | Plain function or HTTP route | `receipts.tool("calendar.create_event", handler)` |
100
+ | MCP `tools/call` callback | `receipts.mcpTool("calendar.create_event", handler)` |
101
+ | A2A JSON-RPC message handler | `receipts.a2aMessage(handler)` |
102
+ | Owner action viewing | `npx sello actions` |
103
+
81
104
  ## Troubleshooting
82
105
 
83
106
  - **Port already in use:** run `npx sello dev --port 8791`.
84
107
  - **No actions found:** make sure `sello dev` is running from the same project folder where you emitted the receipt.
85
108
  - **Missing token:** run `npx sello dev` first so `.sello/dev.json` exists.
86
109
 
110
+ ## Contributing From This Repo
111
+
87
112
  Inside this repo, start the local log and action viewer:
88
113
 
89
114
  ```bash
@@ -126,6 +151,30 @@ receipts.mcpTool("calendar.create_event", handler, {
126
151
  });
127
152
  ```
128
153
 
154
+ For the complete MCP placement notes, see [MCP Integration](mcp.md).
155
+
156
+ For an A2A agent, wrap the message handler you already expose:
157
+
158
+ ```ts
159
+ import { sello } from "sello";
160
+
161
+ const receipts = sello.service();
162
+
163
+ export const sendMessage = receipts.a2aMessage(async (request, context) => {
164
+ return agent.handleMessage(request, context);
165
+ });
166
+ ```
167
+
168
+ `receipts.a2aMessage(...)` uses action type `a2a.<method>`, for example `a2a.message/send`, and hashes only the A2A JSON-RPC method and params. It excludes request ids, headers, bearer tokens, and runtime context. If your transport stores the token somewhere else, pass an extractor:
169
+
170
+ ```ts
171
+ receipts.a2aMessage(handler, {
172
+ authorizationToken: ({ context }) => context.session.token,
173
+ });
174
+ ```
175
+
176
+ For the complete A2A placement notes, see [A2A Integration](a2a.md).
177
+
129
178
  Or run the matching Python example:
130
179
 
131
180
  ```bash
@@ -30,6 +30,7 @@ These notes cover the first Stripe-style SDK implementation pass. They are writt
30
30
  - Success, error, and denied paths emit receipts without including plaintext request or response bodies.
31
31
  - The wrapper uses the configured service identity and key for every receipt.
32
32
  - `receipts.mcpTool(...)` reuses the same wrapper path. It defaults to hashing the MCP `tools/call` method, tool name, and arguments, while excluding bearer tokens and transport context from the action input hash.
33
+ - `receipts.a2aMessage(...)` reuses the same wrapper path. It defaults to hashing the A2A JSON-RPC method and params, while excluding bearer tokens, request ids, headers, and runtime context from the action input hash.
33
34
 
34
35
  ## Phase 4: Logs And Action Viewing
35
36
 
@@ -44,6 +45,7 @@ These notes cover the first Stripe-style SDK implementation pass. They are writt
44
45
  - The example uses `submit: { mode: "await" }` so the command succeeds only after the receipt append completes.
45
46
  - The example canonicalizes only tool input fields and excludes the authorization token wrapper from the action input hash.
46
47
  - The MCP-style example reads the bearer token from the transport header, but hashes only the `tools/call` method and params.
48
+ - The A2A-style example reads the bearer token from the transport header, but hashes only the JSON-RPC method and params.
47
49
  - README and quickstart docs keep self-hosting first-class and describe `sello.build` as optional convenience.
48
50
 
49
51
  ## Residual Risks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sello",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Reference implementation of the Sello protocol for service-signed AI agent receipts.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -31,10 +31,13 @@
31
31
  "bench": "node --experimental-strip-types sdks/typescript/src/cli/bench.ts",
32
32
  "demo": "node --experimental-strip-types sdks/typescript/src/cli/demo.ts",
33
33
  "dev": "node --experimental-strip-types sdks/typescript/src/cli/sello.ts dev",
34
+ "example:a2a:minimal": "node --test --experimental-strip-types sdks/typescript/test/examples/a2a-minimal-server.test.ts",
34
35
  "example:mcp:minimal": "node --test --experimental-strip-types sdks/typescript/test/examples/mcp-minimal-server.test.ts",
35
36
  "example:mcp": "node --experimental-strip-types sdks/typescript/examples/mcp-tool-server.ts",
36
37
  "example:tool": "node --experimental-strip-types sdks/typescript/examples/quickstart-tool.ts",
37
38
  "build": "node --disable-warning=ExperimentalWarning sdks/typescript/scripts/build-dist.mjs",
39
+ "package:a2a-test": "node sdks/typescript/scripts/package-a2a-smoke.mjs",
40
+ "package:mcp-test": "node sdks/typescript/scripts/package-mcp-smoke.mjs",
38
41
  "package:test": "node sdks/typescript/scripts/package-smoke.mjs",
39
42
  "pack:check": "node sdks/typescript/scripts/pack-check.mjs",
40
43
  "prepack": "node --disable-warning=ExperimentalWarning sdks/typescript/scripts/build-dist.mjs",
@@ -49,7 +52,8 @@
49
52
  "transparency-log",
50
53
  "cose",
51
54
  "hpke",
52
- "mcp"
55
+ "mcp",
56
+ "a2a"
53
57
  ],
54
58
  "publishConfig": {
55
59
  "access": "public"
package/sdks/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The protocol, paper, and project docs live at the repository root. Language SDKs live here:
4
4
 
5
- - [`typescript/`](typescript/): npm package source, CLI, examples, fixtures, and Node tests.
5
+ - [`typescript/`](typescript/): npm package source, CLI, MCP/A2A helpers, examples, fixtures, and Node tests.
6
6
  - [`python/`](python/): Python package source and tests.
7
7
 
8
8
  Root commands still work from the repository top level:
@@ -0,0 +1,85 @@
1
+ import { sello, type SelloReceipts } from "../src/index.ts";
2
+
3
+ export type MinimalA2aRequest = {
4
+ headers: {
5
+ authorization?: string;
6
+ Authorization?: string;
7
+ };
8
+ body: {
9
+ jsonrpc: "2.0";
10
+ id: string | number | null;
11
+ method: string;
12
+ params: {
13
+ message: {
14
+ role: "user" | "agent";
15
+ parts: { kind: "text"; text: string }[];
16
+ };
17
+ };
18
+ };
19
+ };
20
+
21
+ export type MinimalA2aResponse = {
22
+ jsonrpc: "2.0";
23
+ id: string | number | null;
24
+ result?: {
25
+ kind: "message";
26
+ messageId: string;
27
+ role: "agent";
28
+ parts: { kind: "text"; text: string }[];
29
+ };
30
+ error?: {
31
+ code: number;
32
+ message: string;
33
+ };
34
+ };
35
+
36
+ export function createCalendarA2aAgent(
37
+ receipts: SelloReceipts = sello.service(),
38
+ ) {
39
+ const sendMessage = receipts.a2aMessage<
40
+ MinimalA2aRequest["body"],
41
+ MinimalA2aResponse,
42
+ MinimalA2aRequest
43
+ >(async (body) => {
44
+ const text = body.params.message.parts
45
+ .filter((part) => part.kind === "text")
46
+ .map((part) => part.text)
47
+ .join(" ");
48
+ const title = text || "untitled";
49
+
50
+ return {
51
+ jsonrpc: "2.0",
52
+ id: body.id,
53
+ result: {
54
+ kind: "message",
55
+ messageId: "calendar-reply-1",
56
+ role: "agent",
57
+ parts: [
58
+ {
59
+ kind: "text",
60
+ text: `created ${title}`,
61
+ },
62
+ ],
63
+ },
64
+ };
65
+ });
66
+
67
+ return {
68
+ async handle(request: MinimalA2aRequest): Promise<MinimalA2aResponse> {
69
+ if (request.body.method !== "message/send") {
70
+ return {
71
+ jsonrpc: "2.0",
72
+ id: request.body.id,
73
+ error: {
74
+ code: -32601,
75
+ message: "method not found",
76
+ },
77
+ };
78
+ }
79
+
80
+ return await sendMessage(request.body, request);
81
+ },
82
+
83
+ flush: () => receipts.flush(),
84
+ };
85
+ }
@@ -97,6 +97,12 @@ try {
97
97
  case "init-http-demo":
98
98
  initHttpDemoCommand(process.argv.slice(3));
99
99
  break;
100
+ case "init-mcp-demo":
101
+ initMcpDemoCommand(process.argv.slice(3));
102
+ break;
103
+ case "init-a2a-demo":
104
+ initA2aDemoCommand(process.argv.slice(3));
105
+ break;
100
106
  case "keys":
101
107
  keysCommand(process.argv.slice(3));
102
108
  break;
@@ -338,13 +344,13 @@ function initDemoCommand(args: string[]): void {
338
344
  console.log(`Created ${output}`);
339
345
  console.log("");
340
346
  console.log("Terminal 1: keep the local dev log running");
341
- console.log(" npx sello dev");
347
+ console.log(` ${devCommandHint()}`);
342
348
  console.log("");
343
349
  console.log("Terminal 2: emit and view a receipt");
344
350
  console.log(` node ${output}`);
345
351
  console.log(" npx sello actions");
346
352
  console.log("");
347
- console.log("Then open http://localhost:8787/actions");
353
+ console.log(`Then open ${actionViewerUrlHint()}`);
348
354
  }
349
355
 
350
356
  function initHttpDemoCommand(args: string[]): void {
@@ -360,7 +366,7 @@ function initHttpDemoCommand(args: string[]): void {
360
366
  console.log(`Created ${output}`);
361
367
  console.log("");
362
368
  console.log("Terminal 1: keep the local dev log running");
363
- console.log(" npx sello dev");
369
+ console.log(` ${devCommandHint()}`);
364
370
  console.log("");
365
371
  console.log("Terminal 2: start the route");
366
372
  console.log(` node ${output}`);
@@ -369,7 +375,51 @@ function initHttpDemoCommand(args: string[]): void {
369
375
  console.log(" npx sello call-http-demo");
370
376
  console.log(" npx sello actions");
371
377
  console.log("");
372
- console.log("Then open http://localhost:8787/actions");
378
+ console.log(`Then open ${actionViewerUrlHint()}`);
379
+ }
380
+
381
+ function initMcpDemoCommand(args: string[]): void {
382
+ const output = readFlag(args, "--output") ?? "sello-mcp-demo.mjs";
383
+ const force = args.includes("--force");
384
+
385
+ if (existsSync(output) && !force) {
386
+ throw new TypeError(`${output} already exists. Pass --force to overwrite it.`);
387
+ }
388
+
389
+ writeFileSync(output, mcpDemoSource(), { mode: 0o644 });
390
+
391
+ console.log(`Created ${output}`);
392
+ console.log("");
393
+ console.log("Terminal 1: keep the local dev log running");
394
+ console.log(` ${devCommandHint()}`);
395
+ console.log("");
396
+ console.log("Terminal 2: run the MCP-shaped tool call and view the receipt");
397
+ console.log(` node ${output}`);
398
+ console.log(" npx sello actions");
399
+ console.log("");
400
+ console.log(`Then open ${actionViewerUrlHint()}`);
401
+ }
402
+
403
+ function initA2aDemoCommand(args: string[]): void {
404
+ const output = readFlag(args, "--output") ?? "sello-a2a-demo.mjs";
405
+ const force = args.includes("--force");
406
+
407
+ if (existsSync(output) && !force) {
408
+ throw new TypeError(`${output} already exists. Pass --force to overwrite it.`);
409
+ }
410
+
411
+ writeFileSync(output, a2aDemoSource(), { mode: 0o644 });
412
+
413
+ console.log(`Created ${output}`);
414
+ console.log("");
415
+ console.log("Terminal 1: keep the local dev log running");
416
+ console.log(` ${devCommandHint()}`);
417
+ console.log("");
418
+ console.log("Terminal 2: run the A2A-shaped message and view the receipt");
419
+ console.log(` node ${output}`);
420
+ console.log(" npx sello actions");
421
+ console.log("");
422
+ console.log(`Then open ${actionViewerUrlHint()}`);
373
423
  }
374
424
 
375
425
  function keysCommand(args: string[]): void {
@@ -852,6 +902,8 @@ function printHelp(): void {
852
902
  sello call-http-demo [--url http://localhost:8790/calendar/events] [--title title]
853
903
  sello init-demo [--output emit-receipt.mjs] [--force]
854
904
  sello init-http-demo [--output sello-http-route.mjs] [--force]
905
+ sello init-mcp-demo [--output sello-mcp-demo.mjs] [--force]
906
+ sello init-a2a-demo [--output sello-a2a-demo.mjs] [--force]
855
907
  sello actions [--token agent-token]
856
908
  sello keys service
857
909
  sello inspect-env
@@ -918,6 +970,25 @@ function formatHttpDemoResponse(responseText: string): string {
918
970
  }
919
971
  }
920
972
 
973
+ function devCommandHint(): string {
974
+ const state = loadDevStateIfPresent();
975
+ const port = state ? portFromDevState(state) : undefined;
976
+ return port && port !== "8787" ? `npx sello dev --port ${port}` : "npx sello dev";
977
+ }
978
+
979
+ function actionViewerUrlHint(): string {
980
+ const state = loadDevStateIfPresent();
981
+ return state ? actionViewerUrl(state) : "http://localhost:8787/actions";
982
+ }
983
+
984
+ function portFromDevState(state: DevState): string | undefined {
985
+ try {
986
+ return new URL(state.logEndpoint).port;
987
+ } catch {
988
+ return undefined;
989
+ }
990
+ }
991
+
921
992
  function slug(value: string): string {
922
993
  return value
923
994
  .toLowerCase()
@@ -1081,3 +1152,124 @@ function slug(value) {
1081
1152
  }
1082
1153
  `;
1083
1154
  }
1155
+
1156
+ function mcpDemoSource(): string {
1157
+ return `import { readFileSync } from "node:fs";
1158
+ import { sello } from "sello";
1159
+
1160
+ const state = JSON.parse(readFileSync(".sello/dev.json", "utf8"));
1161
+
1162
+ const receipts = sello.service({
1163
+ service: state.serviceId,
1164
+ serviceKey: state.serviceKey,
1165
+ tokenIssuer: state.tokenIssuerPublicKey,
1166
+ log: sello.logs.http(state.logUrl, { endpoint: state.logEndpoint }),
1167
+ submit: { mode: "await" },
1168
+ });
1169
+
1170
+ const createEvent = receipts.mcpTool("calendar.create_event", async (args) => {
1171
+ const title = readString(args.title, "title");
1172
+ return {
1173
+ content: [
1174
+ {
1175
+ type: "text",
1176
+ text: "created " + title,
1177
+ },
1178
+ ],
1179
+ };
1180
+ });
1181
+
1182
+ const response = await createEvent(
1183
+ {
1184
+ title: "MCP launch checklist",
1185
+ },
1186
+ {
1187
+ requestInfo: {
1188
+ headers: new Headers({
1189
+ authorization: "Bearer " + state.agentToken,
1190
+ }),
1191
+ },
1192
+ },
1193
+ );
1194
+
1195
+ await receipts.flush();
1196
+
1197
+ console.log("MCP tool response:");
1198
+ console.log(JSON.stringify(response, null, 2));
1199
+
1200
+ function readString(value, name) {
1201
+ if (typeof value !== "string" || value.length === 0) {
1202
+ throw new TypeError(name + " must be a non-empty string");
1203
+ }
1204
+ return value;
1205
+ }
1206
+ `;
1207
+ }
1208
+
1209
+ function a2aDemoSource(): string {
1210
+ return `import { readFileSync } from "node:fs";
1211
+ import { sello } from "sello";
1212
+
1213
+ const state = JSON.parse(readFileSync(".sello/dev.json", "utf8"));
1214
+
1215
+ const receipts = sello.service({
1216
+ service: state.serviceId,
1217
+ serviceKey: state.serviceKey,
1218
+ tokenIssuer: state.tokenIssuerPublicKey,
1219
+ log: sello.logs.http(state.logUrl, { endpoint: state.logEndpoint }),
1220
+ submit: { mode: "await" },
1221
+ });
1222
+
1223
+ const sendMessage = receipts.a2aMessage(async (request) => ({
1224
+ jsonrpc: "2.0",
1225
+ id: request.id,
1226
+ result: {
1227
+ kind: "message",
1228
+ messageId: "calendar-reply-1",
1229
+ role: "agent",
1230
+ parts: [
1231
+ {
1232
+ kind: "text",
1233
+ text: "created " + readMessageText(request),
1234
+ },
1235
+ ],
1236
+ },
1237
+ }));
1238
+
1239
+ const response = await sendMessage(
1240
+ {
1241
+ jsonrpc: "2.0",
1242
+ id: "a2a-demo-1",
1243
+ method: "message/send",
1244
+ params: {
1245
+ message: {
1246
+ role: "user",
1247
+ parts: [
1248
+ {
1249
+ kind: "text",
1250
+ text: "A2A launch checklist",
1251
+ },
1252
+ ],
1253
+ },
1254
+ },
1255
+ },
1256
+ {
1257
+ headers: new Headers({
1258
+ authorization: "Bearer " + state.agentToken,
1259
+ }),
1260
+ },
1261
+ );
1262
+
1263
+ await receipts.flush();
1264
+
1265
+ console.log("A2A message response:");
1266
+ console.log(JSON.stringify(response, null, 2));
1267
+
1268
+ function readMessageText(request) {
1269
+ return request.params.message.parts
1270
+ .filter((part) => part.kind === "text")
1271
+ .map((part) => part.text)
1272
+ .join(" ") || "untitled";
1273
+ }
1274
+ `;
1275
+ }