@noodleseed/agent-kit 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/manifest.json +251 -251
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +1 -1
  4. package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
  5. package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
  6. package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
  7. package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
  8. package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
  9. package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
  10. package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
  11. package/skills/claude-code/examples/customer-auth/README.md +184 -155
  12. package/skills/claude-code/examples/customer-auth/src/server.ts +66 -20
  13. package/skills/claude-code/examples/customer-auth/test/server.test.ts +51 -9
  14. package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
  15. package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
  16. package/skills/claude-code/references/authoring-workflow.md +92 -3
  17. package/skills/claude-code/references/compile-errors.md +9 -0
  18. package/skills/claude-code/references/examples.md +1 -1
  19. package/skills/claude-code/references/sdk-surface.md +1 -0
  20. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
  21. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  22. package/skills/codex/SKILL.md +1 -1
  23. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  24. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  25. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  26. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  27. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  28. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  29. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  30. package/skills/codex/examples/customer-auth/README.md +184 -155
  31. package/skills/codex/examples/customer-auth/src/server.ts +66 -20
  32. package/skills/codex/examples/customer-auth/test/server.test.ts +51 -9
  33. package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
  34. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  35. package/skills/codex/references/authoring-workflow.md +92 -3
  36. package/skills/codex/references/compile-errors.md +9 -0
  37. package/skills/codex/references/examples.md +1 -1
  38. package/skills/codex/references/sdk-surface.md +1 -0
  39. package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
  40. package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/agent-kit",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "private": false,
5
5
  "description": "Self-checking, self-updating agent skills for the Noodle Seed CLI. Authored in this repo by @noodle-borg/agent-kit; this is the published, independently-versioned canonical skills artifact the CLI fetches and verifies.",
6
6
  "license": "Apache-2.0",
@@ -3,7 +3,7 @@ name: noodle-seed
3
3
  description: "Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:ec5bfcd0d8165205 -->
6
+ <!-- noodle-skill version:0.56.0 hash:ec5bfcd0d8165205 -->
7
7
 
8
8
  # Noodle Seed
9
9
 
@@ -3,7 +3,7 @@ name: authoring-mcp-servers
3
3
  description: "Use when creating or extending a headless Noodle Seed MCP server, tool, resource, prompt, or typed model-facing capability."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:0b2fd8c7e43fc69f -->
6
+ <!-- noodle-skill version:0.56.0 hash:0b2fd8c7e43fc69f -->
7
7
 
8
8
  # authoring-mcp-servers
9
9
 
@@ -3,7 +3,7 @@ name: building-mcp-apps
3
3
  description: "Use when a Noodle Seed MCP App, widget, interactive card, visual interaction, or host-visible UI is the primary requested outcome."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:f7fa54992c8d7692 -->
6
+ <!-- noodle-skill version:0.56.0 hash:f7fa54992c8d7692 -->
7
7
 
8
8
  # building-mcp-apps
9
9
 
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
3
3
  description: "Use when credentials, an API URL, an OpenAPI document, or an observed response must become real Noodle Seed MCP behavior."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:1e86b8704f407bd3 -->
6
+ <!-- noodle-skill version:0.56.0 hash:1e86b8704f407bd3 -->
7
7
 
8
8
  # connecting-apis-to-mcp
9
9
 
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
3
3
  description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:aa715bae12041d7c -->
6
+ <!-- noodle-skill version:0.56.0 hash:aa715bae12041d7c -->
7
7
 
8
8
  # debugging-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ name: deploying-mcp-services
3
3
  description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:93e735b7ffb45df1 -->
6
+ <!-- noodle-skill version:0.56.0 hash:93e735b7ffb45df1 -->
7
7
 
8
8
  # deploying-mcp-services
9
9
 
@@ -3,7 +3,7 @@ name: designing-mcp-products
3
3
  description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:76cce86729cffbee -->
6
+ <!-- noodle-skill version:0.56.0 hash:76cce86729cffbee -->
7
7
 
