@noodleseed/agent-kit 0.8.1 → 0.10.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.
@@ -3,8 +3,13 @@
3
3
  ## Contents
4
4
 
5
5
  - Input paths
6
+ - Fit check
6
7
  - Repair loop
7
8
  - Connectors
9
+ - HTTP connector example
10
+ - Worked example (full server)
11
+ - Compute connector example
12
+ - Tests
8
13
  - Secrets and variables
9
14
  - Boundaries
10
15
 
@@ -14,6 +19,10 @@
14
19
  2. **OpenAPI import** — `noodle import openapi <file>` emits a starter `server.ts` from a spec. Use it when the user provides an OpenAPI document.
15
20
  3. **User interview** — Noodle does not interview; you do. Cover custom APIs/integrations, eligibility rules, quoting/approval logic, and private schemas (SQL DDL or JSON samples for custom `connector` declarations). Ask for concrete examples and sample payloads; do not guess a schema from a URL or invent endpoints.
16
21
 
22
+ ## Fit check
23
+
24
+ Before building, confirm the idea fits a conversational surface: 1–3 focused actions where saying it beats clicking, plus data or actions the model lacks on its own. Poor fits — long-form or static content, dashboards, deep multi-step navigation, or a full app port. When an idea does not fit, narrow the scope to the actions that do.
25
+
17
26
  ## Repair loop
18
27
 
19
28
  Author in `server.ts`, then `noodle validate` → fix cited errors (see `compile-errors.md`) → re-validate → `noodle test` → `noodle dev`. Keep the loop tight and error-driven.
@@ -27,6 +36,118 @@ Declare connectors as data, not imperative code:
27
36
 
28
37
  Tools record connector calls into a flow; recording is not execution. Do not branch on runtime outputs with native `if` — use declarative `when(...)` conditions.
29
38
 
