@noodleseed/agent-kit 0.100.0 → 0.102.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 (60) hide show
  1. package/manifest.json +371 -371
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +1 -1
  4. package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
  5. package/skills/claude-code/authoring-mcp-servers/references/authoring-workflow.md +18 -3
  6. package/skills/claude-code/authoring-mcp-servers/references/sdk-surface.md +2 -1
  7. package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
  8. package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
  9. package/skills/claude-code/connecting-apis-to-mcp/references/authoring-workflow.md +18 -3
  10. package/skills/claude-code/creating-product-agent-guides/SKILL.md +1 -1
  11. package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
  12. package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
  13. package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
  14. package/skills/claude-code/designing-mcp-products/references/authoring-workflow.md +18 -3
  15. package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
  16. package/skills/claude-code/embedding-mcp-assistants/references/authoring-workflow.md +18 -3
  17. package/skills/claude-code/examples/acme-bistro/README.md +15 -48
  18. package/skills/claude-code/examples/acme-bistro/src/server.ts +58 -16
  19. package/skills/claude-code/examples/acme-bistro/test/server.test.ts +4 -4
  20. package/skills/claude-code/examples/acme-tasks/README.md +11 -11
  21. package/skills/claude-code/examples/gmail-multi-account/README.md +53 -31
  22. package/skills/claude-code/examples/weather/src/server.ts +3 -3
  23. package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
  24. package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
  25. package/skills/claude-code/references/authoring-workflow.md +18 -3
  26. package/skills/claude-code/references/compile-errors.md +4 -0
  27. package/skills/claude-code/references/sdk-surface.md +2 -1
  28. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
  29. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  30. package/skills/claude-code/wrapping-existing-applications/SKILL.md +1 -1
  31. package/skills/claude-code/wrapping-existing-applications/references/authoring-workflow.md +18 -3
  32. package/skills/codex/SKILL.md +1 -1
  33. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  34. package/skills/codex/authoring-mcp-servers/references/authoring-workflow.md +18 -3
  35. package/skills/codex/authoring-mcp-servers/references/sdk-surface.md +2 -1
  36. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  37. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  38. package/skills/codex/connecting-apis-to-mcp/references/authoring-workflow.md +18 -3
  39. package/skills/codex/creating-product-agent-guides/SKILL.md +1 -1
  40. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  41. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  42. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  43. package/skills/codex/designing-mcp-products/references/authoring-workflow.md +18 -3
  44. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  45. package/skills/codex/embedding-mcp-assistants/references/authoring-workflow.md +18 -3
  46. package/skills/codex/examples/acme-bistro/README.md +15 -48
  47. package/skills/codex/examples/acme-bistro/src/server.ts +58 -16
  48. package/skills/codex/examples/acme-bistro/test/server.test.ts +4 -4
  49. package/skills/codex/examples/acme-tasks/README.md +11 -11
  50. package/skills/codex/examples/gmail-multi-account/README.md +53 -31
  51. package/skills/codex/examples/weather/src/server.ts +3 -3
  52. package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
  53. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  54. package/skills/codex/references/authoring-workflow.md +18 -3
  55. package/skills/codex/references/compile-errors.md +4 -0
  56. package/skills/codex/references/sdk-surface.md +2 -1
  57. package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
  58. package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
  59. package/skills/codex/wrapping-existing-applications/SKILL.md +1 -1
  60. package/skills/codex/wrapping-existing-applications/references/authoring-workflow.md +18 -3
@@ -19,13 +19,13 @@ describe('acme-bistro example', () => {
19
19
  expect(text).toContain('create_checkout');
20
20
  });
21
21
 
22
- it('declares reusable guest-request record intent without generating a submit tool', async () => {
22
+ it('declares guest records and explicitly authors the native submission tool', async () => {
23
23
  const manifest = await app.toManifest();
24
24
  expect(manifest.server.collections).toEqual([
25
25
  expect.objectContaining({ name: 'guest_requests', schemaVersion: 1 }),
26
26
  ]);
27
- expect(manifest.tools).not.toContainEqual(
28
- expect.objectContaining({ name: 'submit_guest_requests' }),
29
- );
27
+ expect(
28
+ manifest.tools.find((tool) => tool.name === 'submit_guest_request')?.fulfilment.steps,
29
+ ).toMatchObject([{ use: 'records.submit_record' }]);
30
30
  });
31
31
  });
@@ -1,16 +1,11 @@
1
1
  # Acme Tasks — designed around its top-3 prioritized user flows
2
2
 
3
- A Noodle MCP App for **Acme Tasks**, a fictional productivity tool. It is the flagship for
4
- **designing an app around its prioritized user flows**: a two-way (read + write) experience where the
5
- top-3 flows **Capture, Prioritize, Complete** all happen in chat, each mapped to a tool and surfaced
6
- in one `TaskList` widget. It shows the "identify and prioritize the flows first, then build" discipline
7
- the `noodle-seed` skill's `references/experience-design.md` teaches.
8
-
9
- Capability slots: prioritized multi-flow app design, a two-way (read + write) in-chat pattern, a task-list
10
- widget with `tool` helpers, and a worked **design-first** artifact (the flow spec + wireframe
11
- below). A real deployment would connect the user's account with the end-user auth pattern — see
12
- [`../customer-auth/README.md`](../customer-auth/README.md); this example seeds a list so the focus stays
13
- on the flows.
3
+ A fictional productivity MCP App demonstrating three prioritized flows: **Capture, Prioritize, Complete**.
4
+ Each maps to a tool and the shared `TaskList` widget. The design spec and wireframe demonstrate the
5
+ `noodle-seed` skill's `references/experience-design.md` workflow: prioritize user flows before building.
6
+
7
+ This read/write example uses fictional seed data. A real deployment connects the user's account through
8
+ [customer authentication](../customer-auth/README.md).
14
9
 
15
10
  ## Design spec (write this before the code)
16
11
 
@@ -150,3 +145,8 @@ do not introduce a second lockfile.
150
145
 
151
146
  This example has no connector secrets and does not include tokens, caller-key mechanisms, or
152
147
  `.env.noodle` values. All tasks are fictional seed data.
