@noodleseed/agent-kit 0.84.1 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.json +271 -271
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
- package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/claude-code/creating-product-agent-guides/SKILL.md +1 -1
- package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
- package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
- package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/claude-code/examples/acme-discovery/README.md +58 -15
- package/skills/claude-code/examples/acme-discovery/src/server.ts +127 -13
- package/skills/claude-code/examples/acme-discovery/test/server.test.ts +39 -0
- package/skills/claude-code/examples/customer-auth/README.md +9 -1
- package/skills/claude-code/examples/customer-auth/src/server.ts +11 -3
- package/skills/claude-code/examples/customer-auth/test/server.test.ts +1 -1
- package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
- package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/claude-code/references/embedded-assistant.md +34 -19
- package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/claude-code/wrapping-existing-applications/SKILL.md +1 -1
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/codex/building-mcp-apps/SKILL.md +1 -1
- package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/codex/creating-product-agent-guides/SKILL.md +1 -1
- package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
- package/skills/codex/designing-mcp-products/SKILL.md +1 -1
- package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/codex/examples/acme-discovery/README.md +58 -15
- package/skills/codex/examples/acme-discovery/src/server.ts +127 -13
- package/skills/codex/examples/acme-discovery/test/server.test.ts +39 -0
- package/skills/codex/examples/customer-auth/README.md +9 -1
- package/skills/codex/examples/customer-auth/src/server.ts +11 -3
- package/skills/codex/examples/customer-auth/test/server.test.ts +1 -1
- package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
- package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/codex/references/embedded-assistant.md +34 -19
- package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/wrapping-existing-applications/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.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.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:13ddce01769caae4 -->
|
|
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.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:11523cb33b9473c0 -->
|
|
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.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:9fd67d4d24328e15 -->
|
|
7
7
|
|
|
8
8
|
# building-mcp-apps
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
|
|
|
3
3
|
description: "Use when all four API-evidence inputs exist—and only then: API base URL, authentication scheme, representative safe read, and observed response."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:21bbd3ec441ffd30 -->
|
|
7
7
|
|
|
8
8
|
# connecting-apis-to-mcp
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: creating-product-agent-guides
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP server needs a new or revised product agent guide, App Package skill, or explicit product-skill regeneration."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:0fa48a82fe836cf0 -->
|
|
7
7
|
|
|
8
8
|
# creating-product-agent-guides
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
|
|
|
3
3
|
description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.86.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.
|
|
6
|
+
<!-- noodle-skill version:0.86.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.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:78a6f181b61f92f1 -->
|
|
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.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:cc54a67f21c0ecdb -->
|
|
7
7
|
|
|
8
8
|
# embedding-mcp-assistants
|
|
9
9
|
|
|
@@ -13,30 +13,73 @@ it" flow the `noodle-seed` skill's `references/experience-design.md` teaches.
|
|
|
13
13
|
|
|
14
14
|
## The same tools on Acme's own website
|
|
15
15
|
|
|
16
|
-
The funnel does not only start in ChatGPT. The `assistant` block projects these same
|
|
17
|
-
Acme's marketing site for a visitor with **no account and no session backend
|
|
16
|
+
The funnel does not only start in ChatGPT. The `assistant` block projects these same tools onto
|
|
17
|
+
Acme's marketing site for a visitor with **no account and no session backend** — as a **mixed**
|
|
18
|
+
surface, so the visitor can also sign in mid-conversation:
|
|
18
19
|
|
|
19
20
|
```ts
|
|
20
|
-
access:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
access: [
|
|
22
|
+
publicWebsite({
|
|
23
|
+
origins: ['https://getaways.acme.example'],
|
|
24
|
+
capabilities: [destinations, discoverGetaways, createHandoff, shortlistGetaway, captureLead, myTrips],
|
|
25
|
+
signIn: true, // my_trips reads ${user}; reaching it raises the sign-in card
|
|
26
|
+
instructions:
|
|
27
|
+
'Be a friendly, consultative travel guide, never pushy. Help visitors narrow a getaway before suggesting the next useful step.',
|
|
28
|
+
}),
|
|
29
|
+
authenticatedWebsite({
|
|
30
|
+
origins: ['https://account.acme.example'],
|
|
31
|
+
capabilities: [destinations, discoverGetaways, createHandoff, myTrips],
|
|
32
|
+
instructions: 'The traveler is signed in. Help them plan from their saved trips.',
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
26
35
|
```
|
|
27
36
|
|
|
28
|
-
There is no second tool set and no second app — one `server.ts`, projected onto
|
|
37
|
+
There is no second tool set and no second app — one `server.ts`, projected onto its front doors.
|
|
29
38
|
The flagship selects `model: noodleManaged()`, so an enrolled hosted deployment needs no customer model
|
|
30
39
|
endpoint, name, or key and its artifact remains provider-neutral. Sponsored beta enrollment is exact to the
|
|
31
40
|
org/app/environment and fails closed outside that cohort; `openAICompatible(...)` remains the BYO alternative.
|
|
32
41
|
The surface `instructions` add only the website-specific voice and goal; shared product truth stays in
|
|
33
|
-
`server.instructions`. This public guidance is injected into
|
|
42
|
+
`server.instructions`. This public guidance is injected into that surface's assistant turns, never MCP
|
|
34
43
|
`initialize` or another assistant surface.
|
|
35
|
-
`capabilities` is the entire externally reachable surface, so it stays short enough to
|
|
36
|
-
and closed by default: a tool added to this server later is unreachable from the
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
`capabilities` is the entire externally reachable surface per front door, so it stays short enough to
|
|
45
|
+
review at a glance and closed by default: a tool added to this server later is unreachable from the
|
|
46
|
+
website until someone lists it.
|
|
47
|
+
|
|
48
|
+
## Sign in mid-conversation, land in the account
|
|
49
|
+
|
|
50
|
+
`signIn: true` makes the marketing surface **mixed**: `my_trips` stays visible so the assistant can
|
|
51
|
+
offer it, and an anonymous visitor who reaches for it sees a branded card — *Sign in* plus, because
|
|
52
|
+
`labels.signUpAction` is authored, *Create free account*. Both raise `assistant-sign-in-requested`
|
|
53
|
+
with a single-use `signInTicket`; the detail's `intent` tells the page whether to route its login or
|
|
54
|
+
its registration. Acme's page signs the visitor in exactly as it already does, its backend spends the
|
|
55
|
+
ticket with `createAssistantSession({ ..., signInTicket })`, and the **same conversation continues**
|
|
56
|
+
on whichever origin the backend designates:
|
|
57
|
+
|
|
58
|
+
- landing back on `getaways.acme.example` keeps the mixed surface's projection with identity attached;
|
|
59
|
+
- landing on `account.acme.example` rebinds the conversation to the authenticated surface — its
|
|
60
|
+
capabilities and its voice — and the widget repaints the visible transcript and auto-answers the
|
|
61
|
+
intercepted `my_trips` ask under the new identity.
|
|
62
|
+
|
|
63
|
+
The ticket spend after account creation is identical to the one after sign-in; the service never
|
|
64
|
+
operates a login of its own.
|
|
65
|
+
|
|
66
|
+
## The consultative sales gateway
|
|
67
|
+
|
|
68
|
+
When a visitor's plans firm up but they would rather not sign up, the assistant may — with explicit
|
|
69
|
+
confirmation — take their details and deliver them to Acme's own sink. The recipe is a composition of
|
|
70
|
+
existing primitives, not a platform feature:
|
|
71
|
+
|
|
72
|
+
- `capture_lead` is an ordinary tool with `annotations.action({ confirm: true })`: the confirmation
|
|
73
|
+
card, showing every field, is the visitor's consent moment.
|
|
74
|
+
- Delivery is a declarative HTTP connector whose endpoint is `variable('LEAD_SINK_URL')` and whose
|
|
75
|
+
credential is `secret('LEAD_SINK_TOKEN')` — the operator supplies values with
|
|
76
|
+
`noodle variables set` / `noodle secrets set`; the example stays credential-free and one authored
|
|
77
|
+
class serves any business.
|
|
78
|
+
- The request mapping sets `source: 'website-assistant'` itself, so Acme's sink can trust the
|
|
79
|
+
attribution; the model never supplies it.
|
|
80
|
+
- The lead rests only in Acme's own system. The platform stores no lead, and a vendor sink is just
|
|
81
|
+
different data: Resend/Postmark are `auth: { kind: 'apiKey', … }`, a HubSpot private app is
|
|
82
|
+
`auth: { kind: 'bearer', … }` — never a named vendor package.
|
|
40
83
|
|
|
41
84
|
## Design spec (write this before the code)
|
|
42
85
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
annotations,
|
|
3
|
+
authenticatedWebsite,
|
|
4
|
+
connector,
|
|
3
5
|
embeddedAssistant,
|
|
4
6
|
file,
|
|
5
7
|
knowledge,
|
|
6
8
|
noodleManaged,
|
|
7
9
|
publicWebsite,
|
|
10
|
+
secret,
|
|
8
11
|
server,
|
|
9
12
|
site,
|
|
10
13
|
tool,
|
|
14
|
+
variable,
|
|
11
15
|
z,
|
|
12
16
|
} from '@noodleseed/one';
|
|
13
17
|
|
|
@@ -197,6 +201,90 @@ const shortlistGetaway = tool('shortlist_getaway', {
|
|
|
197
201
|
}),
|
|
198
202
|
});
|
|
199
203
|
|
|
204
|
+
// The consultative sales gateway (ADR 0214): when a visitor would rather not sign up, the assistant
|
|
205
|
+
// may — with explicit confirmation — take their details and deliver them to Acme's own sink. The
|
|
206
|
+
// recipe is a composition of existing primitives, not a platform feature: an ordinary confirm-gated
|
|
207
|
+
// action plus a declarative HTTP connector whose endpoint and credential are operator-managed
|
|
208
|
+
// (`noodle variables set LEAD_SINK_URL …`, `noodle secrets set LEAD_SINK_TOKEN …`). The payload
|
|
209
|
+
// rests only in Acme's own system; the platform stores no lead. A vendor sink is the same shape as
|
|
210
|
+
// data: Resend/Postmark take `auth: { kind: 'apiKey', … }`, a HubSpot private app takes
|
|
211
|
+
// `auth: { kind: 'bearer', … }` — never a named vendor package.
|
|
212
|
+
const leadSink = connector('lead_sink')
|
|
213
|
+
.version('1.0.0')
|
|
214
|
+
.http({
|
|
215
|
+
baseUrl: variable('LEAD_SINK_URL'),
|
|
216
|
+
allowedOrigins: ['https://acme.example'],
|
|
217
|
+
auth: { kind: 'bearer', secret: secret('LEAD_SINK_TOKEN') },
|
|
218
|
+
operations: {
|
|
219
|
+
submit_lead: {
|
|
220
|
+
type: 'action',
|
|
221
|
+
method: 'POST',
|
|
222
|
+
path: '/api/assistant-lead',
|
|
223
|
+
input: z.object({
|
|
224
|
+
name: z.string().trim().min(2).max(120),
|
|
225
|
+
workEmail: z.email().max(240),
|
|
226
|
+
company: z.string().trim().min(2).max(200),
|
|
227
|
+
note: z.string().max(500),
|
|
228
|
+
}),
|
|
229
|
+
output: z.object({ ok: z.boolean() }),
|
|
230
|
+
request: {
|
|
231
|
+
name: '${args.name}',
|
|
232
|
+
workEmail: '${args.workEmail}',
|
|
233
|
+
company: '${args.company}',
|
|
234
|
+
note: '${args.note}',
|
|
235
|
+
// Fixed attribution, set here rather than model-supplied: Acme's sink can trust it.
|
|
236
|
+
source: 'website-assistant',
|
|
237
|
+
},
|
|
238
|
+
response: { ok: '${response.ok}' },
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const captureLead = tool('capture_lead', {
|
|
244
|
+
title: 'Send my details to Acme',
|
|
245
|
+
description:
|
|
246
|
+
'Send the visitor’s contact details and trip interest to Acme Getaways so the team may follow ' +
|
|
247
|
+
'up. Call only after the visitor explicitly agrees to be contacted; the confirmation card is ' +
|
|
248
|
+
'their consent moment. After a confirmed success, say only that the details were sent — never ' +
|
|
249
|
+
'promise response timing.',
|
|
250
|
+
annotations: annotations.action({ confirm: true }),
|
|
251
|
+
input: z.object({
|
|
252
|
+
name: z.string().trim().min(2).max(120).meta({ title: 'Your name' }),
|
|
253
|
+
workEmail: z.email().max(240).meta({ title: 'Work email' }),
|
|
254
|
+
company: z.string().trim().min(2).max(200).meta({ title: 'Company' }),
|
|
255
|
+
note: z.string().max(500).default('').meta({ title: 'What are you planning?' }),
|
|
256
|
+
}),
|
|
257
|
+
output: z.object({ ok: z.boolean() }),
|
|
258
|
+
fulfil: ({ input, connectors }) => {
|
|
259
|
+
const result = connectors.leads.submitLead({
|
|
260
|
+
name: input.name,
|
|
261
|
+
workEmail: input.workEmail,
|
|
262
|
+
company: input.company,
|
|
263
|
+
note: input.note,
|
|
264
|
+
});
|
|
265
|
+
return { ok: result.ok };
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// The mixed surface's sign-in trigger (ADR 0201): reading `${user.id}` classifies this tool
|
|
270
|
+
// requires-identity, so an anonymous visitor who reaches for it sees the sign-in card instead of an
|
|
271
|
+
// error — and after signing in on Acme's account origin, the conversation continues under the
|
|
272
|
+
// authenticated surface below.
|
|
273
|
+
const myTrips = tool('my_trips', {
|
|
274
|
+
title: 'My saved trips',
|
|
275
|
+
description: 'Read the signed-in traveler’s saved trips and their booking status.',
|
|
276
|
+
annotations: readOnly,
|
|
277
|
+
input: z.object({}),
|
|
278
|
+
output: z.object({
|
|
279
|
+
traveler: z.string(),
|
|
280
|
+
status: z.string(),
|
|
281
|
+
}),
|
|
282
|
+
fulfil: ({ user }) => ({
|
|
283
|
+
traveler: user.id as string,
|
|
284
|
+
status: 'No trips booked yet — shortlist a getaway to start one.',
|
|
285
|
+
}),
|
|
286
|
+
});
|
|
287
|
+
|
|
200
288
|
// Grounding beyond the catalog: two controlled files answer policy/pricing/support questions with
|
|
201
289
|
// citations, and Acme's live public site is crawled on deploy and re-crawled on the declared
|
|
202
290
|
// refresh cadence — no sync job, no handwritten search tool. One declaration, one generated
|
|
@@ -241,23 +329,49 @@ export default server(
|
|
|
241
329
|
handoff: {
|
|
242
330
|
allowedDomains: ['https://book.acme.example', 'https://acme.example'],
|
|
243
331
|
},
|
|
244
|
-
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
332
|
+
use: { leads: leadSink },
|
|
333
|
+
// The same tools also serve Acme's own websites, with no second tool set. The marketing site is
|
|
334
|
+
// a **mixed** surface (`signIn: true`): a visitor with no account gets discovery, the booking
|
|
335
|
+
// handoff, and the confirm-gated lead capture — and reaching for `my_trips` raises the sign-in
|
|
336
|
+
// card instead of an error, with `signUpAction` offering account creation through Acme's own
|
|
337
|
+
// registration. After the login redirect lands on the account origin, the same conversation
|
|
338
|
+
// continues under the authenticated surface's capabilities and instructions (ADR 0201).
|
|
339
|
+
// `capabilities` is the whole externally reachable surface per front door — short enough to
|
|
340
|
+
// review in one glance, and closed by default when a tool is added to the server later.
|
|
249
341
|
assistant: embeddedAssistant({
|
|
250
342
|
model: noodleManaged(),
|
|
251
|
-
access:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
343
|
+
access: [
|
|
344
|
+
publicWebsite({
|
|
345
|
+
origins: ['https://getaways.acme.example'],
|
|
346
|
+
capabilities: [
|
|
347
|
+
destinations,
|
|
348
|
+
discoverGetaways,
|
|
349
|
+
createHandoff,
|
|
350
|
+
shortlistGetaway,
|
|
351
|
+
captureLead,
|
|
352
|
+
myTrips,
|
|
353
|
+
],
|
|
354
|
+
signIn: true,
|
|
355
|
+
instructions:
|
|
356
|
+
'Be a friendly, consultative travel guide, never pushy. Help visitors narrow a getaway before suggesting the next useful step. Ground recommendations in Acme knowledge, and clearly separate discovery from booking. When a visitor’s plans firm up, invite them to sign in or create an account; if they would rather not, offer — once — to send their details to the Acme team instead.',
|
|
357
|
+
}),
|
|
358
|
+
authenticatedWebsite({
|
|
359
|
+
origins: ['https://account.acme.example'],
|
|
360
|
+
capabilities: [destinations, discoverGetaways, createHandoff, myTrips],
|
|
361
|
+
instructions:
|
|
362
|
+
'The traveler is signed in. Help them plan from their saved trips, and keep booking on Acme’s own pages through the handoff.',
|
|
363
|
+
}),
|
|
364
|
+
],
|
|
257
365
|
layout: { mode: 'floating', position: 'bottom-right' },
|
|
258
|
-
labels: {
|
|
366
|
+
labels: {
|
|
367
|
+
welcomeHeading: 'Where would you like to go?',
|
|
368
|
+
signInHeading: 'Continue with your Acme account',
|
|
369
|
+
signInBody: 'Saved trips need an account.',
|
|
370
|
+
signInAction: 'Sign in',
|
|
371
|
+
signUpAction: 'Create free account',
|
|
372
|
+
},
|
|
259
373
|
}),
|
|
260
374
|
knowledge: [destinations],
|
|
261
375
|
},
|
|
262
|
-
[discoverGetaways, createHandoff, shortlistGetaway],
|
|
376
|
+
[discoverGetaways, createHandoff, shortlistGetaway, captureLead, myTrips],
|
|
263
377
|
);
|
|
@@ -30,6 +30,45 @@ describe('acme-discovery example', () => {
|
|
|
30
30
|
);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
it('keeps the lead capture behind explicit confirmation and a managed customer sink', async () => {
|
|
34
|
+
const manifest = (await app.toManifest()) as {
|
|
35
|
+
tools: { name: string; annotations?: Record<string, unknown> }[];
|
|
36
|
+
};
|
|
37
|
+
const captureLead = manifest.tools.find((tool) => tool.name === 'capture_lead');
|
|
38
|
+
// The confirmation card is the visitor's consent moment (ADR 0214): a lead may never leave the
|
|
39
|
+
// conversation without it, and the sink endpoint/credential stay operator-managed data.
|
|
40
|
+
expect(captureLead?.annotations?.confirm).toBe(true);
|
|
41
|
+
const catalog = JSON.stringify(
|
|
42
|
+
(app as unknown as { toConnectorCatalog: () => unknown }).toConnectorCatalog(),
|
|
43
|
+
);
|
|
44
|
+
expect(catalog).toContain('${env.LEAD_SINK_URL}');
|
|
45
|
+
expect(catalog).toContain('LEAD_SINK_TOKEN');
|
|
46
|
+
// Fixed attribution set in the request mapping, never model-supplied; no named vendor host.
|
|
47
|
+
expect(catalog).toContain('website-assistant');
|
|
48
|
+
expect(catalog).not.toContain('api.resend.com');
|
|
49
|
+
expect(catalog).not.toContain('api.hubapi.com');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('serves a mixed marketing surface and an authenticated account surface from one server', async () => {
|
|
53
|
+
const manifest = await app.toManifest();
|
|
54
|
+
const surfaces = manifest.server.assistant?.surfaces ?? [];
|
|
55
|
+
expect(surfaces.map((surface) => surface.mode)).toEqual(['mixed', 'authenticated']);
|
|
56
|
+
// The sign-in trigger is listed on the mixed surface so the assistant can offer it; the
|
|
57
|
+
// authenticated surface carries its own narrowed list and voice.
|
|
58
|
+
const capabilityNames = (surface: (typeof surfaces)[number]) =>
|
|
59
|
+
surface.capabilities?.map((capability) => capability.name) ?? [];
|
|
60
|
+
expect(capabilityNames(surfaces[0]!)).toContain('my_trips');
|
|
61
|
+
expect(capabilityNames(surfaces[0]!)).toContain('capture_lead');
|
|
62
|
+
expect(capabilityNames(surfaces[1]!)).toEqual([
|
|
63
|
+
'destinations',
|
|
64
|
+
'discover_getaways',
|
|
65
|
+
'create_handoff',
|
|
66
|
+
'my_trips',
|
|
67
|
+
]);
|
|
68
|
+
// Authoring the sign-up label is the opt-in for the card's create-account button.
|
|
69
|
+
expect(manifest.server.assistant?.labels?.signUpAction).toBe('Create free account');
|
|
70
|
+
});
|
|
71
|
+
|
|
33
72
|
it('declares the grounded knowledge component and its live site scope', async () => {
|
|
34
73
|
const manifest = (await app.toManifest()) as { server: { knowledge?: unknown[] } };
|
|
35
74
|
// One declaration: controlled files plus the live public site, compiled later into the
|
|
@@ -380,7 +380,15 @@ loopback development origins such as `http://localhost:3000`, `http://127.0.0.1:
|
|
|
380
380
|
|
|
381
381
|
The bounded `presentation` object configures the panel, launcher, header, composer, and messages. Its
|
|
382
382
|
primitives derive colors from shared server `branding`; raw HTML, CSS, inline SVG, renderer classes, and
|
|
383
|
-
callbacks are not accepted.
|
|
383
|
+
callbacks are not accepted. This example omits `presentation.panel.surface`, so the renderer keeps the
|
|
384
|
+
opaque default panel treatment while the example's light/dark `branding` surfaces provide its customer colors;
|
|
385
|
+
set the bounded surface to `glass` only when translucency is intentional.
|
|
386
|
+
|
|
387
|
+
These TypeScript values remain the reusable developer defaults. After deployment, an environment operator
|
|
388
|
+
can adjust theme, logo, launcher style, position, and the bounded color palette from the Console's
|
|
389
|
+
**Assistant** tab or `noodle assistant appearance` without changing the customer's embed code. See the
|
|
390
|
+
[embedded assistant guide](https://docs.noodleseed.dev/guides/embedded-assistant) for precedence and reset
|
|
391
|
+
behavior.
|
|
384
392
|
|
|
385
393
|
Create the backend credential after deployment. The CLI writes it to a mode-0600 file and never prints the
|
|
386
394
|
secret:
|
|
@@ -173,14 +173,22 @@ export default server(
|
|
|
173
173
|
access: authenticatedWebsite({
|
|
174
174
|
origins: [assistantOrigin, 'https://dev.noodleseed.com', 'http://localhost:3000'],
|
|
175
175
|
}),
|
|
176
|
-
|
|
176
|
+
theme: 'auto',
|
|
177
|
+
layout: { mode: 'floating', position: 'bottom-center', panelWidth: 970 },
|
|
178
|
+
behavior: { showPoweredBy: true },
|
|
177
179
|
labels: {
|
|
178
180
|
welcomeHeading: 'How can I help with Noodle Seed?',
|
|
181
|
+
launcherPlaceholder: 'Ask Noodle Seed anything',
|
|
179
182
|
composerPlaceholder: 'Ask about your apps…',
|
|
180
183
|
},
|
|
181
184
|
presentation: {
|
|
182
|
-
panel: {
|
|
183
|
-
launcher: {
|
|
185
|
+
panel: { elevation: 'soft', border: 'subtle' },
|
|
186
|
+
launcher: {
|
|
187
|
+
style: 'pill',
|
|
188
|
+
icon: 'brand-mark',
|
|
189
|
+
status: 'session',
|
|
190
|
+
effect: 'pulse',
|
|
191
|
+
},
|
|
184
192
|
header: {
|
|
185
193
|
mark: 'status',
|
|
186
194
|
badge: { text: 'Workspace online', tone: 'success', indicator: true },
|
|
@@ -9,7 +9,7 @@ describe('customer-auth example', () => {
|
|
|
9
9
|
model: { kind: 'openai-compatible', apiKey: 'ASSISTANT_MODEL_API_KEY' },
|
|
10
10
|
layout: { mode: 'floating' },
|
|
11
11
|
presentation: {
|
|
12
|
-
panel: {
|
|
12
|
+
panel: { elevation: 'soft', border: 'subtle' },
|
|
13
13
|
launcher: { icon: 'brand-mark', status: 'session', effect: 'pulse' },
|
|
14
14
|
header: { mark: 'status', badge: { text: 'Workspace online', tone: 'success' } },
|
|
15
15
|
},
|
|
@@ -3,7 +3,7 @@ name: executing-noodle-plans
|
|
|
3
3
|
description: "Use when the user asks to execute an approved, decision-complete implementation plan for a Noodle Seed project task by task with test-first changes, review, recovery, and final verification."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:6a9f132ddb79352e -->
|
|
7
7
|
|
|
8
8
|
# Execute a Noodle Seed implementation plan
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: publishing-mcp-integrations
|
|
|
3
3
|
description: "Use when preparing, reviewing, or submitting a Noodle Seed MCP integration to a host or app directory."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:efffbf82007f935d -->
|
|
7
7
|
|
|
8
8
|
# publishing-mcp-integrations
|
|
9
9
|
|
|
@@ -53,7 +53,7 @@ assistant: embeddedAssistant({
|
|
|
53
53
|
access: authenticatedWebsite({
|
|
54
54
|
origins: ["http://localhost:3000", "https://app.example.com"],
|
|
55
55
|
}),
|
|
56
|
-
layout: { mode: "floating", position: "bottom-
|
|
56
|
+
layout: { mode: "floating", position: "bottom-center" },
|
|
57
57
|
}),
|
|
58
58
|
```
|
|
59
59
|
|
|
@@ -107,11 +107,19 @@ access: publicWebsite({
|
|
|
107
107
|
capabilities: [answerProductQuestion, requestDemo, myOrders],
|
|
108
108
|
signIn: true, // `myOrders` reads ${user}; visitors sign in to reach it
|
|
109
109
|
}),
|
|
110
|
+
labels: {
|
|
111
|
+
signInHeading: "Continue with your Acme account",
|
|
112
|
+
signInBody: "Order history needs an account.", // optional; empty hides the line
|
|
113
|
+
signInAction: "Sign in",
|
|
114
|
+
signUpAction: "Create free account", // authoring this label is the sign-up opt-in
|
|
115
|
+
},
|
|
110
116
|
```
|
|
111
117
|
|
|
118
|
+
The sign-in card renders on the same themed chrome as every proposal card and follows the server `branding`. Authoring `signUpAction` adds a second button; leaving it out renders none. Both buttons raise the same `assistant-sign-in-requested` event with the same single-use ticket — the detail adds `intent: "sign-in" | "sign-up"` so the page routes `sign-up` to its registration page instead of its login. The ticket spend after account creation is identical to the one after sign-in; the service does not care which path produced the session. Headless renderers receive the same moment as a `data-sign-in` transcript part from `subscribeChat` (it has no status and is not respondable through `client.respond` — resolution is the elevated session).
|
|
119
|
+
|
|
112
120
|
Elevation runs through the **host application own login**, never a Noodle-operated one. The widget raises `assistant-sign-in-requested` with a single-use `signInTicket` in its detail; the page signs the visitor in as it already does, then its backend spends the ticket with `createAssistantSession({ ..., signInTicket })` from `@noodleseed/assistant/server` — the same session exchange, its own client credentials. A refused spend throws a typed `AssistantSessionExchangeError`: branch on `elevationRefusal` (`elevation_ticket_expired` re-prompt; `elevation_tenant_mismatch` alert, never retry). Possession of the ticket alone elevates nothing, and the service checks the client tenant owns that conversation. The ticket is not the server-held interaction continuation — that value never reaches browser code; this one exists to travel through the page.
|
|
113
121
|
|
|
114
|
-
The conversation is kept server-side: same session, new token, the anonymous one dead. By default the pending request also completes itself: the service re-attempts the intercepted tool under the new principal and streams it as the elevated session first turn (one-shot; mooted if the visitor types first; confirm-gated tools stop at their confirmation card; pass `resume: false` beside the ticket to disable).
|
|
122
|
+
The conversation is kept server-side: same session, new token, the anonymous one dead. By default the pending request also completes itself: the service re-attempts the intercepted tool under the new principal and streams it as the elevated session first turn (one-shot; mooted if the visitor types first; confirm-gated tools stop at their confirmation card; pass `resume: false` beside the ticket to disable). On a backend-exchanged reattach the widget also repaints the bounded visible transcript (`endpoints.transcript`) before the resume runs — only rows the panel actually showed replay, never tool internals or a spent ticket. On an older service without the endpoint the panel starts visually fresh while the model still remembers, so keep copy honest either way: "the assistant remembers". Do not build a second identity provider or client-side resume scaffolding for this.
|
|
115
123
|
|
|
116
124
|
When the login lives on a different origin (marketing site + app), the flow is: the visitor signs in via full-page redirect as the site already does; the backend spends the ticket presenting the **origin the conversation will continue on** (any origin in the deployment allowlist — elevation re-pins the session there, and CORS follows); the token reaches the widget through the customer own **same-origin** session endpoint on that origin. The redirect handoff is mandatory, not stylistic: the widget calls the session endpoint with `credentials: "same-origin"`, so pointing a marketing page at a cross-origin endpoint is a guaranteed cookie-less 401. Persist the ticket across the login redirect (single-use, expires in minutes); a refused origin does not burn it.
|
|
117
125
|
|
|
@@ -134,13 +142,15 @@ Keep portable identity and semantic light/dark colors in the one server-level `b
|
|
|
134
142
|
|
|
135
143
|
```ts
|
|
136
144
|
assistant: embeddedAssistant({
|
|
137
|
-
model,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
model,
|
|
146
|
+
access: authenticatedWebsite({ origins: ["https://app.example.com"] }),
|
|
147
|
+
theme: "invert",
|
|
148
|
+
layout: { position: "bottom-right", panelWidth: 520, panelMinHeight: 540, panelMaxHeight: 740, edgeOffset: 24 },
|
|
149
|
+
behavior: { showTimestamps: true, showPoweredBy: false },
|
|
150
|
+
labels: { launcherPlaceholder: "Ask Acme anything", composerPlaceholder: "Message Acme Support…", sessionReady: "Acme support is online" },
|
|
141
151
|
presentation: {
|
|
142
152
|
panel: { surface: "solid", elevation: "dramatic", border: "strong", radius: 20 },
|
|
143
|
-
launcher: { icon: "chat", size: "lg", status: "session", effect: "pulse" },
|
|
153
|
+
launcher: { style: "bubble", icon: "chat", size: "lg", status: "session", effect: "pulse" },
|
|
144
154
|
header: {
|
|
145
155
|
mark: "status",
|
|
146
156
|
badge: { text: "Online", tone: "success", indicator: true },
|
|
@@ -151,9 +161,9 @@ assistant: embeddedAssistant({
|
|
|
151
161
|
}),
|
|
152
162
|
```
|
|
153
163
|
|
|
154
|
-
The Atlas-style product treatment above is the maximum deployment-configurable presentation. The bounded surface covers panel treatment, launcher icon/size/session pulse, header mark/status badge, composer controls, and message treatment; it does not accept custom header actions, structured empty-state layouts, footers,
|
|
164
|
+
The Atlas-style product treatment above is the maximum deployment-configurable presentation. The bounded surface covers panel treatment, pill/bubble launcher style plus icon/size/session pulse, header mark/status badge, composer controls, and message treatment; it does not accept custom header actions, structured empty-state layouts, footers, tenant-defined launcher variants/effects, or tenant code.
|
|
155
165
|
|
|
156
|
-
Omitted fields retain the
|
|
166
|
+
Omitted UI fields retain the complete managed baseline: a bottom-center frosted prompt pill, 970px outer desktop shell with 20px side padding, 85vh/1025px height bounds, 24px panel with built-in `#F8F8F8` light and `#0C0A09` dark surfaces, bottom prompt chips and pill composer, plain assistant messages, 85%-wide user bubbles, Noodle Seed attribution, and mobile fullscreen. The pill morphs into an input before opening; `launcher.style: "bubble"` opens directly, while `panel.surface: "glass"` remains an explicit translucent alternative. `theme: "auto"` follows the host page and `"invert"` selects its opposite. The generic suggestions are defaults; an authored list, including `[]`, replaces them. The ChatGPT promotion is intentionally absent. For exact application-owned color roles, pass the typed React `appearance={{ light: { panel: { surface, text, border }, composer: {...}, confirmation: {...}, primaryButton: {...} }, dark: {...} }}` prop or assign the same object to `element.appearance`. CSS custom properties inherit through the assistant host, so those values may reuse existing application tokens such as `surface: "var(--app-surface)"` without copying literals. The appearance surface covers canvas, panel, header, messages, composer, suggestions, confirmation, buttons, launcher, code, and the MCP App frame; the package README publishes the complete role-to-`--ns-assistant-*` map. Exact parseable literal colors are preserved and low contrast emits `assistant-appearance-warning`; contrast for unresolved CSS references remains host-owned. Precedence is host appearance object, host slots/public variables, saved environment operator override, deployed semantic presentation, then defaults. Prefer reusable `server.ts` defaults; use the Console Assistant tab or `noodle assistant appearance show|apply|reset` for environment-owned changes that should reach existing embeds without a redeploy.
|
|
157
167
|
|
|
158
168
|
Give every business action a portable `tool(..., { title: "Complete task", description: "This will mark the task complete for everyone.", input: z.object({ task: z.string().meta({ title: "Task" }) }) })` title. The standard confirmation uses the tool title/description plus schema field `title`, `description`, and `format`; it shows Confirm and Don't proceed and keeps technical action details secondary. Do not put JSON or implementation names in business-facing copy.
|
|
159
169
|
|
|
@@ -311,12 +321,15 @@ const session = await createAssistantSession({
|
|
|
311
321
|
|
|
312
322
|
```ts
|
|
313
323
|
assistant: embeddedAssistant({
|
|
314
|
-
model,
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
324
|
+
model,
|
|
325
|
+
access: authenticatedWebsite({
|
|
326
|
+
origins: ["https://app.example.com"],
|
|
327
|
+
sessionClaims: {
|
|
328
|
+
displayName: { exposeToModel: true },
|
|
329
|
+
accountTier: { exposeToModel: true },
|
|
330
|
+
region: {}, // tools only, never in the prompt
|
|
331
|
+
},
|
|
332
|
+
}),
|
|
320
333
|
}),
|
|
321
334
|
```
|
|
322
335
|
|
|
@@ -372,7 +385,9 @@ import { NoodleAssistant } from "@noodleseed/assistant/react";
|
|
|
372
385
|
|
|
373
386
|
Or import the package root once and mount `<noodle-assistant session-endpoint="/api/assistant/session" theme="auto"></noodle-assistant>`. Mount only inside the authenticated application surface.
|
|
374
387
|
|
|
375
|
-
|
|
388
|
+
That custom element is the complete managed assistant in Vue, Angular, or plain DOM; it has no React runtime requirement. Configure the framework to accept `noodle-assistant` as a custom element. If the session exchange needs an authenticated fetch wrapper, create the element imperatively, assign `element.fetch` and then `element.sessionEndpoint`, and append it only after both properties are set.
|
|
389
|
+
|
|
390
|
+
`theme="auto"` follows an explicit host-page light/dark class or data attribute, then the browser operating-system preference; `theme="invert"` selects the opposite. If the SaaS application owns a theme toggle, obtain its resolved application theme (`"light"` or `"dark"`), pass `theme={resolvedTheme}` to `NoodleAssistant`, and update the custom element's `theme` attribute when that value changes.
|
|
376
391
|
|
|
377
392
|
The component renders a custom element and must mount client-side. In a Next.js App Router tree, put the mount in a `"use client"` component; from a server component or the Pages Router, load it with `next/dynamic` and `ssr: false`.
|
|
378
393
|
|
|
@@ -627,7 +642,7 @@ Devtools privacy gate: default model and connector exercises to synthetic or moc
|
|
|
627
642
|
|
|
628
643
|
- Signed-out session exchange returns `401`.
|
|
629
644
|
- The browser network/DOM/storage contains no client secret or model key.
|
|
630
|
-
- The local and production origins match `
|
|
645
|
+
- The local and production origins match the authored `access.origins` character-for-character.
|
|
631
646
|
- At the manifest/runtime boundary and in TypeScript action helpers, only `confirm: true` enables confirmation; omitted or `false` preserves direct execution. Action hints alone never enforce approval; `annotations.action({ confirm: false })` is equivalent to omission.
|
|
632
647
|
- An expired turn re-exchanges once; interaction decisions never auto-retry. An explicit same-decision repeat returns the stored outcome without executing again.
|
|
633
648
|
- Accept, decline, and cancel are single-use. Only accept executes; the server ignores replacement tool arguments.
|
|
@@ -645,9 +660,9 @@ Devtools privacy gate: default model and connector exercises to synthetic or moc
|
|
|
645
660
|
| `assistant-error` with code `invalid_response` | The turn endpoint returned HTML or non-SSE content (auth redirect, proxy page) | Check the backend session route path and any middleware/rewrites on the embedding app |
|
|
646
661
|
| Build error `Package path ./react is not exported` | Outdated package version with import-only export conditions | Update `@noodleseed/assistant`; do not add webpack aliases or type shims |
|
|
647
662
|
| Deploy fails with `server_auth_required` | `--access customers` without `server.auth` | Add direct/federated OIDC or a built-in Firebase/Microsoft adapter |
|
|
648
|
-
| Validate rejects an origin | Non-loopback HTTP origin in `
|
|
663
|
+
| Validate rejects an origin | Non-loopback HTTP origin in `access.origins` | Use the exact HTTPS production origin; HTTP is only for `localhost`/`127.0.0.1` |
|
|
649
664
|
| Session exchange returns 404 | `serviceUrl` points at the deployment MCP endpoint | Use the control-plane service URL printed by `noodle assistant clients create` |
|
|
650
|
-
| Session exchange returns 403 `origin is not allowed` | Request origin differs from `
|
|
665
|
+
| Session exchange returns 403 `origin is not allowed` | Request origin differs from the authored `access.origins` character-for-character | Align the exact scheme/host/port on both sides and redeploy |
|
|
651
666
|
| Session exchange returns `400` with `invalid assistant routing` | The authenticated backend supplied an unknown endpoint name or a malformed/policy-disallowed URL | Resolve the route from server-owned membership, use the exact authored endpoint name, and ensure the canonical HTTPS URL satisfies its active `customerEndpoint` policy; the error never reflects the URL |
|
|
652
667
|
| A routed assistant tool returns `connector_route_unavailable` | The authenticated backend omitted that endpoint during session exchange | Pass the server-verified route as `routing.endpoints.<name>` when minting a new session; keep it out of browser input |
|
|
653
668
|
| Host session 503 | A required backend environment name is absent or mapped into the wrong deployment environment | Run `noodle assistant embed --check --json`, repair the host CI mapping, then probe the session route again |
|
|
@@ -3,7 +3,7 @@ name: reporting-noodle-feedback
|
|
|
3
3
|
description: "Use when a Noodle Seed bug, misleading instruction, missing capability, or concrete product improvement should be proposed to the user."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.86.0 hash:0f404109f4845683 -->
|
|
7
7
|
|
|
8
8
|
# reporting-noodle-feedback
|
|
9
9
|
|