@noodleseed/agent-kit 0.54.0 → 0.55.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 (38) hide show
  1. package/manifest.json +249 -249
  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 +145 -155
  12. package/skills/claude-code/examples/customer-auth/src/server.ts +27 -20
  13. package/skills/claude-code/examples/customer-auth/test/server.test.ts +38 -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 +72 -3
  17. package/skills/claude-code/references/compile-errors.md +8 -0
  18. package/skills/claude-code/references/sdk-surface.md +1 -0
  19. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
  20. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  21. package/skills/codex/SKILL.md +1 -1
  22. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  23. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  24. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  25. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  26. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  27. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  28. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  29. package/skills/codex/examples/customer-auth/README.md +145 -155
  30. package/skills/codex/examples/customer-auth/src/server.ts +27 -20
  31. package/skills/codex/examples/customer-auth/test/server.test.ts +38 -9
  32. package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
  33. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  34. package/skills/codex/references/authoring-workflow.md +72 -3
  35. package/skills/codex/references/compile-errors.md +8 -0
  36. package/skills/codex/references/sdk-surface.md +1 -0
  37. package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
  38. 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.55.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.55.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.55.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.55.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.55.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.55.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.55.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.55.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.55.0 hash:cc54a67f21c0ecdb -->
7
7
 
8
8
  # embedding-mcp-assistants
9
9
 
@@ -1,102 +1,138 @@
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
+ read-only connector calls 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:
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.
21
19
 
22
20
  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.
21
+ `list_org_apps` takes one of those `org_id`s. Tool code remains independent of the selected origin.
25
22
 
26
- ## Direct or federated OIDC instead of the built-in adapter
23
+ ## Declare the customer endpoint
27
24
 
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.
25
+ `customerEndpoint` names one private routing authority and bounds the origins an IdP may select:
33
26
 
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
27
+ ```ts
28
+ const customerApi = customerEndpoint('customer_api', {
29
+ allowedHttpsHostSuffixes: ['api.noodleseed.dev'],
30
+ });
44
31
  ```
45
32
 
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.
33
+ Use either non-empty `allowedHttpsHostSuffixes` or non-empty `allowedHttpsOrigins`, never both. Exact-origin
34
+ policies may include a non-default port. Suffix policies match only the exact hostname or dot-boundary
35
+ subdomains on port 443. A routed connector must not add `allowedOrigins`; its endpoint policy is the egress
36
+ allowlist.
51
37
 
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.
38
+ The connector uses that declaration as its normal base URL. Its token endpoint remains a fixed, independently
39
+ validated HTTPS URL:
57
40
 
58
- ## How delegated customer credentials are used
41
+ ```ts
42
+ const api = connector('noodleseed_app_api')
43
+ .version('1.0.0')
44
+ .http({
45
+ baseUrl: customerApi,
46
+ auth: {
47
+ kind: 'delegatedTokenExchange',
48
+ tokenUrl: 'https://id.noodleseed.dev/oauth/token',
49
+ clientId: variable('CUSTOMER_API_CLIENT_ID'),
50
+ clientSecret: secret('CUSTOMER_API_CLIENT_SECRET'),
51
+ scopes: ['organizations:read', 'org_apps:read'],
52
+ audience: 'noodleseed-customer-api',
53
+ },
54
+ operations: {
55
+ // read operations...
56
+ },
57
+ });
58
+ ```
59
59
 
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.
60
+ ## Map the endpoint from verified OIDC
66
61
 
67
- The example has two declarations that work together:
62
+ The IdP claim contains the complete base URL, including an optional base path. Routing is separate from the
63
+ public `${user}` expression scope:
68
64
 
69
65
  ```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: {
66
+ auth: customerAuth.oidc({
67
+ issuer: 'https://id.noodleseed.dev',
68
+ audience: 'https://org.cloud.noodleseed.dev/app/mcp',
69
+ claims: {
75
70
  id: 'sub',
76
71
  email: 'email',
77
72
  name: 'name',
78
- tenant: 'firebase.tenant',
79
- orgs: 'claims.orgs',
80
- roles: 'claims.roles',
81
- scopes: 'claims.scopes',
82
- },
73
+ orgs: 'permissions.orgs',
74
+ roles: 'permissions.roles',
75
+ scopes: 'permissions.scopes',
76
+ },
77
+ routing: {
78
+ endpoints: {
79
+ customer_api: { claim: 'tenant.api_base_url' },
80
+ },
81
+ },
83
82
  }),
84
83
  ```
85
84
 
86
- That protects the MCP endpoint with the SaaS customer's Firebase identity. The connector then opts into
87
- delegated customer credentials:
85
+ For federated OIDC, put the same endpoint map on every issuer. Claim paths may differ, but each issuer must
86
+ map every endpoint the app uses:
88
87
 
