@noodleseed/agent-kit 0.34.0 → 0.36.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 (50) hide show
  1. package/manifest.json +129 -29
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +48 -29
  4. package/skills/claude-code/examples/acme-bistro/README.md +1 -1
  5. package/skills/claude-code/examples/customer-auth/README.md +7 -0
  6. package/skills/claude-code/examples/gmail-multi-account/README.md +46 -0
  7. package/skills/claude-code/examples/gmail-multi-account/noodle.json +4 -0
  8. package/skills/claude-code/examples/gmail-multi-account/package.json +16 -0
  9. package/skills/claude-code/examples/gmail-multi-account/src/server.ts +485 -0
  10. package/skills/claude-code/examples/gmail-multi-account/test/server.test.ts +271 -0
  11. package/skills/claude-code/examples/gmail-multi-account/vitest.config.ts +28 -0
  12. package/skills/claude-code/references/app-directory-compliance.md +59 -0
  13. package/skills/claude-code/references/authoring-workflow.md +3 -1
  14. package/skills/claude-code/references/build-an-mcp-app.md +52 -0
  15. package/skills/claude-code/references/build-an-mcp-server.md +54 -0
  16. package/skills/claude-code/references/compile-errors.md +4 -0
  17. package/skills/claude-code/references/connect-an-api.md +60 -20
  18. package/skills/claude-code/references/deploy-and-ops.md +15 -79
  19. package/skills/claude-code/references/embedded-assistant.md +1 -1
  20. package/skills/claude-code/references/examples.md +1 -0
  21. package/skills/claude-code/references/experience-design.md +1 -1
  22. package/skills/claude-code/references/inspect-hosted.md +26 -0
  23. package/skills/claude-code/references/publishing.md +15 -17
  24. package/skills/claude-code/references/sdk-surface.md +6 -0
  25. package/skills/claude-code/references/verify-and-recover.md +65 -0
  26. package/skills/codex/SKILL.md +48 -29
  27. package/skills/codex/examples/acme-bistro/README.md +1 -1
  28. package/skills/codex/examples/customer-auth/README.md +7 -0
  29. package/skills/codex/examples/gmail-multi-account/README.md +46 -0
  30. package/skills/codex/examples/gmail-multi-account/noodle.json +4 -0
  31. package/skills/codex/examples/gmail-multi-account/package.json +16 -0
  32. package/skills/codex/examples/gmail-multi-account/src/server.ts +485 -0
  33. package/skills/codex/examples/gmail-multi-account/test/server.test.ts +271 -0
  34. package/skills/codex/examples/gmail-multi-account/vitest.config.ts +28 -0
  35. package/skills/codex/references/app-directory-compliance.md +59 -0
  36. package/skills/codex/references/authoring-workflow.md +3 -1
  37. package/skills/codex/references/build-an-mcp-app.md +52 -0
  38. package/skills/codex/references/build-an-mcp-server.md +54 -0
  39. package/skills/codex/references/compile-errors.md +4 -0
  40. package/skills/codex/references/connect-an-api.md +60 -20
  41. package/skills/codex/references/deploy-and-ops.md +15 -79
  42. package/skills/codex/references/embedded-assistant.md +1 -1
  43. package/skills/codex/references/examples.md +1 -0
  44. package/skills/codex/references/experience-design.md +1 -1
  45. package/skills/codex/references/inspect-hosted.md +26 -0
  46. package/skills/codex/references/publishing.md +15 -17
  47. package/skills/codex/references/sdk-surface.md +6 -0
  48. package/skills/codex/references/verify-and-recover.md +65 -0
  49. package/skills/claude-code/references/chatgpt-compliance.md +0 -63
  50. package/skills/codex/references/chatgpt-compliance.md +0 -63
