@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.
Files changed (44) hide show
  1. package/manifest.json +83 -33
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +1 -1
  4. package/skills/claude-code/examples/acme-bistro/src/server.ts +1 -1
  5. package/skills/claude-code/examples/acme-discovery/src/server.ts +1 -1
  6. package/skills/claude-code/examples/acme-tasks/src/server.ts +4 -2
  7. package/skills/claude-code/examples/acme-tasks/test/server.test.ts +9 -0
  8. package/skills/claude-code/examples/customer-auth/README.md +237 -0
  9. package/skills/claude-code/examples/customer-auth/noodle.json +4 -0
  10. package/skills/claude-code/examples/customer-auth/package.json +16 -0
  11. package/skills/claude-code/examples/customer-auth/src/server.ts +145 -0
  12. package/skills/claude-code/examples/customer-auth/test/server.test.ts +25 -0
  13. package/skills/claude-code/examples/food-ordering/README.md +7 -3
  14. package/skills/claude-code/examples/food-ordering/src/helpers.ts +2 -0
  15. package/skills/claude-code/examples/food-ordering/src/server.ts +47 -2
  16. package/skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx +56 -3
  17. package/skills/claude-code/examples/food-ordering/test/server.test.ts +46 -0
  18. package/skills/claude-code/references/authoring-workflow.md +117 -2
  19. package/skills/claude-code/references/compile-errors.md +6 -1
  20. package/skills/claude-code/references/embedded-assistant.md +95 -10
  21. package/skills/claude-code/references/examples.md +1 -1
  22. package/skills/claude-code/references/sdk-surface.md +2 -2
  23. package/skills/claude-code/references/widgets-and-apps.md +32 -7
  24. package/skills/codex/SKILL.md +1 -1
  25. package/skills/codex/examples/acme-bistro/src/server.ts +1 -1
  26. package/skills/codex/examples/acme-discovery/src/server.ts +1 -1
  27. package/skills/codex/examples/acme-tasks/src/server.ts +4 -2
  28. package/skills/codex/examples/acme-tasks/test/server.test.ts +9 -0
  29. package/skills/codex/examples/customer-auth/README.md +237 -0
  30. package/skills/codex/examples/customer-auth/noodle.json +4 -0
  31. package/skills/codex/examples/customer-auth/package.json +16 -0
  32. package/skills/codex/examples/customer-auth/src/server.ts +145 -0
  33. package/skills/codex/examples/customer-auth/test/server.test.ts +25 -0
  34. package/skills/codex/examples/food-ordering/README.md +7 -3
  35. package/skills/codex/examples/food-ordering/src/helpers.ts +2 -0
  36. package/skills/codex/examples/food-ordering/src/server.ts +47 -2
  37. package/skills/codex/examples/food-ordering/src/views/ordering-flow.tsx +56 -3
  38. package/skills/codex/examples/food-ordering/test/server.test.ts +46 -0
  39. package/skills/codex/references/authoring-workflow.md +117 -2
  40. package/skills/codex/references/compile-errors.md +6 -1
  41. package/skills/codex/references/embedded-assistant.md +95 -10
  42. package/skills/codex/references/examples.md +1 -1
  43. package/skills/codex/references/sdk-surface.md +2 -2
  44. package/skills/codex/references/widgets-and-apps.md +32 -7
