@webmcp-auto-ui/agent 2.5.8 → 2.5.10
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 +134 -134
- package/src/diagnostics.ts +3 -3
- package/src/loop.ts +6 -6
- package/src/providers/wasm.ts +5 -5
- package/src/recipes/_generated.ts +446 -446
- package/src/recipes/afficher-oeuvres-art-collection-musee.md +45 -45
- package/src/recipes/analyser-actualites-hacker-news.md +52 -52
- package/src/recipes/cartographier-observations-biodiversite.md +44 -44
- package/src/recipes/cross-server.md +48 -48
- package/src/recipes/dashboard-kpi.md +45 -45
- package/src/recipes/explorer-dossiers-legislatifs-parcours-texte.md +48 -48
- package/src/recipes/gallery-images.md +33 -33
- package/src/recipes/parlementaire-profile.md +58 -58
- package/src/recipes/rechercher-textes-juridiques-legifrance.md +38 -38
- package/src/recipes/weather-viz.md +35 -35
- package/src/recipes/widgets/actions.md +6 -6
- package/src/recipes/widgets/alert.md +6 -6
- package/src/recipes/widgets/cards.md +10 -10
- package/src/recipes/widgets/carousel.md +8 -8
- package/src/recipes/widgets/chart-rich.md +10 -10
- package/src/recipes/widgets/chart.md +9 -9
- package/src/recipes/widgets/code.md +6 -6
- package/src/recipes/widgets/d3.md +10 -10
- package/src/recipes/widgets/data-table.md +10 -10
- package/src/recipes/widgets/gallery.md +10 -10
- package/src/recipes/widgets/grid-data.md +11 -11
- package/src/recipes/widgets/hemicycle.md +9 -9
- package/src/recipes/widgets/js-sandbox.md +10 -10
- package/src/recipes/widgets/json-viewer.md +8 -8
- package/src/recipes/widgets/kv.md +9 -9
- package/src/recipes/widgets/list.md +7 -7
- package/src/recipes/widgets/log.md +6 -6
- package/src/recipes/widgets/map.md +10 -10
- package/src/recipes/widgets/profile.md +9 -9
- package/src/recipes/widgets/recipe-browser.md +33 -33
- package/src/recipes/widgets/sankey.md +10 -10
- package/src/recipes/widgets/stat-card.md +7 -7
- package/src/recipes/widgets/stat.md +10 -10
- package/src/recipes/widgets/tags.md +6 -6
- package/src/recipes/widgets/text.md +6 -6
- package/src/recipes/widgets/timeline.md +6 -6
- package/src/recipes/widgets/trombinoscope.md +8 -8
- package/src/summarize.ts +6 -6
- package/src/tool-layers.ts +26 -26
|
@@ -3,49 +3,49 @@
|
|
|
3
3
|
export const RAW_RECIPES: Record<string, string> = {
|
|
4
4
|
'afficher-oeuvres-art-collection-musee': `---
|
|
5
5
|
id: afficher-oeuvres-art-collection-musee
|
|
6
|
-
name:
|
|
6
|
+
name: Display artworks from a museum collection in a visual gallery
|
|
7
7
|
components_used: [gallery, cards, kv, stat-card]
|
|
8
|
-
when:
|
|
8
|
+
when: the user asks for artworks, museum collections, paintings, sculptures, or art objects from the Metropolitan Museum of Art
|
|
9
9
|
servers: [metmuseum]
|
|
10
10
|
layout:
|
|
11
11
|
type: grid
|
|
12
12
|
columns: 2
|
|
13
|
-
arrangement: stats
|
|
13
|
+
arrangement: stats at top, full-width gallery in the center, details at the bottom
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## When to use
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
- "
|
|
20
|
-
- "
|
|
21
|
-
- "
|
|
22
|
-
- "
|
|
23
|
-
- "
|
|
18
|
+
The user is interested in artworks or museum collections:
|
|
19
|
+
- "Show me Impressionist paintings from the Met Museum"
|
|
20
|
+
- "Greek sculptures at the Metropolitan"
|
|
21
|
+
- "What Van Gogh works are at the Met?"
|
|
22
|
+
- "Egyptian art objects from the museum"
|
|
23
|
+
- "Search for Japanese woodblock prints"
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The Met Museum server provides access to the Metropolitan Museum of Art collection in New York (more than 470,000 objects, many with public domain images).
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## How to use
|
|
28
28
|
|
|
29
|
-
1. **
|
|
29
|
+
1. **Search for artworks** by theme, artist, or department:
|
|
30
30
|
\`\`\`
|
|
31
31
|
search_objects({query: "impressionism sunflower", hasImages: true})
|
|
32
32
|
\`\`\`
|
|
33
|
-
|
|
33
|
+
Returns a list of \`objectID\`s.
|
|
34
34
|
|
|
35
|
-
2. **
|
|
35
|
+
2. **Fetch the details** of each artwork (limit to 5-10 for performance):
|
|
36
36
|
\`\`\`
|
|
37
37
|
get_object({objectID: 436524})
|
|
38
38
|
\`\`\`
|
|
39
|
-
|
|
39
|
+
Returns: \`title\`, \`artistDisplayName\`, \`primaryImage\`, \`objectDate\`, \`medium\`, \`department\`, \`culture\`, etc.
|
|
40
40
|
|
|
41
|
-
3. **
|
|
41
|
+
3. **Display search statistics**:
|
|
42
42
|
\`\`\`
|
|
43
|
-
component("stat-card", {label: "
|
|
44
|
-
component("stat-card", {label: "
|
|
45
|
-
component("stat-card", {label: "
|
|
43
|
+
component("stat-card", {label: "Results", value: total, icon: "image"})
|
|
44
|
+
component("stat-card", {label: "With image", value: withImage, icon: "camera"})
|
|
45
|
+
component("stat-card", {label: "Public domain", value: publicDomain, icon: "unlock"})
|
|
46
46
|
\`\`\`
|
|
47
47
|
|
|
48
|
-
4. **
|
|
48
|
+
4. **Artwork gallery** with high-resolution images:
|
|
49
49
|
\`\`\`
|
|
50
50
|
component("gallery", {
|
|
51
51
|
images: objects
|
|
@@ -58,124 +58,124 @@ Le serveur Met Museum donne acces a la collection du Metropolitan Museum of Art
|
|
|
58
58
|
})
|
|
59
59
|
\`\`\`
|
|
60
60
|
|
|
61
|
-
5. **
|
|
61
|
+
5. **Detailed cards** for the main artworks:
|
|
62
62
|
\`\`\`
|
|
63
63
|
component("cards", {
|
|
64
64
|
items: objects.map(o => ({
|
|
65
65
|
title: o.title,
|
|
66
|
-
subtitle: o.artistDisplayName || "
|
|
66
|
+
subtitle: o.artistDisplayName || "Unknown artist",
|
|
67
67
|
image: o.primaryImageSmall,
|
|
68
68
|
body: [o.objectDate, o.medium, o.department].filter(Boolean).join(" — ")
|
|
69
69
|
}))
|
|
70
70
|
})
|
|
71
71
|
\`\`\`
|
|
72
72
|
|
|
73
|
-
6. **Details
|
|
73
|
+
6. **Details of a specific artwork** in kv:
|
|
74
74
|
\`\`\`
|
|
75
75
|
component("kv", {pairs: [
|
|
76
|
-
["
|
|
77
|
-
["
|
|
76
|
+
["Title", obj.title],
|
|
77
|
+
["Artist", obj.artistDisplayName],
|
|
78
78
|
["Date", obj.objectDate],
|
|
79
79
|
["Medium", obj.medium],
|
|
80
80
|
["Dimensions", obj.dimensions],
|
|
81
|
-
["
|
|
81
|
+
["Department", obj.department],
|
|
82
82
|
["Culture", obj.culture],
|
|
83
83
|
["Credit", obj.creditLine],
|
|
84
|
-
["
|
|
84
|
+
["Public domain", obj.isPublicDomain ? "Yes" : "No"]
|
|
85
85
|
]})
|
|
86
86
|
\`\`\`
|
|
87
87
|
|
|
88
|
-
##
|
|
88
|
+
## Examples
|
|
89
89
|
|
|
90
|
-
###
|
|
90
|
+
### Van Gogh paintings
|
|
91
91
|
\`\`\`
|
|
92
|
-
// 1.
|
|
92
|
+
// 1. Search
|
|
93
93
|
search_objects({query: "van gogh", hasImages: true}) // → [436532, 436529, ...]
|
|
94
94
|
|
|
95
|
-
// 2. Details (
|
|
95
|
+
// 2. Details (first 8 results)
|
|
96
96
|
objectIDs.slice(0, 8).forEach(id => get_object({objectID: id}))
|
|
97
97
|
|
|
98
|
-
// 3.
|
|
99
|
-
component("stat-card", {label: "
|
|
98
|
+
// 3. Render
|
|
99
|
+
component("stat-card", {label: "Van Gogh works", value: "8", icon: "palette"})
|
|
100
100
|
component("gallery", {images: vanGoghWorks.map(w => ({src: w.primaryImageSmall, alt: w.title, caption: w.objectDate}))})
|
|
101
101
|
component("cards", {items: vanGoghWorks.map(w => ({title: w.title, subtitle: w.objectDate, image: w.primaryImageSmall, body: w.medium}))})
|
|
102
102
|
\`\`\`
|
|
103
103
|
|
|
104
|
-
###
|
|
104
|
+
### Egyptian art
|
|
105
105
|
\`\`\`
|
|
106
|
-
// 1.
|
|
106
|
+
// 1. Search by department
|
|
107
107
|
search_objects({query: "egypt pharaoh", departmentId: 10, hasImages: true})
|
|
108
108
|
|
|
109
|
-
// 2.
|
|
109
|
+
// 2. Render with cultural metadata
|
|
110
110
|
component("gallery", {images: egyptWorks.map(w => ({src: w.primaryImageSmall, alt: w.title}))})
|
|
111
|
-
component("table", {columns: ["
|
|
112
|
-
component("kv", {pairs: [["
|
|
111
|
+
component("table", {columns: ["Title", "Period", "Culture", "Medium"], rows: egyptDetails})
|
|
112
|
+
component("kv", {pairs: [["Department", "Egyptian Art"], ["Source", "Met Museum — Open Access"]]})
|
|
113
113
|
\`\`\`
|
|
114
114
|
|
|
115
|
-
##
|
|
115
|
+
## Common mistakes
|
|
116
116
|
|
|
117
|
-
- **
|
|
118
|
-
- **
|
|
119
|
-
- **
|
|
120
|
-
- **
|
|
121
|
-
- **
|
|
117
|
+
- **Too many \`get_object\` calls**: a search sometimes returns hundreds of IDs — limit to 5-10 detail calls for performance
|
|
118
|
+
- **Artworks without images**: many Met objects have no \`primaryImage\` — always filter with \`hasImages: true\` in the search or check the field
|
|
119
|
+
- **Broken high-resolution images**: use \`primaryImageSmall\` (web-large) for the gallery and cards — \`primaryImage\` (original) URLs often return 404s
|
|
120
|
+
- **Forgetting the license**: works in the public domain (\`isPublicDomain: true\`) can be displayed freely; others have a \`rights\` field to respect
|
|
121
|
+
- **Unknown artist**: many ancient works have no \`artistDisplayName\` — display "Unknown artist" or the culture/period instead
|
|
122
122
|
`,
|
|
123
123
|
'analyser-actualites-hacker-news': `---
|
|
124
124
|
id: analyser-actualites-hacker-news
|
|
125
|
-
name:
|
|
125
|
+
name: Analyze Hacker News news and trends with tables and charts
|
|
126
126
|
components_used: [table, chart, stat-card, cards]
|
|
127
|
-
when:
|
|
127
|
+
when: the user asks for tech news, Hacker News trends, top stories, or an analysis of HN discussions and comments
|
|
128
128
|
servers: [hackernews]
|
|
129
129
|
layout:
|
|
130
130
|
type: grid
|
|
131
131
|
columns: 2
|
|
132
|
-
arrangement: stats
|
|
132
|
+
arrangement: stats in a row, full-width table, chart at the bottom
|
|
133
133
|
---
|
|
134
134
|
|
|
135
|
-
##
|
|
135
|
+
## When to use
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
- "
|
|
139
|
-
- "
|
|
140
|
-
- "
|
|
141
|
-
- "
|
|
142
|
-
- "
|
|
137
|
+
The user is interested in technology news or Hacker News community trends:
|
|
138
|
+
- "What are the top Hacker News stories?"
|
|
139
|
+
- "Show me the most commented posts today"
|
|
140
|
+
- "This week's tech trends on HN"
|
|
141
|
+
- "Analyze recent Ask HN posts"
|
|
142
|
+
- "What topics are dominating Hacker News right now?"
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
The Hacker News server provides access to stories, comments, rankings, and post metadata.
|
|
145
145
|
|
|
146
|
-
##
|
|
146
|
+
## How to use
|
|
147
147
|
|
|
148
|
-
1. **
|
|
148
|
+
1. **Fetch the top stories**:
|
|
149
149
|
\`\`\`
|
|
150
150
|
get_top_stories({limit: 30})
|
|
151
151
|
\`\`\`
|
|
152
|
-
|
|
152
|
+
Returns the IDs of the most popular stories.
|
|
153
153
|
|
|
154
|
-
2. **
|
|
154
|
+
2. **Fetch the details** of each story:
|
|
155
155
|
\`\`\`
|
|
156
156
|
get_item({id: storyId})
|
|
157
157
|
\`\`\`
|
|
158
|
-
|
|
158
|
+
Returns: \`title\`, \`url\`, \`score\`, \`by\` (author), \`descendants\` (comment count), \`time\`, \`type\`.
|
|
159
159
|
|
|
160
|
-
3. **
|
|
160
|
+
3. **Display KPIs** in stat-cards:
|
|
161
161
|
\`\`\`
|
|
162
162
|
component("stat-card", {label: "Top Stories", value: "30", icon: "newspaper"})
|
|
163
|
-
component("stat-card", {label: "
|
|
164
|
-
component("stat-card", {label: "
|
|
165
|
-
component("stat-card", {label: "
|
|
163
|
+
component("stat-card", {label: "Average score", value: Math.round(avgScore), icon: "trending-up"})
|
|
164
|
+
component("stat-card", {label: "Average comments", value: Math.round(avgComments), icon: "message-circle"})
|
|
165
|
+
component("stat-card", {label: "Max score", value: maxScore + " pts", icon: "award"})
|
|
166
166
|
\`\`\`
|
|
167
167
|
|
|
168
|
-
4. **
|
|
168
|
+
4. **Stories table** sorted by score:
|
|
169
169
|
\`\`\`
|
|
170
170
|
component("table", {
|
|
171
|
-
columns: ["#", "
|
|
171
|
+
columns: ["#", "Title", "Score", "Comments", "Author"],
|
|
172
172
|
rows: stories.sort((a, b) => b.score - a.score).map((s, i) => [
|
|
173
173
|
i + 1, s.title, s.score, s.descendants, s.by
|
|
174
174
|
])
|
|
175
175
|
})
|
|
176
176
|
\`\`\`
|
|
177
177
|
|
|
178
|
-
5. **
|
|
178
|
+
5. **Score distribution chart**:
|
|
179
179
|
\`\`\`
|
|
180
180
|
component("chart", {
|
|
181
181
|
type: "bar",
|
|
@@ -184,105 +184,105 @@ Le serveur Hacker News donne acces aux stories, commentaires, classements et met
|
|
|
184
184
|
})
|
|
185
185
|
\`\`\`
|
|
186
186
|
|
|
187
|
-
6. **Cards
|
|
187
|
+
6. **Cards for featured stories** (top 5):
|
|
188
188
|
\`\`\`
|
|
189
189
|
component("cards", {
|
|
190
190
|
items: top5.map(s => ({
|
|
191
191
|
title: s.title,
|
|
192
192
|
subtitle: s.by + " — " + s.score + " points",
|
|
193
|
-
body: s.descendants + "
|
|
193
|
+
body: s.descendants + " comments | " + new Date(s.time * 1000).toLocaleDateString(),
|
|
194
194
|
url: s.url
|
|
195
195
|
}))
|
|
196
196
|
})
|
|
197
197
|
\`\`\`
|
|
198
198
|
|
|
199
|
-
##
|
|
199
|
+
## Examples
|
|
200
200
|
|
|
201
|
-
### Top 10 stories
|
|
201
|
+
### Top 10 stories right now
|
|
202
202
|
\`\`\`
|
|
203
|
-
// 1.
|
|
203
|
+
// 1. Fetch
|
|
204
204
|
get_top_stories({limit: 10})
|
|
205
|
-
//
|
|
205
|
+
// For each ID: get_item({id})
|
|
206
206
|
|
|
207
|
-
// 2.
|
|
208
|
-
component("stat-card", {label: "
|
|
209
|
-
component("stat-card", {label: "
|
|
207
|
+
// 2. Render
|
|
208
|
+
component("stat-card", {label: "Total score", value: totalScore, icon: "zap"})
|
|
209
|
+
component("stat-card", {label: "Total comments", value: totalComments, icon: "message-circle"})
|
|
210
210
|
component("table", {
|
|
211
|
-
columns: ["
|
|
211
|
+
columns: ["Rank", "Title", "Score", "Comments", "Author", "Age"],
|
|
212
212
|
rows: rankedStories
|
|
213
213
|
})
|
|
214
214
|
component("cards", {items: top3Stories})
|
|
215
215
|
\`\`\`
|
|
216
216
|
|
|
217
|
-
###
|
|
217
|
+
### Ask HN analysis
|
|
218
218
|
\`\`\`
|
|
219
|
-
// 1.
|
|
219
|
+
// 1. Fetch recent Ask HN posts
|
|
220
220
|
get_ask_stories({limit: 20})
|
|
221
221
|
|
|
222
|
-
// 2.
|
|
223
|
-
component("stat-card", {label: "Ask HN
|
|
224
|
-
component("stat-card", {label: "
|
|
225
|
-
component("table", {columns: ["
|
|
226
|
-
component("chart", {type: "bar", labels: titles, datasets: [{label: "
|
|
222
|
+
// 2. Render
|
|
223
|
+
component("stat-card", {label: "Recent Ask HN", value: "20", icon: "help-circle"})
|
|
224
|
+
component("stat-card", {label: "Average replies", value: avgReplies, icon: "message-circle"})
|
|
225
|
+
component("table", {columns: ["Title", "Replies", "Score", "Author"], rows: askStories})
|
|
226
|
+
component("chart", {type: "bar", labels: titles, datasets: [{label: "Replies", data: replyCounts}]})
|
|
227
227
|
\`\`\`
|
|
228
228
|
|
|
229
|
-
###
|
|
229
|
+
### Trends by domain
|
|
230
230
|
\`\`\`
|
|
231
|
-
// 1.
|
|
231
|
+
// 1. Fetch top stories and extract domains from URLs
|
|
232
232
|
get_top_stories({limit: 50})
|
|
233
233
|
|
|
234
|
-
// 2.
|
|
234
|
+
// 2. Group by domain
|
|
235
235
|
const domains = groupBy(stories, s => new URL(s.url).hostname)
|
|
236
236
|
|
|
237
|
-
// 3.
|
|
238
|
-
component("stat-card", {label: "
|
|
237
|
+
// 3. Render
|
|
238
|
+
component("stat-card", {label: "Unique domains", value: Object.keys(domains).length, icon: "globe"})
|
|
239
239
|
component("chart", {type: "bar", labels: topDomains.map(d => d.name), datasets: [{label: "Stories", data: topDomains.map(d => d.count)}]})
|
|
240
|
-
component("table", {columns: ["
|
|
240
|
+
component("table", {columns: ["Domain", "Stories", "Total score"], rows: domainStats})
|
|
241
241
|
\`\`\`
|
|
242
242
|
|
|
243
|
-
##
|
|
243
|
+
## Common mistakes
|
|
244
244
|
|
|
245
|
-
- **
|
|
246
|
-
- **
|
|
247
|
-
- **
|
|
248
|
-
- **
|
|
249
|
-
- **
|
|
245
|
+
- **Too many \`get_item\` calls**: each story requires an individual call — limit to 20-30 to avoid slowness
|
|
246
|
+
- **Unconverted timestamps**: HN returns Unix timestamps — convert them to human-readable dates
|
|
247
|
+
- **Truncated titles in charts**: HN titles are long — truncate to 30-40 characters for chart labels
|
|
248
|
+
- **Forgetting stories without a URL**: "Ask HN", "Show HN", and "Tell HN" posts don't always have an external URL — handle this case
|
|
249
|
+
- **Not distinguishing types**: HN has stories, jobs, and polls — filter by type if the user asks for a specific type
|
|
250
250
|
`,
|
|
251
251
|
'cartographier-observations-biodiversite': `---
|
|
252
|
-
id:
|
|
253
|
-
name:
|
|
252
|
+
id: map-biodiversity-observations
|
|
253
|
+
name: Map biodiversity observations on a geographic area
|
|
254
254
|
components_used: [map, gallery, table, stat-card]
|
|
255
|
-
when:
|
|
255
|
+
when: the user asks for a map of naturalist observations, the biodiversity of an area, the species present in a location, or iNaturalist observations in a region
|
|
256
256
|
servers: [inaturalist]
|
|
257
257
|
layout:
|
|
258
258
|
type: grid
|
|
259
259
|
columns: 2
|
|
260
|
-
arrangement:
|
|
260
|
+
arrangement: full-width map at top, gallery + stats below
|
|
261
261
|
---
|
|
262
262
|
|
|
263
|
-
##
|
|
263
|
+
## When to use
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
- "
|
|
267
|
-
- "
|
|
268
|
-
- "
|
|
269
|
-
- "
|
|
265
|
+
The user asks a question about the biodiversity of a location or requests a map of observations:
|
|
266
|
+
- "What bird species are observed in Paris?"
|
|
267
|
+
- "Show me a map of butterfly observations in the Alps"
|
|
268
|
+
- "What is the biodiversity around Lake Annecy?"
|
|
269
|
+
- "Endangered species observed in Ile-de-France"
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
The iNaturalist server provides georeferenced observations with photos, taxa, dates, and observers.
|
|
272
272
|
|
|
273
|
-
##
|
|
273
|
+
## How to use
|
|
274
274
|
|
|
275
|
-
1. **
|
|
275
|
+
1. **Search for observations** in the target area:
|
|
276
276
|
\`\`\`
|
|
277
277
|
search_observations({lat: 48.85, lng: 2.35, radius: 10, taxon_name: "Aves", per_page: 50})
|
|
278
278
|
\`\`\`
|
|
279
|
-
|
|
280
|
-
- \`lat\`, \`lng\`, \`radius
|
|
281
|
-
- \`taxon_name
|
|
282
|
-
- \`quality_grade
|
|
283
|
-
- \`per_page
|
|
279
|
+
Useful parameters:
|
|
280
|
+
- \`lat\`, \`lng\`, \`radius\`: center and radius of the area in km
|
|
281
|
+
- \`taxon_name\`: taxonomic filter ("Aves", "Lepidoptera", "Mammalia", etc.)
|
|
282
|
+
- \`quality_grade\`: "research" for verified observations
|
|
283
|
+
- \`per_page\`: number of results (max 200)
|
|
284
284
|
|
|
285
|
-
2. **
|
|
285
|
+
2. **Display the map** with observation markers:
|
|
286
286
|
\`\`\`
|
|
287
287
|
component("map", {
|
|
288
288
|
center: [48.85, 2.35],
|
|
@@ -296,15 +296,15 @@ Le serveur iNaturalist fournit des observations georeferencees avec photos, taxo
|
|
|
296
296
|
})
|
|
297
297
|
\`\`\`
|
|
298
298
|
|
|
299
|
-
3. **
|
|
299
|
+
3. **Area statistics** in stat-cards:
|
|
300
300
|
\`\`\`
|
|
301
301
|
component("stat-card", {label: "Observations", value: total_results, icon: "eye"})
|
|
302
|
-
component("stat-card", {label: "
|
|
303
|
-
component("stat-card", {label: "
|
|
304
|
-
component("stat-card", {label: "
|
|
302
|
+
component("stat-card", {label: "Unique species", value: uniqueSpecies.length, icon: "leaf"})
|
|
303
|
+
component("stat-card", {label: "Observers", value: uniqueObservers.length, icon: "users"})
|
|
304
|
+
component("stat-card", {label: "Research grade", value: researchGradeCount, icon: "check-circle"})
|
|
305
305
|
\`\`\`
|
|
306
306
|
|
|
307
|
-
4. **
|
|
307
|
+
4. **Species gallery with photos**:
|
|
308
308
|
\`\`\`
|
|
309
309
|
component("gallery", {
|
|
310
310
|
images: observations
|
|
@@ -317,105 +317,105 @@ Le serveur iNaturalist fournit des observations georeferencees avec photos, taxo
|
|
|
317
317
|
})
|
|
318
318
|
\`\`\`
|
|
319
319
|
|
|
320
|
-
5. **
|
|
320
|
+
5. **Summary table** of species:
|
|
321
321
|
\`\`\`
|
|
322
322
|
component("table", {
|
|
323
|
-
columns: ["
|
|
323
|
+
columns: ["Species", "Scientific name", "Observations", "Last obs."],
|
|
324
324
|
rows: speciesSummary
|
|
325
325
|
})
|
|
326
326
|
\`\`\`
|
|
327
327
|
|
|
328
|
-
##
|
|
328
|
+
## Examples
|
|
329
329
|
|
|
330
|
-
###
|
|
330
|
+
### Birds of Paris
|
|
331
331
|
\`\`\`
|
|
332
|
-
// 1.
|
|
332
|
+
// 1. Search
|
|
333
333
|
search_observations({lat: 48.8566, lng: 2.3522, radius: 10, taxon_name: "Aves", quality_grade: "research", per_page: 100})
|
|
334
334
|
|
|
335
|
-
// 2.
|
|
335
|
+
// 2. Render
|
|
336
336
|
component("map", {center: [48.8566, 2.3522], zoom: 12, markers: birdMarkers})
|
|
337
|
-
component("stat-card", {label: "
|
|
338
|
-
component("stat-card", {label: "
|
|
337
|
+
component("stat-card", {label: "Bird species", value: "47", icon: "bird"})
|
|
338
|
+
component("stat-card", {label: "Verified observations", value: "312", icon: "check"})
|
|
339
339
|
component("gallery", {images: birdPhotos})
|
|
340
|
-
component("table", {columns: ["
|
|
340
|
+
component("table", {columns: ["Species", "Observations", "Last"], rows: birdSummary})
|
|
341
341
|
\`\`\`
|
|
342
342
|
|
|
343
|
-
###
|
|
343
|
+
### Butterflies in the Alps
|
|
344
344
|
\`\`\`
|
|
345
|
-
// 1.
|
|
345
|
+
// 1. Wide area around Chamonix
|
|
346
346
|
search_observations({lat: 45.9237, lng: 6.8694, radius: 30, taxon_name: "Lepidoptera", per_page: 100})
|
|
347
347
|
|
|
348
|
-
// 2.
|
|
348
|
+
// 2. Render with clustering on the map
|
|
349
349
|
component("map", {center: [45.9237, 6.8694], zoom: 10, markers: butterflyMarkers, cluster: true})
|
|
350
|
-
component("stat-card", {label: "
|
|
350
|
+
component("stat-card", {label: "Butterfly species", value: uniqueSpecies.length})
|
|
351
351
|
component("gallery", {images: butterflyPhotos})
|
|
352
|
-
component("table", {columns: ["
|
|
352
|
+
component("table", {columns: ["Species", "Altitude", "Month", "Observer"], rows: enrichedData})
|
|
353
353
|
\`\`\`
|
|
354
354
|
|
|
355
|
-
##
|
|
355
|
+
## Common mistakes
|
|
356
356
|
|
|
357
|
-
- **
|
|
358
|
-
- **
|
|
359
|
-
- **
|
|
360
|
-
- **
|
|
361
|
-
- **
|
|
357
|
+
- **Radius too large**: a 100 km radius returns too many results and buries the information — prefer 5-20 km and increase if few results are found
|
|
358
|
+
- **iNaturalist thumbnails**: default URLs are in "square" format (75x75) — replace "square" with "medium" (200px) or "large" (500px)
|
|
359
|
+
- **No taxonomic filter**: without a filter, iNaturalist returns plants + animals + fungi together — always filter by group if the user mentions one
|
|
360
|
+
- **Forgetting the quality grade**: "casual" observations may be misidentified — prefer \`quality_grade: "research"\` for reliable data
|
|
361
|
+
- **Map with wrong zoom level**: adjust zoom based on the search radius (5 km → zoom 13, 20 km → zoom 11, 50 km → zoom 9)
|
|
362
362
|
`,
|
|
363
363
|
'cross-server': `---
|
|
364
|
-
id:
|
|
365
|
-
name:
|
|
364
|
+
id: cross-reference-data-from-multiple-connected-mcp-servers
|
|
365
|
+
name: Cross-reference data from multiple simultaneously connected MCP servers
|
|
366
366
|
components_used: [map, gallery, table, kv, stat-card]
|
|
367
|
-
when:
|
|
367
|
+
when: the user's question requires combining data from multiple connected MCP servers, for example cross-referencing geolocation and observations, or enriching parliamentary data with Wikipedia
|
|
368
368
|
servers: []
|
|
369
369
|
layout:
|
|
370
370
|
type: grid
|
|
371
371
|
columns: 2
|
|
372
372
|
---
|
|
373
373
|
|
|
374
|
-
##
|
|
374
|
+
## When to use
|
|
375
375
|
|
|
376
|
-
|
|
377
|
-
- "
|
|
378
|
-
- "
|
|
379
|
-
- "Compare
|
|
380
|
-
- "
|
|
376
|
+
The user asks a question that cannot be answered by a single MCP server. Examples:
|
|
377
|
+
- "What birds can be spotted near the Louvre?" → geocoding + iNaturalist
|
|
378
|
+
- "Show me the weather and nature observations in Marseille" → Open-Meteo + iNaturalist
|
|
379
|
+
- "Compare artworks from the Met Museum on the theme of flowers with species observed in New York" → Met Museum + iNaturalist
|
|
380
|
+
- "Give me the Wikipedia profile of the MP who filed the most amendments" → Tricoteuses + Wikipedia
|
|
381
381
|
|
|
382
|
-
|
|
382
|
+
This recipe applies whenever 2+ MCP servers are needed to answer the question.
|
|
383
383
|
|
|
384
|
-
##
|
|
384
|
+
## How to use
|
|
385
385
|
|
|
386
|
-
1. **
|
|
387
|
-
-
|
|
388
|
-
-
|
|
389
|
-
2. **
|
|
386
|
+
1. **Identify which MCP servers provide which data**:
|
|
387
|
+
- Server A provides reference data (coordinates, IDs, names)
|
|
388
|
+
- Server B enriches with complementary data
|
|
389
|
+
2. **Call the first server** to obtain the base data:
|
|
390
390
|
\`\`\`
|
|
391
|
-
//
|
|
391
|
+
// Example: geocode a location
|
|
392
392
|
geocode({query: "Louvre, Paris"}) → {lat: 48.8606, lon: 2.3376}
|
|
393
393
|
\`\`\`
|
|
394
|
-
3. **
|
|
394
|
+
3. **Use the results as input** for the second server:
|
|
395
395
|
\`\`\`
|
|
396
|
-
//
|
|
396
|
+
// Example: search for observations within a radius
|
|
397
397
|
search_observations({lat: 48.8606, lng: 2.3376, radius: 5, taxon: "Aves"})
|
|
398
398
|
\`\`\`
|
|
399
|
-
4. **
|
|
400
|
-
- \`component("map", ...)\`
|
|
401
|
-
- \`component("table", ...)\`
|
|
402
|
-
- \`component("gallery", ...)\`
|
|
403
|
-
5. **
|
|
399
|
+
4. **Combine the results** in a coherent visualization:
|
|
400
|
+
- \`component("map", ...)\` if coordinates are involved (markers from both sources)
|
|
401
|
+
- \`component("table", ...)\` for combined results with columns from both sources
|
|
402
|
+
- \`component("gallery", ...)\` if both sources provide images
|
|
403
|
+
5. **Always cite the sources** with a final \`kv\` component:
|
|
404
404
|
\`\`\`
|
|
405
|
-
component("kv", {pairs: [["Source 1", "iNaturalist"], ["Source 2", "Open-Meteo"], ["
|
|
405
|
+
component("kv", {pairs: [["Source 1", "iNaturalist"], ["Source 2", "Open-Meteo"], ["Area", "5 km around the Louvre"]]})
|
|
406
406
|
\`\`\`
|
|
407
407
|
|
|
408
|
-
##
|
|
408
|
+
## Examples
|
|
409
409
|
|
|
410
|
-
###
|
|
410
|
+
### Birds near a landmark (geocoding + iNaturalist)
|
|
411
411
|
\`\`\`
|
|
412
|
-
// 1.
|
|
412
|
+
// 1. Geocode the location
|
|
413
413
|
geocode({query: "Tour Eiffel, Paris"}) → lat: 48.8584, lon: 2.2945
|
|
414
414
|
|
|
415
|
-
// 2.
|
|
415
|
+
// 2. Search for bird observations
|
|
416
416
|
search_observations({lat: 48.8584, lng: 2.2945, radius: 3, taxon_name: "Aves"})
|
|
417
417
|
|
|
418
|
-
// 3.
|
|
418
|
+
// 3. Combined render
|
|
419
419
|
component("map", {
|
|
420
420
|
center: [48.8584, 2.2945],
|
|
421
421
|
zoom: 14,
|
|
@@ -423,164 +423,164 @@ component("map", {
|
|
|
423
423
|
.concat(observations.map(o => ({lat: o.lat, lon: o.lon, label: o.species_guess})))
|
|
424
424
|
})
|
|
425
425
|
component("gallery", {images: observations.flatMap(o => o.photos.map(p => ({src: p.url, alt: o.species_guess})))})
|
|
426
|
-
component("table", {columns: ["
|
|
427
|
-
component("stat-card", {label: "
|
|
426
|
+
component("table", {columns: ["Species", "Date", "Distance", "Observer"], rows: formattedObs})
|
|
427
|
+
component("stat-card", {label: "Distinct species", value: "23", icon: "bird"})
|
|
428
428
|
\`\`\`
|
|
429
429
|
|
|
430
|
-
###
|
|
430
|
+
### Enriched profile (Tricoteuses + Wikipedia)
|
|
431
431
|
\`\`\`
|
|
432
|
-
// 1.
|
|
432
|
+
// 1. Find the most active MP
|
|
433
433
|
query_sql({sql: "SELECT depute, COUNT(*) as nb FROM amendements GROUP BY depute ORDER BY nb DESC LIMIT 1"})
|
|
434
434
|
|
|
435
|
-
// 2.
|
|
435
|
+
// 2. Enrich with Wikipedia
|
|
436
436
|
search_wikipedia({query: depute.nom})
|
|
437
437
|
|
|
438
|
-
// 3.
|
|
438
|
+
// 3. Combined render
|
|
439
439
|
component("profile", {name: depute.nom, subtitle: depute.groupe, details: wikipedia.extract})
|
|
440
|
-
component("stat-card", {label: "
|
|
441
|
-
component("kv", {pairs: [["
|
|
440
|
+
component("stat-card", {label: "Amendments filed", value: depute.nb})
|
|
441
|
+
component("kv", {pairs: [["Parliamentary source", "Tricoteuses"], ["Biography", "Wikipedia"]]})
|
|
442
442
|
\`\`\`
|
|
443
443
|
|
|
444
|
-
###
|
|
444
|
+
### Weather + Biodiversity in a region
|
|
445
445
|
\`\`\`
|
|
446
|
-
// 1.
|
|
446
|
+
// 1. Weather for Marseille
|
|
447
447
|
get_forecast({latitude: 43.2965, longitude: 5.3698, daily: "temperature_2m_max"})
|
|
448
448
|
|
|
449
|
-
// 2.
|
|
449
|
+
// 2. Nature observations
|
|
450
450
|
search_observations({lat: 43.2965, lng: 5.3698, radius: 20})
|
|
451
451
|
|
|
452
|
-
// 3.
|
|
453
|
-
component("stat-card", {label: "
|
|
454
|
-
component("stat-card", {label: "
|
|
452
|
+
// 3. Combined dashboard
|
|
453
|
+
component("stat-card", {label: "Max temperature", value: "26°C", icon: "thermometer"})
|
|
454
|
+
component("stat-card", {label: "Recent observations", value: "412", icon: "eye"})
|
|
455
455
|
component("map", {center: [43.2965, 5.3698], markers: observations})
|
|
456
456
|
component("chart", {type: "line", labels: dates, datasets: [{label: "Temperature", data: temps}, {label: "Observations", data: obsCounts}]})
|
|
457
457
|
\`\`\`
|
|
458
458
|
|
|
459
|
-
##
|
|
459
|
+
## Common mistakes
|
|
460
460
|
|
|
461
|
-
- **
|
|
462
|
-
- **
|
|
463
|
-
- **
|
|
464
|
-
- **
|
|
461
|
+
- **Making more than 3 DATA calls without an intermediate render**: the user expects visual results between steps
|
|
462
|
+
- **Not explaining which servers are being used**: always display the sources with a \`kv\`
|
|
463
|
+
- **Mixing data without structure**: combined results must be in a coherent table or map, not a raw dump
|
|
464
|
+
- **Forgetting to handle no-match cases**: if geocoding finds nothing, or if iNaturalist has no observations in the area, display that clearly
|
|
465
465
|
`,
|
|
466
466
|
'dashboard-kpi': `---
|
|
467
|
-
id:
|
|
468
|
-
name:
|
|
467
|
+
id: compose-kpi-dashboard-from-aggregated-metrics
|
|
468
|
+
name: Compose a KPI dashboard from aggregated metrics
|
|
469
469
|
components_used: [stat-card, chart, table, kv]
|
|
470
|
-
when:
|
|
470
|
+
when: MCP data contains numeric metrics, counters, totals, percentages, or aggregated statistics that warrant a visual dashboard
|
|
471
471
|
servers: []
|
|
472
472
|
layout:
|
|
473
473
|
type: grid
|
|
474
474
|
columns: 3
|
|
475
|
-
arrangement: stat-cards
|
|
475
|
+
arrangement: stat-cards in a row, chart + table below
|
|
476
476
|
---
|
|
477
477
|
|
|
478
|
-
##
|
|
478
|
+
## When to use
|
|
479
479
|
|
|
480
|
-
|
|
481
|
-
-
|
|
482
|
-
-
|
|
483
|
-
-
|
|
484
|
-
-
|
|
480
|
+
MCP results contain numeric metrics that need to be presented concisely. This recipe is cross-cutting: it applies regardless of the MCP server, as long as the data contains:
|
|
481
|
+
- Totals, counters, or averages (revenue, article count, participation, etc.)
|
|
482
|
+
- Percentages or ratios (churn rate, voter turnout, etc.)
|
|
483
|
+
- Time series of metrics (month-over-month, quarter-over-quarter trends)
|
|
484
|
+
- Breakdowns by category (by political group, by country, by object type)
|
|
485
485
|
|
|
486
|
-
##
|
|
486
|
+
## How to use
|
|
487
487
|
|
|
488
|
-
1. **
|
|
489
|
-
2. **
|
|
488
|
+
1. **Identify the 3–5 main KPIs** in the data returned by the MCP server
|
|
489
|
+
2. **Display each KPI as a stat-card** with clean formatting:
|
|
490
490
|
\`\`\`
|
|
491
|
-
component("stat-card", {label: "
|
|
491
|
+
component("stat-card", {label: "Revenue", value: "45 230 EUR", trend: "+12.4%", trendDir: "up", icon: "trending-up"})
|
|
492
492
|
\`\`\`
|
|
493
|
-
-
|
|
494
|
-
-
|
|
495
|
-
3. **
|
|
493
|
+
- Always format numbers: thousands separators, units, symbols
|
|
494
|
+
- Add \`trend\` and \`trendDir\` if a comparison is available (vs. previous month, vs. previous year)
|
|
495
|
+
3. **If time series exist**, add a chart:
|
|
496
496
|
\`\`\`
|
|
497
|
-
component("chart", {type: "bar", labels: ["Q1", "Q2", "Q3", "Q4"], datasets: [{label: "
|
|
497
|
+
component("chart", {type: "bar", labels: ["Q1", "Q2", "Q3", "Q4"], datasets: [{label: "Revenue", data: [98000, 112000, 128000, 142000]}]})
|
|
498
498
|
\`\`\`
|
|
499
|
-
- "bar"
|
|
500
|
-
- "line"
|
|
501
|
-
4. **
|
|
499
|
+
- "bar" for comparisons between categories/periods
|
|
500
|
+
- "line" for continuous trends
|
|
501
|
+
4. **If tabular details exist**, add a table:
|
|
502
502
|
\`\`\`
|
|
503
|
-
component("table", {columns: ["
|
|
503
|
+
component("table", {columns: ["Category", "Value", "Change"], rows: [...]})
|
|
504
504
|
\`\`\`
|
|
505
|
-
5. **
|
|
505
|
+
5. **For supplementary metadata**, use kv:
|
|
506
506
|
\`\`\`
|
|
507
|
-
component("kv", {pairs: [["Source", "data.gouv.fr"], ["
|
|
507
|
+
component("kv", {pairs: [["Source", "data.gouv.fr"], ["Last updated", "2026-04-01"], ["Period", "Q1 2026"]]})
|
|
508
508
|
\`\`\`
|
|
509
509
|
|
|
510
|
-
##
|
|
510
|
+
## Examples
|
|
511
511
|
|
|
512
|
-
###
|
|
512
|
+
### Parliamentary dashboard (Tricoteuses)
|
|
513
513
|
\`\`\`
|
|
514
|
-
//
|
|
515
|
-
component("stat-card", {label: "
|
|
516
|
-
component("stat-card", {label: "
|
|
517
|
-
component("stat-card", {label: "
|
|
518
|
-
component("chart", {type: "bar", labels:
|
|
519
|
-
component("table", {columns: ["
|
|
514
|
+
// After query_sql on votes for the legislature
|
|
515
|
+
component("stat-card", {label: "Public votes", value: "1 247", icon: "vote"})
|
|
516
|
+
component("stat-card", {label: "Amendments filed", value: "42 831", icon: "file-text"})
|
|
517
|
+
component("stat-card", {label: "Average participation", value: "61.3%", trend: "-2.1%", trendDir: "down"})
|
|
518
|
+
component("chart", {type: "bar", labels: months, datasets: [{label: "Votes/month", data: counts}]})
|
|
519
|
+
component("table", {columns: ["Group", "Amendments", "Adopted", "Rate"], rows: groupStats})
|
|
520
520
|
\`\`\`
|
|
521
521
|
|
|
522
|
-
###
|
|
522
|
+
### Biodiversity dashboard (iNaturalist)
|
|
523
523
|
\`\`\`
|
|
524
524
|
component("stat-card", {label: "Observations", value: "3 412", icon: "eye"})
|
|
525
|
-
component("stat-card", {label: "
|
|
526
|
-
component("stat-card", {label: "
|
|
527
|
-
component("chart", {type: "line", labels: dates, datasets: [{label: "Observations/
|
|
525
|
+
component("stat-card", {label: "Unique species", value: "287", icon: "leaf"})
|
|
526
|
+
component("stat-card", {label: "Observers", value: "156", icon: "users"})
|
|
527
|
+
component("chart", {type: "line", labels: dates, datasets: [{label: "Observations/day", data: dailyCounts}]})
|
|
528
528
|
\`\`\`
|
|
529
529
|
|
|
530
|
-
###
|
|
530
|
+
### News dashboard (Hacker News)
|
|
531
531
|
\`\`\`
|
|
532
532
|
component("stat-card", {label: "Top stories", value: "500", icon: "newspaper"})
|
|
533
|
-
component("stat-card", {label: "
|
|
534
|
-
component("stat-card", {label: "
|
|
535
|
-
component("table", {columns: ["
|
|
533
|
+
component("stat-card", {label: "Average score", value: "142", icon: "trending-up"})
|
|
534
|
+
component("stat-card", {label: "Average comments", value: "87", icon: "message-circle"})
|
|
535
|
+
component("table", {columns: ["Rank", "Title", "Score", "Comments"], rows: topStories})
|
|
536
536
|
\`\`\`
|
|
537
537
|
|
|
538
|
-
##
|
|
538
|
+
## Common mistakes
|
|
539
539
|
|
|
540
|
-
- **
|
|
541
|
-
- **
|
|
542
|
-
- **
|
|
543
|
-
- **
|
|
540
|
+
- **Too many stat-cards**: beyond 5, switch to a \`kv\` or \`table\` for secondary metrics
|
|
541
|
+
- **Unformatted numbers**: displaying "45230" instead of "45 230" hurts readability
|
|
542
|
+
- **Missing units**: "45 230" means nothing without "EUR", "%", "observations", etc.
|
|
543
|
+
- **Chart without context**: always accompany a chart with stat-cards that surface key figures instantly
|
|
544
544
|
`,
|
|
545
545
|
'explorer-dossiers-legislatifs-parcours-texte': `---
|
|
546
|
-
id:
|
|
547
|
-
name:
|
|
546
|
+
id: explore-legislative-files-text-journey
|
|
547
|
+
name: Explore legislative files and the journey of a text between the Assembly and the Senate
|
|
548
548
|
components_used: [timeline, table, kv, stat-card]
|
|
549
|
-
when:
|
|
549
|
+
when: the user asks about the journey of a bill or legislative proposal, the parliamentary shuttle, successive readings, or the tracking of a legislative file
|
|
550
550
|
servers: [tricoteuses]
|
|
551
551
|
layout:
|
|
552
552
|
type: grid
|
|
553
553
|
columns: 2
|
|
554
|
-
arrangement: timeline
|
|
554
|
+
arrangement: full-width timeline at top, stats + table below
|
|
555
555
|
---
|
|
556
556
|
|
|
557
|
-
##
|
|
557
|
+
## When to use
|
|
558
558
|
|
|
559
|
-
|
|
560
|
-
- "
|
|
561
|
-
- "
|
|
562
|
-
- "
|
|
563
|
-
- "
|
|
559
|
+
The user is interested in the journey of a bill through the institutions:
|
|
560
|
+
- "Where does the immigration bill currently stand?"
|
|
561
|
+
- "Show me the parliamentary shuttle for the pension reform"
|
|
562
|
+
- "How many readings did this text go through?"
|
|
563
|
+
- "Which amendments were adopted in committee?"
|
|
564
564
|
|
|
565
|
-
|
|
565
|
+
The Tricoteuses server contains legislative files with their stages: filing, referral to committee, floor debate, vote, shuttle, promulgation.
|
|
566
566
|
|
|
567
|
-
##
|
|
567
|
+
## How to use
|
|
568
568
|
|
|
569
|
-
1. **
|
|
569
|
+
1. **Search for the legislative file**:
|
|
570
570
|
\`\`\`
|
|
571
571
|
query_sql({sql: "SELECT * FROM assemblee.dossiers WHERE titre ILIKE '%immigration%' ORDER BY date_depot DESC LIMIT 5"})
|
|
572
572
|
\`\`\`
|
|
573
|
-
|
|
573
|
+
Or via Tricoteuses recipes:
|
|
574
574
|
\`\`\`
|
|
575
575
|
search_recipes({query: "dossier legislatif parcours"})
|
|
576
576
|
\`\`\`
|
|
577
577
|
|
|
578
|
-
2. **
|
|
578
|
+
2. **Retrieve the journey stages**:
|
|
579
579
|
\`\`\`
|
|
580
580
|
query_sql({sql: "SELECT etape, chambre, date, resultat FROM assemblee.dossier_etapes WHERE dossier_id = $id ORDER BY date"})
|
|
581
581
|
\`\`\`
|
|
582
582
|
|
|
583
|
-
3. **
|
|
583
|
+
3. **Display the journey timeline**:
|
|
584
584
|
\`\`\`
|
|
585
585
|
component("timeline", {
|
|
586
586
|
events: etapes.map(e => ({
|
|
@@ -592,77 +592,77 @@ Le serveur Tricoteuses contient les dossiers legislatifs avec leurs etapes : dep
|
|
|
592
592
|
})
|
|
593
593
|
\`\`\`
|
|
594
594
|
|
|
595
|
-
4. **
|
|
595
|
+
4. **File statistics** in stat-cards:
|
|
596
596
|
\`\`\`
|
|
597
|
-
component("stat-card", {label: "
|
|
598
|
-
component("stat-card", {label: "
|
|
599
|
-
component("stat-card", {label: "
|
|
600
|
-
component("stat-card", {label: "
|
|
597
|
+
component("stat-card", {label: "Readings", value: "3", icon: "book-open"})
|
|
598
|
+
component("stat-card", {label: "Amendments filed", value: "1 247", icon: "file-text"})
|
|
599
|
+
component("stat-card", {label: "Amendments adopted", value: "312", icon: "check"})
|
|
600
|
+
component("stat-card", {label: "Total duration", value: "14 months", icon: "clock"})
|
|
601
601
|
\`\`\`
|
|
602
602
|
|
|
603
|
-
5. **
|
|
603
|
+
5. **Amendment details by stage** in a table:
|
|
604
604
|
\`\`\`
|
|
605
605
|
component("table", {
|
|
606
|
-
columns: ["
|
|
606
|
+
columns: ["Stage", "Chamber", "Amendments filed", "Adopted", "Rejected"],
|
|
607
607
|
rows: etapeStats
|
|
608
608
|
})
|
|
609
609
|
\`\`\`
|
|
610
610
|
|
|
611
|
-
6. **
|
|
611
|
+
6. **File metadata** in kv:
|
|
612
612
|
\`\`\`
|
|
613
613
|
component("kv", {pairs: [
|
|
614
|
-
["
|
|
614
|
+
["Title", dossier.titre],
|
|
615
615
|
["Nature", "Projet de loi"],
|
|
616
|
-
["
|
|
617
|
-
["
|
|
618
|
-
["
|
|
616
|
+
["Author", "Gouvernement"],
|
|
617
|
+
["Filing date", dossier.date_depot],
|
|
618
|
+
["Current status", dossier.etat],
|
|
619
619
|
["Source", "Tricoteuses"]
|
|
620
620
|
]})
|
|
621
621
|
\`\`\`
|
|
622
622
|
|
|
623
|
-
##
|
|
623
|
+
## Examples
|
|
624
624
|
|
|
625
|
-
###
|
|
625
|
+
### Complete text journey
|
|
626
626
|
\`\`\`
|
|
627
|
-
// 1.
|
|
627
|
+
// 1. Retrieve the file
|
|
628
628
|
query_sql({sql: "SELECT id, titre, date_depot, etat FROM assemblee.dossiers WHERE titre ILIKE '%retraites%2023%' LIMIT 1"})
|
|
629
629
|
|
|
630
|
-
// 2.
|
|
630
|
+
// 2. Stages
|
|
631
631
|
query_sql({sql: "SELECT * FROM assemblee.dossier_etapes WHERE dossier_id = $id ORDER BY date"})
|
|
632
632
|
|
|
633
|
-
// 3.
|
|
633
|
+
// 3. Amendments by stage
|
|
634
634
|
query_sql({sql: "SELECT etape, COUNT(*) as total, COUNT(*) FILTER (WHERE sort='Adopte') as adoptes FROM assemblee.amendements WHERE dossier_id = $id GROUP BY etape"})
|
|
635
635
|
|
|
636
|
-
// 4.
|
|
637
|
-
component("kv", {pairs: [["
|
|
636
|
+
// 4. Render
|
|
637
|
+
component("kv", {pairs: [["File", titre], ["Status", etat]]})
|
|
638
638
|
component("timeline", {events: etapes})
|
|
639
|
-
component("stat-card", {label: "Total
|
|
640
|
-
component("stat-card", {label: "
|
|
641
|
-
component("table", {columns: ["
|
|
639
|
+
component("stat-card", {label: "Total amendments", value: totalAmendements})
|
|
640
|
+
component("stat-card", {label: "Adopted", value: totalAdoptes})
|
|
641
|
+
component("table", {columns: ["Stage", "Filed", "Adopted", "Rate"], rows: etapeStats})
|
|
642
642
|
\`\`\`
|
|
643
643
|
|
|
644
|
-
###
|
|
644
|
+
### Comparison between chambers
|
|
645
645
|
\`\`\`
|
|
646
|
-
//
|
|
646
|
+
// Amendments by chamber
|
|
647
647
|
query_sql({sql: "SELECT chambre, COUNT(*) as deposes, COUNT(*) FILTER (WHERE sort='Adopte') as adoptes FROM assemblee.amendements WHERE dossier_id = $id GROUP BY chambre"})
|
|
648
648
|
|
|
649
|
-
component("stat-card", {label: "
|
|
650
|
-
component("stat-card", {label: "
|
|
651
|
-
component("chart", {type: "bar", labels: ["Assemblee", "Senat"], datasets: [{label: "
|
|
649
|
+
component("stat-card", {label: "Assembly — Adopted", value: an_adoptes + "/" + an_deposes})
|
|
650
|
+
component("stat-card", {label: "Senate — Adopted", value: senat_adoptes + "/" + senat_deposes})
|
|
651
|
+
component("chart", {type: "bar", labels: ["Assemblee", "Senat"], datasets: [{label: "Filed", data: [an_deposes, senat_deposes]}, {label: "Adopted", data: [an_adoptes, senat_adoptes]}]})
|
|
652
652
|
\`\`\`
|
|
653
653
|
|
|
654
|
-
##
|
|
654
|
+
## Common mistakes
|
|
655
655
|
|
|
656
|
-
- **
|
|
657
|
-
- **
|
|
658
|
-
- **
|
|
659
|
-
- **
|
|
656
|
+
- **Confusing bill types**: a "projet de loi" comes from the government, a "proposition de loi" comes from a parliamentary member — check the \`nature\` field
|
|
657
|
+
- **Not following the shuttle**: a text can make several back-and-forth trips between the Assembly and the Senate — display ALL stages
|
|
658
|
+
- **Forgetting the CMP**: the Commission Mixte Paritaire (Joint Committee) is a crucial stage between the two chambers — do not omit it from the timeline
|
|
659
|
+
- **Non-chronological timeline**: always sort stages by ascending date
|
|
660
660
|
`,
|
|
661
661
|
'gallery-images': `---
|
|
662
|
-
id:
|
|
663
|
-
name:
|
|
662
|
+
id: display-image-gallery-from-mcp-urls
|
|
663
|
+
name: Display an image gallery from URLs returned by an MCP server
|
|
664
664
|
components_used: [gallery, carousel, cards]
|
|
665
|
-
when:
|
|
665
|
+
when: MCP data contains URL fields pointing to images (jpg, png, webp, svg) such as hdurl, primaryImage, image_url, photos[].url, or any similar field
|
|
666
666
|
servers: [nasa, metmuseum, inaturalist]
|
|
667
667
|
layout:
|
|
668
668
|
type: grid
|
|
@@ -671,32 +671,32 @@ interactions:
|
|
|
671
671
|
- source: gallery, target: lightbox, event: click, action: zoom
|
|
672
672
|
---
|
|
673
673
|
|
|
674
|
-
##
|
|
674
|
+
## When to use
|
|
675
675
|
|
|
676
|
-
|
|
677
|
-
- **NASA
|
|
678
|
-
- **Met Museum
|
|
679
|
-
- **iNaturalist
|
|
676
|
+
The results of an MCP call contain real image URLs. This includes:
|
|
677
|
+
- **NASA**: \`hdurl\` or \`url\` field in APOD, Earth, and Mars Rover Photos responses
|
|
678
|
+
- **Met Museum**: \`primaryImage\` or \`primaryImageSmall\` field after \`get_object\`
|
|
679
|
+
- **iNaturalist**: \`photos[].url\` field in observations
|
|
680
680
|
|
|
681
|
-
|
|
681
|
+
This recipe applies whenever at least 2 images are available in the data. For a single image, prefer a \`card\` component with the image as a header.
|
|
682
682
|
|
|
683
|
-
##
|
|
683
|
+
## How to use
|
|
684
684
|
|
|
685
|
-
1. **
|
|
686
|
-
2. **
|
|
687
|
-
3. **
|
|
688
|
-
4. **
|
|
689
|
-
- 2-5 images
|
|
690
|
-
- 6+ images
|
|
691
|
-
- Images
|
|
692
|
-
5. **
|
|
693
|
-
6. **
|
|
685
|
+
1. **Call the DATA tool** from the MCP server to retrieve the data containing the images
|
|
686
|
+
2. **Extract the real URLs** from the results — NEVER invent placeholder URLs
|
|
687
|
+
3. **Verify that the URLs are valid**: they must point to known domains (apod.nasa.gov, images.metmuseum.org, inaturalist-open-data.s3.amazonaws.com, etc.)
|
|
688
|
+
4. **Choose the component**:
|
|
689
|
+
- 2-5 images: \`component("gallery", {images: [{src, alt, caption?}]})\`
|
|
690
|
+
- 6+ images: \`component("carousel", {images: [{src, alt}]})\` to avoid an overly long page
|
|
691
|
+
- Images with rich metadata: \`component("cards", {items: [{title, image, subtitle, body}]})\`
|
|
692
|
+
5. **Always provide a descriptive \`alt\`** for each image (artwork title, species name, APOD title)
|
|
693
|
+
6. **Add a contextual title** before the gallery with \`component("text", {content: "..."})\`
|
|
694
694
|
|
|
695
|
-
##
|
|
695
|
+
## Examples
|
|
696
696
|
|
|
697
697
|
### NASA APOD (Astronomy Picture of the Day)
|
|
698
|
-
|
|
699
|
-
|
|
698
|
+
Tool: \`nasa_apod\` or \`nasa_apod_range\`
|
|
699
|
+
Typical response: \`{hdurl: "https://apod.nasa.gov/apod/image/2401/...", title: "Horsehead Nebula", explanation: "..."}\`
|
|
700
700
|
|
|
701
701
|
\`\`\`
|
|
702
702
|
component("gallery", {
|
|
@@ -708,9 +708,9 @@ component("gallery", {
|
|
|
708
708
|
})
|
|
709
709
|
\`\`\`
|
|
710
710
|
|
|
711
|
-
### Met Museum —
|
|
712
|
-
|
|
713
|
-
|
|
711
|
+
### Met Museum — Artwork search
|
|
712
|
+
Step 1: \`search_objects({query: "impressionism sunflower"})\` → list of IDs
|
|
713
|
+
Step 2: for each ID, \`get_object({objectID: id})\` → \`{primaryImage, title, artistDisplayName}\`
|
|
714
714
|
|
|
715
715
|
\`\`\`
|
|
716
716
|
component("gallery", {
|
|
@@ -722,9 +722,9 @@ component("gallery", {
|
|
|
722
722
|
})
|
|
723
723
|
\`\`\`
|
|
724
724
|
|
|
725
|
-
### iNaturalist — Observations
|
|
726
|
-
|
|
727
|
-
|
|
725
|
+
### iNaturalist — Observations with photos
|
|
726
|
+
Tool: \`search_observations({taxon_name: "Parus major", lat: 48.85, lng: 2.35, radius: 10})\`
|
|
727
|
+
Typical response: \`{photos: [{url: "..."}], species_guess: "Mesange charbonniere", place_guess: "Paris"}\`
|
|
728
728
|
|
|
729
729
|
\`\`\`
|
|
730
730
|
component("gallery", {
|
|
@@ -738,49 +738,49 @@ component("gallery", {
|
|
|
738
738
|
})
|
|
739
739
|
\`\`\`
|
|
740
740
|
|
|
741
|
-
##
|
|
741
|
+
## Common mistakes
|
|
742
742
|
|
|
743
|
-
- **
|
|
744
|
-
- **
|
|
745
|
-
- **
|
|
746
|
-
- **
|
|
743
|
+
- **Inventing placeholder URLs** (\`https://example.com/image.jpg\`, \`via.placeholder.com\`, \`placehold.co\`, \`dummyimage.com\`, \`?text=...\`) — strictly FORBIDDEN. If no real image is returned by the API, do NOT display a gallery.
|
|
744
|
+
- **Forgetting to check** that the image field exists in the returned data (some Met Museum objects have no \`primaryImage\`)
|
|
745
|
+
- **Using \`text\` to display URLs** instead of \`gallery\` — images must be rendered visually
|
|
746
|
+
- **Not adapting the size**: iNaturalist returns "square" thumbnails by default — replace with "medium" or "large" in the URL
|
|
747
747
|
`,
|
|
748
748
|
'parlementaire-profile': `---
|
|
749
|
-
id:
|
|
750
|
-
name:
|
|
749
|
+
id: display-parliamentary-profile-with-hemicycle-and-votes
|
|
750
|
+
name: Display a parliamentary profile with hemicycle, votes and mandate timeline
|
|
751
751
|
components_used: [profile, hemicycle, timeline, table, stat-card, kv]
|
|
752
|
-
when:
|
|
752
|
+
when: MCP data concerns a deputy, senator, parliamentary group, vote, or amendment from a parliamentary database
|
|
753
753
|
servers: [tricoteuses]
|
|
754
754
|
layout:
|
|
755
755
|
type: grid
|
|
756
756
|
columns: 2
|
|
757
|
-
arrangement: profile + stats
|
|
757
|
+
arrangement: profile + stats at top, hemicycle + timeline below
|
|
758
758
|
---
|
|
759
759
|
|
|
760
|
-
##
|
|
760
|
+
## When to use
|
|
761
761
|
|
|
762
|
-
|
|
763
|
-
-
|
|
764
|
-
-
|
|
765
|
-
-
|
|
766
|
-
-
|
|
767
|
-
-
|
|
762
|
+
MCP results come from the Tricoteuses server (French parliamentary database) and concern:
|
|
763
|
+
- A **deputy or senator**: individual profile, mandates, activity
|
|
764
|
+
- A **political group**: composition, votes, positioning
|
|
765
|
+
- **Public votes**: vote results, breakdown by group
|
|
766
|
+
- **Amendments**: text, author, outcome, targeted article
|
|
767
|
+
- **Legislative files**: text journey, parliamentary shuttle
|
|
768
768
|
|
|
769
|
-
|
|
769
|
+
This recipe is specific to the Tricoteuses server and its tools: \`query_sql\`, \`search_recipes\`, \`list_tables\`, \`describe_table\`.
|
|
770
770
|
|
|
771
|
-
##
|
|
771
|
+
## How to use
|
|
772
772
|
|
|
773
|
-
1. **
|
|
773
|
+
1. **Retrieve the parliamentary member's information** via the Tricoteuses tools:
|
|
774
774
|
\`\`\`
|
|
775
775
|
query_sql({sql: "SELECT * FROM acteurs WHERE nom ILIKE '%dupont%' AND type = 'depute'"})
|
|
776
776
|
\`\`\`
|
|
777
|
-
|
|
777
|
+
Or via Tricoteuses recipes:
|
|
778
778
|
\`\`\`
|
|
779
779
|
search_recipes({query: "profil depute"})
|
|
780
780
|
get_recipe({name: "recipe-name"})
|
|
781
781
|
\`\`\`
|
|
782
782
|
|
|
783
|
-
2. **
|
|
783
|
+
2. **Display the profile card**:
|
|
784
784
|
\`\`\`
|
|
785
785
|
component("profile", {
|
|
786
786
|
name: "Jean Dupont",
|
|
@@ -790,15 +790,15 @@ La recette est specifique au serveur Tricoteuses et a ses outils : \`query_sql\`
|
|
|
790
790
|
})
|
|
791
791
|
\`\`\`
|
|
792
792
|
|
|
793
|
-
3. **
|
|
793
|
+
3. **Display activity statistics** in stat-cards:
|
|
794
794
|
\`\`\`
|
|
795
|
-
component("stat-card", {label: "
|
|
796
|
-
component("stat-card", {label: "
|
|
797
|
-
component("stat-card", {label: "
|
|
798
|
-
component("stat-card", {label: "
|
|
795
|
+
component("stat-card", {label: "Vote participation", value: "87%", trend: "+3%", trendDir: "up"})
|
|
796
|
+
component("stat-card", {label: "Amendments filed", value: "42", icon: "file-text"})
|
|
797
|
+
component("stat-card", {label: "Written questions", value: "18", icon: "help-circle"})
|
|
798
|
+
component("stat-card", {label: "Hemicycle speeches", value: "7", icon: "mic"})
|
|
799
799
|
\`\`\`
|
|
800
800
|
|
|
801
|
-
4. **
|
|
801
|
+
4. **If vote data by group is available**, display the hemicycle:
|
|
802
802
|
\`\`\`
|
|
803
803
|
component("hemicycle", {
|
|
804
804
|
groups: [
|
|
@@ -810,219 +810,219 @@ La recette est specifique au serveur Tricoteuses et a ses outils : \`query_sql\`
|
|
|
810
810
|
})
|
|
811
811
|
\`\`\`
|
|
812
812
|
|
|
813
|
-
5. **
|
|
813
|
+
5. **For the mandate history or key votes**, use the timeline:
|
|
814
814
|
\`\`\`
|
|
815
815
|
component("timeline", {
|
|
816
816
|
events: [
|
|
817
|
-
{date: "2022-06-19", title: "
|
|
818
|
-
{date: "2023-03-16", title: "Vote 49.3
|
|
819
|
-
{date: "2024-07-07", title: "
|
|
817
|
+
{date: "2022-06-19", title: "Elected deputy", description: "3e circ. du Rhone"},
|
|
818
|
+
{date: "2023-03-16", title: "Vote 49.3 pensions reform", description: "Motion de censure rejetee"},
|
|
819
|
+
{date: "2024-07-07", title: "Re-elected deputy", description: "2nd round"}
|
|
820
820
|
]
|
|
821
821
|
})
|
|
822
822
|
\`\`\`
|
|
823
823
|
|
|
824
|
-
6. **
|
|
824
|
+
6. **For vote or amendment details**, use a table:
|
|
825
825
|
\`\`\`
|
|
826
826
|
component("table", {
|
|
827
|
-
columns: ["Date", "
|
|
827
|
+
columns: ["Date", "Vote", "Position", "Result"],
|
|
828
828
|
rows: votes.map(v => [v.date, v.intitule, v.position, v.resultat])
|
|
829
829
|
})
|
|
830
830
|
\`\`\`
|
|
831
831
|
|
|
832
|
-
7. **
|
|
832
|
+
7. **Complete with metadata** in kv:
|
|
833
833
|
\`\`\`
|
|
834
834
|
component("kv", {pairs: [
|
|
835
835
|
["Legislature", "XVIe (2022-2027)"],
|
|
836
|
-
["
|
|
837
|
-
["
|
|
836
|
+
["Group", "Renaissance"],
|
|
837
|
+
["Standing committee", "Lois"],
|
|
838
838
|
["Source", "Tricoteuses — mcp.code4code.eu"]
|
|
839
839
|
]})
|
|
840
840
|
\`\`\`
|
|
841
841
|
|
|
842
|
-
##
|
|
842
|
+
## Examples
|
|
843
843
|
|
|
844
|
-
###
|
|
844
|
+
### Complete deputy profile
|
|
845
845
|
\`\`\`
|
|
846
|
-
// 1.
|
|
846
|
+
// 1. Look up the deputy
|
|
847
847
|
query_sql({sql: "SELECT * FROM acteurs WHERE nom ILIKE '%dupont%' LIMIT 1"})
|
|
848
848
|
|
|
849
|
-
// 2.
|
|
849
|
+
// 2. Their amendments
|
|
850
850
|
query_sql({sql: "SELECT COUNT(*) as total, SUM(CASE WHEN sort='Adopte' THEN 1 ELSE 0 END) as adoptes FROM amendements WHERE auteur_id = $id"})
|
|
851
851
|
|
|
852
|
-
// 3.
|
|
852
|
+
// 3. Their recent votes
|
|
853
853
|
query_sql({sql: "SELECT s.date, s.intitule, v.position FROM scrutins s JOIN votes v ON s.id = v.scrutin_id WHERE v.acteur_id = $id ORDER BY s.date DESC LIMIT 10"})
|
|
854
854
|
|
|
855
|
-
// 4.
|
|
855
|
+
// 4. Full render
|
|
856
856
|
component("profile", {name, subtitle, photo})
|
|
857
|
-
component("stat-card", {label: "
|
|
858
|
-
component("stat-card", {label: "
|
|
859
|
-
component("stat-card", {label: "
|
|
860
|
-
component("table", {columns: ["Date", "
|
|
857
|
+
component("stat-card", {label: "Amendments", value: total, icon: "file-text"})
|
|
858
|
+
component("stat-card", {label: "Adopted", value: adoptes, icon: "check"})
|
|
859
|
+
component("stat-card", {label: "Adoption rate", value: Math.round(adoptes/total*100) + "%"})
|
|
860
|
+
component("table", {columns: ["Date", "Vote", "Position"], rows: votes})
|
|
861
861
|
component("timeline", {events: mandats})
|
|
862
862
|
\`\`\`
|
|
863
863
|
|
|
864
|
-
###
|
|
864
|
+
### Vote result with hemicycle
|
|
865
865
|
\`\`\`
|
|
866
|
-
// 1.
|
|
866
|
+
// 1. Retrieve the vote
|
|
867
867
|
query_sql({sql: "SELECT * FROM scrutins WHERE intitule ILIKE '%budget%' ORDER BY date DESC LIMIT 1"})
|
|
868
868
|
|
|
869
|
-
// 2.
|
|
869
|
+
// 2. Breakdown by group
|
|
870
870
|
query_sql({sql: "SELECT g.nom, g.couleur, COUNT(*) FILTER (WHERE v.position='pour') as pour, COUNT(*) FILTER (WHERE v.position='contre') as contre FROM votes v JOIN groupes g ON v.groupe_id = g.id WHERE v.scrutin_id = $id GROUP BY g.nom, g.couleur"})
|
|
871
871
|
|
|
872
|
-
// 3.
|
|
873
|
-
component("stat-card", {label: "
|
|
874
|
-
component("stat-card", {label: "
|
|
872
|
+
// 3. Render
|
|
873
|
+
component("stat-card", {label: "For", value: "312", trendDir: "up"})
|
|
874
|
+
component("stat-card", {label: "Against", value: "245", trendDir: "down"})
|
|
875
875
|
component("hemicycle", {groups: groupResults, result: {pour: 312, contre: 245, abstention: 18}})
|
|
876
|
-
component("table", {columns: ["
|
|
876
|
+
component("table", {columns: ["Group", "For", "Against", "Abstention"], rows: groupDetails})
|
|
877
877
|
\`\`\`
|
|
878
878
|
|
|
879
|
-
##
|
|
879
|
+
## Components specific to the parliamentary domain
|
|
880
880
|
|
|
881
|
-
- **hemicycle
|
|
882
|
-
- **profile
|
|
883
|
-
- **timeline
|
|
884
|
-
- **trombinoscope
|
|
881
|
+
- **hemicycle**: semicircular arc showing vote distribution by political group, with party colors
|
|
882
|
+
- **profile**: individual card with photo, name, role, structured details
|
|
883
|
+
- **timeline**: chronology of mandates, key votes, parliamentary events
|
|
884
|
+
- **trombinoscope**: photo grid for a group of parliamentary members (useful for committees)
|
|
885
885
|
|
|
886
|
-
##
|
|
886
|
+
## Common mistakes
|
|
887
887
|
|
|
888
|
-
- **
|
|
889
|
-
- **
|
|
890
|
-
- **
|
|
891
|
-
- **Hemicycle
|
|
888
|
+
- **Overly broad SQL queries**: always use LIMIT and precise filters to avoid overloading the server
|
|
889
|
+
- **Confusing actors and mandates**: a deputy can have multiple mandates in the database — filter by current legislature
|
|
890
|
+
- **Forgetting the source**: always credit "Tricoteuses" as the source in a final kv
|
|
891
|
+
- **Hemicycle without context**: always accompany the hemicycle component with stat-cards showing for/against/abstention totals
|
|
892
892
|
`,
|
|
893
893
|
'rechercher-textes-juridiques-legifrance': `---
|
|
894
|
-
id:
|
|
895
|
-
name:
|
|
894
|
+
id: search-legal-texts-legifrance
|
|
895
|
+
name: Search legal texts, codes and laws via Legifrance data
|
|
896
896
|
components_used: [table, text, kv, code]
|
|
897
|
-
when:
|
|
897
|
+
when: the user asks for information about French laws, legal codes, articles of law, decrees, ordinances or legislative texts available in the Tricoteuses database
|
|
898
898
|
servers: [tricoteuses]
|
|
899
899
|
layout:
|
|
900
900
|
type: grid
|
|
901
901
|
columns: 1
|
|
902
902
|
---
|
|
903
903
|
|
|
904
|
-
##
|
|
904
|
+
## When to use
|
|
905
905
|
|
|
906
|
-
|
|
907
|
-
- "
|
|
908
|
-
- "
|
|
909
|
-
- "
|
|
910
|
-
- "
|
|
906
|
+
The user asks a question about French law, legislation, or a specific legal text:
|
|
907
|
+
- "What does article 49-3 of the Constitution say?"
|
|
908
|
+
- "Which decrees were published on labour law in 2025?"
|
|
909
|
+
- "Show me the articles of the Civil Code on filiation"
|
|
910
|
+
- "Which laws were enacted this month?"
|
|
911
911
|
|
|
912
|
-
|
|
912
|
+
The Tricoteuses database contains data from Legifrance (consolidated texts, codes, laws, decrees). Use \`list_tables\` to discover the available tables in the legifrance schema, then \`describe_table\` and \`query_sql\` to extract the texts.
|
|
913
913
|
|
|
914
|
-
##
|
|
914
|
+
## How to use
|
|
915
915
|
|
|
916
|
-
1. **
|
|
916
|
+
1. **Discover available tables**:
|
|
917
917
|
\`\`\`
|
|
918
918
|
list_tables({schema: "legifrance"})
|
|
919
919
|
\`\`\`
|
|
920
|
-
|
|
920
|
+
Typical tables: \`textes_versions\`, \`articles\`, \`codes\`, \`sections\`
|
|
921
921
|
|
|
922
|
-
2. **
|
|
922
|
+
2. **Describe the structure** of a table to understand the columns:
|
|
923
923
|
\`\`\`
|
|
924
924
|
describe_table({schema: "legifrance", table: "articles"})
|
|
925
925
|
\`\`\`
|
|
926
926
|
|
|
927
|
-
3. **
|
|
927
|
+
3. **Search for texts** with SQL queries:
|
|
928
928
|
\`\`\`
|
|
929
929
|
query_sql({sql: "SELECT titre, date_publi, nature FROM legifrance.textes_versions WHERE titre ILIKE '%travail%' ORDER BY date_publi DESC LIMIT 20"})
|
|
930
930
|
\`\`\`
|
|
931
931
|
|
|
932
|
-
4. **
|
|
933
|
-
- **Table**
|
|
932
|
+
4. **Display results**:
|
|
933
|
+
- **Table** for lists of texts:
|
|
934
934
|
\`\`\`
|
|
935
|
-
component("table", {columns: ["
|
|
935
|
+
component("table", {columns: ["Title", "Nature", "Date", "Status"], rows: textes})
|
|
936
936
|
\`\`\`
|
|
937
|
-
- **Text**
|
|
937
|
+
- **Text** for the content of an article:
|
|
938
938
|
\`\`\`
|
|
939
939
|
component("text", {content: "### Article 49-3 de la Constitution\\n\\n" + article.contenu})
|
|
940
940
|
\`\`\`
|
|
941
|
-
- **Code**
|
|
941
|
+
- **Code** for legislative excerpts with formatting:
|
|
942
942
|
\`\`\`
|
|
943
943
|
component("code", {language: "text", content: article.texte_integral})
|
|
944
944
|
\`\`\`
|
|
945
|
-
- **KV**
|
|
945
|
+
- **KV** for text metadata:
|
|
946
946
|
\`\`\`
|
|
947
|
-
component("kv", {pairs: [["Nature", "Loi organique"], ["Date", "2023-04-14"], ["NOR", "JUSX2300001L"], ["
|
|
947
|
+
component("kv", {pairs: [["Nature", "Loi organique"], ["Date", "2023-04-14"], ["NOR", "JUSX2300001L"], ["Status", "En vigueur"]]})
|
|
948
948
|
\`\`\`
|
|
949
949
|
|
|
950
|
-
##
|
|
950
|
+
## Examples
|
|
951
951
|
|
|
952
|
-
### Articles
|
|
952
|
+
### Articles from a legal code
|
|
953
953
|
\`\`\`
|
|
954
|
-
// 1.
|
|
954
|
+
// 1. Find articles of the Civil Code on filiation
|
|
955
955
|
query_sql({sql: "SELECT num_article, contenu, etat FROM legifrance.articles WHERE code = 'Code civil' AND section ILIKE '%filiation%' ORDER BY num_article"})
|
|
956
956
|
|
|
957
|
-
// 2.
|
|
958
|
-
component("kv", {pairs: [["Code", "Code civil"], ["Section", "De la filiation"], ["Articles
|
|
959
|
-
component("table", {columns: ["Article", "
|
|
957
|
+
// 2. Render
|
|
958
|
+
component("kv", {pairs: [["Code", "Code civil"], ["Section", "De la filiation"], ["Articles found", results.length]]})
|
|
959
|
+
component("table", {columns: ["Article", "Content (excerpt)", "Status"], rows: results.map(r => [r.num_article, r.contenu.slice(0, 200) + "...", r.etat])})
|
|
960
960
|
\`\`\`
|
|
961
961
|
|
|
962
|
-
###
|
|
962
|
+
### Recent texts by nature
|
|
963
963
|
\`\`\`
|
|
964
|
-
// 1.
|
|
964
|
+
// 1. Laws enacted in 2026
|
|
965
965
|
query_sql({sql: "SELECT titre, date_publi, nor FROM legifrance.textes_versions WHERE nature = 'LOI' AND date_publi >= '2026-01-01' ORDER BY date_publi DESC"})
|
|
966
966
|
|
|
967
|
-
// 2.
|
|
968
|
-
component("stat-card", {label: "
|
|
969
|
-
component("table", {columns: ["
|
|
967
|
+
// 2. Render
|
|
968
|
+
component("stat-card", {label: "Laws enacted in 2026", value: results.length, icon: "scale"})
|
|
969
|
+
component("table", {columns: ["Title", "Date", "NOR"], rows: results})
|
|
970
970
|
\`\`\`
|
|
971
971
|
|
|
972
|
-
##
|
|
972
|
+
## Common mistakes
|
|
973
973
|
|
|
974
|
-
- **
|
|
975
|
-
- **
|
|
976
|
-
- **
|
|
977
|
-
- **
|
|
974
|
+
- **Confusing schemas**: Legifrance data is in the \`legifrance\` schema, not in \`assemblee\` — verify with \`list_tables\`
|
|
975
|
+
- **Overly broad queries**: always use LIMIT and precise WHERE filters
|
|
976
|
+
- **Displaying raw full text**: use the \`text\` component with Markdown formatting rather than dumping the JSON
|
|
977
|
+
- **Forgetting to specify the text status**: an article can be repealed, amended or in force — always indicate it
|
|
978
978
|
`,
|
|
979
979
|
'weather-viz': `---
|
|
980
|
-
id:
|
|
981
|
-
name:
|
|
980
|
+
id: visualize-weather-forecasts-with-charts-and-kpis
|
|
981
|
+
name: Visualize weather forecasts with time series charts and key indicators
|
|
982
982
|
components_used: [stat-card, chart-rich, map, kv]
|
|
983
|
-
when:
|
|
983
|
+
when: MCP data contains weather measurements (temperature, humidity, wind, precipitation) or hourly/daily forecasts
|
|
984
984
|
servers: [openmeteo]
|
|
985
985
|
layout:
|
|
986
986
|
type: grid
|
|
987
987
|
columns: 2
|
|
988
|
-
arrangement: stat-cards
|
|
988
|
+
arrangement: stat-cards at the top, chart spanning full width at the bottom
|
|
989
989
|
---
|
|
990
990
|
|
|
991
|
-
##
|
|
991
|
+
## When to use
|
|
992
992
|
|
|
993
|
-
|
|
994
|
-
- **Open-Meteo
|
|
995
|
-
-
|
|
993
|
+
MCP results contain structured weather data. Typically:
|
|
994
|
+
- **Open-Meteo**: hourly forecasts (\`hourly.temperature_2m\`, \`hourly.precipitation\`), daily forecasts (\`daily.temperature_2m_max/min\`), current conditions (\`current.temperature_2m\`, \`current.wind_speed_10m\`)
|
|
995
|
+
- Any source returning time series of climate measurements
|
|
996
996
|
|
|
997
|
-
|
|
997
|
+
This recipe applies even for a single city: stat-cards show current conditions, the chart shows the trend over time.
|
|
998
998
|
|
|
999
|
-
##
|
|
999
|
+
## How to use
|
|
1000
1000
|
|
|
1001
|
-
1. **
|
|
1002
|
-
- Open-Meteo
|
|
1003
|
-
2. **
|
|
1004
|
-
-
|
|
1005
|
-
-
|
|
1006
|
-
-
|
|
1007
|
-
-
|
|
1008
|
-
3. **
|
|
1001
|
+
1. **Call the weather tool** with coordinates or city name:
|
|
1002
|
+
- Open-Meteo: \`get_forecast({latitude: 48.85, longitude: 2.35, hourly: "temperature_2m,precipitation", daily: "temperature_2m_max,temperature_2m_min"})\`
|
|
1003
|
+
2. **Extract current KPIs** and display them as stat-cards:
|
|
1004
|
+
- Current temperature: \`component("stat-card", {label: "Temperature", value: "18.5°C", icon: "thermometer"})\`
|
|
1005
|
+
- Wind: \`component("stat-card", {label: "Wind", value: "12.3 km/h", icon: "wind"})\`
|
|
1006
|
+
- Humidity: \`component("stat-card", {label: "Humidity", value: "72%", icon: "droplets"})\`
|
|
1007
|
+
- Precipitation: \`component("stat-card", {label: "Rain (24h)", value: "2.1 mm", icon: "cloud-rain"})\`
|
|
1008
|
+
3. **Build the time series chart** from hourly or daily series:
|
|
1009
1009
|
- \`component("chart-rich", {type: "line", labels: hourly.time, datasets: [{label: "Temperature °C", data: hourly.temperature_2m}]})\`
|
|
1010
|
-
-
|
|
1011
|
-
4. **
|
|
1010
|
+
- For precipitation, prefer an overlaid "bar" type
|
|
1011
|
+
4. **Add the map** if coordinates are available:
|
|
1012
1012
|
- \`component("map", {center: [lat, lon], zoom: 10, markers: [{lat, lon, label: "Paris"}]})\`
|
|
1013
|
-
5. **
|
|
1014
|
-
- \`component("kv", {pairs: [["
|
|
1013
|
+
5. **Complete with details** in kv:
|
|
1014
|
+
- \`component("kv", {pairs: [["Sunrise", "06:42"], ["Sunset", "20:15"], ["UV Index", "5 (moderate)"]]})\`
|
|
1015
1015
|
|
|
1016
|
-
##
|
|
1016
|
+
## Examples
|
|
1017
1017
|
|
|
1018
|
-
###
|
|
1018
|
+
### 7-day forecast for Paris
|
|
1019
1019
|
\`\`\`
|
|
1020
|
-
//
|
|
1020
|
+
// MCP call
|
|
1021
1021
|
get_forecast({latitude: 48.8566, longitude: 2.3522, daily: "temperature_2m_max,temperature_2m_min,precipitation_sum"})
|
|
1022
1022
|
|
|
1023
|
-
//
|
|
1024
|
-
component("stat-card", {label: "
|
|
1025
|
-
component("stat-card", {label: "
|
|
1023
|
+
// Render
|
|
1024
|
+
component("stat-card", {label: "Today", value: "22°C / 14°C", icon: "thermometer"})
|
|
1025
|
+
component("stat-card", {label: "Expected rain", value: "0 mm", icon: "sun"})
|
|
1026
1026
|
component("chart-rich", {
|
|
1027
1027
|
type: "line",
|
|
1028
1028
|
labels: daily.time, // ["2026-04-09", "2026-04-10", ...]
|
|
@@ -1033,23 +1033,23 @@ component("chart-rich", {
|
|
|
1033
1033
|
})
|
|
1034
1034
|
\`\`\`
|
|
1035
1035
|
|
|
1036
|
-
###
|
|
1036
|
+
### Hourly forecast with precipitation
|
|
1037
1037
|
\`\`\`
|
|
1038
1038
|
component("chart-rich", {
|
|
1039
1039
|
type: "line",
|
|
1040
1040
|
labels: hourly.time.slice(0, 24),
|
|
1041
1041
|
datasets: [
|
|
1042
1042
|
{label: "Temperature °C", data: hourly.temperature_2m.slice(0, 24), yAxisID: "y"},
|
|
1043
|
-
{label: "
|
|
1043
|
+
{label: "Rain mm", data: hourly.precipitation.slice(0, 24), yAxisID: "y1", type: "bar"}
|
|
1044
1044
|
]
|
|
1045
1045
|
})
|
|
1046
1046
|
\`\`\`
|
|
1047
1047
|
|
|
1048
|
-
##
|
|
1048
|
+
## Common mistakes
|
|
1049
1049
|
|
|
1050
|
-
- **
|
|
1051
|
-
- **
|
|
1052
|
-
- **
|
|
1053
|
-
- **
|
|
1050
|
+
- **Displaying raw JSON data** instead of visualizing it with components
|
|
1051
|
+
- **Not converting units**: Open-Meteo returns SI units by default (m/s for wind → convert to km/h if appropriate for the user)
|
|
1052
|
+
- **Forgetting stat-cards** for the main KPIs: a chart alone does not surface key figures immediately
|
|
1053
|
+
- **Too many points on the chart**: for hourly forecasts over 7 days (168 points), prefer daily resampling or limit to 48h in hourly mode
|
|
1054
1054
|
`,
|
|
1055
1055
|
};
|