148
+
149
+ ## Collection scope
150
+
151
+ Schema compilation is not hosted activation. External systems are changed through application tools;
152
+ a local read-only replica does not create a second writable authority.
@@ -1,31 +1,15 @@
1
1
  # Gmail multi-account automation
2
2
 
3
- **Owns:** The flagship proof that one reusable connector can be bound to multiple independently
4
- authenticated accounts inside one MCP server.
3
+ **Owns:** One reusable connector bound to independently authenticated accounts in one MCP server.
5
4
 
6
- This fictional example binds `gmailConnector()` twice through separate `externalExchange()` logical
7
- connections. Public tools always accept `accounts: [...]`; reads accept either account or the canonical
8
- personal-then-work pair, while mutations accept exactly one account and require runtime confirmation.
5
+ [`src/server.ts`](src/server.ts) binds `gmailConnector()` twice using `externalExchange()`. Its
6
+ `accounts` input selects either account or the ordered personal/work pair for reads. Mutations select
7
+ one account and require confirmation against that binding. The displayed email labels are fictional;
8
+ operators supply real authorization through the credential provider.
9
9
 
10
- Capability slot: **reusable connector + independently authenticated multi-account bindings**. It is distinct
11
- from `customer-auth`, which owns authentication of the MCP caller rather than downstream connector accounts.
12
-
13
- The labels `personal@example.com` and `work@example.com` are static display labels, not provider identities.
14
- The deployment-owned credential provider maps each logical connection to its real Google authorization.
15
- No Google client, provider account id, token, or real email address belongs in this project.
16
-
17
- ## Safety and API boundary
18
-
19
- - Search, message/thread reads, draft reads, and vacation-setting reads may target one or both accounts.
20
- - Draft creation/update/send, label changes, archive, raw send, trash, and vacation updates target one account.
21
- - Every mutation is prepared against the exact selected binding and must be confirmed before dispatch.
22
- - `send_message.raw` and draft `raw` are RFC 2822 MIME bytes encoded with base64url. This example does not
23
- pretend that `to`/`subject`/`body` strings are sufficient to encode Unicode MIME correctly.
24
- - Vacation `startTime`/`endTime` schemas enforce only digit-shaped 1–19 character epoch-millisecond strings.
25
- When both are supplied, Gmail's backend remains authoritative for the required `startTime < endTime`
26
- relationship; this example does not claim cross-field JSON Schema validation.
27
- - Trash is reversible. Permanent message/thread/draft deletion, delegation, forwarding/sharing settings,
28
- and unrestricted raw HTTP requests are intentionally absent.
10
+ - Message/draft `raw` values are base64url-encoded RFC 2822 MIME, not separate address/body fields.
11
+ - Vacation timestamps validate digit shape; Gmail enforces the start-before-end relationship.
12
+ - Trash is reversible. Permanent deletion, sharing/delegation and arbitrary HTTP requests are absent.
29
13
 
30
14
  ## Local checks
31
15
 
@@ -34,13 +18,51 @@ noodle validate
34
18
  noodle test
35
19
  ```
36
20
 
37
- The committed tests compile hermetic fake connector responses. They never contact Gmail or load OAuth
38
- credentials. A real deployment additionally needs an operator-provided external credential exchange
39
- endpoint for each logical connection.
21
+ Tests use fake responses; they do not prove live Gmail authorization. Hosted execution requires an
22
+ operator-provided external credential exchange for each logical connection. The installed skill's
23
+ `references/authoring-workflow.md` owns binding and credential setup guidance.
24
+
25
+ ## Optional application-owned gateway
26
+
27
+ The executable example calls Gmail directly. An application-owned gateway may additionally require a
28
+ service key. Gmail does not require this key, and this example supplies no gateway implementation:
29
+
30
+ ```ts
31
+ import {
32
+ bind, connection, connector, externalExchange, secret, variable, z,
33
+ } from '@noodleseed/one';
34
+
35
+ const gateway = connector('mail_gateway').version('1.0.0').http({
36
+ baseUrl: variable('MAIL_GATEWAY_URL'),
37
+ allowedOrigins: ['https://gateway.example.com'],
38
+ transportAuth: {
39
+ kind: 'apiKey',
40
+ header: 'X-Gateway-Key',
41
+ secret: secret('MAIL_GATEWAY_KEY'),
42
+ },
43
+ credentialProfiles: { account: { kind: 'bearer' } },
44
+ operations: {
45
+ inspect: {
46
+ type: 'read', method: 'POST', path: '/inspect',
47
+ credentials: { profiles: ['account'] },
48
+ input: z.object({}),
49
+ output: z.object({ available: z.boolean() }),
50
+ },
51
+ },
52
+ });
53
+
54
+ // Register this binding under server(..., { use: { mail: mailGateway } }, ...).
55
+ const mailGateway = bind(gateway, {
56
+ profile: 'account',
57
+ connection: connection('work_mail', externalExchange()),
58
+ });
59
+ ```
60
+
61
+ The operator configures the gateway URL/key and account separately. The broker resolves both credentials;
62
+ neither belongs in tool arguments or ordinary headers. The authored compile test checks this composition,
63
+ not live gateway access. See the installed skill's `references/authoring-workflow.md` for transport rules.
40
64
 
41
65
  ## Personal automation skill
42
66
 
43
- The source skill is [`skills/personal-email-automation/SKILL.md`](skills/personal-email-automation/SKILL.md).
44
- Validate it with the standard skill validator before distribution. The source skill is shipped as part of
45
- this example; canonical export of an app and its skill as an installable Codex plugin remains roadmap work
46
- and is not currently provided by Noodle Seed.
67
+ [`skills/personal-email-automation/SKILL.md`](skills/personal-email-automation/SKILL.md) is the source skill;
68
+ validate it before distribution. Canonical app-plus-skill plugin export remains roadmap work.
@@ -158,8 +158,8 @@ const placeNarrow = connector('geo_places')
158
158
  type: 'read',
159
159
  input: z.object({ results: z.unknown().optional() }),
160
160
  output: z.object({ places: z.array(z.unknown()) }),
161
- // Self-contained: no imports, no closure over outer variables, synchronous.
162
- run: (input) => {
161
+ // Synchronous and self-contained.
162
+ run: (input, host) => {
163
163
  const raw = input.results;
164
164
  const list = Array.isArray(raw) ? raw : [];
165
165
  const places = list.map((entry) => {
@@ -169,7 +169,7 @@ const placeNarrow = connector('geo_places')
169
169
  const id =
170
170
  entry.id !== undefined && entry.id !== null
171
171
  ? String(entry.id)
172
- : `${entry.latitude},${entry.longitude}`;
172
+ : host.digest(`${entry.latitude},${entry.longitude}`);
173
173
  return { id, label: parts.join(', ') };
174
174
  });
175
175
  return { places };
@@ -3,7 +3,7 @@ name: executing-noodle-plans
3
3
  description: "Use when the user asks to execute an approved, decision-complete implementation plan for a Noodle Seed project task by task with test-first changes, review, recovery, and final verification."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.100.0 hash:6a9f132ddb79352e -->
6
+ <!-- noodle-skill version:0.102.0 hash:6a9f132ddb79352e -->
7
7
 
8
8
  # Execute a Noodle Seed implementation plan
9
9
 
@@ -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.100.0 hash:0ccafb222038f553 -->
6
+ <!-- noodle-skill version:0.102.0 hash:0ccafb222038f553 -->
7
7
 
8
8
  # publishing-mcp-integrations
9
9
 
@@ -42,11 +42,14 @@ Author in `server.ts`, then `noodle validate` → fix cited errors (see `compile
42
42
 
