@webmcp-auto-ui/agent 2.5.25 → 2.5.27
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/package.json +1 -1
- package/src/autoui-server.ts +44 -0
- package/src/diagnostics.ts +6 -6
- package/src/discovery-cache.ts +17 -3
- package/src/index.ts +18 -4
- package/src/loop.ts +31 -34
- package/src/notebook-widgets/compact.ts +312 -0
- package/src/notebook-widgets/document.ts +372 -0
- package/src/notebook-widgets/editorial.ts +348 -0
- package/src/notebook-widgets/recipes/compact.md +104 -0
- package/src/notebook-widgets/recipes/document.md +100 -0
- package/src/notebook-widgets/recipes/editorial.md +104 -0
- package/src/notebook-widgets/recipes/workspace.md +94 -0
- package/src/notebook-widgets/shared.ts +1064 -0
- package/src/notebook-widgets/workspace.ts +328 -0
- package/src/prompts/claude-prompt-builder.ts +81 -0
- package/src/prompts/gemma4-prompt-builder.ts +205 -0
- package/src/prompts/index.ts +55 -0
- package/src/prompts/mistral-prompt-builder.ts +90 -0
- package/src/prompts/qwen-prompt-builder.ts +90 -0
- package/src/prompts/tool-call-parsers.ts +322 -0
- package/src/prompts/tool-refs.ts +196 -0
- package/src/providers/factory.ts +20 -3
- package/src/providers/transformers-models.ts +143 -0
- package/src/providers/transformers-serialize.ts +81 -0
- package/src/providers/transformers.ts +329 -0
- package/src/providers/transformers.worker.ts +667 -0
- package/src/providers/wasm.ts +150 -510
- package/src/recipes/_generated.ts +515 -0
- package/src/recipes/canary-data.md +50 -0
- package/src/recipes/canary-display.md +99 -0
- package/src/recipes/canary-middle.md +32 -0
- package/src/recipes/hackathon-assemblee-nationale.md +111 -0
- package/src/recipes/hummingbird-data.md +32 -0
- package/src/recipes/hummingbird-display.md +36 -0
- package/src/recipes/hummingbird-middle.md +18 -0
- package/src/recipes/notebook-playbook.md +129 -0
- package/src/tool-layers.ts +33 -157
- package/src/trace-observer.ts +669 -0
- package/src/types.ts +20 -5
- package/src/util/opfs-cache.ts +265 -0
- package/tests/gemma-prompt.test.ts +472 -0
- package/tests/loop.test.ts +5 -5
- package/tests/transformers-serialize.test.ts +103 -0
- package/src/providers/gemma.worker.legacy.ts +0 -123
- package/src/providers/litert.worker.ts +0 -294
- package/src/recipes/widgets/actions.md +0 -28
- package/src/recipes/widgets/alert.md +0 -27
- package/src/recipes/widgets/cards.md +0 -41
- package/src/recipes/widgets/carousel.md +0 -39
- package/src/recipes/widgets/chart-rich.md +0 -51
- package/src/recipes/widgets/chart.md +0 -32
- package/src/recipes/widgets/code.md +0 -21
- package/src/recipes/widgets/d3.md +0 -36
- package/src/recipes/widgets/data-table.md +0 -46
- package/src/recipes/widgets/gallery.md +0 -39
- package/src/recipes/widgets/grid-data.md +0 -57
- package/src/recipes/widgets/hemicycle.md +0 -43
- package/src/recipes/widgets/js-sandbox.md +0 -32
- package/src/recipes/widgets/json-viewer.md +0 -27
- package/src/recipes/widgets/kv.md +0 -31
- package/src/recipes/widgets/list.md +0 -24
- package/src/recipes/widgets/log.md +0 -39
- package/src/recipes/widgets/map.md +0 -49
- package/src/recipes/widgets/profile.md +0 -49
- package/src/recipes/widgets/recipe-browser.md +0 -102
- package/src/recipes/widgets/sankey.md +0 -54
- package/src/recipes/widgets/stat-card.md +0 -43
- package/src/recipes/widgets/stat.md +0 -35
- package/src/recipes/widgets/tags.md +0 -30
- package/src/recipes/widgets/text.md +0 -19
- package/src/recipes/widgets/timeline.md +0 -38
- package/src/recipes/widgets/trombinoscope.md +0 -39
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: canary-middle
|
|
3
|
+
name: Canary test — middle level (verbose style)
|
|
4
|
+
when: called from canary-display only
|
|
5
|
+
servers: [autoui]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Canary token
|
|
9
|
+
CANARY-MIDDLE-M5392
|
|
10
|
+
|
|
11
|
+
## Secret phrase (must be quoted verbatim)
|
|
12
|
+
"Under the stone bridge, eleven minnows wait for the tide."
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
Internal recipe. Called exclusively by `canary-display` at STEP 1. Do NOT invoke on its own. Do NOT render widgets.
|
|
17
|
+
|
|
18
|
+
## How to use
|
|
19
|
+
|
|
20
|
+
Return to the caller:
|
|
21
|
+
- `middleToken`: `CANARY-MIDDLE-M5392`
|
|
22
|
+
- `middleSecretPhrase`: `Under the stone bridge, eleven minnows wait for the tide.`
|
|
23
|
+
|
|
24
|
+
### STEP 1 — Confirm in chat
|
|
25
|
+
|
|
26
|
+
Reply exactly: `✅ canary-middle followed — M5392`
|
|
27
|
+
|
|
28
|
+
## Forbidden
|
|
29
|
+
|
|
30
|
+
- DO NOT render any widget.
|
|
31
|
+
- DO NOT substitute the token or the secret phrase.
|
|
32
|
+
- DO NOT invent additional fields.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hackathon-assemblee-nationale-mcp-webmcp
|
|
3
|
+
name: Hackathon Assemblée Nationale · MCP & WebMCP starter
|
|
4
|
+
components_used: [notebook-document, notebook-compact]
|
|
5
|
+
when: the user mentions the Assemblée Nationale hackathon, wants to experiment with parliamentary data (Tricoteuses, Legifrance), or asks for a starter notebook to explore deputies, votes, amendments, or legislative texts in a hackathon context. Keywords include "hackathon Assemblée Nationale", "MCP WebMCP hackathon", "tricoteuses playbook", "parlement playground", "hackathon parlementaire".
|
|
6
|
+
servers: [autoui, tricoteuses]
|
|
7
|
+
layout:
|
|
8
|
+
type: single
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
|
|
13
|
+
The user is participating in (or preparing for) the Assemblée Nationale hackathon around MCP and WebMCP. They want a ready-to-fork notebook with:
|
|
14
|
+
- A clear onboarding (what the hackathon is about, what data is available)
|
|
15
|
+
- Seed queries against Tricoteuses (parliamentary database) that return meaningful results immediately
|
|
16
|
+
- A visualization or transformation cell that the participant can iterate on
|
|
17
|
+
- A closing "your turn" note inviting them to modify, run, and share
|
|
18
|
+
|
|
19
|
+
This recipe is a **specialization** of the generic `notebook-playbook` recipe — pre-filled with parliamentary-specific seed cells and tailored to the hackathon narrative.
|
|
20
|
+
|
|
21
|
+
## How to use
|
|
22
|
+
|
|
23
|
+
### Step 1 — Pick the document layout
|
|
24
|
+
|
|
25
|
+
Default to `notebook-document` for the hackathon. Reasons:
|
|
26
|
+
- Participants expect to collaborate and leave traces for each other (avatars, comments)
|
|
27
|
+
- The document layout reads naturally as "hackathon brief + starter code" rather than "dev tool"
|
|
28
|
+
- Margin comments can be seeded to hint at directions to explore
|
|
29
|
+
|
|
30
|
+
If the participant asks for a minimal dev-focused playground instead, fall back to `notebook-compact`.
|
|
31
|
+
|
|
32
|
+
### Step 2 — Seed the cells
|
|
33
|
+
|
|
34
|
+
The notebook should contain 5–7 cells covering:
|
|
35
|
+
|
|
36
|
+
1. **Intro markdown** — title, one-line context of the hackathon, link to relevant docs
|
|
37
|
+
2. **Challenge markdown** — a few axes of exploration suggested by the hackathon organizers (replace placeholders with actual challenges when known)
|
|
38
|
+
3. **Starter SQL** — a safe, visibly meaningful query on Tricoteuses (e.g. recent votes, most active deputies, amendments on a specific text)
|
|
39
|
+
4. **Transform JS** — a small JS cell that post-processes the SQL result (grouping, counting, chart prep)
|
|
40
|
+
5. **Visualization hint** — a markdown cell pointing at available visualization widgets (`hemicycle`, `profile`, `timeline`)
|
|
41
|
+
6. **Your turn** — a closing markdown inviting the participant to fork and edit
|
|
42
|
+
|
|
43
|
+
Example template (to be refined with actual hackathon organizers' briefs):
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
widget_display({name: "notebook-document", params: {
|
|
47
|
+
title: "Hackathon Assemblée Nationale · starter",
|
|
48
|
+
cells: [
|
|
49
|
+
{
|
|
50
|
+
type: "md",
|
|
51
|
+
content: "### Bienvenue au hackathon IA de l'Assemblée Nationale\n\nCe notebook est votre point de départ. Les données parlementaires proviennent du serveur <mark>Tricoteuses</mark>. Forkez, éditez, partagez."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "md",
|
|
55
|
+
content: "### Pistes d'exploration\n\n- Profil d'un parlementaire et son activité\n- Cartographie des votes par groupe politique\n- Amendements sur un texte précis\n- Enrichissement Wikipedia des parlementaires\n- Croisement entre circonscription et données externes"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "sql",
|
|
59
|
+
content: "-- 10 scrutins les plus récents\nSELECT id, date, intitule, pour, contre, abstention\nFROM scrutins\nORDER BY date DESC\nLIMIT 10",
|
|
60
|
+
comment: {
|
|
61
|
+
who: "organizers",
|
|
62
|
+
when: "start",
|
|
63
|
+
body: "Remplacez par une requête sur un texte qui vous intéresse (LIMIT important)."
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: "js",
|
|
68
|
+
content: "// Regroupe les scrutins par mois\nconst byMonth = {};\nfor (const s of rows) {\n const m = s.date.slice(0, 7);\n byMonth[m] = (byMonth[m] || 0) + 1;\n}\nconsole.table(byMonth);"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: "md",
|
|
72
|
+
content: "### Visualiser\n\nPour afficher un profil parlementaire : `widget_display({name: \"profile\", params: {...}})`.\nPour un hémicycle : `widget_display({name: \"hemicycle\", params: {groups, result}})`.\nPour une timeline de mandats : `widget_display({name: \"timeline\", params: {events}})`."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: "md",
|
|
76
|
+
content: "### À vous\n\nAjoutez des cellules avec `+ sql / + code`, réarrangez, exécutez. Quand vous avez quelque chose d'intéressant, cliquez `share` pour partager le lien hyperskill avec votre équipe ou les organisateurs."
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 3 — Adapt to what the user knows
|
|
83
|
+
|
|
84
|
+
- If the user has no background in the Tricoteuses schema, mention they can use `list_tables` and `describe_table` on the Tricoteuses server to discover the data model before writing queries.
|
|
85
|
+
- If the user already has a specific question ("I want to look at votes on the 2024 budget"), rewrite the starter SQL to target that question directly.
|
|
86
|
+
- If the user asks for a minimal playground without the hackathon framing, fall back to the generic `notebook-playbook` recipe instead.
|
|
87
|
+
|
|
88
|
+
### Step 4 — Share
|
|
89
|
+
|
|
90
|
+
After creating the notebook, remind the user that they can:
|
|
91
|
+
- Use `share` → `Hyperskill link` to generate a fork-friendly URL to paste into their team's channel
|
|
92
|
+
- Switch to `view` mode when demoing to organizers
|
|
93
|
+
- Consult `⟲ history` to see their iteration trace and restore cells deleted by mistake
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
This recipe is intended as a **starting skeleton** — the concrete hackathon brief (dates, prizes, specific challenges, dataset access restrictions) will be refined in a dedicated session with the organizers. Placeholders in the example above (axes of exploration, comments) should be replaced with the actual material provided by the hackathon team when it is available.
|
|
98
|
+
|
|
99
|
+
For parliamentary profile pages, vote results with hemicycles, and legislative file browsing, prefer the dedicated recipes:
|
|
100
|
+
- `display-parliamentary-profile-with-hemicycle-and-votes`
|
|
101
|
+
- `explorer-dossiers-legislatifs-parcours-texte`
|
|
102
|
+
- `rechercher-textes-juridiques-legifrance`
|
|
103
|
+
|
|
104
|
+
These recipes produce more specialized layouts than a generic notebook, and are better suited when the output is a single focused page rather than a multi-cell playbook.
|
|
105
|
+
|
|
106
|
+
## Common mistakes
|
|
107
|
+
|
|
108
|
+
- **Forgetting the hackathon framing**: without the "bienvenue" and "à vous" markdown cells, participants land on a bare notebook and lose the playbook feeling.
|
|
109
|
+
- **SQL without LIMIT**: Tricoteuses queries without `LIMIT` can return thousands of rows and slow down the first impression.
|
|
110
|
+
- **Inventing data**: do not seed with fake French parliamentary content (fake deputies, fake votes). If the actual data is not known at seed time, use generic queries (`SELECT * FROM scrutins LIMIT 5`) and let the user discover from there.
|
|
111
|
+
- **Picking the wrong layout**: if the user is solo and wants to code fast, `notebook-compact` is better than `notebook-document`. Default to document for hackathon context because of the collaborative framing, not as a hard rule.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hummingbird-data
|
|
3
|
+
name: Hummingbird test — data + JS (tight style)
|
|
4
|
+
when: called from hummingbird-display
|
|
5
|
+
servers: [autoui]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Hummingbird data
|
|
9
|
+
|
|
10
|
+
Token: HUMMINGBIRD-DATA-D7244
|
|
11
|
+
Phrase: "The weaver counts backwards from seven."
|
|
12
|
+
|
|
13
|
+
## JS
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
document.getElementById('out').textContent = (function(){
|
|
17
|
+
const p = 'EXEC-HUMMINGBIRD-H2947';
|
|
18
|
+
const a = 23 * 2;
|
|
19
|
+
const b = a + 8;
|
|
20
|
+
const c = b % 10;
|
|
21
|
+
return `${p}-${c}${b}`;
|
|
22
|
+
})();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Expected: `EXEC-HUMMINGBIRD-H2947-454`
|
|
26
|
+
|
|
27
|
+
## Steps
|
|
28
|
+
|
|
29
|
+
1. Return `{dataToken: "HUMMINGBIRD-DATA-D7244", dataPhrase: "The weaver counts backwards from seven.", jsCode: "<the JS block above verbatim>", expectedOutput: "EXEC-HUMMINGBIRD-H2947-454"}` to caller.
|
|
30
|
+
2. Chat: `✅ hummingbird-data followed — D7244 + JS`
|
|
31
|
+
|
|
32
|
+
Do NOT render any widget. Do NOT modify the JS.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hummingbird-display
|
|
3
|
+
name: Hummingbird test — entry (tight style)
|
|
4
|
+
when: user asks to "affiche le hummingbird", "show hummingbird", or "test hummingbird"
|
|
5
|
+
components_used: [js-sandbox, kv]
|
|
6
|
+
servers: [autoui]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Hummingbird entry
|
|
10
|
+
|
|
11
|
+
Token: HUMMINGBIRD-DISPLAY-C5619
|
|
12
|
+
Phrase: "Nine moths circle the candle."
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
|
|
16
|
+
1. `get_recipe("hummingbird-middle")` → extract `middleToken`, `middlePhrase`.
|
|
17
|
+
2. `get_recipe("hummingbird-data")` → extract `dataToken`, `dataPhrase`, `jsCode`, `expectedOutput`.
|
|
18
|
+
3. `widget_display({name: "js-sandbox", params: {title: "Hummingbird JS", code: "<jsCode from step 2 verbatim>", html: "<div id='out' style='font-family:monospace;padding:8px;'></div>", css: "body{margin:0;background:#fff;color:#000;font-size:14px;}"}})`
|
|
19
|
+
4. `widget_display({name: "kv", params: {title: "🐦 Hummingbird Retex — tight style", rows: [
|
|
20
|
+
["entry_id", "hummingbird-display"],
|
|
21
|
+
["entry_token", "HUMMINGBIRD-DISPLAY-C5619"],
|
|
22
|
+
["entry_phrase", "Nine moths circle the candle."],
|
|
23
|
+
["middle_id", "hummingbird-middle"],
|
|
24
|
+
["middle_token", "<step 1>"],
|
|
25
|
+
["middle_phrase", "<step 1>"],
|
|
26
|
+
["data_id", "hummingbird-data"],
|
|
27
|
+
["data_token", "<step 2>"],
|
|
28
|
+
["data_phrase", "<step 2>"],
|
|
29
|
+
["js_code_verbatim", "<step 2>"],
|
|
30
|
+
["js_expected_output", "<step 2>"],
|
|
31
|
+
["steps", "1, 2, 3, 4, 5"],
|
|
32
|
+
["style", "tight"]
|
|
33
|
+
]}})`
|
|
34
|
+
5. Chat: `✅ hummingbird chain followed — 3 recipes + JS exec`
|
|
35
|
+
|
|
36
|
+
All tokens, phrases, and JS code MUST be verbatim.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hummingbird-middle
|
|
3
|
+
name: Hummingbird test — middle (tight style)
|
|
4
|
+
when: called from hummingbird-display
|
|
5
|
+
servers: [autoui]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Hummingbird middle
|
|
9
|
+
|
|
10
|
+
Token: HUMMINGBIRD-MIDDLE-N7614
|
|
11
|
+
Phrase: "Salt crystals sing in the kettle at midnight."
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
1. Return `{middleToken: "HUMMINGBIRD-MIDDLE-N7614", middlePhrase: "Salt crystals sing in the kettle at midnight."}` to caller.
|
|
16
|
+
2. Chat: `✅ hummingbird-middle followed — N7614`
|
|
17
|
+
|
|
18
|
+
Do NOT render any widget.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: create-interactive-notebook-playbook
|
|
3
|
+
name: Create an interactive notebook playbook
|
|
4
|
+
components_used: [notebook-compact, notebook-workspace, notebook-document, notebook-editorial]
|
|
5
|
+
when: the user wants to experiment with data, prototype a small analysis, share a reusable scenario, let others fork and try a dataset, or prepare a hackathon-ready playground. Keywords include "playground", "playbook", "experiment", "try", "prototype", "hackathon", "share a notebook", "template", "starter".
|
|
6
|
+
servers: [autoui]
|
|
7
|
+
layout:
|
|
8
|
+
type: single
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
|
|
13
|
+
The user asks for a **notebook-like interactive playground** that combines text, queries, and code cells. Typical triggers:
|
|
14
|
+
- "Give me a playground for exploring X"
|
|
15
|
+
- "Prepare a notebook I can share with my team"
|
|
16
|
+
- "I want to prototype a small analysis"
|
|
17
|
+
- "Set up a hackathon starter"
|
|
18
|
+
- "Make a reusable template for exploring CSVs / this API / these tables"
|
|
19
|
+
|
|
20
|
+
This recipe applies across domains (parliamentary data, biodiversity, news, business datasets, etc.) — it only prescribes the **shape** of the answer, not its content.
|
|
21
|
+
|
|
22
|
+
## How to use
|
|
23
|
+
|
|
24
|
+
### Step 1 — Pick the right notebook layout
|
|
25
|
+
|
|
26
|
+
Choose one of the four `notebook-*` widgets based on the user's implicit intent:
|
|
27
|
+
|
|
28
|
+
| Layout | Use when |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `notebook-compact` | Quick data exploration, reactive dataflow with named outputs, minimal chrome. **Default for most "playground" and "hackathon" requests.** |
|
|
31
|
+
| `notebook-workspace` | The user expects a multi-cell analyst workspace with sources, cell navigation, and a "publish" step. Use when they mention "dashboard", "app", "workspace". |
|
|
32
|
+
| `notebook-document` | The user plans to share and discuss with a team. Use when "collaborate", "review", "comment" appear. |
|
|
33
|
+
| `notebook-editorial` | The user wants a polished, article-like final deliverable mixing prose and code. Use for "memo", "report", "writeup", "blog-style". |
|
|
34
|
+
|
|
35
|
+
When in doubt, pick `notebook-compact`.
|
|
36
|
+
|
|
37
|
+
### Step 2 — Pre-fill the cells with context-aware seeds
|
|
38
|
+
|
|
39
|
+
Never create an empty notebook. Always seed with 3–5 cells that give the user an immediate starting point:
|
|
40
|
+
|
|
41
|
+
1. **First cell: markdown** — title + one-sentence context of what the notebook is for
|
|
42
|
+
2. **Second cell: markdown or code** — if an MCP data source is connected, a starter query that returns something visible (e.g. `SELECT * FROM {table} LIMIT 10`). Otherwise a markdown cell describing the next step
|
|
43
|
+
3. **Third cell: code** — a transformation or a visualization that uses the output of step 2. Use `varname` on the SQL cell (`varname: "rows"`) and reference it in the JS cell
|
|
44
|
+
4. **Last cell: markdown** — a short "to you to play" note inviting the user to add cells, edit, or fork
|
|
45
|
+
|
|
46
|
+
Example seed for a generic data playground:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
widget_display({name: "notebook-compact", params: {
|
|
50
|
+
title: "Exploration playground",
|
|
51
|
+
cells: [
|
|
52
|
+
{type: "md", content: "### Exploration playground\n\nStart by running the first SQL cell, then iterate."},
|
|
53
|
+
{type: "sql", content: "select * from source limit 10", varname: "rows"},
|
|
54
|
+
{type: "js", content: "// explore rows here\nconsole.table(rows)"},
|
|
55
|
+
{type: "md", content: "### Your turn\n\nAdd cells with `+ sql` or `+ js`, reorder via the drag handle, and share via the header button."}
|
|
56
|
+
]
|
|
57
|
+
}})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Step 3 — Adapt seeds to the connected MCP server
|
|
61
|
+
|
|
62
|
+
If a specific MCP server is connected, replace the generic `source` and `select *` placeholders with actual tables and queries from that server:
|
|
63
|
+
- For a parliamentary server (Tricoteuses): use actual tables like `acteurs`, `scrutins`, `amendements` with meaningful LIMIT
|
|
64
|
+
- For a biodiversity server (iNaturalist): use the server's typical queries to return observations
|
|
65
|
+
- For a generic SQL server: list tables first (`list_tables` or `describe_table`), then seed with a `SELECT * FROM {first_table} LIMIT 10`
|
|
66
|
+
|
|
67
|
+
Always keep queries **short** and **limited** so the first run returns quickly and visually.
|
|
68
|
+
|
|
69
|
+
### Step 4 — Share and fork
|
|
70
|
+
|
|
71
|
+
After creating the notebook, mention to the user that they can:
|
|
72
|
+
- Click `share` in the toolbar to open the export modal (hyperskill link for in-session sharing, markdown/png/json for external export)
|
|
73
|
+
- Switch to `view` mode (read-only, no controls visible) when presenting to someone
|
|
74
|
+
- Access the `⟲ history` panel to see the trace of edits, and restore deleted cells
|
|
75
|
+
|
|
76
|
+
For hackathon contexts, prefer seeding a **document** layout (comments + avatars) so participants feel they are joining a shared space.
|
|
77
|
+
|
|
78
|
+
## Examples
|
|
79
|
+
|
|
80
|
+
### Generic CSV / table playground
|
|
81
|
+
```
|
|
82
|
+
// user: "I need a playground to play with this CSV"
|
|
83
|
+
widget_display({name: "notebook-compact", params: {
|
|
84
|
+
title: "CSV playground",
|
|
85
|
+
cells: [
|
|
86
|
+
{type: "md", content: "### CSV playground\n\nRun the SQL cell to see the first rows, then iterate."},
|
|
87
|
+
{type: "sql", content: "select * from source limit 20", varname: "rows"},
|
|
88
|
+
{type: "js", content: "// summarize, chart, or filter rows here"}
|
|
89
|
+
]
|
|
90
|
+
}})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Collaborative analysis
|
|
94
|
+
```
|
|
95
|
+
// user: "Set up a notebook my team can edit together"
|
|
96
|
+
widget_display({name: "notebook-document", params: {
|
|
97
|
+
title: "Team analysis",
|
|
98
|
+
cells: [
|
|
99
|
+
{type: "md", content: "Kick-off: describe the question here."},
|
|
100
|
+
{type: "sql", content: "select * from source limit 10"},
|
|
101
|
+
{type: "md", content: "Your findings: add thoughts, highlights (<mark>key sentence</mark>), and comments on the code cells above."}
|
|
102
|
+
]
|
|
103
|
+
}})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Final memo
|
|
107
|
+
```
|
|
108
|
+
// user: "Prepare a short memo of my findings"
|
|
109
|
+
widget_display({name: "notebook-editorial", params: {
|
|
110
|
+
title: "Findings memo",
|
|
111
|
+
kicker: "memo",
|
|
112
|
+
cells: [
|
|
113
|
+
{type: "md", content: "Three observations from last week's data."},
|
|
114
|
+
{type: "md", content: "First, the volume is up. Here is the query:"},
|
|
115
|
+
{type: "sql", content: "select count(*) from source"},
|
|
116
|
+
{type: "md", content: "Second, the distribution has shifted."},
|
|
117
|
+
{type: "js", content: "// chart distribution"},
|
|
118
|
+
{type: "md", content: "We conclude that..."}
|
|
119
|
+
]
|
|
120
|
+
}})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Common mistakes
|
|
124
|
+
|
|
125
|
+
- **Empty notebook**: never call `widget_display` without at least 3 seed cells. The user expects something they can immediately run.
|
|
126
|
+
- **Wrong layout for the intent**: do not use `notebook-editorial` for quick exploration — it signals "finished article" and intimidates. Use `notebook-compact` unless the user explicitly asks for a publication feel.
|
|
127
|
+
- **Heavy initial queries**: always `LIMIT 10` or `LIMIT 20` in seed SQL cells. Users will expand later if needed.
|
|
128
|
+
- **Missing `varname` on SQL cells** (in compact layout): the named output is what the compact layout showcases. Without it, the notebook loses half its reactive story.
|
|
129
|
+
- **Inventing UUIDs or fork IDs**: leave `id` and `forkId` unset — the widget generates sensible defaults. Only pass `id` when restoring an existing notebook.
|
package/src/tool-layers.ts
CHANGED
|
@@ -8,9 +8,19 @@ import type { SchemaPatch } from '@webmcp-auto-ui/core';
|
|
|
8
8
|
import { DiscoveryCache, type ServerCache } from './discovery-cache.js';
|
|
9
9
|
import type { PipelineTrace } from './pipeline-trace.js';
|
|
10
10
|
|
|
11
|
+
/** Map an internal protocol type to the token used in tool-name prefixes.
|
|
12
|
+
* - `mcp` (remote data sources) → `data`
|
|
13
|
+
* - `webmcp` (local UI widgets) → `ui`
|
|
14
|
+
* Final tool names follow {server}_{token}_{tool} (e.g. `tricoteuses_data_search_recipes`,
|
|
15
|
+
* `autoui_ui_widget_display`). Using neutral tokens avoids lexical confusion between
|
|
16
|
+
* MCP and WebMCP for small LLMs. */
|
|
17
|
+
export function protocolToken(protocol: 'mcp' | 'webmcp'): 'data' | 'ui' {
|
|
18
|
+
return protocol === 'mcp' ? 'data' : 'ui';
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
/** Sanitize a server name for use in tool name prefixes.
|
|
12
22
|
* Returns a clean underscore-separated identifier with no "mcp"/"server" noise.
|
|
13
|
-
* Final tool names follow {server}_{
|
|
23
|
+
* Final tool names follow {server}_{token}_{tool} convention. */
|
|
14
24
|
export function sanitizeServerName(name: string): string {
|
|
15
25
|
let result = name.toLowerCase()
|
|
16
26
|
.replace(/[^a-z0-9]+/g, '_') // all non-alphanumeric → underscore
|
|
@@ -23,6 +33,12 @@ export function sanitizeServerName(name: string): string {
|
|
|
23
33
|
return result || 'mcp';
|
|
24
34
|
}
|
|
25
35
|
|
|
36
|
+
// ── Discovery pseudo-tool descriptions (long form, used in tool schemas) ──
|
|
37
|
+
const longSearchToolsDesc = (serverName: string) =>
|
|
38
|
+
`Search tools by keyword on the ${serverName} server. Use this when you need to find a specific data-fetching or action tool but don't know its exact name. Pass a keyword related to the task (e.g. "weather", "search", "create") and get back matching tool names with descriptions and input schemas. This is more targeted than list_tools — prefer it when you have a clear idea of what you're looking for. Returns an array of {name, description, inputSchema} objects.`;
|
|
39
|
+
const longListToolsDesc = (serverName: string) =>
|
|
40
|
+
`List ALL available tools on the ${serverName} server with their names, descriptions, and input schemas. Use this when search_tools returned no results, or when you want to browse the full capabilities of the server. Returns the complete tool catalog — useful when the user's request doesn't map to an obvious keyword. Does not accept any parameters.`;
|
|
41
|
+
|
|
26
42
|
/** MCP data layer — tools and recipes from a connected MCP server */
|
|
27
43
|
export interface McpLayer {
|
|
28
44
|
protocol: 'mcp';
|
|
@@ -44,6 +60,13 @@ export interface WebMcpLayer {
|
|
|
44
60
|
|
|
45
61
|
export type ToolLayer = McpLayer | WebMcpLayer;
|
|
46
62
|
|
|
63
|
+
/** Which provider syntax to emit in the system prompt.
|
|
64
|
+
* - `generic`: `tool_name()` / `tool_name(arg1, arg2)` — Claude, Ollama, most providers.
|
|
65
|
+
* - `gemma`: `<|tool_call>call:tool_name{}<tool_call|>` — Gemma 4 native format.
|
|
66
|
+
* - `qwen`: `<tool_call>\n{"name":...,"arguments":...}\n</tool_call>` — Qwen 3/3.5 ChatML.
|
|
67
|
+
* - `mistral`: `[TOOL_CALLS][{"name":...,"arguments":...}]` — Mistral/Ministral. */
|
|
68
|
+
export type ProviderKind = 'generic' | 'gemma' | 'qwen' | 'mistral';
|
|
69
|
+
|
|
47
70
|
/** Options controlling how tool schemas are transformed before sending to the LLM */
|
|
48
71
|
export interface SchemaTransformOptions {
|
|
49
72
|
/** Strip oneOf/anyOf/allOf/not/if-then-else/$ref (default: true) */
|
|
@@ -289,7 +312,7 @@ export function buildToolsFromLayers(layers: ToolLayer[], schemaOptions?: Schema
|
|
|
289
312
|
const tools: ProviderTool[] = [];
|
|
290
313
|
|
|
291
314
|
for (const layer of layers) {
|
|
292
|
-
const prefix = `${sanitizeServerName(layer.serverName)}_${layer.protocol}_`;
|
|
315
|
+
const prefix = `${sanitizeServerName(layer.serverName)}_${protocolToken(layer.protocol)}_`;
|
|
293
316
|
|
|
294
317
|
if (layer.protocol === 'mcp') {
|
|
295
318
|
for (const tool of toProviderTools(layer.tools, schemaOptions, trace)) {
|
|
@@ -338,155 +361,8 @@ export function buildToolsFromLayers(layers: ToolLayer[], schemaOptions?: Schema
|
|
|
338
361
|
return { tools: Array.from(seen.values()), pathMaps: localPathMaps };
|
|
339
362
|
}
|
|
340
363
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
prompt: string;
|
|
344
|
-
aliasMap: Map<string, string>;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Build system prompt with a local alias map (parallel-safe).
|
|
349
|
-
* Prefer this over buildSystemPrompt() when running multiple agent loops.
|
|
350
|
-
*/
|
|
351
|
-
export function buildSystemPromptWithAliases(layers: ToolLayer[]): SystemPromptResult {
|
|
352
|
-
const mcpLayers = layers.filter((l): l is McpLayer => l.protocol === 'mcp');
|
|
353
|
-
const webmcpLayers = layers.filter((l): l is WebMcpLayer => l.protocol === 'webmcp');
|
|
354
|
-
|
|
355
|
-
const aliasMap = new Map<string, string>();
|
|
356
|
-
|
|
357
|
-
// ── Collect search_recipes / list_recipes / get_recipe from all layers ──
|
|
358
|
-
const searchRecipes: string[] = [];
|
|
359
|
-
const listRecipes: string[] = [];
|
|
360
|
-
const getRecipes: string[] = [];
|
|
361
|
-
const searchTools: string[] = [];
|
|
362
|
-
const listTools: string[] = [];
|
|
363
|
-
|
|
364
|
-
// WebMCP layers: always exact match (we control the naming)
|
|
365
|
-
for (const l of webmcpLayers) {
|
|
366
|
-
const prefix = `${sanitizeServerName(l.serverName)}_webmcp_`;
|
|
367
|
-
for (const t of l.tools) {
|
|
368
|
-
if (t.name === 'search_recipes') searchRecipes.push(`${prefix}search_recipes()`);
|
|
369
|
-
if (t.name === 'list_recipes') listRecipes.push(`${prefix}list_recipes()`);
|
|
370
|
-
if (t.name === 'get_recipe') getRecipes.push(`${prefix}get_recipe()`);
|
|
371
|
-
}
|
|
372
|
-
// Pseudo-tools for tool discovery on WebMCP servers
|
|
373
|
-
searchTools.push(`${prefix}search_tools(query)`);
|
|
374
|
-
listTools.push(`${prefix}list_tools()`);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// MCP layers: 4-layer matching + alias registration
|
|
378
|
-
for (const l of mcpLayers) {
|
|
379
|
-
const prefix = `${sanitizeServerName(l.serverName)}_mcp_`;
|
|
380
|
-
const matches = resolveCanonicalTools(l.tools);
|
|
381
|
-
|
|
382
|
-
for (const m of matches) {
|
|
383
|
-
const canonicalPrefixed = `${prefix}${m.role}`;
|
|
384
|
-
const realPrefixed = `${prefix}${m.realToolName}`;
|
|
385
|
-
|
|
386
|
-
// Register alias only if names differ
|
|
387
|
-
if (m.role !== m.realToolName) {
|
|
388
|
-
aliasMap.set(canonicalPrefixed, realPrefixed);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (m.role === 'search_recipes') searchRecipes.push(`${canonicalPrefixed}()`);
|
|
392
|
-
if (m.role === 'list_recipes') listRecipes.push(`${canonicalPrefixed}()`);
|
|
393
|
-
if (m.role === 'get_recipe') getRecipes.push(`${canonicalPrefixed}()`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Pseudo-tools for tool discovery on all MCP servers
|
|
397
|
-
searchTools.push(`${prefix}search_tools(query)`);
|
|
398
|
-
listTools.push(`${prefix}list_tools()`);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// ── WebMCP action tools (widget_display, canvas, recall) ──
|
|
402
|
-
const actionTools: string[] = [];
|
|
403
|
-
const ACTION_NAMES = ['widget_display', 'canvas', 'recall'];
|
|
404
|
-
for (const l of webmcpLayers) {
|
|
405
|
-
const prefix = `${sanitizeServerName(l.serverName)}_webmcp_`;
|
|
406
|
-
for (const t of l.tools) {
|
|
407
|
-
if (ACTION_NAMES.includes(t.name)) actionTools.push(`${prefix}${t.name}`);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// ── Build prompt (cascade: list recipes → search recipes → list tools → search tools) ──
|
|
412
|
-
let prompt = `You are an AI assistant that helps users by answering their questions and completing tasks using recipes (also called skills). These are not cooking recipes but instructions for an AI agent with scripts, schemas, and information to help it. If you cannot find a relevant recipe or tool, you may fall back to a traditional chat without tool calling (STEP 5).
|
|
413
|
-
|
|
414
|
-
You MUST NOT skip steps.
|
|
415
|
-
|
|
416
|
-
CRITICAL RULE: You MUST execute all steps silently. Do NOT generate any internal reasoning, thinking, or intermediate text.
|
|
417
|
-
|
|
418
|
-
STEP 1 — List all recipes
|
|
419
|
-
|
|
420
|
-
Look for a relevant recipe among these:
|
|
421
|
-
|
|
422
|
-
${listRecipes.join('\n')}
|
|
423
|
-
|
|
424
|
-
If at least one relevant recipe is found → go to STEP 2.
|
|
425
|
-
If no results → go to STEP 1b.
|
|
426
|
-
|
|
427
|
-
STEP 1b — Search recipes
|
|
428
|
-
|
|
429
|
-
No recipe found by listing. Search with keyword(s) extracted from the request:
|
|
430
|
-
|
|
431
|
-
${searchRecipes.join('\n')}
|
|
432
|
-
|
|
433
|
-
Pick the most relevant recipe for the request.
|
|
434
|
-
If a recipe matches → go to STEP 2.
|
|
435
|
-
If no recipe is available or relevant → go to STEP 1c.
|
|
436
|
-
|
|
437
|
-
STEP 1c — List tools
|
|
438
|
-
|
|
439
|
-
No applicable recipe. List a relevant tool:
|
|
440
|
-
|
|
441
|
-
${listTools.join('\n')}
|
|
442
|
-
|
|
443
|
-
If a relevant tool is found → use it directly to respond (go to STEP 3).
|
|
444
|
-
If no results → go to STEP 1d.
|
|
445
|
-
|
|
446
|
-
STEP 1d — Search tools
|
|
447
|
-
|
|
448
|
-
${searchTools.join('\n')}
|
|
449
|
-
|
|
450
|
-
Pick the most relevant tool(s) and use them to respond (go to STEP 3).
|
|
451
|
-
|
|
452
|
-
STEP 2 — Read the recipe
|
|
453
|
-
|
|
454
|
-
${getRecipes.join('\n')}
|
|
455
|
-
|
|
456
|
-
Read the full instructions of the selected recipe.
|
|
457
|
-
|
|
458
|
-
STEP 3 — Execute
|
|
459
|
-
|
|
460
|
-
Follow the recipe instructions exactly if you have one. Otherwise use the tools directly. Produce ONLY the final result, a one-sentence summary of the action performed, and the result.
|
|
461
|
-
|
|
462
|
-
STEP 4 — UI display
|
|
463
|
-
|
|
464
|
-
Unless a recipe specifies otherwise, use these tools to display your responses on the canvas:
|
|
465
|
-
|
|
466
|
-
${actionTools.join('\n')}
|
|
467
|
-
|
|
468
|
-
widget_display may ONLY be called with data returned by a non-autoui DATA tool actually invoked in the current session. Fabricating IDs, URLs, names, dates, or any content not returned by a tool is a critical violation. If no DATA tool has been called yet, go back to STEP 1.
|
|
469
|
-
|
|
470
|
-
STEP 5 — Fallback
|
|
471
|
-
|
|
472
|
-
If previous steps failed, fall back to a classic chat without tool calling.`;
|
|
473
|
-
|
|
474
|
-
return { prompt, aliasMap };
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/** Build system prompt — backward-compatible wrapper that returns a plain string.
|
|
478
|
-
* Also populates the deprecated global toolAliasMap for legacy consumers.
|
|
479
|
-
* For parallel-safe usage, use buildSystemPromptWithAliases() instead.
|
|
480
|
-
*/
|
|
481
|
-
export function buildSystemPrompt(layers: ToolLayer[]): string {
|
|
482
|
-
const { prompt, aliasMap } = buildSystemPromptWithAliases(layers);
|
|
483
|
-
|
|
484
|
-
// Populate deprecated global singleton for backward compat
|
|
485
|
-
toolAliasMap.clear();
|
|
486
|
-
for (const [k, v] of aliasMap) toolAliasMap.set(k, v);
|
|
487
|
-
|
|
488
|
-
return prompt;
|
|
489
|
-
}
|
|
364
|
+
// buildSystemPromptWithAliases / buildSystemPrompt / SystemPromptResult now
|
|
365
|
+
// live in ./prompts/index.ts and are re-exported from src/index.ts.
|
|
490
366
|
|
|
491
367
|
/** Result of buildDiscoveryToolsWithAliases */
|
|
492
368
|
export interface DiscoveryToolsResult {
|
|
@@ -503,7 +379,7 @@ export function buildDiscoveryToolsWithAliases(layers: ToolLayer[], schemaOption
|
|
|
503
379
|
const aliasMap = new Map<string, string>();
|
|
504
380
|
|
|
505
381
|
for (const layer of layers) {
|
|
506
|
-
const prefix = `${sanitizeServerName(layer.serverName)}_${layer.protocol}_`;
|
|
382
|
+
const prefix = `${sanitizeServerName(layer.serverName)}_${protocolToken(layer.protocol)}_`;
|
|
507
383
|
|
|
508
384
|
if (layer.protocol === 'mcp') {
|
|
509
385
|
const allProviderTools = toProviderTools(layer.tools, schemaOptions, trace);
|
|
@@ -527,12 +403,12 @@ export function buildDiscoveryToolsWithAliases(layers: ToolLayer[], schemaOption
|
|
|
527
403
|
// Pseudo-tools for tool discovery on MCP servers
|
|
528
404
|
tools.push({
|
|
529
405
|
name: `${prefix}search_tools`,
|
|
530
|
-
description:
|
|
406
|
+
description: longSearchToolsDesc(layer.serverName),
|
|
531
407
|
input_schema: { type: 'object', properties: { query: { type: 'string', description: 'Keyword to search for in tool names and descriptions, e.g. "weather", "user", "search". Case-insensitive.' } }, required: ['query'] },
|
|
532
408
|
});
|
|
533
409
|
tools.push({
|
|
534
410
|
name: `${prefix}list_tools`,
|
|
535
|
-
description:
|
|
411
|
+
description: longListToolsDesc(layer.serverName),
|
|
536
412
|
input_schema: { type: 'object', properties: {} },
|
|
537
413
|
});
|
|
538
414
|
} else {
|
|
@@ -547,12 +423,12 @@ export function buildDiscoveryToolsWithAliases(layers: ToolLayer[], schemaOption
|
|
|
547
423
|
// Pseudo-tools for tool discovery on WebMCP servers
|
|
548
424
|
tools.push({
|
|
549
425
|
name: `${prefix}search_tools`,
|
|
550
|
-
description:
|
|
426
|
+
description: longSearchToolsDesc(layer.serverName),
|
|
551
427
|
input_schema: { type: 'object', properties: { query: { type: 'string', description: 'Keyword to search for in tool names and descriptions, e.g. "weather", "user", "search". Case-insensitive.' } }, required: ['query'] },
|
|
552
428
|
});
|
|
553
429
|
tools.push({
|
|
554
430
|
name: `${prefix}list_tools`,
|
|
555
|
-
description:
|
|
431
|
+
description: longListToolsDesc(layer.serverName),
|
|
556
432
|
input_schema: { type: 'object', properties: {} },
|
|
557
433
|
});
|
|
558
434
|
}
|
|
@@ -590,7 +466,7 @@ export function activateServerTools(
|
|
|
590
466
|
schemaOptions?: SchemaTransformOptions,
|
|
591
467
|
trace?: PipelineTrace,
|
|
592
468
|
): ProviderTool[] {
|
|
593
|
-
const prefix = `${sanitizeServerName(layer.serverName)}_${layer.protocol}_`;
|
|
469
|
+
const prefix = `${sanitizeServerName(layer.serverName)}_${protocolToken(layer.protocol)}_`;
|
|
594
470
|
const existing = new Set(currentTools.map(t => t.name));
|
|
595
471
|
const newTools = [...currentTools];
|
|
596
472
|
|