8
8
  # designing-mcp-products
9
9
 
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
3
3
  description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.54.0 hash:cc54a67f21c0ecdb -->
6
+ <!-- noodle-skill version:0.56.0 hash:cc54a67f21c0ecdb -->
7
7
 
8
8
  # embedding-mcp-assistants
9
9
 
@@ -1,102 +1,174 @@
1
- # Customer Auth - NoodleSeed.com Firebase customer identity
1
+ # Customer Auth - OIDC identity and customer-routed APIs
2
2
 
3
- This curated example owns the customer/end-user authentication with Firebase bridge capability slot. It is
4
- the NoodleSeed.com dogfood app for proving that a SaaS app can protect an MCP endpoint with its own customer
5
- identity provider while still using the generic Noodle Seed authoring API.
3
+ This curated example owns the customer/end-user authentication capability slot. It proves that a SaaS app
4
+ can protect an MCP endpoint with direct OIDC, retain role/scope-based tool authorization, and route ordinary
5
+ reads and confirmed actions to the API origin selected by the verified customer's identity provider.
6
6
 
7
- It also owns the embedded-assistant showcase: the same authenticated MCP surface can be dropped into the
8
- SaaS web application as a fully customer-branded assistant with independent light and dark themes. The
9
- assistant loads the active deployment's instructions and model-visible tools rather than installing a stale
10
- second skill bundle. The standard embedded element also hosts linked MCP Apps behind its sandbox bridge.
7
+ It also owns the customer-branded embedded-assistant presentation showcase. Embedded sessions lack
8
+ direct/federated MCP OIDC endpoint claims, so these routed tools fail with `connector_route_unavailable`;
9
+ static connectors still support embedded delegated exchange. Exercise routed tools through the MCP endpoint.
11
10
 
12
- The public developer entrypoint is [`src/server.ts`](src/server.ts). It declares `customerAuth.firebase(...)` with
13
- the NoodleSeed.com Firebase project and Firebase Web App public config. It exposes a deliberately small MCP
14
- surface for org discovery:
11
+ The public developer entrypoint is [`src/server.ts`](src/server.ts). It exposes a deliberately small MCP
12
+ surface for organization discovery and app lifecycle operations:
15
13
 
16
14
  - `list_my_organizations` lists the NoodleSeed.com organizations the signed-in customer belongs to (no
17
15
  arguments — the org set comes from the verified customer session).
18
- - `list_org_apps` lists apps for one of those organizations through the dev app API. It is visible and
16
+ - `list_org_apps` lists apps for one of those organizations through that tenant's API. It is visible and
19
17
  callable only when the verified customer has the `org_apps:read` scope and either the `org_admin` or
20
18
  `org_member` role.
19
+ - `archive_org_app` archives one app only after exact runtime confirmation. It requires the
20
+ `org_apps:write` scope and `org_admin` role.
21
21
 
22
- The two tools chain: `list_my_organizations` surfaces the `org_id`s the customer can act on, and
23
- `list_org_apps` takes one of those `org_id`s. There is no NoodleSeed-specific SDK helper. The downstream API
24
- is an ordinary authored HTTP connector.
22
+ The tools chain: `list_my_organizations` surfaces the `org_id`s the customer can act on,
23
+ `list_org_apps` takes one of those ids, and `archive_org_app` accepts the selected app id. Tool code remains
24
+ independent of the selected origin.
25
25
 
26
- ## Direct or federated OIDC instead of the built-in adapter
26
+ ## Declare the customer endpoint
27
27
 
28
- This flagship uses the managed Firebase adapter. If an app replaces it with `customerAuth.oidc(...)` or
29
- `customerAuth.federatedOidc(...)`, the app developer owns the authorization server. It must publish the
30
- primary path-inserted RFC 8414 URL as direct HTTP 200 JSON with the exact issuer, HTTPS authorization/token/
31
- registration/JWKS endpoints, authorization-code and refresh grants, PKCE S256, public-client auth method
32
- `none`, RFC 8707 resource handling, an access-token `aud` equal to the exact MCP URL, and public signing keys.
28
+ `customerEndpoint` names one private routing authority and bounds the origins an IdP may select:
33
29
 