43
43
  ## Connectors
44
44
 
45
- Declare connectors as data, not imperative code:
45
+ Declare typed connectors:
46
46
 
47
- - **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })` with per-operation `request`/`response` mapping using `${args...}` / `${response...}` expressions.
47
+ - **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })`; map request/response with `${args...}` / `${response...}`. Use `${execution.id}` for provider idempotency, never model input. Optional `evidence: { outcome: "${response.execution_outcome}", reference: "${response.id}" }` classifies completed/rejected/accepted/unknown; reference is a bounded opaque ID, never a bearer URL or payload. No automatic action retry.
48
48
  - **MCP**: `connector("id").version("1.0.0").mcp({ endpoint, allowedOrigins, auth?, operations })` where each operation freezes the separate upstream `tool` wire name plus input/output schema. Import with `noodle import mcp`; do not hand-copy a live surface or call `tools/list` at runtime.
49
49
  - **Compute**: `connector("id").version("1.0.0").compute(name, { input, output, calls?, run })` — a self-contained, sandboxed function (no imports/closure capture) that may call allowlisted operations via `callOperation`.
50
+ - **Explicit helpers**: `run: (input, host) => ...` can use `host.time.parse(iso)`, `host.time.format(epochMs)`, `host.time.parts(epochMs, timeZone)` and `host.digest(text, "hex")` (or `"base32hex"`). They convert bounded explicit data; there is no ambient clock. Pass trusted `context.temporal.instant` through fulfilment when current time is needed. `examples/weather` demonstrates a stable digest fallback for provider records without IDs.
51
+ - **Expected HTTP errors**: declare `responses: { "409": { response: { status: "conflict" }, evidence: { outcome: "rejected" } } }` alongside the ordinary success `response`. Explicit 4xx overrides exclude 401/403/429; validate against the same output schema. Undeclared errors still fail. Overrides never inherit successful completion evidence or introduce action retries.
52
+ - **Coordinated external actions**: a compute action may declare `coordination: { connectionId, namespace, key: "${args.resource}", reference: "${execution.id}" }` with a bounded execution deadline. It requires an installed application with a live bound connection and durable service coordination. Read `host.coordination.acquired` before writing; a blocked invocation may inspect `previous` through a declared read and call `host.resolveCoordination()` only after exact source proof, then require a fresh invocation/confirmation. The runtime permits at most one nested action when acquired and zero when blocked, even after recovery. Report explicit completed/rejected/unknown evidence with `host.reportOutcome(...)`; a successful return alone never unlocks uncertainty. Do not create an external-record collection for this.
50
53
 
51
54
  Tools record connector calls into a flow; recording is not execution. Do not branch on runtime outputs with native `if` — use declarative `when(...)` conditions.
52
55
 
@@ -58,6 +61,7 @@ not imported or forwarded. The runtime opens one guarded session for one operati
58
61
  credential, and closes it; it does not act as an agent for upstream sampling, roots, or elicitation.
59
62
 
60
63
  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.
64
+ An application-owned adapter may require its own service credential in addition to the operator account credential. Declare `http.transportAuth: { kind: "apiKey", header: "X-Adapter-Key", secret: secret("ADAPTER_SERVICE_KEY") }` alongside bound account credential profiles; the credential broker supplies the two independently. Never put either credential in tool arguments or ordinary headers, and never reuse an inbound MCP bearer. This capability does not imply a provider requires two credentials.
61
65
 