39
+ HTTP connector auth variants: `bearer` (`{ kind: "bearer", secret: secret("API_TOKEN") }`), `apiKey` (`{ kind: "apiKey", header: "X-API-Key", secret: secret("API_KEY") }`), `oauth2ClientCredentials`, `delegatedOAuth`, and `delegatedSessionCookie`. Use managed `secret(...)` / `variable(...)` refs for all values that differ by org/app/env.
40
+
41
+ ## HTTP connector example
42
+
43
+ The operation mapping in detail: `request` templates the outbound call, `response` maps the HTTP body into your typed `output`.
44
+
45
+ ```ts
46
+ import { connector, secret, variable } from '@noodleseed/one';
47
+
48
+ const crm = connector('crm').version('1.0.0').http({
49
+ baseUrl: variable('CRM_BASE_URL'),
50
+ allowedOrigins: [variable('CRM_BASE_URL')],
51
+ auth: { kind: 'bearer', secret: secret('CRM_TOKEN') },
52
+ operations: {
53
+ findCustomer: {
54
+ type: 'read',
55
+ input: { email: { type: "string", required: true } },
56
+ output: { id: { type: "string", required: true }, name: { type: "string" } },
57
+ method: "GET",
58
+ path: "/customers",
59
+ request: { query: { email: "${args.email}" } },
60
+ response: { id: "${response.body.data.0.id}", name: "${response.body.data.0.name}" },
61
+ },
62
+ createTicket: {
63
+ type: 'action',
64
+ input: { customerId: { type: "string", required: true }, body: { type: "string", required: true } },
65
+ output: { ticketId: { type: "string", required: true } },
66
+ method: "POST",
67
+ path: "/tickets",
68
+ request: { body: { customer_id: "${args.customerId}", body: "${args.body}" } },
69
+ response: { ticketId: "${response.body.id}" },
70
+ },
71
+ },
72
+ });
73
+ ```
74
+
75
+ Use `${args.field}` for tool/operation inputs and `${response.body.path}` for response mapping. Prefer explicit `allowedOrigins`; OAuth token/session URLs must also be on an allowed origin. For client credentials use `{ kind: "oauth2ClientCredentials", tokenUrl, clientId, clientSecret, scopes? }`; for per-customer downstream calls use `delegatedOAuth` or `delegatedSessionCookie` with a matching `customerAuth` server option.
76
+
77
+ ## Worked example (full server)
78
+
79
+ Declare the API as data, bind it with `use`, then record a call in a tool. `auth` reads a managed `secret(...)` — never inline a key. Map the HTTP body into your typed `output` with `${response...}`.
80
+
81
+ ```ts
82
+ import { connector, secret, server, tool, z } from '@noodleseed/one';
83
+
84
+ const crm = connector('acme_crm')
85
+ .version('1.0.0')
86
+ .http({
87
+ baseUrl: 'https://api.acme.example',
88
+ allowedOrigins: ['https://api.acme.example'],
89
+ auth: { kind: 'apiKey', header: 'X-Api-Key', secret: secret('ACME_CRM_TOKEN') },
90
+ operations: {
91
+ get_ticket: {
92
+ type: 'read',
93
+ method: 'GET',
94
+ path: '/v1/tickets/{id}',
95
+ input: { id: { type: 'string', required: true } },
96
+ output: { subject: { type: 'string' }, status: { type: 'string' } },
97
+ response: { subject: '${response.data.subject}', status: '${response.data.state}' },
98
+ },
99
+ },
100
+ });
101
+
102
+ export default server('support', { title: 'Support', version: '1.0.0', use: { crm } }, [
103
+ tool('ticket_status', {
104
+ description: 'Look up a support ticket by id.',
105
+ input: z.object({ ticket_id: z.string() }),
106
+ output: z.object({ subject: z.string(), status: z.string() }),
107
+ fulfil: ({ input, connectors }) => {
108
+ const ticket = connectors.crm.get_ticket({ id: input.ticket_id });
109
+ return { subject: ticket.subject, status: ticket.status };
110
+ },
111
+ }),
112
+ ]);
113
+ ```
114
+
115
+ More: `auth.kind` is `bearer` | `apiKey` (needs `header`) | `oauth2ClientCredentials` | `delegatedOAuth` | `delegatedSessionCookie`. Use `.compute(name, { input, output, run })` for a sandboxed transform; `provides:` (instead of `use:`) exposes a connector only to compute `callOperation`; and `noodle import openapi <file>` generates a connector from an OpenAPI spec.
116
+
117
+ ## Compute connector example
118
+
119
+ ```ts
120
+ const scoring = connector('scoring').version('1.0.0').compute('normalize', {
121
+ input: { email: { type: "string", required: true }, priority: { type: "string" } },
122
+ output: { score: { type: "number", required: true } },
123
+ calls: { findCustomer: 'crm.findCustomer' },
124
+ limits: { timeoutMs: 1000, maxHostCalls: 2 },
125
+ run(input, { callOperation }) {
126
+ const customer = callOperation("findCustomer", { email: input.email }) as { id?: string };
127
+ return { score: customer.id && input.priority === "high" ? 100 : 50 };
128
+ },
129
+ });
130
+ ```
131
+
132
+ Compute `run` functions are serialized and sandboxed: no imports, no closure capture, no `fetch`, no `process`. Any backing-system call must be declared in `calls` and invoked through `callOperation`. For conditional flow edges, use `when(...)` in recorded fulfilment instead of native branching on connector outputs.
133
+
134
+ ## Tests
135
+
136
+ Use Vitest for app-local tests. Keep fixtures project-local; do not import from `examples/`. A minimum test suite imports the default server, checks the intended definitions compile, then lets `noodle test --json` perform the loopback MCP smoke.
137
+
138
+ ```ts
139
+ import { describe, expect, it } from 'vitest';
140
+ import app from '../src/server.js';
141
+
142
+ describe('server', () => {
143
+ it('declares the expected tool surface', () => {
144
+ expect(app.name).toBe('support_assistant');
145
+ });
146
+ });
147
+ ```
148
+
149
+ After focused tests pass, run `noodle validate --json`, `noodle test --json`, and then `noodle dev` for interactive local verification.
150
+
30
151
  ## Secrets and variables
31
152
 
32
153
  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.