34
- Run `noodle auth doctor src/server.ts` before sharing the endpoint. Its issuer-readiness probes perform
35
- bounded read-only GET checks and never register a client. A successful
36
- `noodle deploy --access customers` reports the same findings as nonblocking warnings; the application team
37
- repairs the issuer rather than adding a Noodle OAuth proxy.
38
-
39
- Adding the embedded assistant does not choose or rewrite MCP customer auth. Inspect the exact active
40
- deployment before changing configuration:
41
-
42
- ```sh
43
- noodle deployments list --org <org> --app <app> --env <env> --json
30
+ ```ts
31
+ const customerApi = customerEndpoint('customer_api', {
32
+ allowedHttpsHostSuffixes: ['api.noodleseed.dev'],
33
+ });
44
34
  ```
45
35
 
46
- This Firebase bridge intentionally advertises the Noodle authorization server. A direct or federated
47
- replacement must advertise its configured tenant issuer. If the exact active direct/federated `customers`
48
- deployment instead advertises the platform issuer, report `customer_auth_state_inconsistent` with only the
49
- endpoint, deployment ID, server version, and sanitized protected-resource metadata. Do not share tokens or
50
- secrets, proxy or rewrite metadata, rotate credentials, or redeploy repeatedly to conceal the mismatch.
36
+ Use either non-empty `allowedHttpsHostSuffixes` or non-empty `allowedHttpsOrigins`, never both. Exact-origin
37
+ policies may include a non-default port. Suffix policies match only the exact hostname or dot-boundary
38
+ subdomains on port 443. A routed connector must not add `allowedOrigins`; its endpoint policy is the egress
39
+ allowlist.
51
40
 
52
- During MCP OAuth login, Noodle Cloud hosts the Firebase bridge page at
53
- `https://cloud.noodleseed.dev/oauth/customer/firebase/authorize`. The customer app does not add an
54
- authorization route. The SaaS operator only configures Firebase Auth to allow the Noodle Cloud origin, and
55
- Noodle Cloud signs the customer in with Firebase before posting the Firebase ID token back to its own bridge
56
- callback.
41
+ The connector uses that declaration as its normal base URL. Its token endpoint remains a fixed, independently
42
+ validated HTTPS URL:
57
43
 
58
- ## How delegated customer credentials are used
44
+ ```ts
45
+ const api = connector('noodleseed_app_api')
46
+ .version('1.0.0')
47
+ .http({
48
+ baseUrl: customerApi,
49
+ auth: {
50
+ kind: 'delegatedTokenExchange',
51
+ tokenUrl: 'https://id.noodleseed.dev/oauth/token',
52
+ clientId: variable('CUSTOMER_API_CLIENT_ID'),
53
+ clientSecret: secret('CUSTOMER_API_CLIENT_SECRET'),
54
+ scopes: ['organizations:read', 'org_apps:read', 'org_apps:write'],
55
+ audience: 'noodleseed-customer-api',
56
+ },
57
+ operations: {
58
+ // read and action operations...
59
+ },
60
+ });
61
+ ```
59
62
 
60
- This flagship uses the legacy Firebase provider bridge because it is runnable today. That is distinct from
61
- Core-v2 account-selecting connector aliases: those declare catalog `credentialProfiles` and bind each alias
62
- with `bind(connector, { profile, connection })`. Hosted bound `managedSecret(...)` connections are runnable;
63
- bound `externalExchange()` and `clientCredentials(...)` currently compile as portable metadata but fail
64
- closed before secret or network access until their provider slice lands. Do not replace this example's
65
- working delegated-session-cookie path with either unsupported bound exchange source.
63
+ ## Map the endpoint from verified OIDC
66
64
 