89
88
  ```ts
90
- auth: {
91
- kind: 'delegatedSessionCookie',
92
- provider: 'firebase',
93
- sessionUrl: `${noodleseedApiOrigin}/api/auth/session`,
94
- tokenField: 'idToken',
95
- },
89
+ auth: customerAuth.federatedOidc({
90
+ issuers: [
91
+ {
92
+ issuer: 'https://id.customer-a.com',
93
+ audience: 'https://org.cloud.noodleseed.dev/app/mcp',
94
+ routing: {
95
+ endpoints: {
96
+ customer_api: { claim: 'tenant.api_base_url' },
97
+ },
98
+ },
99
+ },
100
+ {
101
+ issuer: 'https://login.customer-b.com',
102
+ audience: 'https://org.cloud.noodleseed.dev/app/mcp',
103
+ routing: {
104
+ endpoints: {
105
+ customer_api: { claim: 'organization.routes.customer_api' },
106
+ },
107
+ },
108
+ },
109
+ ],
110
+ }),
96
111
  ```
97
112
 
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:
113
+ At runtime, Noodle Seed validates both the configured audience and exact MCP resource, projects the route
114
+ into private request state, applies its policy, and freezes it for the call. Missing, malformed, or
115
+ disallowed claims return `connector_route_unavailable` before credential lookup or connector egress.
116
+ Resolved URLs never enter artifacts, `${user}`, logs, model output, widgets, confirmation review, broker cache
117
+ keys, or delegated exchange assertions.
118
+
119
+ This release permits routed reads in tools, including declared nested calls. Routed actions fail with
120
+ `customer_endpoint_action_unsupported`; routed resources, prompts, and ambient context fail with
121
+ `customer_endpoint_surface_unsupported`.
122
+
123
+ The application developer owns the direct/federated authorization server. It must publish its path-inserted
124
+ RFC 8414 document as direct HTTP 200 JSON with exact issuer and HTTPS authorization/token/registration/JWKS
125
+ endpoints, authorization-code and refresh grants, PKCE S256, public-client auth method `none`, RFC 8707
126
+ resource handling, and public signing keys. Access tokens must include the exact MCP resource audience; there
127
+ is no audience fallback.
128
+
129
+ Run `noodle auth doctor src/server.ts` before sharing. Its bounded, read-only probes never register a client.
130
+ Adding the embedded assistant does not choose or rewrite MCP customer auth.
131
+
132
+ ## Per-tool authorization remains independent
133
+
134
+ The mapped `roles` and `scopes` paths are read only after OIDC verification. The restricted tool declares its
135
+ rule beside the rest of its public contract:
100
136
 
101
137
  ```ts
102
138
  tool('list_org_apps', {
@@ -109,9 +145,9 @@ tool('list_org_apps', {
109
145
  ```
110
146
 
111
147
  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.
148
+ both conditions apply. Route availability never changes `tools/list`: discovery remains based only on
149
+ roles/scopes. A restricted tool is omitted for an ineligible customer and a guessed direct call still fails
150
+ closed.
115
151
 
116
152
  Tool code calls the connector normally:
117
153
 
@@ -127,39 +163,13 @@ fulfil({ input, connectors }) {
127
163
  }
128
164
  ```
129
165
 
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.
166
+ The broker exchanges a short-lived, platform-signed assertion at the fixed token endpoint and caches the
167
+ result by caller, connector, scopes, and a route fingerprint. The assertion carries only the route key and
168
+ fingerprint, never the URL. The MCP access token is never forwarded to the customer API. The exchange wire
169
+ contract lives in [docs/spec/connectors.md](../../docs/spec/connectors.md).
145
170
 
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
- ```
155
-
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.
171
+ Firebase and Microsoft remain supported managed adapters; their provider-specific contracts and tests live
172
+ in [docs/spec/auth-and-policy.md](../../docs/spec/auth-and-policy.md) and the SharePoint flagship.
163
173
 
164
174
  ## Validate
165
175
 
@@ -184,13 +194,16 @@ The embedded assistant uses a customer-supplied OpenAI Chat Completions-compatib
184
194
  managed values at the Noodle deployment environment; none of these values belongs in the customer web
185
195
  application environment, and the API key never reaches the browser:
186
196
 