@@ -54,7 +54,7 @@ Every `noodle` command, grouped by area. Local authoring commands (`validate`, `
54
54
  | `noodle rollback` | Roll back to a previous deployment. |
55
55
  | `noodle archive` | Archive the whole app: endpoints answer 410 Gone; hard-deleted after the retention window. |
56
56
  | `noodle restore` | Restore an archived app within the retention window. |
57
- | `noodle access` | Set the access mode (owner-only|org-members|authenticated|customers). |
57
+ | `noodle access` | Set the access mode (owner-only\|org-members\|authenticated\|customers). |
58
58
  | `noodle apps` | List or inspect hosted apps for an org (`apps list`/`apps inspect <app>`). |
59
59
  | `noodle envs` | List or inspect environments for an app (`envs list`/`envs inspect <env>`). |
60
60
  | `noodle deployments` | List or inspect individual deployments (`deployments list`/`deployments inspect <id>`). |
@@ -64,7 +64,7 @@ Every `noodle` command, grouped by area. Local authoring commands (`validate`, `
64
64
  | `noodle whoami` | Print the current authenticated user. |
65
65
  | `noodle list` | Removed — promoted to `deployments list` (prints the recovery pointer and exits 2). |
66
66
  | `noodle github` | Connect, inspect, or disconnect the GitHub repository behind an app’s GitHub-native deploys (`connect`/`status`/`disconnect`; `connect` opens a browser install, `--repo` for headless). |
67
- | `noodle target` | Show or set the deployment target (local|cloud|other). |
67
+ | `noodle target` | Show or set the deployment target (local\|cloud\|other). |
68
68
 
69
69
  ## Org & members
70
70
 
@@ -87,8 +87,8 @@ Every `noodle` command, grouped by area. Local authoring commands (`validate`, `
87
87
  | `noodle audit` | Operator governance audit status and event queries. |
88
88
  | `noodle logs` | View service/deployment logs. |
89
89
  | `noodle metrics` | MCP analytics for a deployed server (volume, sessions, latency percentiles, two-tier errors, tools, clients). Agents: `noodle metrics --agent-output` for a health verdict + next actions. |
90
- | `noodle events` | The per-request MCP event stream with status/tool/client filters; `--session <id>` replays one session in order. Agents: add `--json` and filter (`--status tool_error|mcp_error`) when debugging. |
91
- | `noodle alerts` | Analytics alert rules (`add|list|remove|test`): an edge-triggered webhook fires when error share, error count, calls, or p95 latency breaches. Webhook URLs are stored server-side and shown redacted. |
90
+ | `noodle events` | The per-request MCP event stream with status/tool/client filters; `--session <id>` replays one session in order. Agents: add `--json` and filter (`--status tool_error\|mcp_error`) when debugging. |
91
+ | `noodle alerts` | Analytics alert rules (`add\|list\|remove\|test`): an edge-triggered webhook fires when error share, error count, calls, or p95 latency breaches. Webhook URLs are stored server-side and shown redacted. |
92
92
  | `noodle policy` | Manage policy (status/list/show/effective/simulate/suspend/quota/rate/...). |
93
93
 
94
94
  ## CLI maintenance
@@ -7,7 +7,7 @@
7
7
 
8
8
  ## The repair loop
9
9
 
10
- Run `noodle validate` (add `--json` for machine-readable output, `--fix-prompt` for an agent repair prompt). Each error carries a `code`, a dotted `path` to the offending field, and a `message`; many also carry `expected`/`got`, `didYouMean`/`suggestions`, and a `docAnchor`. Fix the specific error the `path` locates, then re-validate. Do not freeform re-edit. Once `noodle validate` passes, run `noodle test`, then `noodle dev`.
10
+ Run `noodle validate` (add `--json` for the machine-readable envelope, `--fix-prompt` for an agent repair prompt). On failure the envelope is `{ok:false,error:{code,message,fix,next,errors:[{code,path,message}]}}`: each entry in `error.errors[]` carries a `code`, a dotted `path` to the offending field, and a `message`; many also carry `expected`/`got`, `didYouMean`/`suggestions`, and a `docAnchor` (the full envelope is in `agent-contract.md`). Fix the specific error the `path` locates, then re-validate. Do not freeform re-edit. Once `noodle validate` passes, run `noodle test`, then `noodle dev`.
11
11
 
12
12
  ## Error codes
13
13
 
@@ -5,9 +5,12 @@
5
5
  - Authenticate
6
6
  - Link and target
7
7
  - Deploy and inspect
8
+ - Connect into a host
8
9
  - Access modes
9
10
  - Org and members
10
11
  - Config and observability
12
+ - Agent-safe CLI recipes
13
+ - Analytics
11
14
 
12
15
  ## Authenticate
13
16
 
@@ -21,6 +24,24 @@
21
24
 
22
25
  `noodle deploy` deploys the server. Then `noodle open` (latest URL), `noodle status`, `noodle inspect` (metadata, no secrets), `noodle smoke` (readiness diagnostics), and `noodle rollback <deploymentId>` to revert.
23
26
 
27
+ ## Connect into a host
28
+
29
+ Once deployed, register the server as a tool in a host with `noodle connect <host>` (`claude-code`, `codex`, `chatgpt`, `cursor`, `vscode`, `claude`, `inspector`) — it prints the exact config to paste.
30
+
31
+ - **Claude Code / Claude Desktop** (verified) — add the `mcpServers` block, or one-shot `claude mcp add-json noodle-server '<json>'`:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "noodle-server": { "type": "https", "url": "https://<app>.mcp.noodleseed.dev" }
37
+ }
38
+ }
39
+ ```
40
+
41
+ - **Codex / Cursor / VS Code** — the same `mcpServers` block is emitted as a starting point (these hosts' config formats are not officially documented). Wiring a deployed Noodle server into Codex means registering that block in Codex's MCP config.
42
+ - **ChatGPT / Claude.ai** — no config file: open the host's Settings → Connectors → Add custom connector, paste the MCP URL, then authenticate.
43
+ - `noodle connect codex|claude-code --write` writes the project-local agent files (only these two targets).
44
+
24
45
  ## Access modes
25
46
 
26
47
  `noodle access set owner-only|org-members|authenticated|customers` controls who can call the deployed server. Hosted access is identity-based; never add static data-plane keys.
@@ -33,6 +54,26 @@
33
54
 
34
55
  Manage runtime config with `noodle secrets` / `noodle variables` (scoped org/app/env). Operators use `noodle logs`, `noodle audit`, and `noodle policy` for logs, governance audit, and policy.
35
56
 
57
+ ## Agent-safe CLI recipes
58
+
59
+ Use explicit flags in headless runs so commands never wait for a prompt:
60
+
61
+ ```sh
62
+ noodle link --org acme --app support-assistant --env prod
63
+ noodle secrets set CRM_TOKEN --scope env --org acme --app support-assistant --env prod --from-env CRM_TOKEN
64
+ noodle secrets set CRM_CERT --scope env --org acme --app support-assistant --env prod --from-file ./cert.pem
65
+ printf %s "$CRM_TOKEN" | noodle secrets set CRM_TOKEN --scope env --org acme --app support-assistant --env prod --from-stdin
66
+ noodle variables set CRM_BASE_URL --scope env --org acme --app support-assistant --env prod --value https://crm.example.com
67
+ noodle secrets list --scope env --org acme --app support-assistant --env prod --json
68
+ noodle validate --json
69
+ noodle test --json
70
+ noodle deploy --json
71
+ noodle smoke --json
72
+ noodle agents doctor --json
73
+ ```
74
+
75
+ `secrets resolve` is for local diagnostics only; do not print resolved values into prompts, logs, tests, or docs. Prefer `--from-env`, `--from-file`, or `--from-stdin` over inline `--value` for sensitive values. Variables may use `--value` when the value is non-secret.
76
+
36
77
  ## Analytics (verify after deploy, debug errors)
37
78
 
38
- After a deploy gets traffic, verify with `noodle metrics --agent-output` — it returns a `health` verdict (`ok`/`attention`), a one-line summary, and `attention[]` items each carrying the exact next command. When a tool errors, drill in with `noodle events --tool <name> --json` (filters: `--status tool_error|mcp_error`, `--client <name>`); `noodle events --session <id> --json` replays one session chronologically. `--json` on both returns the full payload; human runs get the branded report. Two-tier errors: `tool_error` is recoverable (handed back to the model), `mcp_error` needs attention (protocol/timeout/internal).
79
+ After a deploy gets traffic, verify with `noodle metrics --agent-output` — it returns a `health` verdict (`ok`/`attention`), a one-line summary, and `attention[]` items each carrying the exact next command. When a tool errors, drill in with `noodle events --tool <name> --json` (filters: `--status tool_error|mcp_error`, `--client <name>`); `noodle events --session <id> --json` replays one session chronologically. `--json` on both returns the full payload; human runs get the branded report. Two-tier errors: `tool_error` is recoverable (handed back to the model), `mcp_error` needs attention (protocol/timeout/internal). Wire edge-triggered webhooks on error share, error count, calls, or p95 latency with `noodle alerts add|list|remove|test`.
@@ -6,11 +6,14 @@ Flagship examples (one per capability). Read the matching example for the patter
6
6
  | :-- | :-- |
7
7
  | `hello` | Minimal TypeScript quickstart — a single tool, no connectors/widgets. |
8
8
  | `weather` | HTTP connectors, multi-step flows, and the sandboxed compute connector. |
9
- | `restaurant-pickup` | MCP Apps widgets, React `view` authoring, assets, branding, and handoff. |
9
+ | `food-ordering` | Consumer ordering MCP App widgets, app-only helpers, cart state, assets, branding, and handoff. |
10
10
  | `customer-auth` | End-user (customer) auth via OIDC/Firebase bridge with delegated credentials. |
11
11
  | `stateful-draft` | Durable, caller-scoped widget state handles with optimistic revisions. |
12
12
  | `perplexity` | A real SaaS API with bearer auth and a managed `secret`. |
13
+ | `bitcoin` | API-key HTTP connector, custom auth header, and compute normalization. |
14
+ | `sharepoint` | Microsoft SharePoint delegated Microsoft Entra auth and Graph tools. |
13
15
  | `internal-ops-demo` | Governed internal connectivity — tools/resources/prompts, role-shaped output. |
16
+ | `docs-assistant` | Docs/knowledge assistant grounding coding agents in the live docs export. |
14
17
 
15
18
  ## Canonical server.ts
16
19
 
@@ -0,0 +1,31 @@
1
+ # Publish to app directories
2
+
3
+ Directory requirements evolve — treat this as the workflow map and verify against the host’s current submission docs before submitting.
4
+
5
+ ## Contents
6
+
7
+ - Readiness gate
8
+ - ChatGPT apps directory
9
+ - Claude connectors directory
10
+
11
+ ## Readiness gate
12
+
13
+ Before any submission:
14
+
15
+ 1. `noodle check --target chatgpt` must be clean — every widget needs `domain` (one https origin per app) and an exact `csp` (hosts require the CSP to list precisely the domains you fetch from).
16
+ 2. Audit tool responses in developer mode: run realistic prompts and strip anything not strictly needed — PII, internal identifiers (session/trace/request IDs, internal account IDs), and any secrets.
17
+ 3. The server must be deployed and publicly reachable: `noodle deploy`, confirm with `noodle open --print` and `noodle smoke`. Reviewers connect to the real endpoint — never submit a placeholder or loopback URL, and the access mode must not be `owner-only` (`noodle access set`).
18
+ 4. Polish the listing surface: tool descriptions, widget titles, and the `server` branding tokens are what reviewers and users see.
19
+
20
+ ## ChatGPT apps directory
21
+
22
+ Submit from the OpenAI developer dashboard (platform.openai.com → Apps):
23
+
24
+ - Complete organization identity verification first (individual or business) — it is enforced at review time.
25
+ - The submission form asks for the app name, logo, description, company and privacy policy URLs, MCP server URL and tool information, screenshots, test prompts with expected responses, and localization details.
26
+ - One version may be published and one in review at a time; to revise a pending submission, cancel the review and resubmit rather than creating a new app.
27
+ - Review combines automated checks and manual evaluation; rejections come with feedback — fix and resubmit, or reply to appeal. An approved app is also distributed as a Codex plugin.
28
+
29
+ ## Claude connectors directory
30
+
31
+ Anthropic runs a connectors directory for Claude; submission goes through Anthropic’s published process (see the Anthropic connectors directory FAQ on support.claude.com). The same readiness gate applies: deployed public endpoint, clean `noodle check`, and graceful degradation where Apps rendering is unavailable.
@@ -1,39 +1,180 @@
1
1
  # @noodleseed/one SDK surface
2
2
 
3
- Import these from `@noodleseed/one`. They are declarative builders that emit manifest data — do not hand-author the manifest or runtime artifacts. React view helpers come from `@noodleseed/one/react` (`generateHelpers`).
3
+ Import these from `@noodleseed/one`. They are declarative builders that emit manifest data — do not hand-author the manifest or runtime artifacts. React view helpers come from `@noodleseed/one/react` (`generateHelpers`); the hook surface is documented in `widgets-and-apps.md`.
4
+ Platform helper connectors are explicit subpath imports from `@noodleseed/one/platform` (`noodlePlatform`, `noodlePlatformCatalog`) when an app needs first-party hosted state APIs.
4
5
 
5
- ## Server & tools
6
+ ## Contents
7
+
8
+ - Exports by area
9
+ - Authoring signatures
10
+ - Recipes
11
+
12
+ ## Exports by area
13
+
14
+ ### Server & tools
6
15
 
7
16
  - `server(name, options, definitions)` — the server/app root.
8
17
  - `tool(name, { description, input, output, fulfil })` — a model-visible tool.
9
18
  - `toolWithWidget(name, { ..., view })` — a model-visible tool that renders an MCP Apps widget.
10
19
  - `toolForWidget(name, { ... })` — a widget-only helper tool, hidden from the model.
11
20
 
12
- ## Widgets & assets
21
+ ### Widgets & assets
13
22
 
14
23
  - `widget(...)` — declare a widget/view component.
15
24
  - `asset("./path")` — reference a packaged asset (e.g. an image).
16
25
  - `annotations(...)` — tool/Apps annotation metadata.
17
26
 
18
- ## Connectors & flows
27
+ ### Connectors & flows
19
28
 
20
29
  - `connector("id").version(...).http({...})` or `.compute(...)` — declarative data connectors.
21
30
  - `when(...)` — declarative conditions for recorded flows (no native branching on runtime values).
22
31
 
23
- ## Resources & prompts
32
+ ### Resources & prompts
24
33
 
25
34
  - `resource(name, { ... })` — an MCP resource.
26
35
  - `prompt(name, { ... })` — an MCP prompt.
27
36
 
28
- ## Managed config
37
+ ### Managed config
29
38
 
30
39
  - `secret("NAME")` — reference a managed secret (operated via `noodle secrets`).
31
40
  - `variable("NAME")` — reference a managed variable (operated via `noodle variables`).
32
41
 
33
- ## Sessions
42
+ ### Customer auth
43
+
44
+ - `customerAuth.oidc(...)`, `.firebase(...)`, `.microsoft(...)`, or `.bridge(...)` — end-user/customer identity for `--access customers` deployments.
45
+
46
+ ### Sessions
34
47
 
35
48
  - `handoffSession(...)` — typed cross-host handoff session envelopes.
36
49
 
37
- ## Schemas
50
+ ### Schemas
51
+
52
+ - `z` — Zod, for input/output schemas (compiles to JSON Schema 2020-12).
53
+
54
+ ## Authoring signatures
55
+
56
+ - `server(name, options, definitions)` — `options` commonly includes `title`, `version`, `instructions`, `branding`, `auth`, `use`, `provides`, `state`, and `handoff`; `definitions` is the array of tools/resources/prompts/widgets.
57
+ - `tool(name, { description, input, output, annotations?, fulfil })` — `input`/`output` are Zod schemas; `fulfil({ input, connectors, user })` returns data matching `output`.
58
+ - `toolWithWidget(name, { description, input, output, fulfil, view })` — same as `tool`, plus `view: { component, entry }` for a React widget.
59
+ - `toolForWidget(name, { input, output, fulfil })` — helper tool for widget actions; hidden from the model.
60
+ - `resource(name, { uri, description?, mimeType?, fulfil })` and `prompt(name, { description?, arguments?, fulfil })` expose MCP resources/prompts.
61
+ - `widget(name, { title, view, csp?, domain?, permissions? })` declares reusable view metadata; `asset("./path")` packages local files.
62
+ - `customerAuth.*(...)` belongs in `server` options when deployed customer callers need verified identity; inspect `examples/customer-auth` or `examples/sharepoint` before using it.
63
+ - `state` defines durable widget state handles; `handoff` declares allowed external domains for safe host handoff.
64
+
65
+ ## Recipes
66
+
67
+ Minimal, complete, compiling recipes — author in `src/server.ts`, then `noodle validate`. Inside a `fulfil`, `ctx.input` (a prompt’s arguments or a templated resource’s URI variables) and `ctx.connectors` are **symbolic**: reference them to record a flow. Recording is not execution, so never branch on their runtime values with native `if` — use `when(...)`.
68
+
69
+ ### Resource
70
+
71
+ `resource(name, { uri, title?, description?, mimeType?, fulfil })`. `fulfil` returns `{ contents: [{ uri, mimeType, text }] }`. Use a fixed URI for a constant document, or a `{var}` template whose variable arrives on `ctx.input`.
72
+
73
+ ```ts
74
+ import { resource } from '@noodleseed/one';
75
+
76
+ // Fixed-URI resource: one constant document the model can read.
77
+ resource('changelog', {
78
+ uri: 'docs://changelog',
79
+ title: 'Changelog',
80
+ mimeType: 'text/markdown',
81
+ fulfil: () => ({
82
+ contents: [
83
+ { uri: 'docs://changelog', mimeType: 'text/markdown', text: 'Changelog: 1.0.0 first release' },
84
+ ],
85
+ }),
86
+ });
87
+
88
+ // {var} URI-template resource: the URI variable arrives on ctx.input (a symbolic ref).
89
+ resource('ticket', {
90
+ uri: 'tickets://{id}',
91
+ title: 'Support ticket',
92
+ mimeType: 'text/markdown',
93
+ fulfil: (ctx) => ({
94
+ contents: [
95
+ { uri: `tickets://${ctx.input.id}`, mimeType: 'text/markdown', text: `Ticket ${ctx.input.id}` },
96
+ ],
97
+ }),
98
+ });
99
+ ```
100
+
101
+ ### Prompt
102
+
103
+ `prompt(name, { title?, description?, arguments?, fulfil })`. `arguments` is a Zod object (each key becomes a `prompts/list` descriptor) or an explicit `[{ name, description?, required? }]` list. `fulfil` returns `{ messages: [{ role, content: { type: 'text', text } }] }`; supplied argument values arrive on `ctx.input`.
104
+
105
+ ```ts
106
+ import { prompt, z } from '@noodleseed/one';
107
+
108
+ prompt('summarize_ticket', {
109
+ title: 'Summarize ticket',
110
+ description: 'Draft a short summary of a support ticket.',
111
+ // A Zod object: each key becomes a prompts/list descriptor (or pass [{ name, description?, required? }]).
112
+ arguments: z.object({
113
+ ticket_id: z.string().describe('Ticket to summarize'),
114
+ tone: z.enum(['concise', 'detailed']).default('concise'),
115
+ }),
116
+ // Argument values arrive on ctx.input; return the prompts/get messages shape.
117
+ fulfil: (ctx) => ({
118
+ messages: [
119
+ {
120
+ role: 'user',
121
+ content: {
122
+ type: 'text',
123
+ text: `Summarize ticket ${ctx.input.ticket_id} in a ${ctx.input.tone} tone.`,
124
+ },
125
+ },
126
+ ],
127
+ }),
128
+ });
129
+ ```
130
+
131
+ ### Non-trivial tool: ctx connectors, annotations, visibility, async
132
+
133
+ `ctx` is `{ input, user, connectors }`. Bind connectors with `use` on the server, then call one inside `fulfil` to record a step. `annotations.readOnly()` / `annotations.action()` set the tool hints; `visibility` defaults to `['model', 'app']` — set `['app']` to hide a helper from the model. `fulfil` may be `async` (the compiler awaits it while recording).
134
+
135
+ ```ts
136
+ import { annotations, connector, server, tool, z } from '@noodleseed/one';
137
+
138
+ // A tool-facing HTTP connector, bound to the server via `use`, reachable as ctx.connectors.crm.
139
+ const crm = connector('crm')
140
+ .version('1.0.0')
141
+ .http({
142
+ baseUrl: 'https://crm.example.com',
143
+ allowedOrigins: ['https://crm.example.com'],
144
+ operations: {
145
+ get_ticket: {
146
+ type: 'read',
147
+ method: 'GET',
148
+ path: '/tickets',
149
+ query: ['id'],
150
+ input: { id: { type: 'string', required: true } },
151
+ output: { subject: { type: 'string' }, status: { type: 'string' } },
152
+ response: { subject: '${response.subject}', status: '${response.status}' },
153
+ },
154
+ },
155
+ });
38
156
 
