@noodleseed/agent-kit 0.42.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 (56) hide show
  1. package/manifest.json +273 -257
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +5 -4
  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 +4 -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/authoring-workflow.md +1 -60
  22. package/skills/claude-code/references/build-an-mcp-server.md +1 -1
  23. package/skills/claude-code/references/cli-commands.md +1 -1
  24. package/skills/claude-code/references/connect-an-api.md +4 -0
  25. package/skills/claude-code/references/embedded-assistant.md +3 -3
  26. package/skills/claude-code/references/feedback.md +11 -9
  27. package/skills/claude-code/references/tool-design.md +103 -0
  28. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +6 -6
  29. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  30. package/skills/codex/SKILL.md +5 -4
  31. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  32. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  33. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  34. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  35. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  36. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  37. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  38. package/skills/codex/examples/acme-bistro/src/server.ts +5 -1
  39. package/skills/codex/examples/acme-discovery/src/server.ts +5 -1
  40. package/skills/codex/examples/acme-tasks/src/server.ts +4 -1
  41. package/skills/codex/examples/customer-auth/src/server.ts +5 -1
  42. package/skills/codex/examples/food-ordering/src/server.ts +14 -5
  43. package/skills/codex/examples/gmail-multi-account/src/server.ts +20 -3
  44. package/skills/codex/examples/hello/README.md +4 -2
  45. package/skills/codex/examples/hello/src/server.ts +3 -0
  46. package/skills/codex/examples/weather/src/server.ts +10 -2
  47. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  48. package/skills/codex/references/authoring-workflow.md +1 -60
  49. package/skills/codex/references/build-an-mcp-server.md +1 -1
  50. package/skills/codex/references/cli-commands.md +1 -1
  51. package/skills/codex/references/connect-an-api.md +4 -0
  52. package/skills/codex/references/embedded-assistant.md +3 -3
  53. package/skills/codex/references/feedback.md +11 -9
  54. package/skills/codex/references/tool-design.md +103 -0
  55. package/skills/codex/reporting-noodle-feedback/SKILL.md +6 -6
  56. package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/agent-kit",
3
- "version": "0.42.0",
3
+ "version": "0.43.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",
@@ -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.42.0 hash:0c1ebd532b9c2499 -->
6
+ <!-- noodle-skill version:0.43.0 hash:cd6ca0d915e6acb9 -->
7
7
 
8
8
  # Noodle Seed
9
9
 
@@ -35,7 +35,7 @@ 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
 
@@ -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.42.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.42.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.42.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.42.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.42.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.42.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.42.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,10 @@ 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.
15
17
  Every `--json` command writes its canonical success or failure envelope to stdout and leaves stderr
16
18
  empty. One-shot commands write one envelope; streaming commands write NDJSON snapshot, event, and
17
19
  terminal-failure envelopes so agents can parse each line independently.
@@ -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.
@@ -189,6 +189,7 @@ export default server(
189
189
  },
