sello 0.1.16 → 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/README.md +12 -3
- package/dist/cli/sello.js +196 -4
- package/docs/a2a.md +12 -0
- package/docs/mcp.md +12 -0
- package/docs/sdk-quickstart.md +26 -1
- package/package.json +1 -1
- package/sdks/typescript/src/cli/sello.ts +196 -4
package/README.md
CHANGED
|
@@ -104,11 +104,13 @@ SELLO_SUBMIT_MODE=background
|
|
|
104
104
|
|
|
105
105
|
Sello works with your own log server. Using `sello.build` is an optional convenience, not a protocol requirement.
|
|
106
106
|
|
|
107
|
-
To scaffold a tiny
|
|
107
|
+
To scaffold a tiny demo for each boundary:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
110
|
npx --yes sello init-demo
|
|
111
111
|
npx --yes sello init-http-demo
|
|
112
|
+
npx --yes sello init-mcp-demo
|
|
113
|
+
npx --yes sello init-a2a-demo
|
|
112
114
|
```
|
|
113
115
|
|
|
114
116
|
## Add Sello to an MCP Server
|
|
@@ -166,6 +168,15 @@ By default, Sello hashes the A2A JSON-RPC `method` and `params`, excluding reque
|
|
|
166
168
|
|
|
167
169
|
See [docs/a2a.md](docs/a2a.md) for token handling, hash boundaries, unknown-method behavior, and action viewing.
|
|
168
170
|
|
|
171
|
+
## Examples
|
|
172
|
+
|
|
173
|
+
| What you want to wire | Start here |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| A normal tool handler | [SDK Quickstart](docs/sdk-quickstart.md) and [`quickstart-tool.ts`](sdks/typescript/examples/quickstart-tool.ts) |
|
|
176
|
+
| An MCP tool | `npx --yes sello init-mcp-demo`, [MCP Integration](docs/mcp.md), and [`mcp-minimal-server.ts`](sdks/typescript/examples/mcp-minimal-server.ts) |
|
|
177
|
+
| An A2A message handler | `npx --yes sello init-a2a-demo`, [A2A Integration](docs/a2a.md), and [`a2a-minimal-server.ts`](sdks/typescript/examples/a2a-minimal-server.ts) |
|
|
178
|
+
| Verified action viewing | `npx sello actions` or `http://localhost:8787/actions` |
|
|
179
|
+
|
|
169
180
|
## See Logged Actions
|
|
170
181
|
|
|
171
182
|
```bash
|
|
@@ -207,8 +218,6 @@ Sello does not prove that the agent called every service it should have called,
|
|
|
207
218
|
- [Protocol Walkthrough](docs/protocol-walkthrough.md): the primitive receipt loop for implementers.
|
|
208
219
|
- [SPEC.md](SPEC.md): the Sello protocol draft.
|
|
209
220
|
- [Notarized Agents paper](https://arxiv.org/abs/2606.04193): design rationale, threat model, and prior art.
|
|
210
|
-
- [sdks/typescript/examples/mcp-minimal-server.ts](sdks/typescript/examples/mcp-minimal-server.ts): a small MCP integration.
|
|
211
|
-
- [sdks/typescript/examples/a2a-minimal-server.ts](sdks/typescript/examples/a2a-minimal-server.ts): a small A2A integration.
|
|
212
221
|
- [docs/security-review.md](docs/security-review.md) and [docs/sdk-security-audit.md](docs/sdk-security-audit.md): current review notes.
|
|
213
222
|
|
|
214
223
|
The TypeScript SDK is published on [npm](https://www.npmjs.com/package/sello) and lives in [`sdks/typescript/`](sdks/typescript/). The Python SDK is published on [PyPI](https://pypi.org/project/sello/) and lives in [`sdks/python/`](sdks/python/). Both SDKs support the same service-side `sello.service()` flow; Python uses the `@receipts.tool(...)` decorator. MCP and A2A helpers are currently TypeScript-first. Live Rekor proof verification and production identity operations are still future work.
|
package/dist/cli/sello.js
CHANGED
|
@@ -96,6 +96,12 @@ try {
|
|
|
96
96
|
case "init-http-demo":
|
|
97
97
|
initHttpDemoCommand(process.argv.slice(3));
|
|
98
98
|
break;
|
|
99
|
+
case "init-mcp-demo":
|
|
100
|
+
initMcpDemoCommand(process.argv.slice(3));
|
|
101
|
+
break;
|
|
102
|
+
case "init-a2a-demo":
|
|
103
|
+
initA2aDemoCommand(process.argv.slice(3));
|
|
104
|
+
break;
|
|
99
105
|
case "keys":
|
|
100
106
|
keysCommand(process.argv.slice(3));
|
|
101
107
|
break;
|
|
@@ -337,13 +343,13 @@ function initDemoCommand(args ) {
|
|
|
337
343
|
console.log(`Created ${output}`);
|
|
338
344
|
console.log("");
|
|
339
345
|
console.log("Terminal 1: keep the local dev log running");
|
|
340
|
-
console.log(
|
|
346
|
+
console.log(` ${devCommandHint()}`);
|
|
341
347
|
console.log("");
|
|
342
348
|
console.log("Terminal 2: emit and view a receipt");
|
|
343
349
|
console.log(` node ${output}`);
|
|
344
350
|
console.log(" npx sello actions");
|
|
345
351
|
console.log("");
|
|
346
|
-
console.log(
|
|
352
|
+
console.log(`Then open ${actionViewerUrlHint()}`);
|
|
347
353
|
}
|
|
348
354
|
|
|
349
355
|
function initHttpDemoCommand(args ) {
|
|
@@ -359,7 +365,7 @@ function initHttpDemoCommand(args ) {
|
|
|
359
365
|
console.log(`Created ${output}`);
|
|
360
366
|
console.log("");
|
|
361
367
|
console.log("Terminal 1: keep the local dev log running");
|
|
362
|
-
console.log(
|
|
368
|
+
console.log(` ${devCommandHint()}`);
|
|
363
369
|
console.log("");
|
|
364
370
|
console.log("Terminal 2: start the route");
|
|
365
371
|
console.log(` node ${output}`);
|
|
@@ -368,7 +374,51 @@ function initHttpDemoCommand(args ) {
|
|
|
368
374
|
console.log(" npx sello call-http-demo");
|
|
369
375
|
console.log(" npx sello actions");
|
|
370
376
|
console.log("");
|
|
371
|
-
console.log(
|
|
377
|
+
console.log(`Then open ${actionViewerUrlHint()}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function initMcpDemoCommand(args ) {
|
|
381
|
+
const output = readFlag(args, "--output") ?? "sello-mcp-demo.mjs";
|
|
382
|
+
const force = args.includes("--force");
|
|
383
|
+
|
|
384
|
+
if (existsSync(output) && !force) {
|
|
385
|
+
throw new TypeError(`${output} already exists. Pass --force to overwrite it.`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
writeFileSync(output, mcpDemoSource(), { mode: 0o644 });
|
|
389
|
+
|
|
390
|
+
console.log(`Created ${output}`);
|
|
391
|
+
console.log("");
|
|
392
|
+
console.log("Terminal 1: keep the local dev log running");
|
|
393
|
+
console.log(` ${devCommandHint()}`);
|
|
394
|
+
console.log("");
|
|
395
|
+
console.log("Terminal 2: run the MCP-shaped tool call and view the receipt");
|
|
396
|
+
console.log(` node ${output}`);
|
|
397
|
+
console.log(" npx sello actions");
|
|
398
|
+
console.log("");
|
|
399
|
+
console.log(`Then open ${actionViewerUrlHint()}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function initA2aDemoCommand(args ) {
|
|
403
|
+
const output = readFlag(args, "--output") ?? "sello-a2a-demo.mjs";
|
|
404
|
+
const force = args.includes("--force");
|
|
405
|
+
|
|
406
|
+
if (existsSync(output) && !force) {
|
|
407
|
+
throw new TypeError(`${output} already exists. Pass --force to overwrite it.`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
writeFileSync(output, a2aDemoSource(), { mode: 0o644 });
|
|
411
|
+
|
|
412
|
+
console.log(`Created ${output}`);
|
|
413
|
+
console.log("");
|
|
414
|
+
console.log("Terminal 1: keep the local dev log running");
|
|
415
|
+
console.log(` ${devCommandHint()}`);
|
|
416
|
+
console.log("");
|
|
417
|
+
console.log("Terminal 2: run the A2A-shaped message and view the receipt");
|
|
418
|
+
console.log(` node ${output}`);
|
|
419
|
+
console.log(" npx sello actions");
|
|
420
|
+
console.log("");
|
|
421
|
+
console.log(`Then open ${actionViewerUrlHint()}`);
|
|
372
422
|
}
|
|
373
423
|
|
|
374
424
|
function keysCommand(args ) {
|
|
@@ -851,6 +901,8 @@ function printHelp() {
|
|
|
851
901
|
sello call-http-demo [--url http://localhost:8790/calendar/events] [--title title]
|
|
852
902
|
sello init-demo [--output emit-receipt.mjs] [--force]
|
|
853
903
|
sello init-http-demo [--output sello-http-route.mjs] [--force]
|
|
904
|
+
sello init-mcp-demo [--output sello-mcp-demo.mjs] [--force]
|
|
905
|
+
sello init-a2a-demo [--output sello-a2a-demo.mjs] [--force]
|
|
854
906
|
sello actions [--token agent-token]
|
|
855
907
|
sello keys service
|
|
856
908
|
sello inspect-env
|
|
@@ -917,6 +969,25 @@ function formatHttpDemoResponse(responseText ) {
|
|
|
917
969
|
}
|
|
918
970
|
}
|
|
919
971
|
|
|
972
|
+
function devCommandHint() {
|
|
973
|
+
const state = loadDevStateIfPresent();
|
|
974
|
+
const port = state ? portFromDevState(state) : undefined;
|
|
975
|
+
return port && port !== "8787" ? `npx sello dev --port ${port}` : "npx sello dev";
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function actionViewerUrlHint() {
|
|
979
|
+
const state = loadDevStateIfPresent();
|
|
980
|
+
return state ? actionViewerUrl(state) : "http://localhost:8787/actions";
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function portFromDevState(state ) {
|
|
984
|
+
try {
|
|
985
|
+
return new URL(state.logEndpoint).port;
|
|
986
|
+
} catch {
|
|
987
|
+
return undefined;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
920
991
|
function slug(value ) {
|
|
921
992
|
return value
|
|
922
993
|
.toLowerCase()
|
|
@@ -1080,3 +1151,124 @@ function slug(value) {
|
|
|
1080
1151
|
}
|
|
1081
1152
|
`;
|
|
1082
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
function mcpDemoSource() {
|
|
1156
|
+
return `import { readFileSync } from "node:fs";
|
|
1157
|
+
import { sello } from "sello";
|
|
1158
|
+
|
|
1159
|
+
const state = JSON.parse(readFileSync(".sello/dev.json", "utf8"));
|
|
1160
|
+
|
|
1161
|
+
const receipts = sello.service({
|
|
1162
|
+
service: state.serviceId,
|
|
1163
|
+
serviceKey: state.serviceKey,
|
|
1164
|
+
tokenIssuer: state.tokenIssuerPublicKey,
|
|
1165
|
+
log: sello.logs.http(state.logUrl, { endpoint: state.logEndpoint }),
|
|
1166
|
+
submit: { mode: "await" },
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
const createEvent = receipts.mcpTool("calendar.create_event", async (args) => {
|
|
1170
|
+
const title = readString(args.title, "title");
|
|
1171
|
+
return {
|
|
1172
|
+
content: [
|
|
1173
|
+
{
|
|
1174
|
+
type: "text",
|
|
1175
|
+
text: "created " + title,
|
|
1176
|
+
},
|
|
1177
|
+
],
|
|
1178
|
+
};
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
const response = await createEvent(
|
|
1182
|
+
{
|
|
1183
|
+
title: "MCP launch checklist",
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
requestInfo: {
|
|
1187
|
+
headers: new Headers({
|
|
1188
|
+
authorization: "Bearer " + state.agentToken,
|
|
1189
|
+
}),
|
|
1190
|
+
},
|
|
1191
|
+
},
|
|
1192
|
+
);
|
|
1193
|
+
|
|
1194
|
+
await receipts.flush();
|
|
1195
|
+
|
|
1196
|
+
console.log("MCP tool response:");
|
|
1197
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1198
|
+
|
|
1199
|
+
function readString(value, name) {
|
|
1200
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1201
|
+
throw new TypeError(name + " must be a non-empty string");
|
|
1202
|
+
}
|
|
1203
|
+
return value;
|
|
1204
|
+
}
|
|
1205
|
+
`;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function a2aDemoSource() {
|
|
1209
|
+
return `import { readFileSync } from "node:fs";
|
|
1210
|
+
import { sello } from "sello";
|
|
1211
|
+
|
|
1212
|
+
const state = JSON.parse(readFileSync(".sello/dev.json", "utf8"));
|
|
1213
|
+
|
|
1214
|
+
const receipts = sello.service({
|
|
1215
|
+
service: state.serviceId,
|
|
1216
|
+
serviceKey: state.serviceKey,
|
|
1217
|
+
tokenIssuer: state.tokenIssuerPublicKey,
|
|
1218
|
+
log: sello.logs.http(state.logUrl, { endpoint: state.logEndpoint }),
|
|
1219
|
+
submit: { mode: "await" },
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
const sendMessage = receipts.a2aMessage(async (request) => ({
|
|
1223
|
+
jsonrpc: "2.0",
|
|
1224
|
+
id: request.id,
|
|
1225
|
+
result: {
|
|
1226
|
+
kind: "message",
|
|
1227
|
+
messageId: "calendar-reply-1",
|
|
1228
|
+
role: "agent",
|
|
1229
|
+
parts: [
|
|
1230
|
+
{
|
|
1231
|
+
kind: "text",
|
|
1232
|
+
text: "created " + readMessageText(request),
|
|
1233
|
+
},
|
|
1234
|
+
],
|
|
1235
|
+
},
|
|
1236
|
+
}));
|
|
1237
|
+
|
|
1238
|
+
const response = await sendMessage(
|
|
1239
|
+
{
|
|
1240
|
+
jsonrpc: "2.0",
|
|
1241
|
+
id: "a2a-demo-1",
|
|
1242
|
+
method: "message/send",
|
|
1243
|
+
params: {
|
|
1244
|
+
message: {
|
|
1245
|
+
role: "user",
|
|
1246
|
+
parts: [
|
|
1247
|
+
{
|
|
1248
|
+
kind: "text",
|
|
1249
|
+
text: "A2A launch checklist",
|
|
1250
|
+
},
|
|
1251
|
+
],
|
|
1252
|
+
},
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
headers: new Headers({
|
|
1257
|
+
authorization: "Bearer " + state.agentToken,
|
|
1258
|
+
}),
|
|
1259
|
+
},
|
|
1260
|
+
);
|
|
1261
|
+
|
|
1262
|
+
await receipts.flush();
|
|
1263
|
+
|
|
1264
|
+
console.log("A2A message response:");
|
|
1265
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1266
|
+
|
|
1267
|
+
function readMessageText(request) {
|
|
1268
|
+
return request.params.message.parts
|
|
1269
|
+
.filter((part) => part.kind === "text")
|
|
1270
|
+
.map((part) => part.text)
|
|
1271
|
+
.join(" ") || "untitled";
|
|
1272
|
+
}
|
|
1273
|
+
`;
|
|
1274
|
+
}
|
package/docs/a2a.md
CHANGED
|
@@ -122,6 +122,18 @@ For a specific agent token:
|
|
|
122
122
|
npx sello actions --token <agent-token>
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
## Runnable Demo
|
|
126
|
+
|
|
127
|
+
To generate a tiny A2A-shaped demo in your project:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx --yes sello init-a2a-demo
|
|
131
|
+
node sello-a2a-demo.mjs
|
|
132
|
+
npx --yes sello actions
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Keep `npx sello dev` running in another terminal while you run the generated file.
|
|
136
|
+
|
|
125
137
|
## Example
|
|
126
138
|
|
|
127
139
|
See [`sdks/typescript/examples/a2a-minimal-server.ts`](../sdks/typescript/examples/a2a-minimal-server.ts) for a dependency-free A2A-shaped example. It wraps one `message/send` handler and leaves unknown methods unreceipted.
|
package/docs/mcp.md
CHANGED
|
@@ -132,6 +132,18 @@ For a specific agent token:
|
|
|
132
132
|
npx sello actions --token <agent-token>
|
|
133
133
|
```
|
|
134
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
|
+
|
|
135
147
|
## Example
|
|
136
148
|
|
|
137
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.
|
package/docs/sdk-quickstart.md
CHANGED
|
@@ -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
|
|
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
|
package/package.json
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
+
}
|