package/manifest.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
- "packageVersion": "0.21.1",
2
+ "packageVersion": "0.23.0",
3
3
  "files": [
4
4
  {
5
5
  "path": "skills/codex/SKILL.md",
6
- "sha256": "4a48b0a357c2dfab02ff191d68a9ebb47ad8396a63f476a0f60ba69cc7b943ad",
6
+ "sha256": "14feeaa845c5c982e020173b800ecfa825038ac7e5b6d6c242244204f2f478a9",
7
7
  "agentTarget": "codex"
8
8
  },
9
9
  {
10
10
  "path": "skills/codex/references/sdk-surface.md",
11
- "sha256": "0916b1f4df01053c424b46eeb4e8ee0910709d66b956c4d19da77d5abc4b046b",
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": "1039e245421e8c128cf1a81cf7a3ce809b0149478917040abebc124ffcb60423",
26
+ "sha256": "1ed667250f8f14625012bd8e931812c5d80bac323fa2f39a79ec83dc119fc385",
27
27
  "agentTarget": "codex"
28
28
  },
29
29
  {
30
30
  "path": "skills/codex/references/authoring-workflow.md",
31
- "sha256": "b4ca23d7545c8e96dbfee6a4cf2235d4eadfb27302e453fe631031041a17a8c6",
31
+ "sha256": "3f4767bfe361090e172c7554f1121fe415e618f3a90304498535d177b0c633be",
32
32
  "agentTarget": "codex"
33
33
  },
34
34
  {
35
35
  "path": "skills/codex/references/embedded-assistant.md",
36
- "sha256": "687666010deb32bb605ccc23aeac8778fd1d02c53d7d8e232b6d0b8aeef77668",
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": "69ff229945465793e6c9ae10e5185fc3b5e59b9fb508f03a81ab8d2a12b39efc",
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": "bd19dead1f7b1b6610a53694a06569d3c71d149bd6c72c4a1c94c83ded13a34f",
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": "3f707b40fdf84ab2e8738367a2e0cccaf075a07aa945801ffb3a534f98ee1dcd",
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": "3a8d1285b375b6e650db95033af0f1cb0c8f5468a6a454e4cbcc8aaae7f6ff9d",
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": "3876daf95af0e55d245e641b5e91a7cdd40bdccb6a4f0bd50c65a462ac98d37d",
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": "b170fb0963c21c804949dead018b9ce15deae0ecaa014abab19b4a7a05f42070",
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": "4d42dfbaf46ee8003ae84b3ef79c9d2a8b716943b85e2e9329fa875007019524",
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": "9d8a510ddfe3892340446dcadd6e40e3f88ea9f5b82ff780fc743771656f3e5b",
296
+ "sha256": "91f8dec747e4eb050c2e8dc8c22679443f22468c7fe7cf821e97f42eadb629e5",
272
297
  "agentTarget": "codex"
273
298
  },
274
299
  {
275
300
  "path": "skills/codex/examples/food-ordering/src/server.ts",
276
- "sha256": "6e8c4c17e6f3a7867232880e1a8d0e45428ebfd3938282d6bec7d8542ae7d712",
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": "81f8247c4a6370b5075279ac8cd54ddb669b5995c8fa385f4784720b99cb6203",
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": "e2f2fc0462073ba188c4a70251dafb029d7b3ae585275312c5fa55ba54127fc2",
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": "23754f0386b77f112249ca8b948b479792895303f0257ab0961cbbb81a857693",
381
+ "sha256": "3f37fbed3225a691bac9b2f03a533d827479f01c6bfb3abf8b76c262af6ae7c2",
357
382
  "agentTarget": "claude-code"
358
383
  },
359
384
  {
360
385
  "path": "skills/claude-code/references/sdk-surface.md",
361
- "sha256": "0916b1f4df01053c424b46eeb4e8ee0910709d66b956c4d19da77d5abc4b046b",
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": "1039e245421e8c128cf1a81cf7a3ce809b0149478917040abebc124ffcb60423",
401
+ "sha256": "1ed667250f8f14625012bd8e931812c5d80bac323fa2f39a79ec83dc119fc385",
377
402
  "agentTarget": "claude-code"
378
403
  },
379
404
  {
380
405
  "path": "skills/claude-code/references/authoring-workflow.md",
381
- "sha256": "b4ca23d7545c8e96dbfee6a4cf2235d4eadfb27302e453fe631031041a17a8c6",
406
+ "sha256": "3f4767bfe361090e172c7554f1121fe415e618f3a90304498535d177b0c633be",
382
407
  "agentTarget": "claude-code"
383
408
  },
384
409
  {
385
410
  "path": "skills/claude-code/references/embedded-assistant.md",
386
- "sha256": "687666010deb32bb605ccc23aeac8778fd1d02c53d7d8e232b6d0b8aeef77668",
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": "69ff229945465793e6c9ae10e5185fc3b5e59b9fb508f03a81ab8d2a12b39efc",
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": "bd19dead1f7b1b6610a53694a06569d3c71d149bd6c72c4a1c94c83ded13a34f",
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": "3f707b40fdf84ab2e8738367a2e0cccaf075a07aa945801ffb3a534f98ee1dcd",
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": "3a8d1285b375b6e650db95033af0f1cb0c8f5468a6a454e4cbcc8aaae7f6ff9d",
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": "3876daf95af0e55d245e641b5e91a7cdd40bdccb6a4f0bd50c65a462ac98d37d",
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": "b170fb0963c21c804949dead018b9ce15deae0ecaa014abab19b4a7a05f42070",
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": "4d42dfbaf46ee8003ae84b3ef79c9d2a8b716943b85e2e9329fa875007019524",
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": "9d8a510ddfe3892340446dcadd6e40e3f88ea9f5b82ff780fc743771656f3e5b",
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": "6e8c4c17e6f3a7867232880e1a8d0e45428ebfd3938282d6bec7d8542ae7d712",
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": "81f8247c4a6370b5075279ac8cd54ddb669b5995c8fa385f4784720b99cb6203",
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": "e2f2fc0462073ba188c4a70251dafb029d7b3ae585275312c5fa55ba54127fc2",
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.21.1",
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",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: noodle-seed
3
3
  description: Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI.
4
- version: 0.21.1
4
+ version: 0.23.0
5
5
  hash: b4fc528d406e2149
6
6
  ---
7
7
 
@@ -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: localWrite,
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: localWrite,
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,4 @@
1
+ {
2
+ "entrypoint": "src/server.ts",
3
+ "name": "customer-auth"
4
+ }
@@ -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
+ }