197
+ The assistant session carries a verified user, tenant, deployment, roles, and scopes, but not the IdP's
198
+ `tenant.api_base_url` claim. These routed operations are MCP-only; do not copy the route into page context,
199
+ session claims, tool input, or model instructions.
200
+
187
201
  ```bash
188
202
  noodle variables set ASSISTANT_MODEL_BASE_URL https://model.example.com/v1 --scope env
189
203
  noodle variables set ASSISTANT_MODEL your-model --scope env
190
204
  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
205
+ noodle variables set CUSTOMER_API_CLIENT_ID your-broker-client-id --scope env
206
+ noodle secrets set CUSTOMER_API_CLIENT_SECRET --scope env
194
207
  noodle check --target embedded-assistant src/server.ts
195
208
  ```
196
209
 
@@ -198,11 +211,9 @@ Assistant origins are exact. Production embedding origins must use HTTPS; plain
198
211
  loopback development origins such as `http://localhost:3000`, `http://127.0.0.1:3000`, or
199
212
  `http://[::1]:3000`. `noodle dev` serves the MCP project, not that separate embedding application.
200
213
 
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.
214
+ The bounded `presentation` object configures the panel, launcher, header, composer, and messages. Its
215
+ primitives derive colors from shared server `branding`; raw HTML, CSS, inline SVG, renderer classes, and
216
+ callbacks are not accepted.
206
217
 
207
218
  Create the backend credential after deployment. The CLI writes it to a mode-0600 file and never prints the
208
219
  secret:
@@ -382,13 +393,15 @@ secret allowlist; regenerate existing framework-owned environment binding types
382
393
  Devtools/model exercises to synthetic data, and obtain approval before sending real connector data to an
383
394
  external model.
384
395
 
385
- After deployment, verify the public delegated exchange without extracting a customer bearer token or invoking
386
- a business operation:
396
+ After deployment, use the assistant doctor to verify the embed client, model, and static session boundary:
387
397
 
388
398
  ```sh
389
399
  noodle assistant doctor --user-id <real-test-user> --origin "$PUBLIC_APP_ORIGIN" --org <org> --app <app> --env <env>
390
400
  ```
391
401
 
402
+ This does not add an OIDC customer route. Routed operations succeed only through a direct/federated
403
+ customer-authenticated MCP request with an allowed endpoint claim.
404
+
392
405
  If the application deliberately sends a first turn on mount, do not combine a persistent "sent" ref with a
393
406
  mount effect. React Strict Mode can abort that provisional request and then suppress the stable remount.
394
407
  Schedule the send after the provisional cleanup and settle its promise:
@@ -437,43 +450,21 @@ assistant.subscribeChat((state) => {
437
450
  });
438
451
  ```
439
452
 
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.
453
+ `theme="auto"` follows the operating-system preference, not a SaaS-owned toggle. Pass the resolved
454
+ `light`/`dark` theme to `NoodleAssistant` and `NoodleAppView`; updates reach mounted MCP Apps without a
455
+ remount. CSS custom properties inherit through the host, and documented `--ns-assistant-*` variables remain
456
+ the final integration escape hatch. Server `branding` is shared by widgets and the assistant; there is no
457
+ second branding declaration. Text streams progressively. Expired turns re-exchange and retry once;
458
+ confirmations never replay automatically.
468
459
 
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.
460
+ The customer IdP must place the full tenant API base URL in `tenant.api_base_url`. For example, one verified
461
+ customer may receive `https://customer-a.api.noodleseed.dev/v1` and another
462
+ `https://customer-b.api.noodleseed.dev/v1`; both satisfy the declared suffix policy. Application code,
463
+ deployment variables, and connector arguments do not select the tenant route.
474
464
 
475
- Firebase Auth must list `cloud.noodleseed.dev` as an authorized domain before browser sign-in works in
476
- production.
465
+ `CUSTOMER_API_CLIENT_ID` and `CUSTOMER_API_CLIENT_SECRET` authenticate only the broker to the fixed exchange
466
+ endpoint. They are not customer API bearer tokens. The exchange endpoint verifies the platform-signed
467
+ subject assertion and mints a short-lived token scoped to the signed-in user and route binding.
477
468
 
478
469
  ## Deploy customer-protected to Noodle Seed Cloud
479
470
 
@@ -499,11 +490,10 @@ https://cloud.noodleseed.dev/o/noodleseed/customer-auth/mcp
499
490
 
500
491
  ## Auth boundary
