@noodleseed/agent-kit 0.21.1 → 0.23.0
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/manifest.json +83 -33
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/examples/acme-bistro/src/server.ts +1 -1
- package/skills/claude-code/examples/acme-discovery/src/server.ts +1 -1
- package/skills/claude-code/examples/acme-tasks/src/server.ts +4 -2
- package/skills/claude-code/examples/acme-tasks/test/server.test.ts +9 -0
- package/skills/claude-code/examples/customer-auth/README.md +237 -0
- package/skills/claude-code/examples/customer-auth/noodle.json +4 -0
- package/skills/claude-code/examples/customer-auth/package.json +16 -0
- package/skills/claude-code/examples/customer-auth/src/server.ts +145 -0
- package/skills/claude-code/examples/customer-auth/test/server.test.ts +25 -0
- package/skills/claude-code/examples/food-ordering/README.md +7 -3
- package/skills/claude-code/examples/food-ordering/src/helpers.ts +2 -0
- package/skills/claude-code/examples/food-ordering/src/server.ts +47 -2
- package/skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx +56 -3
- package/skills/claude-code/examples/food-ordering/test/server.test.ts +46 -0
- package/skills/claude-code/references/authoring-workflow.md +117 -2
- package/skills/claude-code/references/compile-errors.md +6 -1
- package/skills/claude-code/references/embedded-assistant.md +95 -10
- package/skills/claude-code/references/examples.md +1 -1
- package/skills/claude-code/references/sdk-surface.md +2 -2
- package/skills/claude-code/references/widgets-and-apps.md +32 -7
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/examples/acme-bistro/src/server.ts +1 -1
- package/skills/codex/examples/acme-discovery/src/server.ts +1 -1
- package/skills/codex/examples/acme-tasks/src/server.ts +4 -2
- package/skills/codex/examples/acme-tasks/test/server.test.ts +9 -0
- package/skills/codex/examples/customer-auth/README.md +237 -0
- package/skills/codex/examples/customer-auth/noodle.json +4 -0
- package/skills/codex/examples/customer-auth/package.json +16 -0
- package/skills/codex/examples/customer-auth/src/server.ts +145 -0
- package/skills/codex/examples/customer-auth/test/server.test.ts +25 -0
- package/skills/codex/examples/food-ordering/README.md +7 -3
- package/skills/codex/examples/food-ordering/src/helpers.ts +2 -0
- package/skills/codex/examples/food-ordering/src/server.ts +47 -2
- package/skills/codex/examples/food-ordering/src/views/ordering-flow.tsx +56 -3
- package/skills/codex/examples/food-ordering/test/server.test.ts +46 -0
- package/skills/codex/references/authoring-workflow.md +117 -2
- package/skills/codex/references/compile-errors.md +6 -1
- package/skills/codex/references/embedded-assistant.md +95 -10
- package/skills/codex/references/examples.md +1 -1
- package/skills/codex/references/sdk-surface.md +2 -2
- package/skills/codex/references/widgets-and-apps.md +32 -7
package/manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"packageVersion": "0.
|
|
2
|
+
"packageVersion": "0.23.0",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": "skills/codex/SKILL.md",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "14feeaa845c5c982e020173b800ecfa825038ac7e5b6d6c242244204f2f478a9",
|
|
7
7
|
"agentTarget": "codex"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"path": "skills/codex/references/sdk-surface.md",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "23b901198b1d6a2cb5c140810b2fc4d58b7008c2afb2fdd57a8e2993333a7600",
|
|
12
12
|
"agentTarget": "codex"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
@@ -23,17 +23,17 @@
|
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"path": "skills/codex/references/compile-errors.md",
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "1ed667250f8f14625012bd8e931812c5d80bac323fa2f39a79ec83dc119fc385",
|
|
27
27
|
"agentTarget": "codex"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "skills/codex/references/authoring-workflow.md",
|
|
31
|
-
"sha256": "
|
|
31
|
+
"sha256": "3f4767bfe361090e172c7554f1121fe415e618f3a90304498535d177b0c633be",
|
|
32
32
|
"agentTarget": "codex"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "skills/codex/references/embedded-assistant.md",
|
|
36
|
-
"sha256": "
|
|
36
|
+
"sha256": "3e96c4f322cadbd509957781005060a8526c8e60e610bcb7246edc1e86349bf9",
|
|
37
37
|
"agentTarget": "codex"
|
|
38
38
|
},
|
|
39
39
|
{
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"path": "skills/codex/references/widgets-and-apps.md",
|
|
51
|
-
"sha256": "
|
|
51
|
+
"sha256": "7e77eb1520883ad8bd0492fcefae97dfdb0e5bbe9351a7c554d19ee1f7c087cc",
|
|
52
52
|
"agentTarget": "codex"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"path": "skills/codex/references/examples.md",
|
|
81
|
-
"sha256": "
|
|
81
|
+
"sha256": "69ab3b0e85454f3d5df5f270b760216de7b1a69803a2f081447bc27bb634c524",
|
|
82
82
|
"agentTarget": "codex"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"path": "skills/codex/examples/acme-bistro/src/server.ts",
|
|
121
|
-
"sha256": "
|
|
121
|
+
"sha256": "35b735b5451499dad7b5bba9f4a0e0e57cc579d1ca8bcdd75317a044ed72c91b",
|
|
122
122
|
"agentTarget": "codex"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
"path": "skills/codex/examples/acme-discovery/src/server.ts",
|
|
176
|
-
"sha256": "
|
|
176
|
+
"sha256": "0d5a6c50a126b379ffcc67b5089665e079f2aa5f7ddb1c6a5a99d6a6d37669e4",
|
|
177
177
|
"agentTarget": "codex"
|
|
178
178
|
},
|
|
179
179
|
{
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
230
|
"path": "skills/codex/examples/acme-tasks/src/server.ts",
|
|
231
|
-
"sha256": "
|
|
231
|
+
"sha256": "769982ba567460af08d79517d6bf259ce4e8f7882cd3525fadbd5b22639e08ff",
|
|
232
232
|
"agentTarget": "codex"
|
|
233
233
|
},
|
|
234
234
|
{
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
},
|
|
244
244
|
{
|
|
245
245
|
"path": "skills/codex/examples/acme-tasks/test/server.test.ts",
|
|
246
|
-
"sha256": "
|
|
246
|
+
"sha256": "1a6c729cceae3cf5ac5fea9081eb61c686a0a72c8a90539e5b0dee4307a4ff81",
|
|
247
247
|
"agentTarget": "codex"
|
|
248
248
|
},
|
|
249
249
|
{
|
|
@@ -251,9 +251,34 @@
|
|
|
251
251
|
"sha256": "c486293ce4a0d38450f61533da17e419ca9fa490ef9577e8404f4fd700797747",
|
|
252
252
|
"agentTarget": "codex"
|
|
253
253
|
},
|
|
254
|
+
{
|
|
255
|
+
"path": "skills/codex/examples/customer-auth/README.md",
|
|
256
|
+
"sha256": "ae55e8c5e88216a3a3b1f57084f16e7af0edbf1e5b35ade7be5d5394887347ae",
|
|
257
|
+
"agentTarget": "codex"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"path": "skills/codex/examples/customer-auth/noodle.json",
|
|
261
|
+
"sha256": "15824af8846e8da4f9d14c28216ff95076dfc9f9964fcf127f441d44517d0eea",
|
|
262
|
+
"agentTarget": "codex"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"path": "skills/codex/examples/customer-auth/package.json",
|
|
266
|
+
"sha256": "bfbc1e9aa5f3c7f5449a6ade8ff78876f46ce77b7b867f26bdb556edee3171db",
|
|
267
|
+
"agentTarget": "codex"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"path": "skills/codex/examples/customer-auth/src/server.ts",
|
|
271
|
+
"sha256": "549dd7d0ccfb2fff7b0f3ce23194145e7364f5fc337a27e2b6d50b3dd7fb264d",
|
|
272
|
+
"agentTarget": "codex"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"path": "skills/codex/examples/customer-auth/test/server.test.ts",
|
|
276
|
+
"sha256": "97e9f00f102ea65f74dd9bca93090eb961af431a8c1285c80028dfae191476d9",
|
|
277
|
+
"agentTarget": "codex"
|
|
278
|
+
},
|
|
254
279
|
{
|
|
255
280
|
"path": "skills/codex/examples/food-ordering/README.md",
|
|
256
|
-
"sha256": "
|
|
281
|
+
"sha256": "1e8a08868efa70fe79bacc02d27effa5a65fbb5b497e76a7ce1268586ec559dc",
|
|
257
282
|
"agentTarget": "codex"
|
|
258
283
|
},
|
|
259
284
|
{
|
|
@@ -268,12 +293,12 @@
|
|
|
268
293
|
},
|
|
269
294
|
{
|
|
270
295
|
"path": "skills/codex/examples/food-ordering/src/helpers.ts",
|
|
271
|
-
"sha256": "
|
|
296
|
+
"sha256": "91f8dec747e4eb050c2e8dc8c22679443f22468c7fe7cf821e97f42eadb629e5",
|
|
272
297
|
"agentTarget": "codex"
|
|
273
298
|
},
|
|
274
299
|
{
|
|
275
300
|
"path": "skills/codex/examples/food-ordering/src/server.ts",
|
|
276
|
-
"sha256": "
|
|
301
|
+
"sha256": "55942f7b6424ea6019479ab2df98afd0d5c33e154acddaf6638cf78d144da639",
|
|
277
302
|
"agentTarget": "codex"
|
|
278
303
|
},
|
|
279
304
|
{
|
|
@@ -283,7 +308,7 @@
|
|
|
283
308
|
},
|
|
284
309
|
{
|
|
285
310
|
"path": "skills/codex/examples/food-ordering/src/views/ordering-flow.tsx",
|
|
286
|
-
"sha256": "
|
|
311
|
+
"sha256": "57690826caf1c7d4cd6da95a82fc8df3be1106f0de75716a3ccbb4dece2a953e",
|
|
287
312
|
"agentTarget": "codex"
|
|
288
313
|
},
|
|
289
314
|
{
|
|
@@ -293,7 +318,7 @@
|
|
|
293
318
|
},
|
|
294
319
|
{
|
|
295
320
|
"path": "skills/codex/examples/food-ordering/test/server.test.ts",
|
|
296
|
-
"sha256": "
|
|
321
|
+
"sha256": "6819457eed88b302d8369a24dc22581066d25c9092dca3a1141366665e246e52",
|
|
297
322
|
"agentTarget": "codex"
|
|
298
323
|
},
|
|
299
324
|
{
|
|
@@ -353,12 +378,12 @@
|
|
|
353
378
|
},
|
|
354
379
|
{
|
|
355
380
|
"path": "skills/claude-code/SKILL.md",
|
|
356
|
-
"sha256": "
|
|
381
|
+
"sha256": "3f37fbed3225a691bac9b2f03a533d827479f01c6bfb3abf8b76c262af6ae7c2",
|
|
357
382
|
"agentTarget": "claude-code"
|
|
358
383
|
},
|
|
359
384
|
{
|
|
360
385
|
"path": "skills/claude-code/references/sdk-surface.md",
|
|
361
|
-
"sha256": "
|
|
386
|
+
"sha256": "23b901198b1d6a2cb5c140810b2fc4d58b7008c2afb2fdd57a8e2993333a7600",
|
|
362
387
|
"agentTarget": "claude-code"
|
|
363
388
|
},
|
|
364
389
|
{
|
|
@@ -373,17 +398,17 @@
|
|
|
373
398
|
},
|
|
374
399
|
{
|
|
375
400
|
"path": "skills/claude-code/references/compile-errors.md",
|
|
376
|
-
"sha256": "
|
|
401
|
+
"sha256": "1ed667250f8f14625012bd8e931812c5d80bac323fa2f39a79ec83dc119fc385",
|
|
377
402
|
"agentTarget": "claude-code"
|
|
378
403
|
},
|
|
379
404
|
{
|
|
380
405
|
"path": "skills/claude-code/references/authoring-workflow.md",
|
|
381
|
-
"sha256": "
|
|
406
|
+
"sha256": "3f4767bfe361090e172c7554f1121fe415e618f3a90304498535d177b0c633be",
|
|
382
407
|
"agentTarget": "claude-code"
|
|
383
408
|
},
|
|
384
409
|
{
|
|
385
410
|
"path": "skills/claude-code/references/embedded-assistant.md",
|
|
386
|
-
"sha256": "
|
|
411
|
+
"sha256": "3e96c4f322cadbd509957781005060a8526c8e60e610bcb7246edc1e86349bf9",
|
|
387
412
|
"agentTarget": "claude-code"
|
|
388
413
|
},
|
|
389
414
|
{
|
|
@@ -398,7 +423,7 @@
|
|
|
398
423
|
},
|
|
399
424
|
{
|
|
400
425
|
"path": "skills/claude-code/references/widgets-and-apps.md",
|
|
401
|
-
"sha256": "
|
|
426
|
+
"sha256": "7e77eb1520883ad8bd0492fcefae97dfdb0e5bbe9351a7c554d19ee1f7c087cc",
|
|
402
427
|
"agentTarget": "claude-code"
|
|
403
428
|
},
|
|
404
429
|
{
|
|
@@ -428,7 +453,7 @@
|
|
|
428
453
|
},
|
|
429
454
|
{
|
|
430
455
|
"path": "skills/claude-code/references/examples.md",
|
|
431
|
-
"sha256": "
|
|
456
|
+
"sha256": "69ab3b0e85454f3d5df5f270b760216de7b1a69803a2f081447bc27bb634c524",
|
|
432
457
|
"agentTarget": "claude-code"
|
|
433
458
|
},
|
|
434
459
|
{
|
|
@@ -468,7 +493,7 @@
|
|
|
468
493
|
},
|
|
469
494
|
{
|
|
470
495
|
"path": "skills/claude-code/examples/acme-bistro/src/server.ts",
|
|
471
|
-
"sha256": "
|
|
496
|
+
"sha256": "35b735b5451499dad7b5bba9f4a0e0e57cc579d1ca8bcdd75317a044ed72c91b",
|
|
472
497
|
"agentTarget": "claude-code"
|
|
473
498
|
},
|
|
474
499
|
{
|
|
@@ -523,7 +548,7 @@
|
|
|
523
548
|
},
|
|
524
549
|
{
|
|
525
550
|
"path": "skills/claude-code/examples/acme-discovery/src/server.ts",
|
|
526
|
-
"sha256": "
|
|
551
|
+
"sha256": "0d5a6c50a126b379ffcc67b5089665e079f2aa5f7ddb1c6a5a99d6a6d37669e4",
|
|
527
552
|
"agentTarget": "claude-code"
|
|
528
553
|
},
|
|
529
554
|
{
|
|
@@ -578,7 +603,7 @@
|
|
|
578
603
|
},
|
|
579
604
|
{
|
|
580
605
|
"path": "skills/claude-code/examples/acme-tasks/src/server.ts",
|
|
581
|
-
"sha256": "
|
|
606
|
+
"sha256": "769982ba567460af08d79517d6bf259ce4e8f7882cd3525fadbd5b22639e08ff",
|
|
582
607
|
"agentTarget": "claude-code"
|
|
583
608
|
},
|
|
584
609
|
{
|
|
@@ -593,7 +618,7 @@
|
|
|
593
618
|
},
|
|
594
619
|
{
|
|
595
620
|
"path": "skills/claude-code/examples/acme-tasks/test/server.test.ts",
|
|
596
|
-
"sha256": "
|
|
621
|
+
"sha256": "1a6c729cceae3cf5ac5fea9081eb61c686a0a72c8a90539e5b0dee4307a4ff81",
|
|
597
622
|
"agentTarget": "claude-code"
|
|
598
623
|
},
|
|
599
624
|
{
|
|
@@ -601,9 +626,34 @@
|
|
|
601
626
|
"sha256": "c486293ce4a0d38450f61533da17e419ca9fa490ef9577e8404f4fd700797747",
|
|
602
627
|
"agentTarget": "claude-code"
|
|
603
628
|
},
|
|
629
|
+
{
|
|
630
|
+
"path": "skills/claude-code/examples/customer-auth/README.md",
|
|
631
|
+
"sha256": "ae55e8c5e88216a3a3b1f57084f16e7af0edbf1e5b35ade7be5d5394887347ae",
|
|
632
|
+
"agentTarget": "claude-code"
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
"path": "skills/claude-code/examples/customer-auth/noodle.json",
|
|
636
|
+
"sha256": "15824af8846e8da4f9d14c28216ff95076dfc9f9964fcf127f441d44517d0eea",
|
|
637
|
+
"agentTarget": "claude-code"
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
"path": "skills/claude-code/examples/customer-auth/package.json",
|
|
641
|
+
"sha256": "bfbc1e9aa5f3c7f5449a6ade8ff78876f46ce77b7b867f26bdb556edee3171db",
|
|
642
|
+
"agentTarget": "claude-code"
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
"path": "skills/claude-code/examples/customer-auth/src/server.ts",
|
|
646
|
+
"sha256": "549dd7d0ccfb2fff7b0f3ce23194145e7364f5fc337a27e2b6d50b3dd7fb264d",
|
|
647
|
+
"agentTarget": "claude-code"
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
"path": "skills/claude-code/examples/customer-auth/test/server.test.ts",
|
|
651
|
+
"sha256": "97e9f00f102ea65f74dd9bca93090eb961af431a8c1285c80028dfae191476d9",
|
|
652
|
+
"agentTarget": "claude-code"
|
|
653
|
+
},
|
|
604
654
|
{
|
|
605
655
|
"path": "skills/claude-code/examples/food-ordering/README.md",
|
|
606
|
-
"sha256": "
|
|
656
|
+
"sha256": "1e8a08868efa70fe79bacc02d27effa5a65fbb5b497e76a7ce1268586ec559dc",
|
|
607
657
|
"agentTarget": "claude-code"
|
|
608
658
|
},
|
|
609
659
|
{
|
|
@@ -618,12 +668,12 @@
|
|
|
618
668
|
},
|
|
619
669
|
{
|
|
620
670
|
"path": "skills/claude-code/examples/food-ordering/src/helpers.ts",
|
|
621
|
-
"sha256": "
|
|
671
|
+
"sha256": "91f8dec747e4eb050c2e8dc8c22679443f22468c7fe7cf821e97f42eadb629e5",
|
|
622
672
|
"agentTarget": "claude-code"
|
|
623
673
|
},
|
|
624
674
|
{
|
|
625
675
|
"path": "skills/claude-code/examples/food-ordering/src/server.ts",
|
|
626
|
-
"sha256": "
|
|
676
|
+
"sha256": "55942f7b6424ea6019479ab2df98afd0d5c33e154acddaf6638cf78d144da639",
|
|
627
677
|
"agentTarget": "claude-code"
|
|
628
678
|
},
|
|
629
679
|
{
|
|
@@ -633,7 +683,7 @@
|
|
|
633
683
|
},
|
|
634
684
|
{
|
|
635
685
|
"path": "skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx",
|
|
636
|
-
"sha256": "
|
|
686
|
+
"sha256": "57690826caf1c7d4cd6da95a82fc8df3be1106f0de75716a3ccbb4dece2a953e",
|
|
637
687
|
"agentTarget": "claude-code"
|
|
638
688
|
},
|
|
639
689
|
{
|
|
@@ -643,7 +693,7 @@
|
|
|
643
693
|
},
|
|
644
694
|
{
|
|
645
695
|
"path": "skills/claude-code/examples/food-ordering/test/server.test.ts",
|
|
646
|
-
"sha256": "
|
|
696
|
+
"sha256": "6819457eed88b302d8369a24dc22581066d25c9092dca3a1141366665e246e52",
|
|
647
697
|
"agentTarget": "claude-code"
|
|
648
698
|
},
|
|
649
699
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Self-checking, self-updating agent skills for the Noodle Seed CLI. Authored in this repo by @noodle-borg/agent-kit; this is the published, independently-versioned canonical skills artifact the CLI fetches and verifies.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -29,7 +29,7 @@ const menuItemOutput = z.object({
|
|
|
29
29
|
// Tool annotations for host planners: the menu read is read-only; cart edits are local writes; checkout
|
|
30
30
|
// opens an external (payment) link.
|
|
31
31
|
const readOnly = annotations.readOnly();
|
|
32
|
-
const localWrite = annotations.localAction({ destructive: false });
|
|
32
|
+
const localWrite = annotations.localAction({ destructive: false, confirm: false });
|
|
33
33
|
const openLink = annotations.openAction();
|
|
34
34
|
|
|
35
35
|
export default server(
|
|
@@ -83,7 +83,7 @@ const discoverInput = z.object({
|
|
|
83
83
|
// shortlisting is a local non-destructive write.
|
|
84
84
|
const readOnly = annotations.readOnly();
|
|
85
85
|
const openLink = annotations.openAction();
|
|
86
|
-
const localWrite = annotations.localAction({ destructive: false });
|
|
86
|
+
const localWrite = annotations.localAction({ destructive: false, confirm: false });
|
|
87
87
|
|
|
88
88
|
const destinationOutput = z.object({
|
|
89
89
|
id: z.string(),
|
|
@@ -28,6 +28,8 @@ const priority = z.enum(['high', 'medium', 'low']);
|
|
|
28
28
|
// non-destructive writes.
|
|
29
29
|
const readOnly = annotations.readOnly();
|
|
30
30
|
const localWrite = annotations.localAction({ destructive: false });
|
|
31
|
+
const confirmedWrite = annotations.localAction({ destructive: false, confirm: true });
|
|
32
|
+
const widgetWrite = annotations.localAction({ destructive: false, confirm: false });
|
|
31
33
|
|
|
32
34
|
const taskOutput = z.object({
|
|
33
35
|
id: z.string(),
|
|
@@ -104,7 +106,7 @@ export default server(
|
|
|
104
106
|
// Flow 3 — Complete: mark a task done. Model-visible so the model can complete on request.
|
|
105
107
|
tool('complete_task', {
|
|
106
108
|
description: 'Mark an Acme task complete. Pass the task id and its title.',
|
|
107
|
-
annotations:
|
|
109
|
+
annotations: confirmedWrite,
|
|
108
110
|
input: z.object({
|
|
109
111
|
task: z.string(),
|
|
110
112
|
title: z.string().min(1),
|
|
@@ -122,7 +124,7 @@ export default server(
|
|
|
122
124
|
tool('set_priority', {
|
|
123
125
|
visibility: ['app'],
|
|
124
126
|
description: 'Re-prioritize a task from the list widget.',
|
|
125
|
-
annotations:
|
|
127
|
+
annotations: widgetWrite,
|
|
126
128
|
input: z.object({
|
|
127
129
|
task: z.string(),
|
|
128
130
|
priority,
|
|
@@ -19,4 +19,13 @@ describe('acme-tasks example', () => {
|
|
|
19
19
|
const text = JSON.stringify(await app.toManifest());
|
|
20
20
|
expect(text).toMatch(/"tasks":\[\{"id":"email_vendor".*"priority":"high"/);
|
|
21
21
|
});
|
|
22
|
+
|
|
23
|
+
it('opts the conversational completion action into runtime confirmation', async () => {
|
|
24
|
+
const manifest = await app.toManifest();
|
|
25
|
+
const completeTask = manifest.tools.find((candidate) => candidate.name === 'complete_task');
|
|
26
|
+
const addTask = manifest.tools.find((candidate) => candidate.name === 'add_task');
|
|
27
|
+
|
|
28
|
+
expect(completeTask?.annotations?.confirm).toBe(true);
|
|
29
|
+
expect(addTask?.annotations).not.toHaveProperty('confirm');
|
|
30
|
+
});
|
|
22
31
|
});
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Customer Auth - NoodleSeed.com Firebase customer identity
|
|
2
|
+
|
|
3
|
+
This curated example owns the customer/end-user authentication with Firebase bridge capability slot. It is
|
|
4
|
+
the NoodleSeed.com dogfood app for proving that a SaaS app can protect an MCP endpoint with its own customer
|
|
5
|
+
identity provider while still using the generic Noodle Seed authoring API.
|
|
6
|
+
|
|
7
|
+
It also owns the embedded-assistant showcase: the same authenticated MCP surface can be dropped into the
|
|
8
|
+
SaaS web application as a fully customer-branded assistant with independent light and dark themes. The
|
|
9
|
+
assistant loads the active deployment's instructions and model-visible tools rather than installing a stale
|
|
10
|
+
second skill bundle. Interactive MCP Apps widgets continue to render in supported external hosts; the
|
|
11
|
+
initial embedded surface renders text and native tool confirmations.
|
|
12
|
+
|
|
13
|
+
The public developer entrypoint is [`src/server.ts`](src/server.ts). It declares `customerAuth.firebase(...)` with
|
|
14
|
+
the NoodleSeed.com Firebase project and Firebase Web App public config. It exposes a deliberately small MCP
|
|
15
|
+
surface for org discovery:
|
|
16
|
+
|
|
17
|
+
- `list_my_organizations` lists the NoodleSeed.com organizations the signed-in customer belongs to (no
|
|
18
|
+
arguments — the org set comes from the verified customer session).
|
|
19
|
+
- `list_org_apps` lists apps for one of those organizations through the dev app API.
|
|
20
|
+
|
|
21
|
+
The two tools chain: `list_my_organizations` surfaces the `org_id`s the customer can act on, and
|
|
22
|
+
`list_org_apps` takes one of those `org_id`s. There is no NoodleSeed-specific SDK helper. The downstream API
|
|
23
|
+
is an ordinary authored HTTP connector.
|
|
24
|
+
|
|
25
|
+
During MCP OAuth login, Noodle Cloud hosts the Firebase bridge page at
|
|
26
|
+
`https://cloud.noodleseed.dev/oauth/customer/firebase/authorize`. The customer app does not add an
|
|
27
|
+
authorization route. The SaaS operator only configures Firebase Auth to allow the Noodle Cloud origin, and
|
|
28
|
+
Noodle Cloud signs the customer in with Firebase before posting the Firebase ID token back to its own bridge
|
|
29
|
+
callback.
|
|
30
|
+
|
|
31
|
+
## How delegated customer credentials are used
|
|
32
|
+
|
|
33
|
+
The example has two declarations that work together:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
auth: customerAuth.firebase({
|
|
37
|
+
projectId: variable('FIREBASE_PROJECT_ID'),
|
|
38
|
+
apiKey: variable('FIREBASE_WEB_API_KEY'),
|
|
39
|
+
authDomain: variable('FIREBASE_AUTH_DOMAIN'),
|
|
40
|
+
user: {
|
|
41
|
+
id: 'sub',
|
|
42
|
+
email: 'email',
|
|
43
|
+
name: 'name',
|
|
44
|
+
tenant: 'firebase.tenant',
|
|
45
|
+
orgs: 'claims.orgs',
|
|
46
|
+
roles: 'claims.roles',
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
That protects the MCP endpoint with the SaaS customer's Firebase identity. The connector then opts into
|
|
52
|
+
delegated customer credentials:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
auth: {
|
|
56
|
+
kind: 'delegatedSessionCookie',
|
|
57
|
+
provider: 'firebase',
|
|
58
|
+
sessionUrl: `${noodleseedApiOrigin}/api/auth/session`,
|
|
59
|
+
tokenField: 'idToken',
|
|
60
|
+
},
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Tool code calls the connector normally:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
fulfil({ input, connectors }) {
|
|
67
|
+
const apps = connectors.app_api.listOrgApps({
|
|
68
|
+
org_id: input.org_id,
|
|
69
|
+
skip: input.skip,
|
|
70
|
+
limit: input.limit,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return { result: apps.result };
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
At runtime, Noodle Seed verifies the Firebase customer during MCP OAuth, stores that customer's delegated
|
|
78
|
+
Firebase refresh token in the credential broker, and refreshes a short-lived Firebase ID token only when a
|
|
79
|
+
connector-backed tool calls the NoodleSeed.com API. For this app API, the broker exchanges that ID token at
|
|
80
|
+
the existing Next.js `/api/auth/session` route and sends the resulting session cookie to the API. The MCP
|
|
81
|
+
access token remains a Noodle-issued resource-bound token and is never sent to the downstream API.
|
|
82
|
+
|
|
83
|
+
## Delegated downstream auth for your own API (token exchange)
|
|
84
|
+
|
|
85
|
+
The Firebase path above only works for Firebase-session downstreams. When the downstream is **your own
|
|
86
|
+
API** with its own token issuance, use `delegatedTokenExchange` instead
|
|
87
|
+
([ADR 0152](../../docs/decisions/0152-delegated-token-exchange-connector-auth.md)): the platform signs a
|
|
88
|
+
short-lived, JWKS-verifiable assertion of the signed-in user and exchanges it (RFC 8693) at a token
|
|
89
|
+
endpoint you implement, which mints your own user-scoped token — so your API enforces its own per-user
|
|
90
|
+
authorization on every call. It works for identities from `customerAuth.bridge(...)` and from embedded
|
|
91
|
+
assistant sessions, with no per-user OAuth enrollment.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
auth: {
|
|
95
|
+
kind: 'delegatedTokenExchange',
|
|
96
|
+
tokenUrl: 'https://app.example.com/api/assistant/oauth/token', // origin must be in allowedOrigins
|
|
97
|
+
clientId: variable('EXAMPLE_DELEG_CLIENT_ID'),
|
|
98
|
+
clientSecret: secret('EXAMPLE_DELEG_CLIENT_SECRET'),
|
|
99
|
+
scopes: ['time_off'],
|
|
100
|
+
},
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Your endpoint authenticates the broker's client credential, verifies the `subject_token` JWT against the
|
|
104
|
+
platform issuer JWKS (claims include the verified `sub`, `email`, `name`, declared session `claims`,
|
|
105
|
+
`tenant`, and `deployment`), mints a short-lived user-scoped token, and returns the standard
|
|
106
|
+
`{ access_token, token_type, expires_in }` response. The exact wire contract and a copyable endpoint
|
|
107
|
+
implementation live in [docs/spec/connectors.md](../../docs/spec/connectors.md) and the Agent Kit
|
|
108
|
+
authoring-workflow reference ("Delegated downstream auth"). `noodle auth doctor` reports each declared
|
|
109
|
+
exchange endpoint.
|
|
110
|
+
|
|
111
|
+
## Validate
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
noodle auth doctor examples/customer-auth/src/server.ts
|
|
115
|
+
noodle validate examples/customer-auth/src/server.ts
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Run locally
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
noodle dev examples/customer-auth/src/server.ts --app noodleseed-customer-auth
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configuration
|
|
125
|
+
|
|
126
|
+
The embedded assistant uses a customer-supplied OpenAI Chat Completions-compatible endpoint. Configure its
|
|
127
|
+
managed values at the Noodle deployment environment; none of these values belongs in the customer web
|
|
128
|
+
application environment, and the API key never reaches the browser:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
noodle variables set ASSISTANT_MODEL_BASE_URL https://model.example.com/v1 --scope env
|
|
132
|
+
noodle variables set ASSISTANT_MODEL your-model --scope env
|
|
133
|
+
noodle secrets set ASSISTANT_MODEL_API_KEY --scope env
|
|
134
|
+
noodle variables set FIREBASE_PROJECT_ID your-firebase-project --scope env
|
|
135
|
+
noodle variables set FIREBASE_WEB_API_KEY your-firebase-web-api-key --scope env
|
|
136
|
+
noodle variables set FIREBASE_AUTH_DOMAIN your-firebase-project.firebaseapp.com --scope env
|
|
137
|
+
noodle check --target embedded-assistant src/server.ts
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Assistant origins are exact and HTTPS-only. A local embedding application must serve itself over HTTPS and
|
|
141
|
+
declare an origin such as `https://localhost:3000`; `noodle dev` does not provide TLS for that separate web
|
|
142
|
+
application.
|
|
143
|
+
|
|
144
|
+
Create the backend credential after deployment. The CLI writes it to a mode-0600 file and never prints the
|
|
145
|
+
secret:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
noodle assistant clients create --name web --org noodleseed --app customer-auth --env prod
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Only the Noodle service URL, assistant client ID, and assistant client secret belong in the authenticated
|
|
152
|
+
customer backend. The model URL, model name, and model API key remain managed by the Noodle deployment.
|
|
153
|
+
|
|
154
|
+
The customer's authenticated backend calls `createAssistantSession(...)` from
|
|
155
|
+
`@noodleseed/assistant/server`, passing the already-verified user and browser origin. The browser then uses
|
|
156
|
+
the returned short-lived session through the Web Component or React wrapper:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pnpm add @noodleseed/assistant
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { NoodleAssistant } from '@noodleseed/assistant/react';
|
|
164
|
+
|
|
165
|
+
<NoodleAssistant
|
|
166
|
+
sessionEndpoint="/api/noodle-assistant/session"
|
|
167
|
+
theme="auto"
|
|
168
|
+
onSessionExpired={() => console.info('Assistant session renewed')}
|
|
169
|
+
/>;
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`theme="auto"` follows the SaaS application. The server-level `branding` block is inherited by both MCP App
|
|
173
|
+
widgets and the assistant; documented `--ns-assistant-*` semantic CSS variables remain the final integration
|
|
174
|
+
escape hatch. There is no second assistant branding declaration.
|
|
175
|
+
The end-user UI contains only customer branding.
|
|
176
|
+
Text streams progressively. Expired turns re-exchange through the authenticated backend and retry once;
|
|
177
|
+
consent-bound tool confirmations never replay automatically.
|
|
178
|
+
|
|
179
|
+
The Firebase project ID is required because Firebase ID tokens use the project ID as the token audience and
|
|
180
|
+
issuer suffix. The runtime verifies `aud` against the project ID and `iss` against
|
|
181
|
+
`https://securetoken.google.com/<projectId>`.
|
|
182
|
+
|
|
183
|
+
The Firebase Web API key and auth domain are public Firebase browser configuration. They let the Noodle
|
|
184
|
+
Cloud-hosted bridge initialize Firebase Auth for this customer project; they are not server secrets. Keep
|
|
185
|
+
them out of source with `variable(...)`, restrict the Firebase key to the expected browser origins and APIs,
|
|
186
|
+
and use `secret(...)` only for credentials that must never reach a browser.
|
|
187
|
+
|
|
188
|
+
The `noodleseed_app_api` connector currently points at the NoodleSeed.com dev app surface:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
https://dev.noodleseed.com
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
When the customer app moves from `dev.noodleseed.com` to `app.noodleseed.com`, update the connector's
|
|
195
|
+
`noodleseedApiOrigin` constant to the production API origin that serves the same paths.
|
|
196
|
+
|
|
197
|
+
The connector uses delegated Firebase customer credentials. During the customer OAuth bridge, Noodle Seed
|
|
198
|
+
verifies the Firebase ID token, stores the Firebase refresh token through the credential broker, and refreshes
|
|
199
|
+
a Firebase ID token when the connector calls the NoodleSeed.com app API. The broker then exchanges that ID
|
|
200
|
+
token for the app's existing Next.js session cookie. There is no shared `NOODLESEED_APP_API_TOKEN` for this
|
|
201
|
+
example.
|
|
202
|
+
|
|
203
|
+
Firebase Auth must list `cloud.noodleseed.dev` as an authorized domain before browser sign-in works in
|
|
204
|
+
production.
|
|
205
|
+
|
|
206
|
+
## Deploy customer-protected to Noodle Seed Cloud
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
noodle deploy examples/customer-auth/src/server.ts \
|
|
210
|
+
--org noodleseed \
|
|
211
|
+
--app customer-auth \
|
|
212
|
+
--env prod \
|
|
213
|
+
--access customers
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Endpoint:
|
|
217
|
+
|
|
218
|
+
```text
|
|
219
|
+
https://cloud.noodleseed.dev/o/noodleseed/customer-auth/mcp
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## MCP Primitives
|
|
223
|
+
|
|
224
|
+
- Tool `list_my_organizations`: calls `GET /api/organizations` and returns the organizations the signed-in
|
|
225
|
+
customer is a member of. Takes no arguments; the org set is scoped by the verified customer session.
|
|
226
|
+
- Tool `list_org_apps`: calls `GET /api/organizations/{org_id}/apps` for one organization `org_id`.
|
|
227
|
+
|
|
228
|
+
## Auth boundary
|
|
229
|
+
|
|
230
|
+
Firebase ID-token verification is handled by Noodle Seed's generic Firebase bridge adapter during OAuth
|
|
231
|
+
issuance. The MCP client receives a Noodle-issued, resource-bound access token marked as a Firebase customer
|
|
232
|
+
identity; raw Firebase tokens and inbound MCP bearer tokens are never forwarded to tools, connectors,
|
|
233
|
+
widgets, or downstream systems.
|
|
234
|
+
|
|
235
|
+
The connector-backed tools use the credential broker to turn the signed-in Firebase customer session into the
|
|
236
|
+
same session-cookie credential that the existing NoodleSeed.com Next.js API already expects. The inbound MCP
|
|
237
|
+
bearer token is never used as an app API credential.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "customer-auth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"validate": "noodle validate",
|
|
9
|
+
"dev": "noodle dev",
|
|
10
|
+
"deploy": "noodle deploy"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@noodleseed/one": "latest",
|
|
14
|
+
"vitest": "latest"
|
|
15
|
+
}
|
|
16
|
+
}
|