@@ -0,0 +1,271 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+ import {
6
+ compileManifest,
7
+ InMemoryCatalog,
8
+ validateJsonSchemaWithDefaults,
9
+ } from '../../../packages/compiler/src/index.js';
10
+ import { compileConnectors } from '../../../packages/connector-defs/src/index.js';
11
+ import {
12
+ type CredentialBroker,
13
+ type CredentialRequest,
14
+ type DownstreamCredential,
15
+ executePreparedTool,
16
+ executeTool,
17
+ InMemoryConnectorRegistry,
18
+ isConfirmationRequired,
19
+ prepareToolForConfirmation,
20
+ } from '../../../packages/runtime/src/index.js';
21
+ import app, { PERSONAL_ACCOUNT, WORK_ACCOUNT } from '../src/server.js';
22
+
23
+ const here = dirname(fileURLToPath(import.meta.url));
24
+
25
+ async function compiledFixture() {
26
+ const catalog = structuredClone(app.toConnectorCatalog());
27
+ if (catalog === undefined) throw new Error('expected connector catalog');
28
+ for (const connector of catalog.connectors) {
29
+ if ('http' in connector) {
30
+ for (const [name, operation] of Object.entries(connector.operations)) {
31
+ operation.fake = {
32
+ response: {
33
+ id: `${name}-fixture`,
34
+ messages: [{ id: `${name}-message` }],
35
+ drafts: [{ id: `${name}-draft` }],
36
+ },
37
+ };
38
+ }
39
+ }
40
+ }
41
+ const connectors = compileConnectors(JSON.stringify(catalog), { mode: 'fake' });
42
+ if (!connectors.ok) throw new Error(JSON.stringify(connectors.errors));
43
+ const manifest = await app.toManifest();
44
+ const compiled = compileManifest(manifest, {
45
+ catalog: new InMemoryCatalog(connectors.catalog),
46
+ });
47
+ if (!compiled.ok) throw new Error(JSON.stringify(compiled.errors));
48
+ const requests: CredentialRequest[] = [];
49
+ const broker: CredentialBroker = {
50
+ getCredential(request): Promise<DownstreamCredential> {
51
+ requests.push(request);
52
+ return Promise.resolve({ token: `${request.bindingId}-fixture-token` });
53
+ },
54
+ };
55
+ return {
56
+ artifact: compiled.artifact,
57
+ deps: { connectors: new InMemoryConnectorRegistry(connectors.connectors), broker },
58
+ requests,
59
+ };
60
+ }
61
+
62
+ describe('multi-account Gmail flagship', () => {
63
+ it('binds the same curated connector twice without provider ids, credentials, or real labels', async () => {
64
+ const manifest = await app.toManifest();
65
+ expect(manifest.connectors).toMatchObject({
66
+ personal_gmail: {
67
+ id: 'gmail',
68
+ binding: {
69
+ profile: 'user_oauth',
70
+ connection: { id: 'personal_gmail', source: { kind: 'externalExchange' } },
71
+ },
72
+ },
73
+ work_gmail: {
74
+ id: 'gmail',
75
+ binding: {
76
+ profile: 'user_oauth',
77
+ connection: { id: 'work_gmail', source: { kind: 'externalExchange' } },
78
+ },
79
+ },
80
+ });
81
+ const wire = JSON.stringify({ manifest, catalog: app.toConnectorCatalog() });
82
+ expect(wire).not.toMatch(/access[_-]?token|client[_-]?secret/i);
83
+ });
84
+
85
+ it('emits enforceable canonical account arrays on every tool', async () => {
86
+ const manifest = await app.toManifest();
87
+ for (const tool of manifest.tools) {
88
+ expect(tool.inputSchema.properties).toHaveProperty('accounts');
89
+ expect(JSON.stringify(tool.inputSchema.properties.accounts)).toContain(PERSONAL_ACCOUNT);
90
+ expect(JSON.stringify(tool.inputSchema.properties.accounts)).toContain(WORK_ACCOUNT);
91
+ }
92
+ const read = manifest.tools.find((tool) => tool.name === 'search_messages');
93
+ const write = manifest.tools.find((tool) => tool.name === 'trash_message');
94
+ const readAccountsSchema = JSON.stringify(read?.inputSchema.properties.accounts);
95
+ const writeAccountsSchema = JSON.stringify(write?.inputSchema.properties.accounts);
96
+ expect(readAccountsSchema).toContain(PERSONAL_ACCOUNT);
97
+ expect(readAccountsSchema).toContain(WORK_ACCOUNT);
98
+ expect(readAccountsSchema).toContain('"maxItems":2');
99
+ expect(writeAccountsSchema).not.toContain('"maxItems":2');
100
+ });
101
+
102
+ it('allows subject-only or plain-body-only vacation replies but rejects an empty enabled reply', async () => {
103
+ const manifest = await app.toManifest();
104
+ const schema = manifest.tools.find((tool) => tool.name === 'update_vacation')?.inputSchema;
105
+ if (schema === undefined) throw new Error('expected update_vacation schema');
106
+ const base = { accounts: [PERSONAL_ACCOUNT], settings: { enable_auto_reply: true } };
107
+
108
+ expect(
109
+ validateJsonSchemaWithDefaults(schema, {
110
+ ...base,
111
+ settings: { ...base.settings, response_subject: 'Away' },
112
+ }).issues,
113
+ ).toHaveLength(0);
114
+ expect(
115
+ validateJsonSchemaWithDefaults(schema, {
116
+ ...base,
117
+ settings: { ...base.settings, response_body_plain_text: 'Back soon' },
118
+ }).issues,
119
+ ).toHaveLength(0);
120
+ expect(validateJsonSchemaWithDefaults(schema, base).issues).not.toHaveLength(0);
121
+ });
122
+
123
+ it.each([
124
+ [],
125
+ ['unknown@example.com'],
126
+ [PERSONAL_ACCOUNT, PERSONAL_ACCOUNT],
127
+ [WORK_ACCOUNT, PERSONAL_ACCOUNT],
128
+ ])('rejects invalid read accounts %j before connector dispatch', async (accounts) => {
129
+ const setup = await compiledFixture();
130
+ const schema = setup.artifact.tools.find(
131
+ (candidate) => candidate.name === 'search_messages',
132
+ )?.inputSchema;
133
+ if (!schema) throw new Error('expected search schema');
134
+ const result = validateJsonSchemaWithDefaults(schema, { accounts, query: '' });
135
+ expect(result.issues).not.toHaveLength(0);
136
+ expect(setup.requests).toHaveLength(0);
137
+ });
138
+
139
+ it('rejects multi-account mutations at the write schema boundary', async () => {
140
+ const setup = await compiledFixture();
141
+ const schema = setup.artifact.tools.find(
142
+ (candidate) => candidate.name === 'trash_message',
143
+ )?.inputSchema;
144
+ if (!schema) throw new Error('expected trash schema');
145
+
146
+ const result = validateJsonSchemaWithDefaults(schema, {
147
+ accounts: [PERSONAL_ACCOUNT, WORK_ACCOUNT],
148
+ message_id: 'fixture-message',
149
+ });
150
+
151
+ expect(result.issues).not.toHaveLength(0);
152
+ expect(setup.requests).toHaveLength(0);
153
+ });
154
+
155
+ it('rejects a no-op label mutation before confirmation or credential lookup', async () => {
156
+ const setup = await compiledFixture();
157
+
158
+ await expect(
159
+ prepareToolForConfirmation(
160
+ setup.artifact,
161
+ 'modify_message_labels',
162
+ { accounts: [PERSONAL_ACCOUNT], message_id: 'fixture-message' },
163
+ setup.deps,
164
+ ),
165
+ ).resolves.toMatchObject({ status: 'failed', error: { code: 'arg_invalid' } });
166
+ expect(setup.requests).toHaveLength(0);
167
+ });
168
+
169
+ it('routes personal, work, and combined reads to isolated bindings and preserves account labels', async () => {
170
+ const personal = await compiledFixture();
171
+ const personalResult = await executeTool(
172
+ personal.artifact,
173
+ 'search_messages',
174
+ { accounts: [PERSONAL_ACCOUNT], query: 'is:unread' },
175
+ personal.deps,
176
+ );
177
+ expect(personal.requests.flatMap((request) => request.bindingId ?? [])).toEqual([
178
+ 'personal_gmail',
179
+ ]);
180
+ expect(personalResult).toMatchObject({
181
+ ok: true,
182
+ output: { results: [{ account: PERSONAL_ACCOUNT }] },
183
+ });
184
+
185
+ const work = await compiledFixture();
186
+ const workResult = await executeTool(
187
+ work.artifact,
188
+ 'search_messages',
189
+ { accounts: [WORK_ACCOUNT], query: 'is:unread' },
190
+ work.deps,
191
+ );
192
+ expect(work.requests.flatMap((request) => request.bindingId ?? [])).toEqual(['work_gmail']);
193
+ expect(workResult).toMatchObject({
194
+ ok: true,
195
+ output: { results: [{ account: WORK_ACCOUNT }] },
196
+ });
197
+
198
+ const both = await compiledFixture();
199
+ const bothResult = await executeTool(
200
+ both.artifact,
201
+ 'search_messages',
202
+ { accounts: [PERSONAL_ACCOUNT, WORK_ACCOUNT], query: 'is:unread' },
203
+ both.deps,
204
+ );
205
+ expect(both.requests.flatMap((request) => request.bindingId ?? [])).toEqual([
206
+ 'personal_gmail',
207
+ 'work_gmail',
208
+ ]);
209
+ expect(bothResult).toMatchObject({
210
+ ok: true,
211
+ output: {
212
+ results: [{ account: PERSONAL_ACCOUNT }, { account: WORK_ACCOUNT }],
213
+ },
214
+ });
215
+ });
216
+
217
+ it('prepares the exact selected write binding, dispatches only after confirmation, and rejects replay drift', async () => {
218
+ const setup = await compiledFixture();
219
+ const prepared = await prepareToolForConfirmation(
220
+ setup.artifact,
221
+ 'trash_message',
222
+ { accounts: [WORK_ACCOUNT], message_id: 'fixture-message' },
223
+ setup.deps,
224
+ );
225
+ expect(isConfirmationRequired(prepared)).toBe(true);
226
+ if (!isConfirmationRequired(prepared)) throw new Error('expected confirmation');
227
+ expect(setup.requests).toHaveLength(0);
228
+ expect(prepared.review.action).toMatchObject({
229
+ bindingId: 'work_gmail',
230
+ connectionId: 'work_gmail',
231
+ operation: 'trash_message',
232
+ });
233
+
234
+ await expect(
235
+ executePreparedTool(setup.artifact, prepared.continuation, setup.deps),
236
+ ).resolves.toMatchObject({ status: 'completed' });
237
+ expect(setup.requests.flatMap((request) => request.bindingId ?? [])).toEqual(['work_gmail']);
238
+
239
+ const personalPrepared = structuredClone(prepared.continuation);
240
+ if (personalPrepared.reviewedAction) {
241
+ (personalPrepared.reviewedAction as { bindingId?: string }).bindingId = 'personal_gmail';
242
+ }
243
+ await expect(
244
+ executePreparedTool(setup.artifact, personalPrepared, setup.deps),
245
+ ).resolves.toMatchObject({ status: 'failed' });
246
+ });
247
+
248
+ it('makes every mutation confirmable and keeps the personal-email skill concise and safe', async () => {
249
+ const manifest = await app.toManifest();
250
+ const catalog = app.toConnectorCatalog();
251
+ const actionNames = new Set(
252
+ catalog?.connectors.flatMap((connector) =>
253
+ Object.entries(connector.operations)
254
+ .filter(([, operation]) => operation.type === 'action')
255
+ .map(([name]) => name),
256
+ ),
257
+ );
258
+ for (const tool of manifest.tools.filter((candidate) => actionNames.has(candidate.name))) {
259
+ expect(tool.annotations?.confirm, tool.name).toBe(true);
260
+ }
261
+
262
+ const skill = readFileSync(join(here, '../skills/personal-email-automation/SKILL.md'), 'utf8');
263
+ expect(skill).toMatch(/^---\nname: personal-email-automation\ndescription:/);
264
+ expect(skill.split('\n').length).toBeLessThan(140);
265
+ expect(skill).toContain('accounts');
266
+ expect(skill).toMatch(/draft/i);
267
+ expect(skill).toMatch(/confirm/i);
268
+ expect(skill).toMatch(/permanent delete/i);
269
+ expect(skill).not.toMatch(/client[_-]?secret|access[_-]?token/i);
270
+ });
271
+ });
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ resolve: {
5
+ alias: {
6
+ '@noodleseed/one': new URL('../../packages/authoring/src/index.ts', import.meta.url).pathname,
7
+ '@noodle-borg/capabilities': new URL(
8
+ '../../packages/capabilities/src/index.ts',
9
+ import.meta.url,
10
+ ).pathname,
11
+ '@noodle-borg/compiler': new URL('../../packages/compiler/src/index.ts', import.meta.url)
12
+ .pathname,
13
+ '@noodle-borg/compute': new URL('../../packages/compute/src/index.ts', import.meta.url)
14
+ .pathname,
15
+ '@noodle-borg/connector-defs': new URL(
16
+ '../../packages/connector-defs/src/index.ts',
17
+ import.meta.url,
18
+ ).pathname,
19
+ '@noodle-borg/connector-http': new URL(
20
+ '../../packages/connector-http/src/index.ts',
21
+ import.meta.url,
22
+ ).pathname,
23
+ '@noodle-borg/runtime': new URL('../../packages/runtime/src/index.ts', import.meta.url)
24
+ .pathname,
25
+ },
26
+ },
27
+ test: { include: ['test/**/*.test.ts'] },
28
+ });
@@ -0,0 +1,59 @@
1
+ # App directory compliance (pre-submission)
2
+
3
+ Use this shared checklist against the built integration before preparing a directory submission. It
4
+ covers evidence common to app and connector directories without assuming a particular host, review
5
+ portal, client framework, or vendor policy.
6
+
7
+ ## Contents
8
+
9
+ - Validation evidence
10
+ - Capability and interaction quality
11
+ - Safety, privacy, and data handling
12
+ - Reliability and accessibility
13
+ - Directory-specific delta
14
+
15
+ ## Validation evidence
16
+
17
+ A clean local validation result proves only the checks that actually ran. Record server validation,
18
+ behavior tests, protocol conformance, production reachability, and interactive rendering as separate
19
+ evidence levels. Never treat metadata readiness as proof of host rendering or directory acceptance.
20
+
21
+ ## Capability and interaction quality
22
+
23
+ 1. **User value** — each exposed capability solves a concrete user job and cites built behavior rather
24
+ than an aspiration.
25
+ 2. **Grounded capability** — knowledge, actions, and presentation come from authoritative application
26
+ data or bounded operations instead of invented state.
27
+ 3. **Atomic interfaces** — every action has a focused purpose, explicit input and output schemas, honest
28
+ effect annotations, and useful failure output.
29
+ 4. **Helpful UI only** — every interactive surface earns its place and preserves a useful text or
30
+ structured fallback when rendering is unavailable.
31
+ 5. **Meaningful completion** — the user can complete the promised task within the declared boundary,
32
+ with any external handoff clearly identified.
33
+
34
+ ## Safety, privacy, and data handling
35
+
36
+ - Minimize model-visible and UI-visible data; remove secrets, internal identifiers, unnecessary personal
37
+ data, and continuation credentials from results and logs.
38
+ - Document authentication, authorization scopes, retention, deletion, subprocessors, and external
39
+ handoffs accurately in the public privacy and support material.
40
+ - Make mutations explicit, bounded, and confirmation-aware. Never imply that a read or preparation
41
+ request authorizes a write.
42
+ - For regulated or consequential workflows, show source provenance, uncertainty, cautions, and the
43
+ boundary between information and a professional decision.
44
+
45
+ ## Reliability and accessibility
46
+
47
+ - Exercise representative positive, negative, empty, loading, error, and recovery cases against the
48
+ production-shaped endpoint.
49
+ - Preserve keyboard access, readable contrast, responsive layout, concise status feedback, and graceful
50
+ degradation when an interactive surface is unsupported.
51
+ - State latency, availability, rate-limit, and support expectations using observed evidence rather than
52
+ unverified claims.
53
+
54
+ ## Directory-specific delta
55
+
56
+ After the shared checklist passes, read the selected directory’s current official documentation and add
57
+ only its verified requirements. Keep directory-specific metadata, screenshots, test accounts, policy
58
+ statements, and review procedures in that submission evidence—not in this shared skill reference. Mark
59
+ unknown or untested requirements explicitly, and never reuse another directory’s checklist as a proxy.
@@ -41,6 +41,8 @@ Tools record connector calls into a flow; recording is not execution. Do not bra
41
41
 