67
- The example has two declarations that work together:
65
+ The IdP claim contains the complete base URL, including an optional base path. Routing is separate from the
66
+ public `${user}` expression scope:
68
67
 
69
68
  ```ts
70
- auth: customerAuth.firebase({
71
- projectId: variable('FIREBASE_PROJECT_ID'),
72
- apiKey: variable('FIREBASE_WEB_API_KEY'),
73
- authDomain: variable('FIREBASE_AUTH_DOMAIN'),
74
- user: {
69
+ auth: customerAuth.oidc({
70
+ issuer: 'https://id.noodleseed.dev',
71
+ audience: 'https://org.cloud.noodleseed.dev/app/mcp',
72
+ claims: {
75
73
  id: 'sub',
76
74
  email: 'email',
77
75
  name: 'name',
78
- tenant: 'firebase.tenant',
79
- orgs: 'claims.orgs',
80
- roles: 'claims.roles',
81
- scopes: 'claims.scopes',
76
+ orgs: 'permissions.orgs',
77
+ roles: 'permissions.roles',
78
+ scopes: 'permissions.scopes',
79
+ },
80
+ routing: {
81
+ endpoints: {
82
+ customer_api: { claim: 'tenant.api_base_url' },
83
+ },
84
+ },
85
+ }),
86
+ ```
87
+
88
+ For federated OIDC, put the same endpoint map on every issuer. Claim paths may differ, but each issuer must
89
+ map every endpoint the app uses:
90
+
91
+ ```ts
92
+ auth: customerAuth.federatedOidc({
93
+ issuers: [
94
+ {
95
+ issuer: 'https://id.customer-a.com',
96
+ audience: 'https://org.cloud.noodleseed.dev/app/mcp',
97
+ routing: {
98
+ endpoints: {
99
+ customer_api: { claim: 'tenant.api_base_url' },
100
+ },
82
101
  },
102
+ },
103
+ {
104
+ issuer: 'https://login.customer-b.com',
105
+ audience: 'https://org.cloud.noodleseed.dev/app/mcp',
106
+ routing: {
107
+ endpoints: {
108
+ customer_api: { claim: 'organization.routes.customer_api' },
109
+ },
110
+ },
111
+ },
112
+ ],
83
113
  }),
84
114
  ```
85
115
 
86
- That protects the MCP endpoint with the SaaS customer's Firebase identity. The connector then opts into
87
- delegated customer credentials:
116
+ At runtime, Noodle Seed validates both the configured audience and exact MCP resource, projects the route
117
+ into private request state, applies its policy, and freezes it for the call. Missing, malformed, or
118
+ disallowed claims return `connector_route_unavailable` before credential lookup or connector egress.
119
+ Resolved URLs never enter artifacts, `${user}`, logs, model output, widgets, public confirmation review,
120
+ broker cache keys, or delegated exchange assertions.
121
+
122
+ Routed reads work in tools, including declared nested calls. Routed actions require exact
123
+ `annotations.confirm: true`; otherwise they fail with `customer_endpoint_action_unsupported`. Routed
124
+ resources, prompts, and ambient context fail with `customer_endpoint_surface_unsupported`.
125
+
126
+ The flagship's routed action uses the normal TypeScript action helper:
88
127
 
89
128
  ```ts
90
- auth: {
91
- kind: 'delegatedSessionCookie',
92
- provider: 'firebase',
93
- sessionUrl: `${noodleseedApiOrigin}/api/auth/session`,
94
- tokenField: 'idToken',
129
+ tool('archive_org_app', {
130
+ authorization: {
131
+ requiredScopes: ['org_apps:write'],
132
+ allowedRoles: ['org_admin'],
133
+ },
134
+ annotations: annotations.openAction({ destructive: false, confirm: true }),
135
+ // input, output, and the normal connectors.app_api.archiveOrgApp(...) call...
136
+ });
137
+ ```
138
+
139
+ The flagship also opts into the current stateless hosted MCP path:
140
+
141
+ ```ts
142
+ interactions: {
143
+ confirmationFallback: 'host',
95
144
  },
96
145
  ```