501
492
 
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.
493
+ Noodle Seed verifies the configured OIDC issuer, audience, and exact MCP resource before reading identity or
494
+ routing claims. Public caller identity contains the user/role/scope projection; the customer route remains
495
+ private request state.
506
496
 
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.
497
+ Connector-backed tools ask the broker for a route-bound delegated credential; only the endpoint key and
498
+ fingerprint enter broker cache/single-flight state or the assertion. The route claim and inbound MCP bearer
499
+ token never reach tools, connectors, widgets, model output, or downstream systems.
@@ -2,6 +2,7 @@ import {
2
2
  annotations,
3
3
  connector,
4
4
  customerAuth,
5
+ customerEndpoint,
5
6
  embeddedAssistant,
6
7
  openAICompatible,
7
8
  secret,
@@ -11,18 +12,21 @@ import {
11
12
  z,
12
13
  } from '@noodleseed/one';
13
14
 
14
- const noodleseedApiOrigin = 'https://dev.noodleseed.com';
15
+ const customerApi = customerEndpoint('customer_api', {
16
+ allowedHttpsHostSuffixes: ['api.noodleseed.dev'],
17
+ });
15
18
 
16
19
  const noodleseedApi = connector('noodleseed_app_api')
17
20
  .version('1.0.0')
18
21
  .http({
19
- baseUrl: noodleseedApiOrigin,
20
- allowedOrigins: [noodleseedApiOrigin],
22
+ baseUrl: customerApi,
21
23
  auth: {
22
- kind: 'delegatedSessionCookie',
23
- provider: 'firebase',
24
- sessionUrl: `${noodleseedApiOrigin}/api/auth/session`,
25
- tokenField: 'idToken',
24
+ kind: 'delegatedTokenExchange',
25
+ tokenUrl: 'https://id.noodleseed.dev/oauth/token',
26
+ clientId: variable('CUSTOMER_API_CLIENT_ID'),
27
+ clientSecret: secret('CUSTOMER_API_CLIENT_SECRET'),
28
+ scopes: ['organizations:read', 'org_apps:read'],
29
+ audience: 'noodleseed-customer-api',
26
30
  },
27
31
  operations: {
28
32
  list_org_apps: {
@@ -69,22 +73,25 @@ export default server(
69
73
  },
70
74
  },
71
75
  use: { app_api: noodleseedApi },
72
- auth: customerAuth.firebase({
73
- projectId: variable('FIREBASE_PROJECT_ID'),
74
- apiKey: variable('FIREBASE_WEB_API_KEY'),
75
- authDomain: variable('FIREBASE_AUTH_DOMAIN'),
76
- user: {
76
+ auth: customerAuth.oidc({
77
+ issuer: 'https://id.noodleseed.dev',
78
+ audience: 'https://org.cloud.noodleseed.dev/app/mcp',
79
+ claims: {
77
80
  id: 'sub',
78
81
  email: 'email',
79
82
  name: 'name',
80
- tenant: 'firebase.tenant',
81
- orgs: 'claims.orgs',
82
- roles: 'claims.roles',
83
- scopes: 'claims.scopes',
83
+ orgs: 'permissions.orgs',
84
+ roles: 'permissions.roles',
85
+ scopes: 'permissions.scopes',
86
+ },
87
+ routing: {
88
+ endpoints: {
89
+ customer_api: { claim: 'tenant.api_base_url' },
90
+ },
84
91
  },
85
92
  }),
86
93
  instructions:
87
- 'Customer-authenticated demo. Firebase proves the customer identity, while read-only NoodleSeed.com API calls use broker-managed delegated Firebase customer credentials.',
94
+ 'Direct/federated MCP OIDC demo. The customer IdP proves identity and privately selects the tenant API base URL for read-only MCP calls, while the broker supplies delegated credentials.',
88
95
  assistant: embeddedAssistant({
89
96
  model: openAICompatible({
90
97
  baseUrl: variable('ASSISTANT_MODEL_BASE_URL'),
@@ -111,13 +118,13 @@ export default server(
111
118
  },
112
119
  composer: { leadingIcon: 'brand-mark', shape: 'pill' },
113
120
  },
114
- suggestedPrompts: ['Show my organizations', 'List the apps in my organization'],
121
+ suggestedPrompts: ['Explain how to connect this customer-authenticated MCP server'],
115
122
  }),
116
123
  },
117
124
  [
118
125
  tool('list_org_apps', {
119
126
  title: 'List organization apps',
120
- description: 'List NoodleSeed.com apps for an organization from the dev app API.',
127
+ description: 'List NoodleSeed.com apps for an organization from its customer API.',
121
128
  authorization: {
122
129
  requiredScopes: ['org_apps:read'],
123
130
  allowedRoles: ['org_admin', 'org_member'],
@@ -147,7 +154,7 @@ export default server(
147
154
  description: 'List the NoodleSeed.com organizations the signed-in customer belongs to.',
148
155
  contextProvider: true,
149
156
  input: z.object({}),
150
- // The dev app API returns every organization for the signed-in customer in one response, with no
157
+ // The customer API returns every organization for the signed-in customer in one response, with no
151
158
  // page parameter to pass through, so the bound is declared on the shape. A customer belongs to a
152
159
  // handful of organizations; `noodle check` reports an unbounded list as
153
160
  // `tool_design_output_bounds`.