42
42
  HTTP connector auth variants: `bearer` (`{ kind: "bearer", secret: secret("API_TOKEN") }`), `apiKey` (`{ kind: "apiKey", header: "X-API-Key", secret: secret("API_KEY") }`), `clientCredentials`, `delegatedOAuth`, `delegatedSessionCookie`, and `delegatedTokenExchange` (per-user calls to your own API — see "Delegated downstream auth" below). Use managed `secret(...)` / `variable(...)` refs for all values that differ by org/app/env.
43
43
 
44
+ When one connector needs independently selectable accounts, declare catalog `credentialProfiles` plus each operation’s accepted `credentials.profiles`, then bind each `server.use` alias with `bind(connector, { profile, connection: connection("logical_id", managedSecret(secret("NAME"), { scopes, audience })) })`. The alias is the stable account boundary; never put provider account ids, labels, or credential values in it. `gmailConnector()` is the curated Gmail catalog helper; reuse it under independent aliases and accept canonical `accounts` arrays in tools (one account for writes, or an explicitly ordered supported combination for reads). See the bundled `gmail-multi-account` flagship. Bound managed secrets are supported by hosted execution. `externalExchange()` is runnable only when the deployment operator injects an exact HTTPS provider endpoint/origin/audience and durable shared subject-pin store through service ports; Noodle sends a short-lived platform-signed deployment workload assertion and accepts only a bounded bearer response. Provider implementations must consume assertion replay ids through durable shared atomic storage across instances and restarts. There is intentionally no hosted enrollment or provider CRUD surface yet. The provider wire contract is public, but its conformance kit is workspace/source-only and is not an installable npm package. Bound `clientCredentials(...)` remains fail-closed until its provider slice lands.
45
+
44
46
  ## HTTP connector example (full server)