190
190
  [
191
191
  tool('weather_briefing', {
192
+ title: 'Weather briefing',
192
193
  description:
193
194
  'Look up a city, fetch its current weather, and return a human-readable briefing. Runs a ' +
194
195
  'three-step flow: geocode the city, fetch the forecast, then derive the briefing in a sandboxed compute step.',
@@ -233,14 +234,21 @@ export default server(
233
234
  // the whole array; the compute connector narrows each element to the two fields the model speaks
234
235
  // from. Append new tools AFTER existing ones so `tools[0]` stays stable for host harnesses.
235
236
  tool('search_places', {
237
+ title: 'Search places',
236
238
  description:
237
239
  'Search a place name and return the matching locations as a list of { id, label } options.',
238
- input: z.object({ query: z.string() }),
240
+ // Bound the list at the source: `limit` is capped in the schema and passed through to the
241
+ // upstream `count` parameter, so the model can never pull an unbounded page into its context.
242
+ // `noodle check` reports an unbounded array output as `tool_design_output_bounds`.
243
+ input: z.object({
244
+ query: z.string(),
245
+ limit: z.number().int().min(1).max(10).default(5),
246
+ }),
239
247
  output: z.object({
240
248
  places: z.array(z.object({ id: z.string(), label: z.string() })),
241
249
  }),
242
250
  fulfil: ({ input, connectors }) => {
243
- const found = connectors.geo.search_list({ name: input.query });
251
+ const found = connectors.geo.search_list({ name: input.query, count: input.limit });
244
252
  const narrowed = connectors.places.narrow({ results: found.results });
245
253
  return { places: narrowed.places };
246
254
  },
@@ -3,7 +3,7 @@ name: publishing-mcp-integrations
3
3
  description: "Use when preparing, reviewing, or submitting a Noodle Seed MCP integration to a host or app directory."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.42.0 hash:efffbf82007f935d -->
6
+ <!-- noodle-skill version:0.43.0 hash:efffbf82007f935d -->
7
7
 
8
8
  # publishing-mcp-integrations
9
9
 
@@ -8,7 +8,6 @@
8
8
  - Connectors
9
9
  - HTTP connector example (full server)
10
10
  - Delegated downstream auth (call your API as the signed-in user)
11
- - Design tools for the model
12
11
  - Invocation context
13
12
  - Compute connector example
14
13
  - Tests
@@ -173,65 +172,7 @@ Diagnose statically with `noodle auth doctor`; set a short-lived real customer t
173
172
 
174
173
  ## Design tools for the model
175
174
 
176
- 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:
177
-
178
- - **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.
179
- - **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.
180
- - **Return only what the model needs.** Map the response to a small, typed `output` (a few labelled fields), not the raw API payload.
181
-
182
- 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`.
183
-
184
- ```ts
185
- import { connector, secret, server, tool, z } from '@noodleseed/one';
186
-
187
- const tasks = connector('tasks').version('1.0.0').http({
188
- baseUrl: 'https://api.tasks.example',
189
- allowedOrigins: ['https://api.tasks.example'],
190
- auth: { kind: 'bearer', secret: secret('TASKS_TOKEN') },
191
- operations: {
192
- search_tasks: {
193
- type: 'read',
194
- method: 'GET',
195
- path: '/tasks',
196
- query: ['query'],
197
- input: z.object({ query: z.string() }),
198
- output: z.object({ matches: z.array(z.unknown()) }),
199
- response: { matches: '${response.results}' },
200
- },
201
- close_task: {
202
- type: 'action',
203
- method: 'POST',
204
- path: '/tasks/{id}/close',
205
- input: z.object({ id: z.string() }),
206
- output: z.object({ ok: z.boolean() }),
207
- response: { ok: '${response.ok}' },
208
- },
209
- },
210
- });
211
-
212
- export default server('todo', { title: 'Tasks', version: '1.0.0', use: { tasks } }, [
213
- tool('find_tasks', {
214
- 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.',
215
- input: z.object({ query: z.string() }),
216
- output: z.object({ matches: z.array(z.object({ id: z.string(), title: z.string() })) }),
217
- fulfil: ({ input, connectors }) => {
218
- const found = connectors.tasks.search_tasks({ query: input.query });
219
- return { matches: found.matches };
220
- },
221
- }),
222
- tool('complete_task', {
223
- description: 'Mark a task complete by its id (get the id from find_tasks).',
224
- input: z.object({ id: z.string() }),
225
- output: z.object({ ok: z.boolean() }),
226
- fulfil: ({ input, connectors }) => {
227
- const result = connectors.tasks.close_task({ id: input.id });
228
- return { ok: result.ok };
229
- },
230
- }),
231
- ]);
232
- ```
233
-
234
- 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.
175
+ Shape tools around what a user says, not 1:1 around API endpoints. `references/tool-design.md` owns the doctrine: intent-shaped tools, titles and annotations, bounded outputs, a small tool surface, and deliberate context.
235
176
 
236
177
  ## Invocation context
237
178
 
@@ -22,7 +22,7 @@ Establish only the inputs needed for the requested stopping point. Follow `refer
22
22
  1. **Confirm conversational fit.** Name one to three focused jobs where saying the request is easier than navigating the underlying system, and identify the data or action the model cannot provide by itself.
23
23
  2. **Define the product contract.** For each job, write the user phrase, the intent-shaped tool or resource, its minimal typed input, the useful output, read/write effect, and backing operation. Design for user intent, not a 1:1 API endpoint wrapper.
24
24
  3. **Choose the smallest implementation.** Use native tools, resources, or prompts for local/static behavior; add a connector only when external data or actions are required. Keep response output small and model-readable.
25
- 4. **Author in TypeScript.** Follow `references/authoring-workflow.md` for connector and flow patterns and `references/sdk-surface.md` for exact builders. These are this route’s complete canonical support set; use the router lookup catalog only when observed evidence names a different concern.
25
+ 4. **Author in TypeScript.** Follow `references/authoring-workflow.md` for connector and flow patterns, `references/tool-design.md` for the model-facing tool surface, and `references/sdk-surface.md` for exact builders. These are this route’s complete canonical support set; use the router lookup catalog only when observed evidence names a different concern.
26
26
  5. **Validate and repair.** Run `noodle validate --json`. Parse `error.errors[]`, repair the cited `path`, and rerun validation. Consult the lookup catalog only for the specific reported error code; do not open another reference speculatively.
27
27
  6. **Run the local smoke.** After validation succeeds, run `noodle test --json` and repair any failure at that evidence layer.
28
28
  7. **Prove external behavior.** For connector-backed reads, set credentials through the effective local target and run a safe representative `noodle tools call`. Confirm populated mapped fields from real output, not merely successful registration.
@@ -27,7 +27,7 @@ Developer-facing `noodle` commands, grouped by area. Local authoring commands (`
27
27
  | `noodle import` | Import an OpenAPI spec into a starter `server.ts`. |
28
28
  | `noodle export` | Compile locally and write the portable manifest JSON (no service). |
29
29
  | `noodle validate` | Author-time compile/schema/connector check; no service (`--json`, `--fix-prompt`). |
30
- | `noodle check` | Check MCP Apps/widget readiness; no service. |
30
+ | `noodle check` | Check tool design (`tool_design_*`) and MCP Apps/widget readiness; no service. `--min-severity warn` shows only what needs fixing. |
31
31
  | `noodle test` | Local compile plus a loopback MCP smoke. |
32
32
 
33
33
  ## Local run & inspect
@@ -157,8 +157,12 @@ Local `dev`, smoke commands, secrets, and variables resolve one effective target
157
157
  noodle secrets set SOME_API_KEY --runtime local --from-env SOME_API_KEY
158
158
  # Explicit flags remain available when intentionally testing a different local target:
159
159
  noodle secrets set SOME_API_KEY --runtime local --scope env --org <org> --app <app> --env <env> --from-env SOME_API_KEY
160
+ # Configuring a deployed environment is a different store — say so:
161
+ noodle secrets set SOME_API_KEY --runtime cloud --scope env --org <org> --app <app> --env <env> --from-env SOME_API_KEY
160
162
  ```
161
163
 
164
+ `--runtime` is required whenever `--org`, `--app`, and `--env` together name a complete hosted target: a command that looks remote must never fall back to writing `.env.noodle`. Omitting it fails with `runtime_required` before anything is written.
165
+
162
166
  Local secrets live in `./.env.noodle` (never commit it). A required `secret(...)` or `variable(...)` that cannot resolve fails boot closed. `noodle tools call` / `noodle test` / `noodle dev` / `noodle devtools` stop before exposing an empty endpoint and print the exact effective target plus recovery command.
163
167
 
164
168
  ### Prove real output
@@ -91,9 +91,9 @@ Give every business action a portable `tool(..., { title: "Complete task", descr
91
91
  Local MCP authoring and tests need no account, but an external browser embed needs an active assistant-enabled deployment before a backend client can be created. Set the model values on the target org/app/env, then deploy:
92
92
 
93
93
  ```sh
94
- noodle variables set ASSISTANT_MODEL_BASE_URL --scope env --org <org> --app <app> --env <env> --value <https-model-base-url>
95
- noodle variables set ASSISTANT_MODEL --scope env --org <org> --app <app> --env <env> --value <model>
96
- noodle secrets set ASSISTANT_MODEL_API_KEY --scope env --org <org> --app <app> --env <env> --from-env ASSISTANT_MODEL_API_KEY
94
+ noodle variables set ASSISTANT_MODEL_BASE_URL --scope env --runtime cloud --org <org> --app <app> --env <env> --value <https-model-base-url>
95
+ noodle variables set ASSISTANT_MODEL --scope env --runtime cloud --org <org> --app <app> --env <env> --value <model>
96
+ noodle secrets set ASSISTANT_MODEL_API_KEY --scope env --runtime cloud --org <org> --app <app> --env <env> --from-env ASSISTANT_MODEL_API_KEY
97
97
  noodle deploy --org <org> --app <app> --env <env>
98
98
  ```
99
99
 
@@ -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