@noodleseed/agent-kit 0.41.0 → 0.43.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 (58) hide show
  1. package/manifest.json +275 -259
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +6 -5
  4. package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
  5. package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
  6. package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
  7. package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
  8. package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
  9. package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
  10. package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
  11. package/skills/claude-code/examples/acme-bistro/src/server.ts +5 -1
  12. package/skills/claude-code/examples/acme-discovery/src/server.ts +5 -1
  13. package/skills/claude-code/examples/acme-tasks/src/server.ts +4 -1
  14. package/skills/claude-code/examples/customer-auth/src/server.ts +5 -1
  15. package/skills/claude-code/examples/food-ordering/src/server.ts +14 -5
  16. package/skills/claude-code/examples/gmail-multi-account/src/server.ts +20 -3
  17. package/skills/claude-code/examples/hello/README.md +7 -2
  18. package/skills/claude-code/examples/hello/src/server.ts +3 -0
  19. package/skills/claude-code/examples/weather/src/server.ts +10 -2
  20. package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
  21. package/skills/claude-code/references/agent-contract.md +4 -3
  22. package/skills/claude-code/references/authoring-workflow.md +1 -60
  23. package/skills/claude-code/references/build-an-mcp-server.md +1 -1
  24. package/skills/claude-code/references/cli-commands.md +1 -1
  25. package/skills/claude-code/references/connect-an-api.md +4 -0
  26. package/skills/claude-code/references/embedded-assistant.md +3 -3
  27. package/skills/claude-code/references/feedback.md +11 -9
  28. package/skills/claude-code/references/tool-design.md +103 -0
  29. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +6 -6
  30. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  31. package/skills/codex/SKILL.md +6 -5
  32. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  33. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  34. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  35. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  36. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  37. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  38. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  39. package/skills/codex/examples/acme-bistro/src/server.ts +5 -1
  40. package/skills/codex/examples/acme-discovery/src/server.ts +5 -1
  41. package/skills/codex/examples/acme-tasks/src/server.ts +4 -1
  42. package/skills/codex/examples/customer-auth/src/server.ts +5 -1
  43. package/skills/codex/examples/food-ordering/src/server.ts +14 -5
  44. package/skills/codex/examples/gmail-multi-account/src/server.ts +20 -3
  45. package/skills/codex/examples/hello/README.md +7 -2
  46. package/skills/codex/examples/hello/src/server.ts +3 -0
  47. package/skills/codex/examples/weather/src/server.ts +10 -2
  48. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  49. package/skills/codex/references/agent-contract.md +4 -3
  50. package/skills/codex/references/authoring-workflow.md +1 -60
  51. package/skills/codex/references/build-an-mcp-server.md +1 -1
  52. package/skills/codex/references/cli-commands.md +1 -1
  53. package/skills/codex/references/connect-an-api.md +4 -0
  54. package/skills/codex/references/embedded-assistant.md +3 -3
  55. package/skills/codex/references/feedback.md +11 -9
  56. package/skills/codex/references/tool-design.md +103 -0
  57. package/skills/codex/reporting-noodle-feedback/SKILL.md +6 -6
  58. package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
@@ -1,6 +1,6 @@
1
1
  # Send product feedback
2
2
 
3
- When you — the coding agent — discover a way Noodle Seed could be better, prepare one sanitized feedback proposal. Feedback crosses the customer project boundary and lands in Noodle Seed’s private tracker, so the user must make an informed choice. Show the user the exact command and ask for explicit user approval. Do not run or submit it until approval is given.
3
+ When you — the coding agent — discover a way Noodle Seed could be better, prepare one sanitized feedback proposal. Feedback crosses the customer project boundary and lands in the Noodle Seed private feedback tracker, so the user must make an informed choice. Preview the exact normalized submission locally, show it with the exact live command, and ask for explicit user approval. Do not submit it until approval is given.
4
4
 
5
5
  ## Contents
6
6
 
@@ -26,20 +26,22 @@ Do not batch several findings into one proposal, and do not re-propose the same
26
26
 
27
27
  ## Approval workflow
28
28
 