97
146
 
98
- The mapped `roles` and `scopes` paths are read only after Firebase verifies the ID token. The restricted
99
- tool declares its rule beside the rest of its public contract:
147
+ A bidirectional client that negotiated form elicitation can complete the standard confirmation exchange
148
+ instead. The explicit host fallback trusts the MCP host to have collected native write approval before the
149
+ tool call reaches Noodle Seed; it is never inferred from client identity and does not replace auth, policy,
150
+ or accurate action/destructive annotations. Omit the fallback when connected hosts are not trusted to
151
+ provide that approval. If neither standard confirmation nor the fallback is available, the action fails
152
+ closed with `interaction_unavailable`.
153
+
154
+ Preparation stores only sorted route `{ key, fingerprint }` bindings in its private server-held
155
+ continuation; the public review exposes none of them. Acceptance re-resolves the current request route and
156
+ returns `invalid_continuation` if it is missing or changed, before policy, credentials, or egress. A match
157
+ reuses the current frozen snapshot for the action and all nested or later reads.
158
+
159
+ The application developer owns the direct/federated authorization server. It must publish its path-inserted
160
+ RFC 8414 document as direct HTTP 200 JSON with exact issuer and HTTPS authorization/token/registration/JWKS
161
+ endpoints, authorization-code and refresh grants, PKCE S256, public-client auth method `none`, RFC 8707
162
+ resource handling, and public signing keys. Access tokens must include the exact MCP resource audience; there
163
+ is no audience fallback.
164
+
165
+ Run `noodle auth doctor src/server.ts` before sharing. Its bounded, read-only probes never register a client.
166
+ Adding the embedded assistant does not choose or rewrite MCP customer auth.
167
+
168
+ ## Per-tool authorization remains independent
169
+
170
+ The mapped `roles` and `scopes` paths are read only after OIDC verification. The restricted tool declares its
171
+ rule beside the rest of its public contract:
100
172
 
101
173
  ```ts
102
174
  tool('list_org_apps', {
@@ -109,9 +181,9 @@ tool('list_org_apps', {
109
181
  ```
110
182
 
111
183
  Every required scope must be present and at least one allowed role must match. When both lists are declared,
112
- both conditions apply. A restricted tool is omitted from `tools/list` for an ineligible customer and a
113
- direct `tools/call` still fails closed. Do not use unverified page context, request arguments, connector
114
- responses, or an arbitrary generic `roles` claim as authorization input.
184
+ both conditions apply. Route availability never changes `tools/list`: discovery remains based only on
185
+ roles/scopes. A restricted tool is omitted for an ineligible customer and a guessed direct call still fails
186
+ closed.
115
187
 
116
188
  Tool code calls the connector normally:
117
189
 
@@ -127,39 +199,13 @@ fulfil({ input, connectors }) {
127
199
  }
