@noodleseed/agent-kit 0.99.1 → 0.101.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.
- package/manifest.json +373 -373
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/claude-code/authoring-mcp-servers/references/authoring-workflow.md +21 -2
- package/skills/claude-code/authoring-mcp-servers/references/sdk-surface.md +3 -1
- package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
- package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/claude-code/connecting-apis-to-mcp/references/authoring-workflow.md +21 -2
- package/skills/claude-code/creating-product-agent-guides/SKILL.md +1 -1
- package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
- package/skills/claude-code/deploying-mcp-services/references/cli-commands.md +1 -0
- package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
- package/skills/claude-code/designing-mcp-products/references/authoring-workflow.md +21 -2
- package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/claude-code/embedding-mcp-assistants/references/authoring-workflow.md +21 -2
- package/skills/claude-code/examples/acme-bistro/README.md +15 -44
- package/skills/claude-code/examples/acme-bistro/src/server.ts +66 -11
- package/skills/claude-code/examples/acme-bistro/test/server.test.ts +10 -0
- package/skills/claude-code/examples/acme-tasks/README.md +11 -11
- package/skills/claude-code/examples/gmail-multi-account/README.md +53 -31
- package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
- package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/claude-code/references/authoring-workflow.md +21 -2
- package/skills/claude-code/references/cli-commands.md +1 -0
- package/skills/claude-code/references/compile-errors.md +4 -1
- package/skills/claude-code/references/sdk-surface.md +3 -1
- package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/claude-code/wrapping-existing-applications/SKILL.md +1 -1
- package/skills/claude-code/wrapping-existing-applications/references/authoring-workflow.md +21 -2
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/codex/authoring-mcp-servers/references/authoring-workflow.md +21 -2
- package/skills/codex/authoring-mcp-servers/references/sdk-surface.md +3 -1
- package/skills/codex/building-mcp-apps/SKILL.md +1 -1
- package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/codex/connecting-apis-to-mcp/references/authoring-workflow.md +21 -2
- package/skills/codex/creating-product-agent-guides/SKILL.md +1 -1
- package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
- package/skills/codex/deploying-mcp-services/references/cli-commands.md +1 -0
- package/skills/codex/designing-mcp-products/SKILL.md +1 -1
- package/skills/codex/designing-mcp-products/references/authoring-workflow.md +21 -2
- package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/codex/embedding-mcp-assistants/references/authoring-workflow.md +21 -2
- package/skills/codex/examples/acme-bistro/README.md +15 -44
- package/skills/codex/examples/acme-bistro/src/server.ts +66 -11
- package/skills/codex/examples/acme-bistro/test/server.test.ts +10 -0
- package/skills/codex/examples/acme-tasks/README.md +11 -11
- package/skills/codex/examples/gmail-multi-account/README.md +53 -31
- package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
- package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/codex/references/authoring-workflow.md +21 -2
- package/skills/codex/references/cli-commands.md +1 -0
- package/skills/codex/references/compile-errors.md +4 -1
- package/skills/codex/references/sdk-surface.md +3 -1
- package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/wrapping-existing-applications/SKILL.md +1 -1
- package/skills/codex/wrapping-existing-applications/references/authoring-workflow.md +21 -2
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- Tests
|
|
18
18
|
- Secrets and variables
|
|
19
19
|
- Embedded assistant
|
|
20
|
+
- Managed collections
|
|
20
21
|
- Boundaries
|
|
21
22
|
|
|
22
23
|
## Input paths
|
|
@@ -41,9 +42,9 @@ Author in `server.ts`, then `noodle validate` → fix cited errors (see `compile
|
|
|
41
42
|
|
|
42
43
|
## Connectors
|
|
43
44
|
|
|
44
|
-
Declare connectors
|
|
45
|
+
Declare typed connectors:
|
|
45
46
|
|
|
46
|
-
- **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })
|
|
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.
|
|
47
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.
|
|
48
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`.
|
|
49
50
|
|
|
@@ -57,6 +58,7 @@ not imported or forwarded. The runtime opens one guarded session for one operati
|
|
|
57
58
|
credential, and closes it; it does not act as an agent for upstream sampling, roots, or elicitation.
|
|
58
59
|
|
|
59
60
|
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.
|
|
61
|
+
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.
|
|
60
62
|
|
|
61
63
|
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.
|
|
62
64
|
|
|
@@ -407,6 +409,12 @@ Adapt the representative arguments and assertions when business contracts change
|
|
|
407
409
|
|
|
408
410
|
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.
|
|
409
411
|
|
|
412
|
+
For business-editable configuration, declare `const notice = variable("SERVICE_NOTICE", { schema: z.string().max(500), default: "Welcome", portal: { label: "Service notice", group: "Guest experience" }, requiredFor: ["show_menu"] })` and register it in `server(..., { variables: [notice], ... }, tools)`. Use the same ref in ordinary fulfilment/connector arguments. 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.
|
|
413
|
+
|
|
414
|
+
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.
|
|
415
|
+
|
|
416
|
+
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.
|
|
417
|
+
|
|
410
418
|
## Embedded assistant
|
|
411
419
|
|
|
412
420
|
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.
|
|
@@ -441,6 +449,17 @@ The compiler validates and hashes every document at build time (bad extensions,
|
|
|
441
449
|
|
|
442
450
|
The managed crawler and managed index are the defaults and need no configuration. A component may instead bring its own crawler (`crawler: firecrawl({ apiKey: secret('FIRECRAWL_API_KEY') })` or `tavily(...)`) and/or its own index (`index: algolia({ appId: variable('ALGOLIA_APP_ID'), apiKey: secret('ALGOLIA_API_KEY') })` or `meilisearch({ host: variable(...), apiKey: secret(...) })`). The code declares only config names; operators supply values with `noodle secrets set` / `noodle variables set`, and deploy preflight fails closed naming any unset reference.
|
|
443
451
|
|
|
452
|
+
## Managed collections
|
|
453
|
+
|
|
454
|
+
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.
|
|
455
|
+
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.
|
|
456
|
+
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.
|
|
457
|
+
|
|
458
|
+
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.
|
|
459
|
+
|
|
460
|
+
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.
|
|
461
|
+
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.
|
|
462
|
+
|
|
444
463
|
## Boundaries
|
|
445
464
|
|
|
446
465
|
Import public app authoring only from `@noodleseed/one` (and its documented `/react` or `/platform` subpaths). Packages and subpaths under `@noodle-borg/*` are runtime implementation details, including `/portable` boundaries, and must never appear in customer `server.ts` code. Do not hand-author manifest JSON/YAML, runtime artifacts, connector IR, or hosted asset metadata. Do not read or copy secrets, bearer tokens, refresh tokens, static access keys, `.env`, `.env.noodle`, or `~/.noodle/config.json`. Hosted access is identity-based — do not add static data-plane credential paths.
|
|
@@ -3,7 +3,7 @@ name: creating-product-agent-guides
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP server needs a new or revised product agent guide, App Package skill, or explicit product-skill regeneration."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.101.0 hash:0e06e11254563b6a -->
|
|
7
7
|
|
|
8
8
|
# creating-product-agent-guides
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
|
|
|
3
3
|
description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.101.0 hash:8d75ad13f4b3120a -->
|
|
7
7
|
|
|
8
8
|
# debugging-mcp-delivery
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: deploying-mcp-services
|
|
|
3
3
|
description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.101.0 hash:3a1e14ff9b98ddbd -->
|
|
7
7
|
|
|
8
8
|
# deploying-mcp-services
|
|
9
9
|
|
|
@@ -61,6 +61,7 @@ Developer-facing `noodle` commands, grouped by area. Local authoring commands (`
|
|
|
61
61
|
| `noodle deployments` | List or inspect individual deployments (`deployments list`/`deployments inspect <id>`). |
|
|
62
62
|
| `noodle distributions` | Publish immutable host archives, record their lifecycle, and operate bounded delivery. |
|
|
63
63
|
| `noodle service` | Query hosted service capabilities. |
|
|
64
|
+
| `noodle solutions` | Install managed solutions and operate their grants, records, activity, exports, and deletion. |
|
|
64
65
|
| `noodle login` | Authenticate with Noodle Seed Cloud. |
|
|
65
66
|
| `noodle logout` | Clear saved credentials. |
|
|
66
67
|
| `noodle whoami` | Print the current authenticated user. |
|
|
@@ -3,7 +3,7 @@ name: designing-mcp-products
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.101.0 hash:a21b2ed9997f9454 -->
|
|
7
7
|
|
|
8
8
|
# designing-mcp-products
|
|
9
9
|
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- Tests
|
|
18
18
|
- Secrets and variables
|
|
19
19
|
- Embedded assistant
|
|
20
|
+
- Managed collections
|
|
20
21
|
- Boundaries
|
|
21
22
|
|
|
22
23
|
## Input paths
|
|
@@ -41,9 +42,9 @@ Author in `server.ts`, then `noodle validate` → fix cited errors (see `compile
|
|
|
41
42
|
|
|
42
43
|
## Connectors
|
|
43
44
|
|
|
44
|
-
Declare connectors
|
|
45
|
+
Declare typed connectors:
|
|
45
46
|
|
|
46
|
-
- **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })
|
|
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.
|
|
47
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.
|
|
48
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`.
|
|
49
50
|
|
|
@@ -57,6 +58,7 @@ not imported or forwarded. The runtime opens one guarded session for one operati
|
|
|
57
58
|
credential, and closes it; it does not act as an agent for upstream sampling, roots, or elicitation.
|
|
58
59
|
|
|
59
60
|
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.
|
|
61
|
+
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.
|
|
60
62
|
|
|
61
63
|
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.
|
|
62
64
|
|
|
@@ -407,6 +409,12 @@ Adapt the representative arguments and assertions when business contracts change
|
|
|
407
409
|
|
|
408
410
|
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.
|
|
409
411
|
|
|
412
|
+
For business-editable configuration, declare `const notice = variable("SERVICE_NOTICE", { schema: z.string().max(500), default: "Welcome", portal: { label: "Service notice", group: "Guest experience" }, requiredFor: ["show_menu"] })` and register it in `server(..., { variables: [notice], ... }, tools)`. Use the same ref in ordinary fulfilment/connector arguments. 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.
|
|
413
|
+
|
|
414
|
+
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.
|
|
415
|
+
|
|
416
|
+
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.
|
|
417
|
+
|
|
410
418
|
## Embedded assistant
|
|
411
419
|
|
|
412
420
|
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.
|
|
@@ -441,6 +449,17 @@ The compiler validates and hashes every document at build time (bad extensions,
|
|
|
441
449
|
|
|
442
450
|
The managed crawler and managed index are the defaults and need no configuration. A component may instead bring its own crawler (`crawler: firecrawl({ apiKey: secret('FIRECRAWL_API_KEY') })` or `tavily(...)`) and/or its own index (`index: algolia({ appId: variable('ALGOLIA_APP_ID'), apiKey: secret('ALGOLIA_API_KEY') })` or `meilisearch({ host: variable(...), apiKey: secret(...) })`). The code declares only config names; operators supply values with `noodle secrets set` / `noodle variables set`, and deploy preflight fails closed naming any unset reference.
|
|
443
451
|
|
|
452
|
+
## Managed collections
|
|
453
|
+
|
|
454
|
+
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.
|
|
455
|
+
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.
|
|
456
|
+
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.
|
|
457
|
+
|
|
458
|
+
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.
|
|
459
|
+
|
|
460
|
+
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.
|
|
461
|
+
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.
|
|
462
|
+
|
|
444
463
|
## Boundaries
|
|
445
464
|
|
|
446
465
|
Import public app authoring only from `@noodleseed/one` (and its documented `/react` or `/platform` subpaths). Packages and subpaths under `@noodle-borg/*` are runtime implementation details, including `/portable` boundaries, and must never appear in customer `server.ts` code. Do not hand-author manifest JSON/YAML, runtime artifacts, connector IR, or hosted asset metadata. Do not read or copy secrets, bearer tokens, refresh tokens, static access keys, `.env`, `.env.noodle`, or `~/.noodle/config.json`. Hosted access is identity-based — do not add static data-plane credential paths.
|
|
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
|
|
|
3
3
|
description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.101.0 hash:d554d661ed7ac7be -->
|
|
7
7
|
|
|
8
8
|
# embedding-mcp-assistants
|
|
9
9
|
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- Tests
|
|
18
18
|
- Secrets and variables
|
|
19
19
|
- Embedded assistant
|
|
20
|
+
- Managed collections
|
|
20
21
|
- Boundaries
|
|
21
22
|
|
|
22
23
|
## Input paths
|
|
@@ -41,9 +42,9 @@ Author in `server.ts`, then `noodle validate` → fix cited errors (see `compile
|
|
|
41
42
|
|
|
42
43
|
## Connectors
|
|
43
44
|
|
|
44
|
-
Declare connectors
|
|
45
|
+
Declare typed connectors:
|
|
45
46
|
|
|
46
|
-
- **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })
|
|
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.
|
|
47
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.
|
|
48
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`.
|
|
49
50
|
|
|
@@ -57,6 +58,7 @@ not imported or forwarded. The runtime opens one guarded session for one operati
|
|
|
57
58
|
credential, and closes it; it does not act as an agent for upstream sampling, roots, or elicitation.
|
|
58
59
|
|
|
59
60
|
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.
|
|
61
|
+
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.
|
|
60
62
|
|
|
61
63
|
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.
|
|
62
64
|
|
|
@@ -407,6 +409,12 @@ Adapt the representative arguments and assertions when business contracts change
|
|
|
407
409
|
|
|
408
410
|
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.
|
|
409
411
|
|
|
412
|
+
For business-editable configuration, declare `const notice = variable("SERVICE_NOTICE", { schema: z.string().max(500), default: "Welcome", portal: { label: "Service notice", group: "Guest experience" }, requiredFor: ["show_menu"] })` and register it in `server(..., { variables: [notice], ... }, tools)`. Use the same ref in ordinary fulfilment/connector arguments. 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.
|
|
413
|
+
|
|
414
|
+
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.
|
|
415
|
+
|
|
416
|
+
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.
|
|
417
|
+
|
|
410
418
|
## Embedded assistant
|
|
411
419
|
|
|
412
420
|
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.
|
|
@@ -441,6 +449,17 @@ The compiler validates and hashes every document at build time (bad extensions,
|
|
|
441
449
|
|
|
442
450
|
The managed crawler and managed index are the defaults and need no configuration. A component may instead bring its own crawler (`crawler: firecrawl({ apiKey: secret('FIRECRAWL_API_KEY') })` or `tavily(...)`) and/or its own index (`index: algolia({ appId: variable('ALGOLIA_APP_ID'), apiKey: secret('ALGOLIA_API_KEY') })` or `meilisearch({ host: variable(...), apiKey: secret(...) })`). The code declares only config names; operators supply values with `noodle secrets set` / `noodle variables set`, and deploy preflight fails closed naming any unset reference.
|
|
443
451
|
|
|
452
|
+
## Managed collections
|
|
453
|
+
|
|
454
|
+
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.
|
|
455
|
+
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.
|
|
456
|
+
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.
|
|
457
|
+
|
|
458
|
+
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.
|
|
459
|
+
|
|
460
|
+
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.
|
|
461
|
+
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.
|
|
462
|
+
|
|
444
463
|
## Boundaries
|
|
445
464
|
|
|
446
465
|
Import public app authoring only from `@noodleseed/one` (and its documented `/react` or `/platform` subpaths). Packages and subpaths under `@noodle-borg/*` are runtime implementation details, including `/portable` boundaries, and must never appear in customer `server.ts` code. Do not hand-author manifest JSON/YAML, runtime artifacts, connector IR, or hosted asset metadata. Do not read or copy secrets, bearer tokens, refresh tokens, static access keys, `.env`, `.env.noodle`, or `~/.noodle/config.json`. Hosted access is identity-based — do not add static data-plane credential paths.
|
|
@@ -1,51 +1,22 @@
|
|
|
1
|
-
# Acme Bistro —
|
|
1
|
+
# Acme Bistro — ordering with payment handoff
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
transaction** pattern: the customer browses the menu and builds the order *in chat*, and the order
|
|
5
|
-
completes in chat — only **payment** hands off, via a signed checkout deep link (the card never touches
|
|
6
|
-
the app). It pairs a view-backed `tool` menu/cart with app-only `tool` cart helpers and a model-visible
|
|
7
|
-
`create_checkout` tool backed by `handoff.allowedDomains`.
|
|
3
|
+
Fictional [`menu/cart`](src/server.ts) with off-app payment: card data stays outside the app.
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`references/experience-design.md` and `references/app-directory-compliance.md` teach. (Distinct from
|
|
13
|
-
`food-ordering`, which is the broad widget-composition proof; this one owns the design-first end-to-end +
|
|
14
|
-
compliance exemplar.)
|
|
5
|
+
`SERVICE_NOTICE` settings and native `guest_requests` demonstrate status, field exposure and notes. `submit_guest_request` requires an authorized installation and returns a receipt
|
|
6
|
+
for staff review. The installed skill's `references/authoring-workflow.md` owns setup and source contracts.
|
|
7
|
+
External collections declare a bounded connector scan; provider changes remain ordinary tools.
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
Staff can clear optional `guestReference` in Portal or an authored
|
|
10
|
+
`connectors.records.updateRecord({ collection: 'guest_requests', id, expectedRevision, patch: {}, unset: ['guestReference'] })`.
|
|
11
|
+
Required fields cannot be removed; `null` is a value, not deletion.
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
- [`design/wireframe.html`](design/wireframe.html) — the single-file wireframe (open in a browser) with the
|
|
20
|
-
embedded OpenAI Apps SDK compliance audit.
|
|
21
|
-
- [`design/api-contract.md`](design/api-contract.md) — the Recommended API shapes for the partner's kitchen/
|
|
22
|
-
ordering backend.
|
|
13
|
+
## Design deliverables
|
|
23
14
|
|
|
24
|
-
|
|
15
|
+
- [UX Document](design/UX-Document.md)
|
|
16
|
+
- [Browser wireframe and compliance audit](design/wireframe.html)
|
|
17
|
+
- [Partner API contract](design/api-contract.md)
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
noodle validate
|
|
28
|
-
noodle test
|
|
29
|
-
noodle dev
|
|
30
|
-
```
|
|
19
|
+
## Run
|
|
31
20
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
noodle tools list
|
|
36
|
-
noodle tools call show_menu --args '{"customer":"Asha"}'
|
|
37
|
-
noodle tools call create_checkout --args '{"customer":"Asha","cartToken":"cart_1","total":36}'
|
|
38
|
-
noodle check --target chatgpt
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Deploy
|
|
42
|
-
|
|
43
|
-
```sh
|
|
44
|
-
noodle link --org demo --app acme-bistro
|
|
45
|
-
noodle deploy --access owner-only
|
|
46
|
-
noodle open
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
This example has no connector secrets and does not include tokens, caller-key mechanisms, or
|
|
50
|
-
`.env.noodle` values. The menu, prices, and URLs are fictional; payment is handled off-app on
|
|
51
|
-
`acme.example`, never in chat.
|
|
21
|
+
Use `noodle validate`, `noodle test`, and `noodle dev` for the local menu/cart.
|
|
22
|
+
Native submission requires installed storage, grants and public intake.
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
annotations,
|
|
3
|
+
managedCollection,
|
|
4
|
+
noodlePlatform,
|
|
5
|
+
server,
|
|
6
|
+
tool,
|
|
7
|
+
variable,
|
|
8
|
+
z,
|
|
9
|
+
} from '@noodleseed/one';
|
|
2
10
|
|
|
3
|
-
//
|
|
4
|
-
// menu and builds the order in chat, and the order completes in chat — only *payment* hands off, via a
|
|
5
|
-
// checkout deep link (the card never touches the app). It is the flagship for the end-to-end pattern.
|
|
6
|
-
//
|
|
7
|
-
// Authoring notes:
|
|
8
|
-
// - A tool `fulfil` is *recorded*, not run as live JS. Inline inputs directly as `${input.x}` into
|
|
9
|
-
// output strings; do not transform them (no arithmetic/encode/filter) or substitution breaks. The
|
|
10
|
-
// cart total is summed in the widget (live React), not in a `fulfil`. The menu below is static data.
|
|
11
|
-
// - Tool inputs stay business-facing. `__noodleIntent` is reserved for Noodle's optional serve-time
|
|
12
|
-
// analytics adapter and is removed before `fulfil`, so applications never declare or handle it.
|
|
11
|
+
// Fictional ordering app. Payment uses a checkout link; native guest requests require installation.
|
|
13
12
|
|
|
14
13
|
const menu = [
|
|
15
14
|
{ id: 'stone_pizza', name: 'Stone-baked Margherita', price: 14, kind: 'Mains' },
|
|
@@ -21,6 +20,14 @@ const menu = [
|
|
|
21
20
|
|
|
22
21
|
const itemId = z.enum(['stone_pizza', 'roast_bowl', 'house_salad', 'lemon_tart', 'sparkling']);
|
|
23
22
|
|
|
23
|
+
// A business administrator may change this notice; the reusable application stays the same.
|
|
24
|
+
const serviceNotice = variable('SERVICE_NOTICE', {
|
|
25
|
+
schema: z.string().max(500),
|
|
26
|
+
default: 'Ask us about dietary requirements before placing your order.',
|
|
27
|
+
portal: { label: 'Service notice', group: 'Guest experience' },
|
|
28
|
+
requiredFor: ['show_menu'],
|
|
29
|
+
});
|
|
30
|
+
|
|
24
31
|
const menuItemOutput = z.object({
|
|
25
32
|
id: z.string(),
|
|
26
33
|
name: z.string(),
|
|
@@ -28,6 +35,30 @@ const menuItemOutput = z.object({
|
|
|
28
35
|
kind: z.string(),
|
|
29
36
|
});
|
|
30
37
|
|
|
38
|
+
const guestRequestRecord = z.object({
|
|
39
|
+
locationReference: z.string().min(1).max(120),
|
|
40
|
+
requestType: z.enum(['reservation_help', 'accessibility', 'dietary_question', 'other']),
|
|
41
|
+
summary: z.string().min(1).max(1000),
|
|
42
|
+
guestReference: z.string().max(120).optional(),
|
|
43
|
+
progress: z.enum(['received', 'reviewing', 'handled']).default('received'),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const guestRequests = managedCollection('guest_requests', {
|
|
47
|
+
title: 'Guest requests',
|
|
48
|
+
description: 'Guest service requests that restaurant staff can review and resolve.',
|
|
49
|
+
schemaVersion: 1,
|
|
50
|
+
// No source is declared, so Noodle is authoritative. Outside-owned records would name an exact
|
|
51
|
+
// connector scan contract here; changes in that outside system would remain ordinary tools.
|
|
52
|
+
record: guestRequestRecord,
|
|
53
|
+
management: { notes: true },
|
|
54
|
+
publicFields: ['locationReference', 'requestType', 'summary'],
|
|
55
|
+
editableFields: ['locationReference', 'requestType', 'summary', 'guestReference', 'progress'],
|
|
56
|
+
fields: { progress: { label: 'Progress' }, summary: { label: 'Guest request' } },
|
|
57
|
+
summaryFields: ['requestType', 'summary', 'progress'],
|
|
58
|
+
filterFields: ['progress'],
|
|
59
|
+
sortFields: ['progress'],
|
|
60
|
+
});
|
|
61
|
+
|
|
31
62
|
// Tool annotations for host planners: the menu read is read-only; cart edits are local writes; checkout
|
|
32
63
|
// opens an external (payment) link.
|
|
33
64
|
const readOnly = annotations.readOnly();
|
|
@@ -52,8 +83,30 @@ export default server(
|
|
|
52
83
|
handoff: {
|
|
53
84
|
allowedDomains: ['https://pay.acme.example', 'https://acme.example'],
|
|
54
85
|
},
|
|
86
|
+
// Reusable business-record intent. Storage, lifecycle, access, and public intake bind separately.
|
|
87
|
+
collections: [guestRequests],
|
|
88
|
+
use: { records: noodlePlatform.records.v1 },
|
|
89
|
+
variables: [serviceNotice],
|
|
55
90
|
},
|
|
56
91
|
[
|
|
92
|
+
tool('submit_guest_request', {
|
|
93
|
+
title: 'Submit a guest request',
|
|
94
|
+
description: 'Record a guest request for staff review; this does not confirm a reservation.',
|
|
95
|
+
annotations: annotations.localAction({ destructive: false, confirm: true }),
|
|
96
|
+
input: guestRequestRecord.pick({ locationReference: true, requestType: true, summary: true }),
|
|
97
|
+
output: z.object({ recordId: z.string() }),
|
|
98
|
+
fulfil: ({ input, connectors }) => {
|
|
99
|
+
const receipt = connectors.records.submitRecord({
|
|
100
|
+
collection: 'guest_requests',
|
|
101
|
+
payload: {
|
|
102
|
+
locationReference: input.locationReference,
|
|
103
|
+
requestType: input.requestType,
|
|
104
|
+
summary: input.summary,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
return { recordId: receipt.recordId };
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
57
110
|
tool('show_menu', {
|
|
58
111
|
title: 'Show the menu',
|
|
59
112
|
description: 'Show the Acme Bistro menu and render the ordering widget.',
|
|
@@ -62,6 +115,7 @@ export default server(
|
|
|
62
115
|
output: z.object({
|
|
63
116
|
status: z.string(),
|
|
64
117
|
customer: z.string(),
|
|
118
|
+
serviceNotice: z.string().max(500),
|
|
65
119
|
// Bounded list: the menu is a fixed catalog, so the ceiling is declared on the shape rather
|
|
66
120
|
// than taken as a pagination input. `noodle check` reports `tool_design_output_bounds`.
|
|
67
121
|
items: z.array(menuItemOutput).max(50),
|
|
@@ -69,6 +123,7 @@ export default server(
|
|
|
69
123
|
fulfil: ({ input }) => ({
|
|
70
124
|
status: `Acme Bistro menu is ready for ${input.customer}. Build the order here; pay at checkout.`,
|
|
71
125
|
customer: input.customer,
|
|
126
|
+
serviceNotice,
|
|
72
127
|
items: menu,
|
|
73
128
|
}),
|
|
74
129
|
viewTitle: 'Order at Acme Bistro',
|
|
@@ -18,4 +18,14 @@ describe('acme-bistro example', () => {
|
|
|
18
18
|
expect(text).toContain('add_to_cart');
|
|
19
19
|
expect(text).toContain('create_checkout');
|
|
20
20
|
});
|
|
21
|
+
|
|
22
|
+
it('declares guest records and explicitly authors the native submission tool', async () => {
|
|
23
|
+
const manifest = await app.toManifest();
|
|
24
|
+
expect(manifest.server.collections).toEqual([
|
|
25
|
+
expect.objectContaining({ name: 'guest_requests', schemaVersion: 1 }),
|
|
26
|
+
]);
|
|
27
|
+
expect(
|
|
28
|
+
manifest.tools.find((tool) => tool.name === 'submit_guest_request')?.fulfilment.steps,
|
|
29
|
+
).toMatchObject([{ use: 'records.submit_record' }]);
|
|
30
|
+
});
|
|
21
31
|
});
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
# Acme Tasks — designed around its top-3 prioritized user flows
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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:**
|
|
4
|
-
authenticated accounts inside one MCP server.
|
|
3
|
+
**Owns:** One reusable connector bound to independently authenticated accounts in one MCP server.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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.
|