29
- 1. Draft and sanitize the title, message, labels, and exact shell command.
30
- 2. Show the user the exact command and explain that it sends the shown content plus CLI version, OS/platform, and Node version to Noodle Seed’s private tracker.
31
- 3. Ask for explicit user approval. Do not run, submit, or send anything until the user clearly approves that exact proposal.
32
- 4. After approval, run it once. If authentication is missing, report that feedback was not sent and offer the normal `noodle login` path; never sign in or retry without direction.
29
+ 1. Discover the current positional arguments, flags, choices, defaults, and limits from `noodle commands --json`; `noodle feedback --help` is the human-readable view. Do not guess or rely on a remembered catalog.
30
+ 2. Draft one finding, then sanitize its title and message using the rules below.
31
+ 3. Run the proposal with `--dry-run --json`. This local preview needs no login and sends nothing. Parse `{"ok":true,"data":{"mode":"preview","willSubmit":false,"destination":"Noodle Seed private feedback tracker","submission":{...}}}`.
32
+ 4. Inspect the complete `submission`, including its normalized defaults and automatically attached diagnostics. Show the user the exact previewed proposal, its `destination`, and a POSIX-safely quoted live command containing the same fields but without `--dry-run`.
33
+ 5. Ask for explicit approval of that exact previewed proposal. If the user changes any field, preview the changed proposal again before asking.
34
+ 6. Only after approval, submit it once by running the disclosed live command without `--dry-run`. Never auto-login and never retry-loop. If authentication fails before the request or a rate limit denies it, report that nothing was sent. For `feedback_recording_failed`, report that no reference was returned and the outcome may be unknown; do not retry because the private issue might already exist.
33
35
 
34
36
  ## The command
35
37
 
36
38
  ```sh
37
- noodle feedback "resources list --json omits the truncated flag the docs promise" \
38
- --title "resources list --json missing truncated flag" \
39
- --type fix --severity P2 --area cli --json
39
+ noodle feedback 'resources list --json omits the truncated flag the docs promise' \
40
+ --title 'resources list --json missing truncated flag' \
41
+ --type fix --severity P2 --area cli --dry-run --json
40
42
  ```
41
43
 
42
- This is an example only; build the exact command for the finding and show it before execution. Read `noodle feedback --help` for human-readable constraints or inspect `noodle commands --json` for the same machine-readable usage, enum, and length metadata instead of guessing accepted values. The message is required (1–4000 chars). Pass `--json` and parse the envelope: success is `{ok:true,data:{reference,labels}}`; a `429` means the per-user hourly budget (5) is spent — report that it was not sent and never retry-loop. The CLI attaches only the disclosed light diagnostics automatically: CLI version, OS/platform, Node version. Nothing else is collected.
44
+ This is a preview example only. Build the exact command for the finding using current `noodle commands --json` metadata, POSIX-quote every user-controlled value, and inspect the returned submission instead of reconstructing it. The message is required (1–4000 chars). The CLI attaches only the disclosed light diagnostics automatically: CLI version, OS/platform, Node version. Nothing else is collected. After approval, the live success envelope is `{ok:true,data:{reference,labels}}`; a `429` means the per-user hourly budget (5) is spent — report that it was not sent and never retry-loop.
43
45
 
44
46
  ## Choose the structured fields
45
47
 