128
200
  ```
129
201
 
130
- At runtime, Noodle Seed verifies the Firebase customer during MCP OAuth, stores that customer's delegated
131
- Firebase refresh token in the credential broker, and refreshes a short-lived Firebase ID token only when a
132
- connector-backed tool calls the NoodleSeed.com API. For this app API, the broker exchanges that ID token at
133
- the existing Next.js `/api/auth/session` route and sends the resulting session cookie to the API. The MCP
134
- access token remains a Noodle-issued resource-bound token and is never sent to the downstream API.
135
-
136
- ## Delegated downstream auth for your own API (token exchange)
137
-
138
- The Firebase path above only works for Firebase-session downstreams. When the downstream is **your own
139
- API** with its own token issuance, use `delegatedTokenExchange` instead
140
- ([ADR 0152](../../docs/decisions/0152-delegated-token-exchange-connector-auth.md)): the platform signs a
141
- short-lived, JWKS-verifiable assertion of the signed-in user and exchanges it (RFC 8693) at a token
142
- endpoint you implement, which mints your own user-scoped token — so your API enforces its own per-user
143
- authorization on every call. It works for verified direct/federated OIDC, built-in provider identities, and
144
- embedded assistant sessions, with no per-user OAuth enrollment.
145
-
146
- ```ts
147
- auth: {
148
- kind: 'delegatedTokenExchange',
149
- tokenUrl: 'https://app.example.com/api/assistant/oauth/token', // origin must be in allowedOrigins
150
- clientId: variable('EXAMPLE_DELEG_CLIENT_ID'),
151
- clientSecret: secret('EXAMPLE_DELEG_CLIENT_SECRET'),
152
- scopes: ['time_off'],
153
- },
154
- ```
202
+ The broker exchanges a short-lived, platform-signed assertion at the fixed token endpoint and caches the
203
+ result by caller, connector, scopes, and a route fingerprint. The assertion carries only the route key and
204
+ fingerprint, never the URL. The MCP access token is never forwarded to the customer API. The exchange wire
205
+ contract lives in [docs/spec/connectors.md](../../docs/spec/connectors.md).
155
206
 
156
- Your endpoint authenticates the broker's client credential, verifies the `subject_token` JWT against the
157
- platform issuer JWKS (claims include the verified `sub`, `email`, `name`, declared session `claims`,
158
- `tenant`, and `deployment`), mints a short-lived user-scoped token, and returns the standard
159
- `{ access_token, token_type, expires_in }` response. The exact wire contract and a copyable endpoint
160
- implementation live in [docs/spec/connectors.md](../../docs/spec/connectors.md) and the Agent Kit
161
- authoring-workflow reference ("Delegated downstream auth"). `noodle auth doctor` reports each declared
162
- exchange endpoint.
207
+ Firebase and Microsoft remain supported managed adapters; their provider-specific contracts and tests live
208
+ in [docs/spec/auth-and-policy.md](../../docs/spec/auth-and-policy.md) and the SharePoint flagship.
163
209
 
164
210
  ## Validate
165
211
 
@@ -170,7 +216,7 @@ noodle validate examples/customer-auth/src/server.ts
170
216
 
171
217
  Against a deployed customer-protected environment, set a short-lived real customer token only in
172
218
  `NOODLE_CUSTOMER_TOKEN` and add `--live --org <org> --app <app> --env <env>`. The live doctor performs
173
- credential exchanges without invoking either business tool.
219
+ credential exchanges without invoking any business tool.
174
220
 
175
221
  ## Run locally
176
222
 
@@ -184,13 +230,16 @@ The embedded assistant uses a customer-supplied OpenAI Chat Completions-compatib
184
230
  managed values at the Noodle deployment environment; none of these values belongs in the customer web
185
231
  application environment, and the API key never reaches the browser:
186
232
 
233
+ The assistant session carries a verified user, tenant, deployment, roles, and scopes, but not the IdP's
234
+ `tenant.api_base_url` claim. These routed operations are MCP-only; do not copy the route into page context,
235
+ session claims, tool input, or model instructions.
236
+
187
237
  ```bash
188
238
  noodle variables set ASSISTANT_MODEL_BASE_URL https://model.example.com/v1 --scope env
189
239
  noodle variables set ASSISTANT_MODEL your-model --scope env
190
240
  noodle secrets set ASSISTANT_MODEL_API_KEY --scope env
191
- noodle variables set FIREBASE_PROJECT_ID your-firebase-project --scope env
192
- noodle variables set FIREBASE_WEB_API_KEY your-firebase-web-api-key --scope env
193
- noodle variables set FIREBASE_AUTH_DOMAIN your-firebase-project.firebaseapp.com --scope env
241
+ noodle variables set CUSTOMER_API_CLIENT_ID your-broker-client-id --scope env
242
+ noodle secrets set CUSTOMER_API_CLIENT_SECRET --scope env
194
243
  noodle check --target embedded-assistant src/server.ts
195
244
  ```
196
245
 
@@ -198,11 +247,9 @@ Assistant origins are exact. Production embedding origins must use HTTPS; plain
198
247
  loopback development origins such as `http://localhost:3000`, `http://127.0.0.1:3000`, or