62
66
  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. For deployed-server access to Google APIs, use `googleWorkloadIdentity({ provider: variable("GOOGLE_WIF_PROVIDER"), access: { kind: "direct" } })`, or add `serviceAccountImpersonation` with a managed service-account email. This is keyless Google Workload Identity Federation: exact Google scopes/audience come from the catalog operation, while `noodle auth google prepare|status|doctor|revoke` owns operator lifecycle. See the bundled `google-bigquery` flagship. `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.
63
67
 
@@ -408,6 +412,12 @@ Adapt the representative arguments and assertions when business contracts change
408
412
 
409
413
  Author managed config as `secret("NAME")` / `variable("NAME")` and operate it with `noodle secrets set` / `noodle variables set` (scoped org/app/env). Never inline secret values in `server.ts`, tests, or generated files.
410
414
 
415
+ For business-editable configuration, declare `const guestExperience = variable("GUEST_EXPERIENCE", { schema: z.object({ notice: z.string().max(500) }), default: { notice: "Welcome" }, portal: { label: "Guest experience" }, requiredFor: ["show_menu"] })` and register it in `server(..., { variables: [guestExperience], ... }, tools)`. Use the whole ref or `guestExperience.field("notice")` in ordinary fulfilment/connector arguments. `.field()` selects a schema-declared object property, can chain for nested objects, and retains the parent variable and full configuration snapshot; it never reads operator data at author time. Keep confirmed action arguments shallow enough for complete review; do not label ordinary settings sensitive to bypass the review. The default is reusable safe intent; each business supplies its own values without editing source. Only explicit `portal` metadata exposes a setting. Name-only references and declared variables without `portal` remain technical configuration; secrets remain credential slots. See the bundled `acme-bistro` source.
416
+
417
+ Business schemas must be bounded: booleans, finite bounded numbers, bounded text, string enums, bounded arrays, and closed objects. Put defaults in the declaration, not inside Zod schema defaults; custom transforms/refinements and arbitrary schema code cannot run in the shared runtime. `requiredFor` names existing tools whose invocation needs the value; unresolved settings must leave only dependent capabilities unavailable. Publisher compilation does not require a future buyer’s values. Application code enforces business rules server-side; exposing a control or placing the rule in a prompt is insufficient.
418
+
419
+ For an installed application, use the same `noodle variables list|resolve|set|delete` family with `--installation <id> --org <org> --runtime cloud|other`; do not combine it with app/env/scope flags or secrets. Inspection shows schema metadata, provenance, readiness and revision without saved values. Set inputs are JSON (including quotes for strings), read the current projection and save atomically; `--expected-revision <digest>` pins an explicit prior inspection. Delete resets to the current declared default or unset state. Business-administrator permission is required for mutations, conflicts never retry writes, and mutation output excludes values.
420
+
411
421
  ## Embedded assistant
412
422
 
413
423
  To place the same server tools inside a SaaS web app, declare `assistant: embeddedAssistant(...)` alongside the one server-level brand kit. Read `embedded-assistant.md` before integrating: it owns the HTTPS-origin rule, managed model configuration, required deploy-before-client sequence, customer-backend exchange, browser mount, and verification checklist.
@@ -445,8 +455,13 @@ The managed crawler and managed index are the defaults and need no configuration
445
455
  ## Managed collections
446
456
 
447
457
  Use `managedCollection(name, { title, description, schemaVersion, record })` when an application needs reusable typed intent for business records that Noodle may later hold. Pass it through `server(..., { collections: [...] }, definitions)`. `record` is one bounded, closed Zod object; names use lowercase letters, numbers, and underscores. Keep payment-card, credentials, passport/government identity, health, and biometric fields out of this surface.
458
+ Native record controls are independent: `management: { assignment: true }`, `{ notes: true }`, both, or neither. Status is an ordinary application field such as `progress: z.enum(["received", "reviewing", "handled"]).default("received")`; no framework transition graph exists. Declare `publicFields` explicitly (default empty), `editableFields` for authorized staff (default all schema properties), optional field labels/help, `summaryFields`, `filterFields` and `sortFields`. References must name actual fields and filters/sorts select scalars. Public callers cannot set staff-only fields; a required private field needs a valid creation default. Staff updates merge only admitted fields and revalidate the complete record. See the notes-only `acme-bistro` collection.
459
+ Operator record queries use `noodle solutions records list --filters '[{"field":"progress","value":"received"}]' --sort-field progress`. Only declared scalar equality/sort fields apply. Payload queries scan at most 10,000 candidates; narrow `--created-at-from`/`--created-at-to` if the API returns `query_limit_exceeded`. Ordinary listing/export has no such scan cap. Cursor reuse requires unchanged query/schema and anchor revision. An administrator can explicitly migrate an eligible historical request with `records migrate-schema ... --expected-revision N`; never implement read-time rewrites.
460
+
461
+ Omitting `source` makes Noodle authoritative for the collection. To project a read-only collection from an outside system, bind an HTTP connector in `server(..., { use: [...] })` and add `source: { connector: connector.ref(), scan: "scan_operation" }`. The compiler requires the exact normalized scan contract and the same record schema. Noodle ingests a one-way replica; create, update, and delete in the outside system remain ordinary application tools with their own confirmation and policy.
448
462
 
449
- This declaration compiles schema metadata only. It does not create a submit tool, grant access, select retention or residency, provision storage, or make records available to MCP or the embedded assistant. Operators bind lifecycle and authority separately. Until the service intake path is shipped, keep live business operations on their existing connector tools.
463
+ For an explicitly authored native operation, bind `use: { records: noodlePlatform.records.v1 }` and call `connectors.records.submitRecord({ collection: "guest_requests", payload: input })` from an ordinary confirmed tool. Submission accepts only public fields and returns a receipt. `createRecord`, `getRecord`, `listRecords`, `updateRecord`, and `deleteRecord` require a verified platform caller with a live business grant; updates/deletes require the current revision. `updateRecord({ collection, id, expectedRevision, patch, unset: ["optional_field"] })` removes named optional editable fields; omitted keys stay unchanged, `null` is data, and overlap with `patch`, required fields or undeclared fields is rejected. Runtime identity supplies write idempotency and admission; never accept those controls as model arguments. An uninstalled collection is unavailable, including during local tool execution.
464
+ A collection declaration does not create a submit tool, grant access, select retention or residency, provision storage, or expose records to MCP or an embedded assistant. Operators activate the definition and configure lifecycle, access, and any external binding. A connector does not become a collection unless the app explicitly supplies the source contract, and an external collection never becomes a writable mirror.
450
465
 
451
466
  ## Boundaries
452
467
 
@@ -59,10 +59,14 @@ Run `noodle validate` (add `--json` for the machine-readable envelope, `--fix-pr
59
59
  | `invalid_knowledge` | Fix the `knowledge()` declaration: documents must be existing UTF-8 `.md`/`.txt` files inside the project root (no symlinks), within the 100-file / 1 MiB / 25 MiB bounds, and sites need an exact HTTPS origin plus at least one include glob. |
60
60
  | `knowledge_unhashed` | Compile from the project root (`noodle validate`/`noodle dev`) so declared knowledge documents can be read and hashed. |
61
61
  | `invalid_managed_collection` | Fix the `managedCollection()` declaration: use a bounded, closed Zod object without credential, payment-card, government-identity, health, or biometric field names. |
62
+ | `invalid_variable_declaration` | Fix the typed `variable()` declaration: use a bounded closed schema, a matching JSON default, plain Portal metadata, existing requiredFor tool names, and one consistent definition per key. |
63
+ | `invalid_managed_collection_source` | Bind the declared source connector and select read-only scan operations whose closed normalized envelopes use the collection record schema exactly. |
62
64
  | `unknown_connector_alias` | The tool calls a connector alias not declared in `use`/`provides`; add it or fix the alias (see `suggestions`). |