@@ -0,0 +1,103 @@
1
+ # Tool design
2
+
3
+ ## Contents
4
+
5
+ - Design tools for the model
6
+ - Title and annotations
7
+ - Bound every output
8
+ - Keep the tool surface small
9
+ - Provision context deliberately
10
+ - Errors an agent can act on
11
+ - What `noodle check` reports
12
+
13
+ ## Design tools for the model
14
+
15
+ Design tools around what a user says, not 1:1 around API endpoints. A raw wrapper per endpoint (`get_task`, `list_tasks`, `close_task`) forces the model to orchestrate low-level calls and to know identifiers the user never sees — an MCP connector, but not a usable product. Instead:
16
+
17
+ - **Shape by intent.** Name and scope tools for the job to be done — "find my overdue tasks", "complete the task matching this text" — combining multiple backing calls in one recorded flow (`when(...)`) where it helps.
18
+ - **Prefer names/text over raw IDs.** When an action needs an id the user does not know, pair the id-taking operation with a find/search operation that returns model-friendly summaries (id + a human label), so the model resolves text → id itself. Write descriptions that tell the model when to use each tool and how they chain.
19
+ - **Return only what the model needs.** Map the response to a small, typed `output` (a few labelled fields), not the raw API payload.
20
+
21
+ This example pairs a name resolver with an id-taking action: the model calls `find_tasks` to turn the user’s words into an id, then `complete_task`. It is compile-verified on every `pnpm test`.
22
+
23
+ ```ts
24
+ import { connector, secret, server, tool, z } from '@noodleseed/one';
25
+
26
+ const tasks = connector('tasks').version('1.0.0').http({
27
+ baseUrl: 'https://api.tasks.example',
28
+ allowedOrigins: ['https://api.tasks.example'],
29
+ auth: { kind: 'bearer', secret: secret('TASKS_TOKEN') },
30
+ operations: {
31
+ search_tasks: {
32
+ type: 'read',
33
+ method: 'GET',
34
+ path: '/tasks',
35
+ query: ['query'],
36
+ input: z.object({ query: z.string() }),
37
+ output: z.object({ matches: z.array(z.unknown()) }),
38
+ response: { matches: '${response.results}' },
39
+ },
40
+ close_task: {
41
+ type: 'action',
42
+ method: 'POST',
43
+ path: '/tasks/{id}/close',
44
+ input: z.object({ id: z.string() }),
45
+ output: z.object({ ok: z.boolean() }),
46
+ response: { ok: '${response.ok}' },
47
+ },
48
+ },
49
+ });
50
+
51
+ export default server('todo', { title: 'Tasks', version: '1.0.0', use: { tasks } }, [
52
+ tool('find_tasks', {
53
+ description: 'Find tasks whose text matches a query — call this first to resolve a task the user names by text into its id, then pass that id to complete_task.',
54
+ input: z.object({ query: z.string() }),
55
+ output: z.object({ matches: z.array(z.object({ id: z.string(), title: z.string() })) }),
56
+ fulfil: ({ input, connectors }) => {
57
+ const found = connectors.tasks.search_tasks({ query: input.query });
58
+ return { matches: found.matches };
59
+ },
60
+ }),
61
+ tool('complete_task', {
62
+ description: 'Mark a task complete by its id (get the id from find_tasks).',
63
+ input: z.object({ id: z.string() }),
64
+ output: z.object({ ok: z.boolean() }),
65
+ fulfil: ({ input, connectors }) => {
66
+ const result = connectors.tasks.close_task({ id: input.id });
67
+ return { ok: result.ok };
68
+ },
69
+ }),
70
+ ]);
71
+ ```
72
+
73
+ The model never sees a task id from the user; `find_tasks` returns `{ id, title }` summaries it can pick from, then `complete_task` acts by id. Keep write actions (`complete_task`) separate and explicitly described so the host can gate them.
74
+
75
+ ## Title and annotations
76
+
77
+ Every model-visible tool needs a `title` — the action name hosts show in tool pickers and confirmation prompts — and `annotations`. `annotations.readOnly()` is a closed-world safe read; `annotations.action()` affects the world; `annotations.localAction()` affects only this app's data; `annotations.openAction()` reaches the open internet. Keep reads and writes in separate tools: one tool that both lists and mutates cannot be annotated honestly, so no host can gate it correctly. Missing titles and hints are also the most common consumer-directory rejection.
78
+
79
+ ## Bound every output
80
+
81
+ Always declare `output`. Without it the model has to parse prose and hosts have no structured result to render. Then bound any list: cap the array with `z.array(item).max(50)`, or take a bounded pagination input (`limit`, `cursor`). An unbounded list either exhausts the context window or is truncated somewhere you do not control. Map the response to the few labelled fields the model needs, never the raw upstream payload — every field you pass through is context paid for on every later turn.
82
+
83
+ ## Keep the tool surface small
84
+
85
+ `noodle check` warns above 20 model-visible tools. That is a documented heuristic, not a host limit: no host publishes a hard number, and the real threshold depends on how distinct your descriptions are. Collapse variants that differ only by a filter into one intent-shaped tool with a typed enum, and mark widget-only helpers `visibility: ['app']` so they stay callable from the app surface without entering the model's list.
86
+
87
+ ## Provision context deliberately
88
+
89
+ Most “the model guessed wrong” bugs are missing context, not a missing tool. Every invocation already carries a server-authoritative instant plus locale and time zone, so never ask the model for today's date. Set `server(..., { context: { defaults: { locale, timeZone } } })` for ambient defaults, and mark one zero-input tool `contextProvider: true` when the model needs portable application context such as workspace, plan, or permissions. `references/authoring-workflow.md` owns the full invocation-context contract.
90
+
91
+ ## Errors an agent can act on
92
+
93
+ An agent cannot recover from “Request failed”. Say which argument was wrong, which tool resolves it, and whether retrying helps. Error text is part of the tool interface and is read far more often by a model than by a human.
94
+
95
+ ## What `noodle check` reports
96
+
97
+ - `tool_design_titles` — a model-visible tool has no `title`.
98
+ - `tool_design_output_shape` — a model-visible tool has no `output` schema.
99
+ - `tool_design_output_bounds` — an output array has no `maxItems` and the tool takes no pagination input.
100
+ - `tool_design_surface_budget` — more than 20 model-visible tools.
101
+ - `tool_design_context` — which tool provides application context (informational).
102
+
103
+ All five are warnings, never failures, so they never change the exit code. Use `noodle check --min-severity warn` to see only what needs fixing and `noodle check --json` to consume them programmatically.
@@ -3,11 +3,11 @@ name: reporting-noodle-feedback
3
3
  description: "Use when a Noodle Seed bug, misleading instruction, missing capability, or concrete product improvement should be proposed to the user."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:1e23938b39956ef8 -->
