@noodleseed/agent-kit 0.84.1 → 0.85.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 +265 -265
- 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/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 +9 -1
- package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/claude-code/wrapping-existing-applications/SKILL.md +1 -1
- package/skills/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/executing-noodle-plans/SKILL.md +1 -1
- package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/codex/references/embedded-assistant.md +9 -1
- package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/wrapping-existing-applications/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.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.85.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.85.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.85.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.85.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.85.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.85.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.85.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.85.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.85.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
|
|
@@ -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.85.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.85.0 hash:efffbf82007f935d -->
|
|
7
7
|
|
|
8
8
|
# publishing-mcp-integrations
|
|
9
9
|
|
|
@@ -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
|
|
|
@@ -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.85.0 hash:0f404109f4845683 -->
|
|
7
7
|
|
|
8
8
|
# reporting-noodle-feedback
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: verifying-mcp-delivery
|
|
|
3
3
|
description: "Use when proving a Noodle Seed MCP project works at a named compile, local, connector, App, host, deployment, or production evidence level."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.85.0 hash:6ef6ef551e26b78e -->
|
|
7
7
|
|
|
8
8
|
# verifying-mcp-delivery
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: wrapping-existing-applications
|
|
|
3
3
|
description: "Use when an existing application has no stable usable API and needs a read-only, identity-first Noodle Seed integration plan before implementation."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.85.0 hash:eccc3c158dcafba8 -->
|
|
7
7
|
|
|
8
8
|
# wrapping-existing-applications
|
|
9
9
|
|
package/skills/codex/SKILL.md
CHANGED
|
@@ -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.85.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.85.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.85.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.85.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.85.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.85.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.85.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.85.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.85.0 hash:cc54a67f21c0ecdb -->
|
|
7
7
|
|
|
8
8
|
# embedding-mcp-assistants
|
|
9
9
|
|