45
47
 
46
48
  Declare the API as data, bind it with `use`, then record calls in tools. The operation mapping in detail: `request` builds the JSON request body, `query: [...]` names the input args sent as URL query parameters, and `response` maps the parsed HTTP body (bound to `${response}`) into your typed `output`. `auth` reads a managed `secret(...)` — never inline a key. This whole example is compile-verified on every `pnpm test`.
@@ -273,7 +275,7 @@ tool('prepare_time_off', {
273
275
  });
274
276
  ```
275
277
 
276
- Use a stable lowercase/number/underscore id and a flat form of string/number/integer/boolean, string choices or multi-select, with optional `email`, `uri`, `date`, or `date-time` formats. Nested objects and credential-shaped fields fail with `invalid_elicitation_schema`. Every interactive flow must place all `ctx.elicit` calls before its first connector operation or compilation fails with `invalid_elicitation_flow`. Embedded/headless clients receive `input_requested`; bidirectional MCP transports map the primitive to standard form `elicitation/create`. On stateless hosts, the adapter returns a structured non-executing `interaction_unavailable` result; linked Apps render its business-user form and retry in request `_meta`, while models can use the advertised reserved retry field. Accept validates and replays only the operation-free input prefix; invalid content returns `arg_invalid`, and decline/cancel stop. Elicitation gathers missing input and does not replace confirmation. In a flow marked `confirm: true`, every eligible `input_requested` precedes `tool_proposed`; the final proposal reviews the original input, elicited values, and sole exact connector action. Accept is bound to that action and only then may execution start. A confirmable flow may contain at most one connector operation; additional operations fail with `invalid_confirmation_flow`. MCP uses final standard form confirmation on capable bidirectional transports and fails closed otherwise. Setting `interactions: { confirmationFallback: "host" }` in the server options explicitly trusts native host approval only when confirmation transport is unavailable and after every elicited field is collected; it is never inferred from client name and does not replace authorization. Omitted or `false` annotations execute directly; hints alone never gate. `annotations.action({ confirm: true })` explicitly enables confirmation; `annotations.action({ confirm: false })` explicitly preserves direct execution.
278
+ Use a stable lowercase/number/underscore id and a flat form of string/number/integer/boolean, string choices or multi-select, with optional `email`, `uri`, `date`, or `date-time` formats. Nested objects and credential-shaped fields fail with `invalid_elicitation_schema`. Every interactive flow must place all `ctx.elicit` calls before its first connector operation or compilation fails with `invalid_elicitation_flow`. Embedded/headless clients receive `input_requested`; bidirectional MCP transports map the primitive to standard form `elicitation/create`. On stateless hosts, the adapter returns a structured non-executing `interaction_unavailable` result; linked Apps render its business-user form and retry in request `_meta`, while models can use the advertised reserved retry field. Accept validates and replays only the operation-free input prefix; invalid content returns `arg_invalid`, and decline/cancel stop. Elicitation gathers missing input and does not replace confirmation. In a flow marked `confirm: true`, every eligible `input_requested` precedes `tool_proposed`; the final proposal reviews the original input, elicited values, and sole exact eligible connector action. Conditional branches may declare multiple candidate actions only when preparation resolves exactly one eligible action from input/context or completed pure steps; zero or multiple actions fail with `invalid_confirmation_flow` before I/O. Acceptance is bound to that exact action. Later read-only operations and pure compute may assemble output, but a second eligible action fails closed. Use `ref.at(index)` for array access in recorded expressions. MCP uses final standard form confirmation on capable bidirectional transports and fails closed otherwise. Setting `interactions: { confirmationFallback: "host" }` in the server options explicitly trusts native host approval only when confirmation transport is unavailable and after every elicited field is collected; it still uses preparation and prepared execution, is never inferred from client name, and does not replace authorization. Omitted or `false` annotations execute directly; hints alone never gate. `annotations.action({ confirm: true })` explicitly enables confirmation; `annotations.action({ confirm: false })` explicitly preserves direct execution.
277
279
 
278
280
  ## Compute connector example
279
281
 
@@ -0,0 +1,52 @@
1
+ # Outcome
2
+
3
+ Deliver an MCP App whose visual interaction gives the user a concrete benefit beyond a good text response, while preserving useful model-visible output when the widget is unavailable.
4
+
5
+ ## Use when
6
+
7
+ - The user asks for an MCP App, widget, interactive card, visual workflow, or host-visible UI.
8
+ - Comparison, selection, progress, editing, confirmation, or another visual interaction materially improves the conversational job.
9
+
10
+ ## Do not use when
11
+
12
+ - A concise text or structured tool result fully serves the user. UI must earn its place.
13
+ - The requested task is a headless server, API connector, diagnosis, deployment, or publication with no UI change; select that route.
14
+ - The agent lacks the product inputs needed to explain who benefits, what action the UI enables, and what happens without it.
15
+
16
+ ## Required inputs
17
+
18
+ Before implementation, capture a short design spec: target user, conversational job, explicit user benefit, information hierarchy, primary interaction, states (loading/empty/error/success), model-visible result, widget-only data, and useful text fallback. Use `references/experience-design.md` for the deeper product-design questions only when needed.
19
+
20
+ ## Workflow
21
+
22
+ 1. **Pass the UI fit check.** State why a visual interaction is better than text for this request. If there is no defensible user benefit, keep the capability headless and stop the App route.
23
+ 2. **Agree on the design spec.** Describe the smallest complete experience and its states before writing the component. Avoid recreating a full dashboard or website inside the conversation.
24
+ 3. **Define the output boundary.** Keep concise facts and action results model-visible. Put presentation-heavy or interactive widget data in the widget-only channel. The model must not depend on opaque UI state to continue the conversation.
25
+ 4. **Preserve fallback.** Every tool that launches a widget must still return useful text without the widget, so unsupported hosts and failed rendering remain usable.
26
+ 5. **Author and wire the App contract.** Follow `references/widgets-and-apps.md` for the canonical component guidance, view registration, hooks, state, CSP, tool visibility, and output shaping. Keep tool effects and confirmation semantics correct independently of the UI.
27
+ 6. **Validate the local artifact.** Run `noodle validate --json`, `noodle test --json`, and `noodle check --json`. Repair failures at the layer that produced them.
28
+ 7. **Inspect the experience.** Run `noodle devtools` and verify loading, empty, error, success, responsive layout, focus/keyboard behavior, and the text fallback.
29
+ 8. **Escalate evidence only on request.** Run a host test only when the user requested host verification. Run host-specific compliance only when preparing that host submission; select the exact host-testing or compliance entry from the router lookup catalog only after that evidence level is explicitly requested.
30
+
31
+ ## Verification evidence
32
+
33
+ - **Product:** the design spec states the user benefit and the UI fit decision.
34
+ - **Server:** `noodle validate --json` and `noodle test --json` succeeded.
35
+ - **App contract:** `noodle check --json` succeeded.
36
+ - **Local UX:** `noodle devtools` exercised the relevant states and the useful text fallback without the widget.
37
+ - **Host/compliance:** report each requested host or compliance check with its evidence; report every unperformed higher level as not run.
38
+
39
+ ## Recovery paths
40
+
41
+ - Weak UI fit: remove the widget and ship the stronger headless result, or narrow the visual interaction to the one decision it improves.
42
+ - App check failure: repair the cited view, metadata, output, CSP, or accessibility issue and rerun `noodle check --json` before reopening devtools.
43
+ - Blank or stale widget: verify the tool returns the intended widget data, the view is registered, and state derives from supported hooks rather than hidden global state.
44
+ - Model cannot continue without UI: move the essential facts into model-visible output and keep only presentation data widget-only.
45
+ - Host-only mismatch: record local checks as passed, isolate the host symptom, and select the host-testing lookup only for that observed host; do not rewrite a working local contract without host evidence.
46
+
47
+ ## Stop conditions
48
+
49
+ - Stop complete at the locally requested boundary when product fit, server tests, App checks, devtools states, and text fallback are evidenced.
50
+ - Stop before host connection, deployment, or submission unless the user requested that next evidence level.
51
+ - Stop blocked when the required design decision, external data, credentials, or host access is unavailable; name the missing input and the exact next action.
52
+ - Never claim host compatibility, directory compliance, or production behavior from local devtools evidence alone.
@@ -0,0 +1,54 @@
1
+ # Outcome
2
+
3
+ Deliver the smallest useful Noodle Seed MCP server that turns a real user intent into a safe, typed result. Author only the configured TypeScript entrypoint, normally `src/server.ts`; keep the public authoring surface TypeScript-only and never hand-author generated manifests or connector IR.
4
+
5
+ ## Use when
6
+
7
+ - The user asks to create or extend a headless MCP server, tools, resources, prompts, or connector-backed behavior.
8
+ - The requested result is primarily model-facing and does not require a widget or host-visible UI.
9
+
10
+ ## Do not use when
11
+
12
+ - The primary outcome is an MCP App, widget, or visual interaction; select the App route.
13
+ - The task is only to diagnose existing failures, deploy, publish, embed, or report feedback; select that dedicated route.
14
+ - The idea has no conversational fit: static content, a dashboard, deep navigation, or a full existing app port should be narrowed to the few actions that are better said than clicked.
15
+
16
+ ## Required inputs
17
+
18
+ Establish only the inputs needed for the requested stopping point. Follow `references/authoring-workflow.md` for the canonical discovery paths. Do not guess or invent a private schema, endpoint, authentication model, eligibility rule, or approval flow. If a required input is unavailable, state exactly what evidence is missing and stop before fabricating behavior.
19
+
20
+ ## Workflow
21
+
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
+ 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
+ 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.
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
+ 6. **Run the local smoke.** After validation succeeds, run `noodle test --json` and repair any failure at that evidence layer.
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.
29
+ 8. **Stop at the requested boundary.** Do not add an App, host test, hosted environment, publication work, or deployment unless the user requested that outcome. Deploy only when the selected route or the user explicitly requires it.
30
+
31
+ ## Verification evidence
32
+
33
+ Report evidence as a ladder and claim only levels actually exercised:
34
+
35
+ - **Authoring:** the requested TypeScript behavior exists with typed inputs and outputs.
36
+ - **Compilation:** `noodle validate --json` returned success.
37
+ - **Local smoke:** `noodle test --json` returned success.
38
+ - **Connector reality:** a representative safe read via `noodle tools call` returned populated mapped fields. This is required for connector-backed work.
39
+ - **Higher levels:** explicitly report host, deployment, and production checks as not run unless they were separately requested and evidenced.
40
+
41
+ ## Recovery paths
42
+
43
+ - Validation failure: fix each structured error at its reported path, rerun validation, then resume at the next unproven layer.
44
+ - Tool registers but returns empty or `undefined` fields: inspect one sanitized real response, correct `${response...}` mappings, and rerun the same read.
45
+ - Credential unavailable: verify `secret(...)` naming and the effective local target; never inline or print the secret.
46
+ - Missing product input: ask for the smallest concrete example, schema, or rule that unblocks the selected job. Do not widen the build to compensate.
47
+ - Repeated failure at the same layer: stop after two evidence-backed repair attempts with the same failure signature and report the command, sanitized error, evidence already proven, and exact next action.
48
+
49
+ ## Stop conditions
50
+
51
+ - Stop complete when the requested behavior passes validation and local smoke, and every connector-backed read has real-output evidence.
52
+ - Stop at the user's requested boundary; do not deploy unless the user requested deployment.
53
+ - Stop blocked when progress requires unavailable credentials, private schemas, external approval, or a live write the user has not approved.
54
+ - In the handoff, name what changed, what passed, what was not run, and any remaining risk without upgrading local evidence into a hosted or production claim.
@@ -59,5 +59,9 @@ Run `noodle validate` (add `--json` for the machine-readable envelope, `--fix-pr
59
59
  | `unknown_connector_alias` | The tool calls a connector alias not declared in `use`/`provides`; add it or fix the alias (see `suggestions`). |
60
60
  | `connector_not_in_catalog` | The referenced connector is not in the resolved catalog; add it to the project connectors or correct the reference. |
61
61
  | `unknown_operation` | The connector has no such operation; use an operation declared on that connector (see `didYouMean`/`suggestions`). |
62
+ | `connector_binding_required` | Bind the connector alias with `bind(connector, { profile, connection })`; credential-requiring operations cannot use an unbound alias. |
63
+ | `unsupported_credential_profile` | Select a credential profile declared by the connector and accepted by the operation; use the reported suggestions instead of inventing a profile name. |
64
+ | `credential_scope_mismatch` | Declare a connection source whose scopes include every operation-required scope, or select an external exchange provider that can mint them. |
65
+ | `credential_audience_mismatch` | Set the connection source audience to the operation-required audience exactly, or use an external exchange provider that can mint it. |
62
66
  | `unused_connector_alias` | A declared connector alias is never called; remove the unused `use` entry or wire it into a tool. |
63
67
  | `arg_mismatch` | A connector call is missing or adds arguments; match the operation signature under `expected`/`got`. |