6
+ <!-- noodle-skill version:0.43.0 hash:0f404109f4845683 -->
7
7
 
8
8
  # reporting-noodle-feedback
9
9
 
10
- Offer one sanitized feedback command and submit it only after informed explicit approval.
10
+ Preview one sanitized feedback proposal and submit it once only after informed explicit approval.
11
11
 
12
12
  ## Use when
13
13
 
@@ -23,7 +23,7 @@ Offer one sanitized feedback command and submit it only after informed explicit
23
23
 
24
24
  - One distinct finding.
25
25
  - Sanitized observed and expected behavior.
26
- - User approval for the exact command.
26
+ - User approval for the exact dry-run preview and live command.
27
27
 
28
28
  ## Workflow
29
29
 
@@ -31,15 +31,15 @@ Read and follow the canonical playbook `references/feedback.md` at `../noodle-se
31
31
 
32
32
  ## Verification evidence
33
33
 
34
- The user saw the exact sanitized command; only a returned reference proves submission.
34
+ The user saw the exact sanitized preview, diagnostics, destination, and live command; only a returned reference proves submission.
35
35
 
36
36
  ## Recovery paths
37
37
 
38
- If login or rate limits block submission, report that nothing was sent and do not retry-loop.
38
+ If login or rate limits block the one live submission, report that nothing was sent. A recording failure has an unknown outcome: report no reference and never auto-login or retry-loop.
39
39
 
40
40
  ## Stop conditions
41
41
 
42
- Stop before running the command until the user explicitly approves it.
42
+ Stop after the local dry-run and before the live command until the user explicitly approves it.
43
43
 
44
44
  ## Handoff contract
45
45
 
@@ -3,7 +3,7 @@ name: verifying-mcp-delivery
3
3
  description: "Use when proving a Noodle Seed MCP project works at a named compile, local, connector, App, host, deployment, or production evidence level."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:6ef6ef551e26b78e -->
6
+ <!-- noodle-skill version:0.43.0 hash:6ef6ef551e26b78e -->
7
7
 
8
8
  # verifying-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ 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
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:2ea7d6dcc8358b35 -->
6
+ <!-- noodle-skill version:0.43.0 hash:cd6ca0d915e6acb9 -->
7
7
 
8
8
  # Noodle Seed
9
9
 
@@ -35,11 +35,11 @@ Negative routing examples: “Inspect hosted logs/status” → `inspect-hosted`
35
35
  | Deploy, configure, connect with writes, change access, or roll back a hosted MCP service when explicitly requested | `deploying-mcp-services` | `references/deploy-and-ops.md` (`references/cli-commands.md`) | The requested hosted state is evidenced without claiming unperformed host or production checks. |
36
36
  | Embed a Noodle assistant in an existing SaaS or web application | `embedding-mcp-assistants` | `references/embedded-assistant.md` (`references/authoring-workflow.md`) | The requested embed boundary works with verified identity and credential separation at the tested level. |
37
37
  | Prepare or submit an integration to a host directory | `publishing-mcp-integrations` | `references/publishing.md` (`references/app-directory-compliance.md`) | The requested submission evidence is complete and any host-review uncertainty is explicit. |
38
- | Report a Noodle Seed bug, documentation gap, or product improvement | `reporting-noodle-feedback` | `references/feedback.md` (None) | A sanitized command is shown to the user and is submitted only after explicit approval. |
38
+ | Report a Noodle Seed bug, documentation gap, or product improvement | `reporting-noodle-feedback` | `references/feedback.md` (None) | A sanitized dry-run preview and exact live command are shown, then one submission occurs only after explicit approval. |
39
39
 
40
40
  ## Common machine loop
41
41
 
42
- The cold-agent commands speak the `--json` envelope. Parse machine state instead of scraping human prose; `references/agent-contract.md` owns the envelope and exit codes.
42
+ Every `--json` command speaks the canonical envelope on stdout. Parse machine state instead of scraping human prose; `references/agent-contract.md` owns the envelope, streaming records, and exit codes.
43
43
 
44
44
  1. **Discover** — use `noodle commands --json` when the required command or flags are uncertain; don't read CLI source.
45
45
  2. **Author** — for build routes, edit the configured TypeScript entrypoint, usually `src/server.ts`.
@@ -57,7 +57,8 @@ This is a lookup catalog, not a discovery checklist. Return here only when the s
57
57
  - `references/cli-commands.md` — every `noodle` command, grouped by area.
58
58
  - `references/compile-errors.md` — fix `noodle validate` errors by code.
59
59
  - `references/build-an-mcp-server.md` — outcome-first workflow for a focused, tested, model-facing MCP product.
60
- - `references/authoring-workflow.md` — input paths, fit checks, connector patterns, authentication, tool design, testing, and managed config.
60
+ - `references/authoring-workflow.md` — input paths, fit checks, connector patterns, authentication, invocation context, testing, and managed config.
61
+ - `references/tool-design.md` — intent-shaped tools, titles and annotations, bounded outputs, tool-surface budget, and context provisioning.
61
62
  - `references/embedded-assistant.md` — HTTPS origins, managed model config, deploy sequencing, session exchange, browser mounting, and credential boundaries.
62
63
  - `references/connect-an-api.md` — secure credentials, probe the live API, model the observed shape, and prove real output.
63
64
  - `references/build-an-mcp-app.md` — product-fit, output-boundary, fallback, and evidence workflow for an MCP App.
@@ -75,7 +76,7 @@ This is a lookup catalog, not a discovery checklist. Return here only when the s
75
76
 
76
77
  ## Product feedback
77
78
 
78
- When you discover a bug, missing capability, misleading doc, or improvement idea, draft one sanitized `noodle feedback` command per finding. Show the user the exact command and ask for explicit user approval. Do not run or submit it until approval is given. Follow `references/feedback.md`; never include customer code, secrets, personal data, or identifying project details.
79
+ When you discover a bug, missing capability, misleading doc, or improvement idea, discover current fields with `noodle commands --json`, draft and sanitize one finding, then run `noodle feedback ... --dry-run --json`. Inspect and show the exact normalized submission, diagnostics, private destination, and POSIX-safely quoted live command. Ask for explicit approval of that exact proposal; do not submit it until approval. Then submit once without `--dry-run`. Follow `references/feedback.md`; never include customer code, secrets, personal data, or identifying project details, and never auto-login or retry-loop.
79
80
 
80
81
  ## Safety
81
82
 
@@ -3,7 +3,7 @@ name: authoring-mcp-servers
3
3
  description: "Use when creating or extending a headless Noodle Seed MCP server, tool, resource, prompt, or typed model-facing capability."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:0b2fd8c7e43fc69f -->
6
+ <!-- noodle-skill version:0.43.0 hash:0b2fd8c7e43fc69f -->
7
7
 
8
8
  # authoring-mcp-servers
9
9
 
@@ -3,7 +3,7 @@ name: building-mcp-apps
3
3
  description: "Use when a Noodle Seed MCP App, widget, interactive card, visual interaction, or host-visible UI is the primary requested outcome."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:f7fa54992c8d7692 -->
6
+ <!-- noodle-skill version:0.43.0 hash:f7fa54992c8d7692 -->
7
7
 
8
8
  # building-mcp-apps
9
9
 
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
3
3
  description: "Use when credentials, an API URL, an OpenAPI document, or an observed response must become real Noodle Seed MCP behavior."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:1e86b8704f407bd3 -->
6
+ <!-- noodle-skill version:0.43.0 hash:1e86b8704f407bd3 -->
7
7
 
8
8
  # connecting-apis-to-mcp
9
9
 
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
3
3
  description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:aa715bae12041d7c -->
6
+ <!-- noodle-skill version:0.43.0 hash:aa715bae12041d7c -->
7
7
 
8
8
  # debugging-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ name: deploying-mcp-services
3
3
  description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:93e735b7ffb45df1 -->
6
+ <!-- noodle-skill version:0.43.0 hash:93e735b7ffb45df1 -->
7
7
 
8
8
  # deploying-mcp-services
9
9
 
@@ -3,7 +3,7 @@ name: designing-mcp-products
3
3
  description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:76cce86729cffbee -->
6
+ <!-- noodle-skill version:0.43.0 hash:76cce86729cffbee -->
7
7
 
8
8
  # designing-mcp-products
9
9
 
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
3
3
  description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.41.0 hash:5d8f40f904d6ab4b -->
6
+ <!-- noodle-skill version:0.43.0 hash:5d8f40f904d6ab4b -->
7
7
 
8
8
  # embedding-mcp-assistants
9
9
 
@@ -53,13 +53,16 @@ export default server(
53
53
  },
54
54
  [
55
55
  tool('show_menu', {
56
+ title: 'Show the menu',
56
57
  description: 'Show the Acme Bistro menu and render the ordering widget.',
57
58
  annotations: readOnly,
58
59
  input: z.object({ customer: z.string().default('Guest') }),
59
60
  output: z.object({
60
61
  status: z.string(),
61
62
  customer: z.string(),
62
- items: z.array(menuItemOutput),
63
+ // Bounded list: the menu is a fixed catalog, so the ceiling is declared on the shape rather
64
+ // than taken as a pagination input. `noodle check` reports `tool_design_output_bounds`.
65
+ items: z.array(menuItemOutput).max(50),
63
66
  }),
64
67
  fulfil: ({ input }) => ({
65
68
  status: `Acme Bistro menu is ready for ${input.customer}. Build the order here; pay at checkout.`,
@@ -125,6 +128,7 @@ export default server(
125
128
  // The only handoff: payment. The widget computes the total (live React) and passes a url-safe cart
126
129
  // token + total; the card is entered on Acme's PCI-scoped checkout, never in chat.
127
130
  tool('create_checkout', {
131
+ title: 'Create checkout',
128
132
  description:
129
133
  'Create the Acme Bistro payment checkout link for the current order. Pass a url-safe cart token ' +
130
134
  'and the numeric total computed in the widget. Payment happens off-app; the card never reaches this app.',
@@ -116,6 +116,7 @@ export default server(
116
116
  },
117
117
  [
118
118
  tool('discover_getaways', {
119
+ title: 'Discover getaways',
119
120
  description:
120
121
  'Suggest Acme Getaways destinations for a vibe and month and render a discovery carousel.',
121
122
  annotations: readOnly,
@@ -125,7 +126,9 @@ export default server(
125
126
  vibe: z.string(),
126
127
  month: z.string(),
127
128
  travelers: z.number(),
128
- options: z.array(destinationOutput),
129
+ // Bounded list: the curated catalog is fixed and small, and the declared ceiling tells the
130
+ // model and host the payload cannot grow. `noodle check` reports `tool_design_output_bounds`.
131
+ options: z.array(destinationOutput).max(20),
129
132
  }),
130
133
  // The carousel presents Acme's curated catalog; the model narrates which fit the stated vibe.
131
134
  // (A tool cannot filter on an input value — that is connector/flow work — so all are returned.)
@@ -154,6 +157,7 @@ export default server(
154
157
  },
155
158
  }),
156
159
  tool('create_handoff', {
160
+ title: 'Create booking handoff',
157
161
  description:
158
162
  'Create the Acme booking deep link for a chosen destination, carrying the configured trip. ' +
159
163
  'Pass the destination id (url-safe slug, e.g. "coral_bay") and its display name.',
@@ -62,10 +62,13 @@ export default server(
62
62
  description: 'Show today’s Acme Tasks and render the task-list widget.',
63
63
  annotations: readOnly,
64
64
  input: z.object({ focus: z.string().default('today') }),
65
+ // Bound the list output. A recorded `fulfil` cannot slice an array, so the honest bound here is
66
+ // a cap on the shape itself; a connector-backed list takes a pagination input instead (see the
67
+ // `weather` example). `noodle check` reports an unbounded array as `tool_design_output_bounds`.
65
68
  output: z.object({
66
69
  status: z.string(),
67
70
  focus: z.string(),
68
- tasks: z.array(taskOutput),
71
+ tasks: z.array(taskOutput).max(20),
69
72
  }),
70
73
  fulfil: ({ input }) => ({
71
74
  status: `Acme Tasks for ${input.focus}: ${today.length} open items, highest priority first.`,
@@ -142,8 +142,12 @@ export default server(
142
142
  description: 'List the NoodleSeed.com organizations the signed-in customer belongs to.',
143
143
  contextProvider: true,
144
144
  input: z.object({}),
145
+ // The dev app API returns every organization for the signed-in customer in one response, with no
146
+ // page parameter to pass through, so the bound is declared on the shape. A customer belongs to a
147
+ // handful of organizations; `noodle check` reports an unbounded list as
148
+ // `tool_design_output_bounds`.
145
149
  output: z.object({
146
- organizations: z.array(z.unknown()),
150
+ organizations: z.array(z.unknown()).max(100),
147
151
  }),
148
152
  annotations: annotations.readOnly(),
149
153
  fulfil({ connectors }) {
@@ -132,7 +132,9 @@ const menuItemShape = z.object({
132
132
  name: z.string(),
133
133
  price: z.number(),
134
134
  description: z.string(),
135
- modifiers: z.array(z.string()),
135
+ // Nested lists count toward the output budget too: unbounded modifiers multiply by every item in a
136
+ // menu payload, so the ceiling is declared here rather than only on the outer array.
137
+ modifiers: z.array(z.string()).max(10),
136
138
  });
137
139
 
138
140
  const cartOutput = z.object({
@@ -240,6 +242,7 @@ export default server(
240
242
  },
241
243
  [
242
244
  tool('open_ordering', {
245
+ title: 'Open food ordering',
243
246
  description:
244
247
  'Open a complete food-ordering widget with store discovery, menu browsing, cart review, and checkout handoff.',
245
248
  annotations: readOnly,
@@ -247,11 +250,14 @@ export default server(
247
250
  query: z.string().optional(),
248
251
  customer: z.string().default('Guest'),
249
252
  }),
253
+ // List outputs declare a ceiling so a host and the model both know the payload is bounded.
254
+ // A recorded `fulfil` cannot slice, so the cap belongs on the shape; connector-backed lists take
255
+ // a pagination input instead. `noodle check` reports `tool_design_output_bounds` without one.
250
256
  output: z.object({
251
257
  status: z.string(),
252
258
  customer: z.string(),
253
- stores: z.array(storeShape),
254
- featuredItems: z.array(menuItemShape),
259
+ stores: z.array(storeShape).max(20),
260
+ featuredItems: z.array(menuItemShape).max(20),
255
261
  localDate: z.string(),
256
262
  serviceArea: z.string(),
257
263
  fallback: z.string(),
@@ -371,16 +377,18 @@ export default server(
371
377
  },
372
378
  }),
373
379
  tool('summarize_ordering_options', {
380
+ title: 'Summarize ordering options',
374
381
  description: 'Summarize available stores and menu examples without opening the widget.',
375
382
  annotations: readOnly,
376
383
  input: z.object({}),
377
384
  output: z.object({
378
- stores: z.array(storeShape),
379
- featuredItems: z.array(menuItemShape),
385
+ stores: z.array(storeShape).max(20),
386
+ featuredItems: z.array(menuItemShape).max(20),
380
387
  }),
381
388
  fulfil: () => ({ stores, featuredItems: menu }),
382
389
  }),
383
390
  tool('plan_order', {
391
+ title: 'Plan an order',
384
392
  description:
385
393
  'Collect a fulfilment method and requested date as structured input, then return a reviewable order plan without placing an order.',
386
394
  annotations: readOnly,
@@ -409,6 +417,7 @@ export default server(
409
417
  },
410
418
  }),
411
419
  tool('show_capabilities', {
420
+ title: 'Show capabilities',
412
421
  description: 'Return a concise summary for the standalone widget capability preview.',
413
422
  annotations: readOnly,
414
423
  input: z.object({}),
@@ -35,10 +35,13 @@ const vacationOptionalFields = {
35
35
  start_time: epochMillis.optional(),
36
36
  end_time: epochMillis.optional(),
37
37
  };
38
+ // One shared output shape for every tool: one entry per account the call fanned out to. The cap is
39
+ // exactly the number of canonical accounts, so it is a true bound rather than a guess — `noodle check`
40
+ // reports an unbounded array output as `tool_design_output_bounds`.
38
41
  const output = z.object({
39
- results: z.array(
40
- z.object({ account: z.enum([PERSONAL_ACCOUNT, WORK_ACCOUNT]), data: z.unknown() }),
41
- ),
42
+ results: z
43
+ .array(z.object({ account: z.enum([PERSONAL_ACCOUNT, WORK_ACCOUNT]), data: z.unknown() }))
44
+ .max(2),
42
45
  });
43
46
 
44
47
  const gmail = gmailConnector();
@@ -84,6 +87,7 @@ export default server(
84
87
  },
85
88
  [
86
89
  tool('search_messages', {
90
+ title: 'Search messages',
87
91
  description: 'Search one connected Gmail account or the canonical personal-and-work pair.',
88
92
  input: z.object({
89
93
  accounts: readAccounts,
@@ -119,6 +123,7 @@ export default server(
119
123
  },
120
124
  }),
121
125
  tool('get_message', {
126
+ title: 'Get message',
122
127
  description: 'Get one Gmail message from one connected account or both canonical accounts.',
123
128
  input: z.object({
124
129
  accounts: readAccounts,
@@ -154,6 +159,7 @@ export default server(
154
159
  },
155
160
  }),
156
161
  tool('get_thread', {
162
+ title: 'Get thread',
157
163
  description: 'Get one Gmail thread from one connected account or both canonical accounts.',
158
164
  input: z.object({
159
165
  accounts: readAccounts,
@@ -189,6 +195,7 @@ export default server(
189
195
  },
190
196
  }),
191
197
  tool('list_drafts', {
198
+ title: 'List drafts',
192
199
  description: 'List drafts from one connected Gmail account or both canonical accounts.',
193
200
  input: z.object({
194
201
  accounts: readAccounts,
@@ -224,6 +231,7 @@ export default server(
224
231
  },
225
232
  }),
226
233
  tool('get_draft', {
234
+ title: 'Get draft',
227
235
  description: 'Get one draft from one connected Gmail account or both canonical accounts.',
228
236
  input: z.object({
229
237
  accounts: readAccounts,
@@ -259,6 +267,7 @@ export default server(
259
267
  },
260
268
  }),
261
269
  tool('get_vacation', {
270
+ title: 'Get vacation responder',
262
271
  description: 'Read vacation-responder settings from one connected account or both accounts.',
263
272
  input: z.object({ accounts: readAccounts }),
264
273
  output,
@@ -281,6 +290,7 @@ export default server(
281
290
  },
282
291
  }),
283
292
  tool('create_draft', {
293
+ title: 'Create draft',
284
294
  description:
285
295
  'Create a Gmail draft in exactly one selected account from a base64url MIME message.',
286
296
  annotations: confirmedWrite,
@@ -301,6 +311,7 @@ export default server(
301
311
  },
302
312
  }),
303
313
  tool('update_draft', {
314
+ title: 'Update draft',
304
315
  description:
305
316
  'Replace a Gmail draft in exactly one selected account with a base64url MIME message.',
306
317
  annotations: confirmedWrite,
@@ -322,6 +333,7 @@ export default server(
322
333
  },
323
334
  }),
324
335
  tool('send_draft', {
336
+ title: 'Send draft',
325
337
  description: 'Send an existing Gmail draft from exactly one selected account.',
326
338
  annotations: confirmedWrite,
327
339
  input: z.object({ accounts: writeAccounts, draft_id: identifier }),
@@ -341,6 +353,7 @@ export default server(
341
353
  },
342
354
  }),
343
355
  tool('modify_message_labels', {
356
+ title: 'Change message labels',
344
357
  description: 'Add or remove Gmail label ids on one message in exactly one selected account.',
345
358
  annotations: confirmedWrite,
346
359
  input: z.object({
@@ -370,6 +383,7 @@ export default server(
370
383
  },
371
384
  }),
372
385
  tool('archive_message', {
386
+ title: 'Archive message',
373
387
  description: 'Archive one Gmail message in exactly one account by removing INBOX.',
374
388
  annotations: confirmedWrite,
375
389
  input: z.object({ accounts: writeAccounts, message_id: identifier }),
@@ -389,6 +403,7 @@ export default server(
389
403
  },
390
404
  }),
391
405
  tool('send_message', {
406
+ title: 'Send message',
392
407
  description: 'Send one base64url MIME message from exactly one selected Gmail account.',
393
408
  annotations: confirmedWrite,
394
409
  input: z.object({
@@ -413,6 +428,7 @@ export default server(
413
428
  },
414
429
  }),
415
430
  tool('trash_message', {
431
+ title: 'Move message to trash',
416
432
  description: 'Move one Gmail message to trash in exactly one account. This is reversible.',
417
433
  annotations: confirmedTrash,
418
434
  input: z.object({ accounts: writeAccounts, message_id: identifier }),
@@ -432,6 +448,7 @@ export default server(
432
448
  },
433
449
  }),
434
450
  tool('update_vacation', {
451
+ title: 'Update vacation responder',
435
452
  description: 'Update vacation-responder settings on exactly one selected Gmail account.',
436
453
  annotations: confirmedWrite,
437
454
  input: z.object({
@@ -10,8 +10,13 @@ When an installed Noodle Developer plugin drives this example, its skill runs th
10
10
  Do not install or update a global CLI: the coding agent writes and tests this source while Noodle
11
11
  guides and operates the validate, preview, deploy, inspect, and debug workflow.
12
12
  If that agent discovers a Noodle Seed product gap while working, the installed skill prepares a
13
- sanitized `noodle feedback` proposal, discovers current fields from `noodle commands --json`, and
14
- submits it only after showing the exact command and receiving explicit user approval.
13
+ sanitized `noodle feedback` proposal, discovers current fields from `noodle commands --json`, runs
14
+ `--dry-run --json` to inspect the exact normalized submission, diagnostics, and private destination,
15
+ then shows a POSIX-safely quoted live command. It submits once without `--dry-run` only after explicit
16
+ user approval of that exact preview; it never auto-logs in or retry-loops.
17
+ Every `--json` command writes its canonical success or failure envelope to stdout and leaves stderr
18
+ empty. One-shot commands write one envelope; streaming commands write NDJSON snapshot, event, and
19
+ terminal-failure envelopes so agents can parse each line independently.
15
20
 
16
21
  ```sh
17
22
  noodle dev examples/hello/src/server.ts --app hello
@@ -14,6 +14,9 @@ export default server(
14
14
  },
15
15
  [
16
16
  tool('greet', {
17
+ // Every model-visible tool declares a title: hosts show it in tool pickers and confirmation
18
+ // prompts, and both consumer directories reject tools without one.
19
+ title: 'Greet someone',
17
20
  description: 'Greet someone by name.',
18
21
  input: z.object({
19
22
  // Defaults are advertised to the model and applied at runtime when the argument is omitted.