39
- - `z` Zod, for input/output schemas (compiles to JSON Schema 2020-12).
157
+ export default server('support', { title: 'Support', version: '1.0.0', use: { crm } }, [
158
+ tool('get_ticket', {
159
+ description: 'Fetch a support ticket by id.',
160
+ input: z.object({ id: z.string() }),
161
+ output: z.object({ subject: z.string(), status: z.string() }),
162
+ annotations: annotations.readOnly(), // read-only hint for hosts
163
+ visibility: ['model', 'app'], // default; use ['app'] to hide the tool from the model
164
+ // ctx is { input, user, connectors }. A connector call records one flow step (a Ref) —
165
+ // recording is not execution, so never branch on the result with native if (use when).
166
+ fulfil: ({ input, connectors }) => {
167
+ const found = connectors.crm.get_ticket({ id: input.id });
168
+ return { subject: found.subject, status: found.status };
169
+ },
170
+ }),
171
+ tool('echo', {
172
+ description: 'Echo text back.',
173
+ input: z.object({ text: z.string() }),
174
+ output: z.object({ echo: z.string() }),
175
+ annotations: annotations.action(), // mutating / world-affecting hint
176
+ // fulfil may be async — the compiler awaits it while recording the flow.
177
+ fulfil: async ({ input }) => ({ echo: input.text }),
178
+ }),
179
+ ]);
180
+ ```
@@ -0,0 +1,39 @@
1
+ # Test in real hosts
2
+
3
+ Local `noodle dev` and `noodle devtools` prove the server works; the widget experience is only proven inside a real host. `noodle connect <client>` prints the exact setup flow per host.
4
+
5
+ ## Contents
6
+
7
+ - Local inspection first
8
+ - Agent hosts (Claude Code, Codex, editors)
9
+ - ChatGPT (developer mode)
10
+ - Claude
11
+ - Public URL for a local server
12
+ - What to verify
13
+
14
+ ## Local inspection first
15
+
16
+ Run `noodle dev` and inspect the loopback endpoint with MCP Inspector: `noodle connect inspector` prints the flow (`npx @modelcontextprotocol/inspector <printed endpoint>`). Preview widget metadata and rendering with `noodle devtools`.
17
+
18
+ ## Agent hosts (Claude Code, Codex, editors)
19
+
20
+ `noodle connect claude-code` / `noodle connect codex` (add `--write` for project-local setup). For other editors (`cursor`, `vscode`, `gemini`), `noodle connect <client>` prints the setup steps, and `noodle docs export --format llms` produces portable context. With a deployed endpoint, `noodle connect <client> --endpoint <url>` prints the MCP client registration config.
21
+
22
+ ## ChatGPT (developer mode)
23
+
24
+ 1. Deploy: `noodle deploy`, then `noodle open --print` for the hosted MCP URL (ChatGPT needs a public HTTPS endpoint, not loopback).
25
+ 2. In ChatGPT: Settings → Connectors → enable Developer mode → add the endpoint (`noodle connect chatgpt` prints these steps).
26
+ 3. Toggle the connector on in a new conversation and sign in when prompted; testers outside your org need a wider access mode (`noodle access set`).
27
+ 4. Test on mobile too — invoke the same connector from the ChatGPT iOS/Android apps to check widget layout.
28
+
29
+ ## Claude
30
+
31
+ `noodle connect claude` prints the flow: deploy, then add the hosted MCP URL as a custom connector in Claude settings and sign in when prompted. Widgets render in Apps-capable Claude surfaces; elsewhere the tool’s text/structured result is shown.
32
+
33
+ ## Public URL for a local server
34
+
35
+ To try an undeployed server in a host that requires a public URL, `noodle dev --tunnel` publishes a temporary public URL for the loopback endpoint (requires the external `cloudflared` binary on PATH). Treat it as a short-lived test URL — deploy for anything shared.
36
+
37
+ ## What to verify
38
+
39
+ Run a golden prompt set — direct (“use <tool> to…”), indirect (a natural request the model should route), and negative (requests that must not trigger the tool). Confirm the model picks the right tool with the right arguments, the widget renders and its actions work, external links open, and the experience degrades to readable text where Apps are unsupported. Symptoms → `references/troubleshooting.md`.
@@ -0,0 +1,29 @@
1
+ # Troubleshooting in hosts
2
+
3
+ ## Contents
4
+
5
+ - First moves
6
+ - Symptom map
7
+
8
+ ## First moves
9
+
10
+ Re-run the local gates before debugging in-host: `noodle validate`, `noodle check` (add `--target chatgpt` for ChatGPT-specific requirements), and `noodle doctor`. Confirm the CLI is current with `noodle update --check` and that the project-local skill is intact with `noodle agents doctor --json` — host metadata requirements evolve and fixes ship in the CLI/agent-kit. Never paste tokens, secrets, or `.env.noodle` values into prompts or logs while debugging.
11
+
12
+ For protocol/conformance checks, the headless harness is `@mcpjam/cli`, not a `noodle` subcommand. Use it against a local `noodle dev` URL without an access token, or against hosted URLs through the host/OAuth flow printed by `noodle connect`.
13
+
14
+ ## Symptom map
15
+
16
+ | Symptom | Likely cause | Fix |
17
+ | :-- | :-- | :-- |
18
+ | Images, fonts, or styles don’t load inside the widget | The host sandbox silently blocks origins not declared in the widget CSP | Add every asset origin to `csp: { resourceDomains: [...] }` (fetch/XHR origins go in `connectDomains`, embedded iframes in `frameDomains`), then re-run `noodle check --target chatgpt` |
19
+ | ChatGPT warns “Widget CSP is not set” | The widget declares no `csp` | Declare `csp` on the widget with the exact origins it uses |
20
+ | ChatGPT warns “Widget domain is not set” | No `domain` on the widget (required for app-store submission) | Set `domain: "https://…"` (one https origin per app) on each widget |
21
+ | External links do nothing, or show a safe-link warning | Link opened outside the host bridge, or the target origin is not allowlisted | Use `useOpenExternal()` (never `window.open`) and add the target origins to the server-level `handoff.allowedDomains` |
22
+ | Tool succeeds but no widget appears | The tool has no view, or the host surface doesn’t support MCP Apps | Use `toolWithWidget`, run `noodle check`, preview with `noodle devtools`; on non-Apps surfaces only the text/structured result renders |
23
+ | Widget shows stale or missing data | The widget reads `structuredContent`, which must match the `output` schema | Make `fulfil` return exactly the `output` shape (arrays and nested objects are supported); inspect the live result with `noodle devtools` |
24
+ | `useCallTool` fails from the widget | Tool name mismatch, or the helper tool is model-visible | List names with `noodle tools`; widget-only helpers must be declared with `toolForWidget` |
25
+ | `noodle validate` passes but React views fail to bundle (“requires Vite”) | Project dependencies are not installed — widget bundling uses the app-local Vite | Run the project’s package install, then retry `noodle dev` / `noodle deploy` |
26
+ | Hosted endpoint returns 401 to probes | Expected: hosted servers challenge unauthenticated calls with OAuth metadata | Sign in from the host when prompted; widen who may call with `noodle access set` if testers are outside the org |
27
+ | Tools error only after deploy | Runtime/config differences surface hosted (secrets, connector reachability) | Run `noodle smoke`, then `noodle metrics --agent-output` and `noodle events --tool <name> --status tool_error --json`; check `noodle secrets list` scope |
28
+ | Need to invoke a deployed tool from the terminal | The `noodle` CLI is not a general MCP client and has no `call` verb | Use `noodle test` for the local smoke, `noodle tools`/`resources`/`prompts` for local listing, MCP Inspector, or `npx @mcpjam/cli@latest tools call --url <url> ...` for headless MCP probing |
29
+ | One customer/session reports a bad answer or protocol error | The failure may be a model/tool error, host protocol error, or connector/runtime error | Run `noodle metrics --agent-output`, then `noodle events --tool <name> --status tool_error --json`; copy the `sessionId` into `noodle events --session <id> --json`, then match timestamps with `noodle logs` |