199
248
  `http://[::1]:3000`. `noodle dev` serves the MCP project, not that separate embedding application.
200
249
 
201
- The assistant's panel, launcher, status header, composer, and message treatment are configured through the
202
- bounded `presentation` object in `src/server.ts`. The Atlas-style product treatment is the supported
203
- customization ceiling. These primitives derive their colors from the shared server `branding` block and
204
- require no edits to `@noodleseed/assistant`; raw HTML, CSS, inline SVG, renderer class names, and callbacks
205
- are intentionally not accepted in deployment configuration.
250
+ The bounded `presentation` object configures the panel, launcher, header, composer, and messages. Its
251
+ primitives derive colors from shared server `branding`; raw HTML, CSS, inline SVG, renderer classes, and
252
+ callbacks are not accepted.
206
253
 
207
254
  Create the backend credential after deployment. The CLI writes it to a mode-0600 file and never prints the
208
255
  secret:
@@ -382,13 +429,15 @@ secret allowlist; regenerate existing framework-owned environment binding types
382
429
  Devtools/model exercises to synthetic data, and obtain approval before sending real connector data to an
383
430
  external model.
384
431
 
385
- After deployment, verify the public delegated exchange without extracting a customer bearer token or invoking
386
- a business operation:
432
+ After deployment, use the assistant doctor to verify the embed client, model, and static session boundary:
387
433
 
388
434
  ```sh
389
435
  noodle assistant doctor --user-id <real-test-user> --origin "$PUBLIC_APP_ORIGIN" --org <org> --app <app> --env <env>
390
436
  ```
391
437
 
438
+ This does not add an OIDC customer route. Routed operations succeed only through a direct/federated
439
+ customer-authenticated MCP request with an allowed endpoint claim.
440
+
392
441
  If the application deliberately sends a first turn on mount, do not combine a persistent "sent" ref with a
393
442
  mount effect. React Strict Mode can abort that provisional request and then suppress the stable remount.
394
443
  Schedule the send after the provisional cleanup and settle its promise:
@@ -437,43 +486,21 @@ assistant.subscribeChat((state) => {
437
486
  });
438
487
  ```
439
488
 
440
- `theme="auto"` follows the browser operating-system preference, not a SaaS-owned theme toggle. Pass the
441
- application's resolved `light`/`dark` theme to both `NoodleAssistant` and `NoodleAppView`; later updates
442
- reach mounted MCP Apps without remounting them. CSS custom properties inherit through the assistant host, so
443
- typed appearance roles may reuse existing application tokens with `var(--app-token)`. The server-level
444
- `branding` block is inherited by both MCP App widgets and the assistant; documented `--ns-assistant-*`
445
- semantic CSS variables remain the final integration escape hatch. There is no second assistant branding
446
- declaration.
447
- The end-user UI contains only customer branding.
448
- Text streams progressively. Expired turns re-exchange through the authenticated backend and retry once;
449
- consent-bound tool confirmations never replay automatically.
450
-
451
- The Firebase project ID is required because Firebase ID tokens use the project ID as the token audience and
452
- issuer suffix. The runtime verifies `aud` against the project ID and `iss` against
453
- `https://securetoken.google.com/<projectId>`.
454
-
455
- The Firebase Web API key and auth domain are public Firebase browser configuration. They let the Noodle
456
- Cloud-hosted bridge initialize Firebase Auth for this customer project; they are not server secrets. Keep
457
- them out of source with `variable(...)`, restrict the Firebase key to the expected browser origins and APIs,
458
- and use `secret(...)` only for credentials that must never reach a browser.
459
-
460
- The `noodleseed_app_api` connector currently points at the NoodleSeed.com dev app surface:
461
-
462
- ```text
463
- https://dev.noodleseed.com
464
- ```
465
-
466
- When the customer app moves from `dev.noodleseed.com` to `app.noodleseed.com`, update the connector's
467
- `noodleseedApiOrigin` constant to the production API origin that serves the same paths.
489
+ `theme="auto"` follows the operating-system preference, not a SaaS-owned toggle. Pass the resolved
490
+ `light`/`dark` theme to `NoodleAssistant` and `NoodleAppView`; updates reach mounted MCP Apps without a
491
+ remount. CSS custom properties inherit through the host, and documented `--ns-assistant-*` variables remain
492
+ the final integration escape hatch. Server `branding` is shared by widgets and the assistant; there is no
493
+ second branding declaration. Text streams progressively. Expired turns re-exchange and retry once;
494
+ confirmations never replay automatically.
468
495
 