63
65
  | `connector_not_in_catalog` | The referenced connector is not in the resolved catalog; add it to the project connectors or correct the reference. |
64
66
  | `unknown_operation` | The connector has no such operation; use an operation declared on that connector (see `didYouMean`/`suggestions`). |
65
67
  | `connector_binding_required` | Bind the connector alias with `bind(connector, { profile, connection })`; credential-requiring operations cannot use an unbound alias. |
68
+ | `ambiguous_nested_connector_binding` | Keep one exact bound alias for each connector/version reached by compute; do not let a nested call guess between authorized accounts. |
69
+ | `invalid_connector_call_graph` | Regenerate the system-owned connector catalog from TypeScript and correct missing, cyclic, excessive or mismatched declared compute calls. |
66
70
  | `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. |
67
71
  | `credential_scope_mismatch` | Declare a connection source whose scopes include every operation-required scope, or select an external exchange provider that can mint them. |
68
72
  | `credential_audience_mismatch` | Set the connection source audience to the operation-required audience exactly, or use an external exchange provider that can mint it. |
@@ -37,7 +37,7 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
37
37
  ### Managed config
38
38
 
39
39
  - `secret("NAME")` — reference a managed secret (operated via `noodle secrets`).
40
- - `variable("NAME")` — reference a managed variable (operated via `noodle variables`).
40
+ - `variable("NAME")` — reference managed configuration; add `{ schema, default?, portal?, requiredFor? }` and register in `server.variables` for typed business settings. Operate through `noodle variables` or authorized Portal settings.
41
41
 
42
42
  ### Customer auth
43
43
 
@@ -67,6 +67,7 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
67
67
  - `managedSecret`
68
68
  - `meilisearch`
69
69
  - `noodleManaged`
70
+ - `noodlePlatform`
70
71
  - `openAICompatible`
71
72
  - `publicWebsite`
72
73
  - `site`
@@ -3,7 +3,7 @@ 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.100.0 hash:660cc6ad9469d90f -->
6
+ <!-- noodle-skill version:0.102.0 hash:660cc6ad9469d90f -->
7
7
 
8
8
  # reporting-noodle-feedback
9
9
 
@@ -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.100.0 hash:292253cbaed9a3c5 -->
6
+ <!-- noodle-skill version:0.102.0 hash:292253cbaed9a3c5 -->
7
7
 
8
8
  # verifying-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ name: wrapping-existing-applications
3
3
  description: "Use when an existing application has no stable usable API and needs a read-only, identity-first Noodle Seed integration plan before implementation."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.100.0 hash:379ab9f64878f1fe -->
6
+ <!-- noodle-skill version:0.102.0 hash:379ab9f64878f1fe -->
7
7
 
8
8
  # wrapping-existing-applications
9
9
 
@@ -42,11 +42,14 @@ Author in `server.ts`, then `noodle validate` → fix cited errors (see `compile
42
42
 
43
43
  ## Connectors
44
44
 
45
- Declare connectors as data, not imperative code:
45
+ Declare typed connectors:
46
46
 
47
- - **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })` with per-operation `request`/`response` mapping using `${args...}` / `${response...}` expressions.
47
+ - **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })`; map request/response with `${args...}` / `${response...}`. Use `${execution.id}` for provider idempotency, never model input. Optional `evidence: { outcome: "${response.execution_outcome}", reference: "${response.id}" }` classifies completed/rejected/accepted/unknown; reference is a bounded opaque ID, never a bearer URL or payload. No automatic action retry.
48
48
  - **MCP**: `connector("id").version("1.0.0").mcp({ endpoint, allowedOrigins, auth?, operations })` where each operation freezes the separate upstream `tool` wire name plus input/output schema. Import with `noodle import mcp`; do not hand-copy a live surface or call `tools/list` at runtime.
49
49
  - **Compute**: `connector("id").version("1.0.0").compute(name, { input, output, calls?, run })` — a self-contained, sandboxed function (no imports/closure capture) that may call allowlisted operations via `callOperation`.
50
+ - **Explicit helpers**: `run: (input, host) => ...` can use `host.time.parse(iso)`, `host.time.format(epochMs)`, `host.time.parts(epochMs, timeZone)` and `host.digest(text, "hex")` (or `"base32hex"`). They convert bounded explicit data; there is no ambient clock. Pass trusted `context.temporal.instant` through fulfilment when current time is needed. `examples/weather` demonstrates a stable digest fallback for provider records without IDs.
51
+ - **Expected HTTP errors**: declare `responses: { "409": { response: { status: "conflict" }, evidence: { outcome: "rejected" } } }` alongside the ordinary success `response`. Explicit 4xx overrides exclude 401/403/429; validate against the same output schema. Undeclared errors still fail. Overrides never inherit successful completion evidence or introduce action retries.
52
+ - **Coordinated external actions**: a compute action may declare `coordination: { connectionId, namespace, key: "${args.resource}", reference: "${execution.id}" }` with a bounded execution deadline. It requires an installed application with a live bound connection and durable service coordination. Read `host.coordination.acquired` before writing; a blocked invocation may inspect `previous` through a declared read and call `host.resolveCoordination()` only after exact source proof, then require a fresh invocation/confirmation. The runtime permits at most one nested action when acquired and zero when blocked, even after recovery. Report explicit completed/rejected/unknown evidence with `host.reportOutcome(...)`; a successful return alone never unlocks uncertainty. Do not create an external-record collection for this.
50
53
 
51
54
  Tools record connector calls into a flow; recording is not execution. Do not branch on runtime outputs with native `if` — use declarative `when(...)` conditions.
52
55
 
@@ -58,6 +61,7 @@ not imported or forwarded. The runtime opens one guarded session for one operati
58
61
  credential, and closes it; it does not act as an agent for upstream sampling, roots, or elicitation.
59
62
 
60
63
  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.
64
+ An application-owned adapter may require its own service credential in addition to the operator account credential. Declare `http.transportAuth: { kind: "apiKey", header: "X-Adapter-Key", secret: secret("ADAPTER_SERVICE_KEY") }` alongside bound account credential profiles; the credential broker supplies the two independently. Never put either credential in tool arguments or ordinary headers, and never reuse an inbound MCP bearer. This capability does not imply a provider requires two credentials.
61
65
 
62
66
  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. For deployed-server access to Google APIs, use `googleWorkloadIdentity({ provider: variable("GOOGLE_WIF_PROVIDER"), access: { kind: "direct" } })`, or add `serviceAccountImpersonation` with a managed service-account email. This is keyless Google Workload Identity Federation: exact Google scopes/audience come from the catalog operation, while `noodle auth google prepare|status|doctor|revoke` owns operator lifecycle. See the bundled `google-bigquery` flagship. `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.
63
67
 
@@ -408,6 +412,12 @@ Adapt the representative arguments and assertions when business contracts change
408
412
 
409
413
  Author managed config as `secret("NAME")` / `variable("NAME")` and operate it with `noodle secrets set` / `noodle variables set` (scoped org/app/env). Never inline secret values in `server.ts`, tests, or generated files.
410
414
 
415
+ For business-editable configuration, declare `const guestExperience = variable("GUEST_EXPERIENCE", { schema: z.object({ notice: z.string().max(500) }), default: { notice: "Welcome" }, portal: { label: "Guest experience" }, requiredFor: ["show_menu"] })` and register it in `server(..., { variables: [guestExperience], ... }, tools)`. Use the whole ref or `guestExperience.field("notice")` in ordinary fulfilment/connector arguments. `.field()` selects a schema-declared object property, can chain for nested objects, and retains the parent variable and full configuration snapshot; it never reads operator data at author time. Keep confirmed action arguments shallow enough for complete review; do not label ordinary settings sensitive to bypass the review. The default is reusable safe intent; each business supplies its own values without editing source. Only explicit `portal` metadata exposes a setting. Name-only references and declared variables without `portal` remain technical configuration; secrets remain credential slots. See the bundled `acme-bistro` source.
416
+
417
+ Business schemas must be bounded: booleans, finite bounded numbers, bounded text, string enums, bounded arrays, and closed objects. Put defaults in the declaration, not inside Zod schema defaults; custom transforms/refinements and arbitrary schema code cannot run in the shared runtime. `requiredFor` names existing tools whose invocation needs the value; unresolved settings must leave only dependent capabilities unavailable. Publisher compilation does not require a future buyer’s values. Application code enforces business rules server-side; exposing a control or placing the rule in a prompt is insufficient.
418
+
419
+ For an installed application, use the same `noodle variables list|resolve|set|delete` family with `--installation <id> --org <org> --runtime cloud|other`; do not combine it with app/env/scope flags or secrets. Inspection shows schema metadata, provenance, readiness and revision without saved values. Set inputs are JSON (including quotes for strings), read the current projection and save atomically; `--expected-revision <digest>` pins an explicit prior inspection. Delete resets to the current declared default or unset state. Business-administrator permission is required for mutations, conflicts never retry writes, and mutation output excludes values.
420
+
411
421
  ## Embedded assistant
412
422
 
413
423
  To place the same server tools inside a SaaS web app, declare `assistant: embeddedAssistant(...)` alongside the one server-level brand kit. Read `embedded-assistant.md` before integrating: it owns the HTTPS-origin rule, managed model configuration, required deploy-before-client sequence, customer-backend exchange, browser mount, and verification checklist.
@@ -445,8 +455,13 @@ The managed crawler and managed index are the defaults and need no configuration
445
455
  ## Managed collections
446
456
 
447
457
  Use `managedCollection(name, { title, description, schemaVersion, record })` when an application needs reusable typed intent for business records that Noodle may later hold. Pass it through `server(..., { collections: [...] }, definitions)`. `record` is one bounded, closed Zod object; names use lowercase letters, numbers, and underscores. Keep payment-card, credentials, passport/government identity, health, and biometric fields out of this surface.
458
+ Native record controls are independent: `management: { assignment: true }`, `{ notes: true }`, both, or neither. Status is an ordinary application field such as `progress: z.enum(["received", "reviewing", "handled"]).default("received")`; no framework transition graph exists. Declare `publicFields` explicitly (default empty), `editableFields` for authorized staff (default all schema properties), optional field labels/help, `summaryFields`, `filterFields` and `sortFields`. References must name actual fields and filters/sorts select scalars. Public callers cannot set staff-only fields; a required private field needs a valid creation default. Staff updates merge only admitted fields and revalidate the complete record. See the notes-only `acme-bistro` collection.
459
+ Operator record queries use `noodle solutions records list --filters '[{"field":"progress","value":"received"}]' --sort-field progress`. Only declared scalar equality/sort fields apply. Payload queries scan at most 10,000 candidates; narrow `--created-at-from`/`--created-at-to` if the API returns `query_limit_exceeded`. Ordinary listing/export has no such scan cap. Cursor reuse requires unchanged query/schema and anchor revision. An administrator can explicitly migrate an eligible historical request with `records migrate-schema ... --expected-revision N`; never implement read-time rewrites.
460
+
461
+ Omitting `source` makes Noodle authoritative for the collection. To project a read-only collection from an outside system, bind an HTTP connector in `server(..., { use: [...] })` and add `source: { connector: connector.ref(), scan: "scan_operation" }`. The compiler requires the exact normalized scan contract and the same record schema. Noodle ingests a one-way replica; create, update, and delete in the outside system remain ordinary application tools with their own confirmation and policy.
448
462
 
449
- This declaration compiles schema metadata only. It does not create a submit tool, grant access, select retention or residency, provision storage, or make records available to MCP or the embedded assistant. Operators bind lifecycle and authority separately. Until the service intake path is shipped, keep live business operations on their existing connector tools.
463
+ For an explicitly authored native operation, bind `use: { records: noodlePlatform.records.v1 }` and call `connectors.records.submitRecord({ collection: "guest_requests", payload: input })` from an ordinary confirmed tool. Submission accepts only public fields and returns a receipt. `createRecord`, `getRecord`, `listRecords`, `updateRecord`, and `deleteRecord` require a verified platform caller with a live business grant; updates/deletes require the current revision. `updateRecord({ collection, id, expectedRevision, patch, unset: ["optional_field"] })` removes named optional editable fields; omitted keys stay unchanged, `null` is data, and overlap with `patch`, required fields or undeclared fields is rejected. Runtime identity supplies write idempotency and admission; never accept those controls as model arguments. An uninstalled collection is unavailable, including during local tool execution.
464
+ A collection declaration does not create a submit tool, grant access, select retention or residency, provision storage, or expose records to MCP or an embedded assistant. Operators activate the definition and configure lifecycle, access, and any external binding. A connector does not become a collection unless the app explicitly supplies the source contract, and an external collection never becomes a writable mirror.
450
465
 
451
466
  ## Boundaries
452
467
 
@@ -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.100.0 hash:d3ceb1902ef4bb72 -->
6
+ <!-- noodle-skill version:0.102.0 hash:d3ceb1902ef4bb72 -->
7
7
 
8
8
  # Noodle Seed
9
9
 
@@ -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.100.0 hash:dd57a15df15d10b2 -->
6
+ <!-- noodle-skill version:0.102.0 hash:dd57a15df15d10b2 -->
7
7
 
8
8
  # authoring-mcp-servers
9
9
 
@@ -42,11 +42,14 @@ Author in `server.ts`, then `noodle validate` → fix cited errors (see `compile
42
42
 