469
- The connector uses delegated Firebase customer credentials. During the customer OAuth bridge, Noodle Seed
470
- verifies the Firebase ID token, stores the Firebase refresh token through the credential broker, and refreshes
471
- a Firebase ID token when the connector calls the NoodleSeed.com app API. The broker then exchanges that ID
472
- token for the app's existing Next.js session cookie. There is no shared `NOODLESEED_APP_API_TOKEN` for this
473
- example.
496
+ The customer IdP must place the full tenant API base URL in `tenant.api_base_url`. For example, one verified
497
+ customer may receive `https://customer-a.api.noodleseed.dev/v1` and another
498
+ `https://customer-b.api.noodleseed.dev/v1`; both satisfy the declared suffix policy. Application code,
499
+ deployment variables, and connector arguments do not select the tenant route.
474
500
 
475
- Firebase Auth must list `cloud.noodleseed.dev` as an authorized domain before browser sign-in works in
476
- production.
501
+ `CUSTOMER_API_CLIENT_ID` and `CUSTOMER_API_CLIENT_SECRET` authenticate only the broker to the fixed exchange
502
+ endpoint. They are not customer API bearer tokens. The exchange endpoint verifies the platform-signed
503
+ subject assertion and mints a short-lived token scoped to the signed-in user and route binding.
477
504
 
478
505
  ## Deploy customer-protected to Noodle Seed Cloud
479
506
 
@@ -496,14 +523,16 @@ https://cloud.noodleseed.dev/o/noodleseed/customer-auth/mcp
496
523
  - Tool `list_my_organizations`: calls `GET /api/organizations` and returns the organizations the signed-in
497
524
  customer is a member of. Takes no arguments; the org set is scoped by the verified customer session.
498
525
  - Tool `list_org_apps`: calls `GET /api/organizations/{org_id}/apps` for one organization `org_id`.
526
+ - Tool `archive_org_app`: after confirmation, calls
527
+ `POST /api/organizations/{org_id}/apps/{app_id}/archive`.
499
528
 
500
529
  ## Auth boundary
501
530
 
502
- Firebase ID-token verification is handled by Noodle Seed's generic Firebase bridge adapter during OAuth
503
- issuance. The MCP client receives a Noodle-issued, resource-bound access token marked as a Firebase customer
504
- identity; raw Firebase tokens and inbound MCP bearer tokens are never forwarded to tools, connectors,
505
- widgets, or downstream systems.
531
+ Noodle Seed verifies the configured OIDC issuer, audience, and exact MCP resource before reading identity or
532
+ routing claims. Public caller identity contains the user/role/scope projection; the customer route remains
533
+ private request state.
506
534
 
507
- The connector-backed tools use the credential broker to turn the signed-in Firebase customer session into the
508
- same session-cookie credential that the existing NoodleSeed.com Next.js API already expects. The inbound MCP
509
- bearer token is never used as an app API credential.
535
+ Connector-backed tools ask the broker for a route-bound delegated credential; only the endpoint key and
536
+ fingerprint enter broker cache/single-flight state or the assertion. The route claim and inbound MCP bearer
537
+ token never reach tools, connectors, widgets, model output, or downstream systems. Confirmed actions keep
538
+ the same URL-blind binding only in private continuation state and reject acceptance-time drift.