43
43
  ## Connectors
44
44
 
45
- Declare connectors as data, not imperative code:
45
+ Declare typed connectors:
46
46
 
47
- - **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })` with per-operation `request`/`response` mapping using `${args...}` / `${response...}` expressions.
47
+ - **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })`; map request/response with `${args...}` / `${response...}`. Use `${execution.id}` for provider idempotency, never model input. Optional `evidence: { outcome: "${response.execution_outcome}", reference: "${response.id}" }` classifies completed/rejected/accepted/unknown; reference is a bounded opaque ID, never a bearer URL or payload. No automatic action retry.
48
48
  - **MCP**: `connector("id").version("1.0.0").mcp({ endpoint, allowedOrigins, auth?, operations })` where each operation freezes the separate upstream `tool` wire name plus input/output schema. Import with `noodle import mcp`; do not hand-copy a live surface or call `tools/list` at runtime.
49
49
  - **Compute**: `connector("id").version("1.0.0").compute(name, { input, output, calls?, run })` — a self-contained, sandboxed function (no imports/closure capture) that may call allowlisted operations via `callOperation`.
50
+ - **Explicit helpers**: `run: (input, host) => ...` can use `host.time.parse(iso)`, `host.time.format(epochMs)`, `host.time.parts(epochMs, timeZone)` and `host.digest(text, "hex")` (or `"base32hex"`). They convert bounded explicit data; there is no ambient clock. Pass trusted `context.temporal.instant` through fulfilment when current time is needed. `examples/weather` demonstrates a stable digest fallback for provider records without IDs.
51
+ - **Expected HTTP errors**: declare `responses: { "409": { response: { status: "conflict" }, evidence: { outcome: "rejected" } } }` alongside the ordinary success `response`. Explicit 4xx overrides exclude 401/403/429; validate against the same output schema. Undeclared errors still fail. Overrides never inherit successful completion evidence or introduce action retries.
52
+ - **Coordinated external actions**: a compute action may declare `coordination: { connectionId, namespace, key: "${args.resource}", reference: "${execution.id}" }` with a bounded execution deadline. It requires an installed application with a live bound connection and durable service coordination. Read `host.coordination.acquired` before writing; a blocked invocation may inspect `previous` through a declared read and call `host.resolveCoordination()` only after exact source proof, then require a fresh invocation/confirmation. The runtime permits at most one nested action when acquired and zero when blocked, even after recovery. Report explicit completed/rejected/unknown evidence with `host.reportOutcome(...)`; a successful return alone never unlocks uncertainty. Do not create an external-record collection for this.
50
53
 
51
54
  Tools record connector calls into a flow; recording is not execution. Do not branch on runtime outputs with native `if` — use declarative `when(...)` conditions.
52
55
 
@@ -58,6 +61,7 @@ not imported or forwarded. The runtime opens one guarded session for one operati
58
61
  credential, and closes it; it does not act as an agent for upstream sampling, roots, or elicitation.
59
62
 
60
63
  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.
64
+ An application-owned adapter may require its own service credential in addition to the operator account credential. Declare `http.transportAuth: { kind: "apiKey", header: "X-Adapter-Key", secret: secret("ADAPTER_SERVICE_KEY") }` alongside bound account credential profiles; the credential broker supplies the two independently. Never put either credential in tool arguments or ordinary headers, and never reuse an inbound MCP bearer. This capability does not imply a provider requires two credentials.
61
65
 
62
66
  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. For deployed-server access to Google APIs, use `googleWorkloadIdentity({ provider: variable("GOOGLE_WIF_PROVIDER"), access: { kind: "direct" } })`, or add `serviceAccountImpersonation` with a managed service-account email. This is keyless Google Workload Identity Federation: exact Google scopes/audience come from the catalog operation, while `noodle auth google prepare|status|doctor|revoke` owns operator lifecycle. See the bundled `google-bigquery` flagship. `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.
63
67
 
@@ -408,6 +412,12 @@ Adapt the representative arguments and assertions when business contracts change
408
412
 
409
413
  Author managed config as `secret("NAME")` / `variable("NAME")` and operate it with `noodle secrets set` / `noodle variables set` (scoped org/app/env). Never inline secret values in `server.ts`, tests, or generated files.
410
414
 
415
+ For business-editable configuration, declare `const guestExperience = variable("GUEST_EXPERIENCE", { schema: z.object({ notice: z.string().max(500) }), default: { notice: "Welcome" }, portal: { label: "Guest experience" }, requiredFor: ["show_menu"] })` and register it in `server(..., { variables: [guestExperience], ... }, tools)`. Use the whole ref or `guestExperience.field("notice")` in ordinary fulfilment/connector arguments. `.field()` selects a schema-declared object property, can chain for nested objects, and retains the parent variable and full configuration snapshot; it never reads operator data at author time. Keep confirmed action arguments shallow enough for complete review; do not label ordinary settings sensitive to bypass the review. The default is reusable safe intent; each business supplies its own values without editing source. Only explicit `portal` metadata exposes a setting. Name-only references and declared variables without `portal` remain technical configuration; secrets remain credential slots. See the bundled `acme-bistro` source.
416
+
417
+ Business schemas must be bounded: booleans, finite bounded numbers, bounded text, string enums, bounded arrays, and closed objects. Put defaults in the declaration, not inside Zod schema defaults; custom transforms/refinements and arbitrary schema code cannot run in the shared runtime. `requiredFor` names existing tools whose invocation needs the value; unresolved settings must leave only dependent capabilities unavailable. Publisher compilation does not require a future buyer’s values. Application code enforces business rules server-side; exposing a control or placing the rule in a prompt is insufficient.
418
+
419
+ For an installed application, use the same `noodle variables list|resolve|set|delete` family with `--installation <id> --org <org> --runtime cloud|other`; do not combine it with app/env/scope flags or secrets. Inspection shows schema metadata, provenance, readiness and revision without saved values. Set inputs are JSON (including quotes for strings), read the current projection and save atomically; `--expected-revision <digest>` pins an explicit prior inspection. Delete resets to the current declared default or unset state. Business-administrator permission is required for mutations, conflicts never retry writes, and mutation output excludes values.
420
+
411
421
  ## Embedded assistant
412
422
 
413
423
  To place the same server tools inside a SaaS web app, declare `assistant: embeddedAssistant(...)` alongside the one server-level brand kit. Read `embedded-assistant.md` before integrating: it owns the HTTPS-origin rule, managed model configuration, required deploy-before-client sequence, customer-backend exchange, browser mount, and verification checklist.
@@ -445,8 +455,13 @@ The managed crawler and managed index are the defaults and need no configuration
445
455
  ## Managed collections
446
456
 
447
457
  Use `managedCollection(name, { title, description, schemaVersion, record })` when an application needs reusable typed intent for business records that Noodle may later hold. Pass it through `server(..., { collections: [...] }, definitions)`. `record` is one bounded, closed Zod object; names use lowercase letters, numbers, and underscores. Keep payment-card, credentials, passport/government identity, health, and biometric fields out of this surface.
458
+ Native record controls are independent: `management: { assignment: true }`, `{ notes: true }`, both, or neither. Status is an ordinary application field such as `progress: z.enum(["received", "reviewing", "handled"]).default("received")`; no framework transition graph exists. Declare `publicFields` explicitly (default empty), `editableFields` for authorized staff (default all schema properties), optional field labels/help, `summaryFields`, `filterFields` and `sortFields`. References must name actual fields and filters/sorts select scalars. Public callers cannot set staff-only fields; a required private field needs a valid creation default. Staff updates merge only admitted fields and revalidate the complete record. See the notes-only `acme-bistro` collection.
459
+ Operator record queries use `noodle solutions records list --filters '[{"field":"progress","value":"received"}]' --sort-field progress`. Only declared scalar equality/sort fields apply. Payload queries scan at most 10,000 candidates; narrow `--created-at-from`/`--created-at-to` if the API returns `query_limit_exceeded`. Ordinary listing/export has no such scan cap. Cursor reuse requires unchanged query/schema and anchor revision. An administrator can explicitly migrate an eligible historical request with `records migrate-schema ... --expected-revision N`; never implement read-time rewrites.
460
+
461
+ Omitting `source` makes Noodle authoritative for the collection. To project a read-only collection from an outside system, bind an HTTP connector in `server(..., { use: [...] })` and add `source: { connector: connector.ref(), scan: "scan_operation" }`. The compiler requires the exact normalized scan contract and the same record schema. Noodle ingests a one-way replica; create, update, and delete in the outside system remain ordinary application tools with their own confirmation and policy.
448
462
 
449
- This declaration compiles schema metadata only. It does not create a submit tool, grant access, select retention or residency, provision storage, or make records available to MCP or the embedded assistant. Operators bind lifecycle and authority separately. Until the service intake path is shipped, keep live business operations on their existing connector tools.
463
+ For an explicitly authored native operation, bind `use: { records: noodlePlatform.records.v1 }` and call `connectors.records.submitRecord({ collection: "guest_requests", payload: input })` from an ordinary confirmed tool. Submission accepts only public fields and returns a receipt. `createRecord`, `getRecord`, `listRecords`, `updateRecord`, and `deleteRecord` require a verified platform caller with a live business grant; updates/deletes require the current revision. `updateRecord({ collection, id, expectedRevision, patch, unset: ["optional_field"] })` removes named optional editable fields; omitted keys stay unchanged, `null` is data, and overlap with `patch`, required fields or undeclared fields is rejected. Runtime identity supplies write idempotency and admission; never accept those controls as model arguments. An uninstalled collection is unavailable, including during local tool execution.
464
+ A collection declaration does not create a submit tool, grant access, select retention or residency, provision storage, or expose records to MCP or an embedded assistant. Operators activate the definition and configure lifecycle, access, and any external binding. A connector does not become a collection unless the app explicitly supplies the source contract, and an external collection never becomes a writable mirror.
450
465
 
451
466
  ## Boundaries
452
467
 
@@ -37,7 +37,7 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
37
37
  ### Managed config
38
38
 
39
39
  - `secret("NAME")` — reference a managed secret (operated via `noodle secrets`).
40
- - `variable("NAME")` — reference a managed variable (operated via `noodle variables`).
40
+ - `variable("NAME")` — reference managed configuration; add `{ schema, default?, portal?, requiredFor? }` and register in `server.variables` for typed business settings. Operate through `noodle variables` or authorized Portal settings.
41
41
 
42
42
  ### Customer auth
43
43
 
@@ -67,6 +67,7 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
67
67
  - `managedSecret`
68
68
  - `meilisearch`
69
69
  - `noodleManaged`
70
+ - `noodlePlatform`
70
71
  - `openAICompatible`
71
72
  - `publicWebsite`
72
73
  - `site`
@@ -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.100.0 hash:98c7b07c82a7d7ce -->
6
+ <!-- noodle-skill version:0.102.0 hash:98c7b07c82a7d7ce -->
7
7
 
8
8
  # building-mcp-apps
9
9
 
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
3
3
  description: "Use when all four API-evidence inputs exist—and only then: API base URL, authentication scheme, representative safe read, and observed response."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.100.0 hash:8020811f1769c538 -->
6
+ <!-- noodle-skill version:0.102.0 hash:8020811f1769c538 -->
7
7
 
8
8
  # connecting-apis-to-mcp
9
9