mcp-recipes 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/PRIVACY.md +168 -0
- package/README.md +491 -708
- package/dist/index.js +594 -88
- package/dist/index.js.map +1 -1
- package/dist/sources/client.d.ts +344 -0
- package/dist/sources/client.js +448 -9
- package/dist/sources/client.js.map +1 -1
- package/package.json +14 -4
- package/server.json +5 -5
package/README.md
CHANGED
|
@@ -9,128 +9,42 @@
|
|
|
9
9
|
[](https://cursor.com/en/install-mcp?name=recipes&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1yZWNpcGVzIl19)
|
|
10
10
|
[](https://insiders.vscode.dev/redirect/mcp/install?name=recipes&config=%7B%22name%22%3A%22recipes%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-recipes%22%5D%7D)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
and English
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
Recipes live on many sites, and each one writes them its own way: a French
|
|
13
|
+
cooking site publishes in French, with its own measures and its own idea of what
|
|
14
|
+
a serving is, and a wiki cookbook in English, with equipment lists and prose the
|
|
15
|
+
first has no field for. Asking a question of one of them answers about one of
|
|
16
|
+
them.
|
|
17
|
+
|
|
18
|
+
This server reads five at once. Three publish in French:
|
|
19
|
+
[Marmiton](https://www.marmiton.org), where home cooks publish,
|
|
20
|
+
[Ptitchef](https://www.ptitchef.com), which files its recipes under a tree of
|
|
21
|
+
ingredients, and [Supertoinette](https://www.supertoinette.com), which prints a
|
|
22
|
+
resting time of its own. Two publish in English: the
|
|
23
|
+
[Wikibooks Cookbook](https://en.wikibooks.org/wiki/Cookbook:Table_of_Contents),
|
|
24
|
+
written and maintained in the open, and
|
|
25
|
+
[BBC Good Food](https://www.bbcgoodfood.com), which groups an ingredient list
|
|
26
|
+
under headings. You can search them all with one question, read a recipe from any
|
|
27
|
+
of them in one shape, put several versions of the same dish side by side, and
|
|
28
|
+
rescale any ingredient list. It needs no API key and no account.
|
|
29
|
+
|
|
30
|
+
_[Version française](#mcp-recipes-français)_
|
|
20
31
|
|
|
21
32
|
---
|
|
22
33
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
Marmiton is a French recipe site with reader ratings and millions of visitors.
|
|
26
|
-
The Wikibooks Cookbook is an English wiki written by whoever edits it, published
|
|
27
|
-
under a Creative Commons licence. They write the same dishes differently, and
|
|
28
|
-
that difference is worth having in one place.
|
|
29
|
-
|
|
30
|
-
This server:
|
|
31
|
-
|
|
32
|
-
- **searches every source at once** and returns one list, each row naming the
|
|
33
|
-
source it came from;
|
|
34
|
-
- **reads one recipe** in full and rescales it to any number of people;
|
|
35
|
-
- **scales an ingredient list** you already hold, offline, in either language or
|
|
36
|
-
a list holding both;
|
|
37
|
-
- **puts one dish side by side**, as each source writes it, all rescaled to the
|
|
38
|
-
same number of servings so the lists stand comparison.
|
|
39
|
-
|
|
40
|
-
### What makes the answers usable
|
|
41
|
-
|
|
42
|
-
Multiplying every number by a factor is arithmetically correct and practically
|
|
43
|
-
useless: it produces "2.4 eggs" and "0.67 pinch of salt" with the same
|
|
44
|
-
confidence as "267 g flour". Every quantity here lands where a cook can act on
|
|
45
|
-
it:
|
|
46
|
-
|
|
47
|
-
| The line says | Times six | Why |
|
|
48
|
-
| ----------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------ |
|
|
49
|
-
| `une pincée de sel` | `6 pincées de sel` | A pinch keeps the size a hand gives it; the count carries the proportion |
|
|
50
|
-
| `1 dose (cup) de Mountain Dew` | `6 doses (cup) de Mountain Dew` | A French line naming an English measure stays French |
|
|
51
|
-
| `2/3 d'un flacon de fleur d'oranger` | `4 flacons de fleur d'oranger` | A share of one thing becomes a count of them |
|
|
52
|
-
| `1/4 litre de lait` | `1,5 l de lait` | French writes its decimals with a comma |
|
|
53
|
-
| `250 ml milk` | `1.5 l milk` | English writes them with a dot, and both climb the metric ladder |
|
|
54
|
-
| `2/3 of a bottle of orange blossom water` | `4 bottles of orange blossom water` | The bottles are what is counted, and the water comes with them |
|
|
55
|
-
| `a capful of rum` | `6 capfuls rum` | A measure named after what holds it is read as one, listed or not |
|
|
56
|
-
|
|
57
|
-
### What divides, and what does not
|
|
58
|
-
|
|
59
|
-
A measure divides as far as half of what it holds is still a quantity a kitchen
|
|
60
|
-
can take out. Almost everything is: what a tin, a sachet or a carton
|
|
61
|
-
holds is poured, weighed or spooned, so half a tin of tomatoes is half a tin of
|
|
62
|
-
tomatoes and the rest keeps in the fridge. A leaf of gelatine and a sprig of
|
|
63
|
-
thyme are cut with a knife. A handful is halved by taking less.
|
|
64
|
-
|
|
65
|
-
An egg is the exception, along with a yolk and an egg white on their own: half
|
|
66
|
-
of one means beating it and weighing the result, which no recipe asks for. A
|
|
67
|
-
clou de girofle, a zeste and a zest go with it, having no half anyone takes.
|
|
68
|
-
|
|
69
|
-
A few measures go the other way and divide further. A pot, a bouteille, a
|
|
70
|
-
bottle, a jar and a block hold enough for a quarter to be a portion someone
|
|
71
|
-
serves. Une tranche and a slice are already cut off something larger, and the
|
|
72
|
-
board that produced one takes a corner off it in the same gesture. And a word can state a number of things rather than a measure of them,
|
|
73
|
-
so `2 dozen mushrooms` at three quarters comes back as **18 mushrooms**. A blanc
|
|
74
|
-
belongs to whichever food the line named: the white of an egg is counted whole,
|
|
75
|
-
a blanc de poulet is meat and halves.
|
|
76
|
-
|
|
77
|
-
A thing counted on its own is divided by the size of one against what a recipe
|
|
78
|
-
puts in, in either language. A crevette and a shrimp, une moule and a mussel,
|
|
79
|
-
une noisette and a hazelnut, un grain de poivre and a peppercorn, un anis étoilé
|
|
80
|
-
and a star anise is already a portion: a recipe counts twelve of them and a
|
|
81
|
-
smaller recipe puts one fewer in the pan, so they land on a whole number. Un
|
|
82
|
-
gigot and a leg of lamb, une baguette, un camembert, un ananas and a pineapple,
|
|
83
|
-
une pastèque and a watermelon, une pintade and a guinea fowl, un poulet and a
|
|
84
|
-
chicken, un poireau and a leek
|
|
85
|
-
sit at the other end of that comparison, asked for by the one or the two and shared out with a knife, so they go as far as the
|
|
86
|
-
quarter. A cut carved off one of them stops at the half, une cuisse and a thigh
|
|
87
|
-
being the portion the knife already produced. Un jus, and a juice, stops at the
|
|
88
|
-
half too: half the jus of a citron is
|
|
89
|
-
taken by squeezing half the fruit, and a quarter of one has to be poured out and
|
|
90
|
-
measured back.
|
|
91
|
-
|
|
92
|
-
The word `clove` names two foods, and the line says which. Garlic in the line
|
|
93
|
-
makes it the wedge broken off a bulb, the gousse d'ail, which a knife splits in
|
|
94
|
-
two; on its own, or written `whole cloves`, it is the dried flower bud, the
|
|
95
|
-
clou de girofle, and a count of those lands on a whole number.
|
|
96
|
-
|
|
97
|
-
- `3 eggs` taken from six people to twenty-five gives **13 eggs**, and never a
|
|
98
|
-
half of one.
|
|
99
|
-
- `1 can tomatoes` at four tenths gives **half a tin**, with a note saying the
|
|
100
|
-
line no longer holds its share of the recipe.
|
|
101
|
-
- `2 tablespoons butter OR 30 g margarine` doubled scales **both** branches, and
|
|
102
|
-
the line is never reported as exact arithmetic: how far one stands for the
|
|
103
|
-
other is the page's own claim.
|
|
104
|
-
- `4 tablespoons` at a tenth comes down to **1.2 teaspoons**. A measurement moves
|
|
105
|
-
to a smaller unit _before_ it is rounded, so a small share survives.
|
|
106
|
-
- `1 lb 4 oz beef` doubled gives **2.5 lb beef**, and `1 kg 500 de farine` gives
|
|
107
|
-
**3 kg de farine**. Two measures off one ladder written side by side are one
|
|
108
|
-
quantity, and the smaller one can be left unwritten.
|
|
109
|
-
- `12 oz can tomatoes` comes back as published, with a note saying the measure
|
|
110
|
-
gives what one tin holds and that the line states no count of them.
|
|
111
|
-
- `2 h de repos` comes back as published. A rest, a proof or a bake takes as long
|
|
112
|
-
for a large batch as for a small one, so a length of time is not a proportion.
|
|
113
|
-
- `quelques feuilles de basilic` comes back as published, as do `a few basil
|
|
114
|
-
leaves` and the same line with `plusieurs`. A word saying there are several
|
|
115
|
-
states no count, and any figure put behind it would be this server's reading
|
|
116
|
-
rather than the page's quantity.
|
|
117
|
-
|
|
118
|
-
Every line comes back with a `scaling` field: `scaled` when the arithmetic
|
|
119
|
-
landed on the exact product, `rounded` when something had to move for the line
|
|
120
|
-
to stay usable, `unscaled` when the line carries nothing to multiply. A rounded
|
|
121
|
-
line says what it was rounded from and in which direction.
|
|
34
|
+
## Install
|
|
122
35
|
|
|
123
|
-
|
|
36
|
+
**One-click install**
|
|
124
37
|
|
|
125
|
-
|
|
38
|
+
[](https://cursor.com/en/install-mcp?name=recipes&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1yZWNpcGVzIl19)
|
|
39
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=recipes&config=%7B%22name%22%3A%22recipes%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-recipes%22%5D%7D)
|
|
126
40
|
|
|
127
|
-
|
|
41
|
+
**Claude Code**
|
|
128
42
|
|
|
129
43
|
```bash
|
|
130
|
-
npx -y mcp-recipes
|
|
44
|
+
claude mcp add recipes -- npx -y mcp-recipes
|
|
131
45
|
```
|
|
132
46
|
|
|
133
|
-
|
|
47
|
+
**Claude Desktop, Cursor, and any client using the standard config format**
|
|
134
48
|
|
|
135
49
|
```json
|
|
136
50
|
{
|
|
@@ -143,6 +57,8 @@ npx -y mcp-recipes
|
|
|
143
57
|
}
|
|
144
58
|
```
|
|
145
59
|
|
|
60
|
+
Node 24 or later is required, and no environment variable has to be set.
|
|
61
|
+
|
|
146
62
|
### With Docker
|
|
147
63
|
|
|
148
64
|
```json
|
|
@@ -150,455 +66,316 @@ npx -y mcp-recipes
|
|
|
150
66
|
"mcpServers": {
|
|
151
67
|
"recipes": {
|
|
152
68
|
"command": "docker",
|
|
153
|
-
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-recipes:
|
|
69
|
+
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-recipes:3.0.0"]
|
|
154
70
|
}
|
|
155
71
|
}
|
|
156
72
|
}
|
|
157
73
|
```
|
|
158
74
|
|
|
159
|
-
`-i` keeps stdin open, which is where the protocol travels, and
|
|
160
|
-
|
|
161
|
-
|
|
75
|
+
`-i` keeps stdin open, which is where the protocol travels, and `-t` is left out
|
|
76
|
+
because a TTY rewrites the stream. The container needs outbound HTTPS to
|
|
77
|
+
`www.marmiton.org`, `api.wikimedia.org`, `www.ptitchef.com`,
|
|
78
|
+
`www.bbcgoodfood.com` and `www.supertoinette.com`, and nothing else:
|
|
79
|
+
no volume, no port, no credential.
|
|
162
80
|
|
|
163
|
-
###
|
|
81
|
+
### Bundle, without npm
|
|
164
82
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
83
|
+
Download `mcp-recipes-3.0.0.mcpb` from
|
|
84
|
+
[the latest release](https://github.com/smeet666/mcp-recipes/releases/latest) and
|
|
85
|
+
open it. A client that supports MCP bundles installs it on its own, with no npm
|
|
86
|
+
and no configuration file to edit. The bundle carries its dependencies, so
|
|
87
|
+
nothing is fetched at install time.
|
|
170
88
|
|
|
171
|
-
|
|
89
|
+
## What you can ask
|
|
172
90
|
|
|
173
|
-
|
|
91
|
+
- "Find me carbonara recipes, from wherever you can."
|
|
92
|
+
- "Compare the French and English versions of that dish."
|
|
93
|
+
- "Read the second one for eight people."
|
|
94
|
+
- "Which of them uses cream?"
|
|
95
|
+
- "Scale this list from my notebook by 1.5."
|
|
174
96
|
|
|
175
|
-
|
|
97
|
+
The ordinary path runs from a search to a reading: a row carries an `id` naming
|
|
98
|
+
its source, and `get_recipe` takes it.
|
|
176
99
|
|
|
177
|
-
|
|
178
|
-
one costs.
|
|
179
|
-
|
|
180
|
-
| Argument | Type | Meaning |
|
|
181
|
-
| ------------------ | ----------------------------- | -------------------------------------------------------------- |
|
|
182
|
-
| `query` | string | A dish or an ingredient, in any language a source publishes in |
|
|
183
|
-
| `limit_per_source` | integer, 1 to 25, default 5 | Rows to take from each source |
|
|
184
|
-
| `sources` | array of source ids, optional | Left out, every source is asked |
|
|
185
|
-
| `fan_out` | boolean, default true | Whether shorter wordings derived from the question go out too |
|
|
186
|
-
|
|
187
|
-
The query goes to each source's own search as free text. There is no filtering:
|
|
188
|
-
a word naming a diet, a time or a calorie count matches only where that source's
|
|
189
|
-
index happens to carry it, and a question long enough to read like a request for
|
|
190
|
-
conditions gets a note saying so.
|
|
191
|
-
|
|
192
|
-
**A question asked in a sentence is also sent in shorter wordings.** These
|
|
193
|
-
indexes answer the words they are handed. One of them requires every word given
|
|
194
|
-
to appear on the same page, so "comment faire un ceviche de truite à l'origan"
|
|
195
|
-
comes back empty from a corpus holding four ceviche recipes; the other ranks on
|
|
196
|
-
the words it was handed, so "comment faire" pulls up whatever shares those words
|
|
197
|
-
and the dish falls out of the list. Both reach a reader looking like a statement
|
|
198
|
-
about a corpus when they are statements about a wording.
|
|
199
|
-
|
|
200
|
-
So a question is turned into an ordered ladder of at most three wordings: the
|
|
201
|
-
words as asked, the words naming the dish with the framing words set aside, and
|
|
202
|
-
the leading dish word on its own. They go out in that order, one at a time, and
|
|
203
|
-
stop as soon as enough rows have come back, so a query that already names a dish
|
|
204
|
-
costs one request. What comes back is unioned and deduplicated on the id naming
|
|
205
|
-
its source. `per_source` lists every wording, what each returned, and the ones
|
|
206
|
-
held back and why. Nothing is translated between the sources' languages: a
|
|
207
|
-
guessed translation is a word nobody wrote, and a source that publishes in
|
|
208
|
-
another language and holds nothing under the words written is an honest absence.
|
|
209
|
-
|
|
210
|
-
**A condition is not a keyword.** "sans beurre", "gluten free" and "pour 6
|
|
211
|
-
personnes" say what a recipe must avoid or serve. No page is named for what it
|
|
212
|
-
leaves out, and the number of people at a table is not printed in a title, so
|
|
213
|
-
searching for those words buries the dish. They stay in the wording sent first,
|
|
214
|
-
where a source whose index carries them can still match, and they are set aside
|
|
215
|
-
from the derived wordings. Dropping them quietly would be the worse move, since
|
|
216
|
-
a recipe found this way can hold exactly what the person was avoiding, so every
|
|
217
|
-
condition set aside is named back in the notes with a reminder to check the
|
|
218
|
-
ingredient list. A number of servings is pointed at the `servings` argument,
|
|
219
|
-
which is what actually rescales a recipe.
|
|
220
|
-
|
|
221
|
-
A condition is read by the role its words play rather than by the turn of phrase
|
|
222
|
-
they are written in, because a list of phrasings has a hole at the first one
|
|
223
|
-
nobody thought of. Three roles put a food out of a dish: a negation, whatever
|
|
224
|
-
word carries it, since negations are a closed class of function words and
|
|
225
|
-
"without butter", "no butter" and "pas de beurre" are all built on one; a
|
|
226
|
-
sentence about the eater rather than the dish, as in "je suis allergique aux
|
|
227
|
-
noix", which is named back as an allergy because that is the condition whose
|
|
228
|
-
failure is a matter of health; and a diet named in one word, which is a class of
|
|
229
|
-
recipes defined by what it leaves out and so states an exclusion with no
|
|
230
|
-
negation written anywhere. A question can still phrase one in a way this reading
|
|
231
|
-
does not catch, and the answer says so rather than presenting the ones it caught
|
|
232
|
-
as all there were.
|
|
233
|
-
|
|
234
|
-
Which words a condition covers is read off the sentence. A joining word straight
|
|
235
|
-
after the marker says its food follows, as in "allergique aux noix", "allergic
|
|
236
|
-
to peanuts" or "free of dairy"; with no such word, the food is the one the
|
|
237
|
-
marker was written onto the end of, as in "peanut allergy", "lactose intolerant"
|
|
238
|
-
or "gluten free". Both orders are needed, since the two languages these sources
|
|
239
|
-
publish in put the food on opposite sides. A food's name ends where nothing
|
|
240
|
-
carries it further, so "sans gluten gateau au chocolat" leaves out gluten and
|
|
241
|
-
looks for the cake. Where a marker has a food on neither side, the answer says a
|
|
242
|
-
condition was stated and that its food was not read: naming the wrong word hides
|
|
243
|
-
a dish and leaves the food being avoided in the search at the same time, and an
|
|
244
|
-
allergen is not something to guess at.
|
|
245
|
-
|
|
246
|
-
Setting `fan_out` to false sends exactly the words typed. The wordings that were
|
|
247
|
-
not sent are still named, so a caller can see what turning it back on would buy.
|
|
248
|
-
|
|
249
|
-
Rows are interleaved one source at a time. No score orders them against each
|
|
250
|
-
other, because the sources share none: one carries reader ratings and another
|
|
251
|
-
has no author and no rating by nature. The answer says how the order was built.
|
|
252
|
-
|
|
253
|
-
`per_source` reports what each source answered in that source's own terms.
|
|
254
|
-
Marmiton counts the rows on the single page it serves. The Cookbook states no
|
|
255
|
-
total at all, so a short list from it is not evidence that little exists. The
|
|
256
|
-
counts measure different things and are never added together.
|
|
100
|
+
## The sources
|
|
257
101
|
|
|
258
|
-
|
|
102
|
+
| Source | Site | Language |
|
|
103
|
+
| --------------- | ----------------------- | -------- |
|
|
104
|
+
| `marmiton` | `www.marmiton.org` | French |
|
|
105
|
+
| `cookbook` | Wikibooks Cookbook | English |
|
|
106
|
+
| `ptitchef` | `www.ptitchef.com` | French |
|
|
107
|
+
| `goodfood` | `www.bbcgoodfood.com` | English |
|
|
108
|
+
| `supertoinette` | `www.supertoinette.com` | French |
|
|
259
109
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
| `servings` | integer, optional | Rescale to this many |
|
|
264
|
-
| `sections` | array, default `["ingredients", "steps"]` | `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment` |
|
|
265
|
-
| `max_steps` | integer, default 20 | Steps to return; the answer says how many more there are |
|
|
266
|
-
| `max_step_chars` | integer, default 600 | Characters kept per step |
|
|
267
|
-
|
|
268
|
-
The identifier names its source, so the right one is read without guessing. A
|
|
269
|
-
raw identifier is routed by its shape, and the answer says which reading it
|
|
270
|
-
used: a bare number is Marmiton's, a page key carrying the Cookbook namespace is
|
|
271
|
-
the Cookbook's, and an address is routed by its host. A string no source would
|
|
272
|
-
have minted is refused, because sending it anywhere returns whatever that guess
|
|
273
|
-
happened to hit.
|
|
274
|
-
|
|
275
|
-
`sections_omitted` names what was left out. A field belonging to one of those
|
|
276
|
-
sections is empty because nobody asked for it, and a field a source genuinely
|
|
277
|
-
does not publish is `null`.
|
|
278
|
-
|
|
279
|
-
A page stating no number of servings comes back as published and says why:
|
|
280
|
-
dividing by a yield nobody wrote would answer for a number of people the page
|
|
281
|
-
never claimed. A page stating a span, such as "4 à 6 personnes", is scaled from
|
|
282
|
-
the lower end, and the answer gives the factor the upper end would have needed.
|
|
283
|
-
|
|
284
|
-
Some sources keep recipes and reference pages under one namespace, so a search
|
|
285
|
-
row can be a page _about_ a dish rather than a recipe _for_ it. Only reading the
|
|
286
|
-
page tells them apart.
|
|
287
|
-
|
|
288
|
-
A part of a recipe this answer holds nothing for is never reported as an absence
|
|
289
|
-
on the page, because reading nothing is a fact about this server and only the
|
|
290
|
-
page can say whether there was anything to read. Where the page shows a sign of
|
|
291
|
-
the part all the same, a heading announcing it or another part of the recipe
|
|
292
|
-
that was read, the answer states that this server failed to read what the page
|
|
293
|
-
carries. Where the page shows neither, it states that the two cannot be told
|
|
294
|
-
apart from here. Either way an empty ingredient list is never evidence that an
|
|
295
|
-
ingredient is absent from the dish: someone avoiding butter has to open the page
|
|
296
|
-
before ruling it out.
|
|
110
|
+
A row's `id` names its source, so an identifier read from one answer goes back to
|
|
111
|
+
the right site. **Counts are never added across sources**, and a source that
|
|
112
|
+
failed is reported as having failed rather than as having found nothing.
|
|
297
113
|
|
|
298
|
-
|
|
114
|
+
## Tools
|
|
115
|
+
|
|
116
|
+
| Tool | What it does |
|
|
117
|
+
| ------------------- | ---------------------------------------------------------- |
|
|
118
|
+
| `search_recipes` | Searches every source with one question. |
|
|
119
|
+
| `get_recipe` | Reads one recipe from any source, in one shape. |
|
|
120
|
+
| `compare_recipes` | Puts several versions of the same dish side by side. |
|
|
121
|
+
| `scale_ingredients` | Rescales any ingredient list, with no request to any site. |
|
|
122
|
+
|
|
123
|
+
### `search_recipes`
|
|
124
|
+
|
|
125
|
+
Searches every source with one question.
|
|
299
126
|
|
|
300
|
-
|
|
301
|
-
|
|
127
|
+
| Argument | Type | Required | What it does |
|
|
128
|
+
| ------------------ | ----------------------------- | -------- | ---------------------------------------------------------------- |
|
|
129
|
+
| `query` | string, 1 to 200 characters | yes | The dish or the ingredient to look for. |
|
|
130
|
+
| `limit_per_source` | integer, 1 to 25, default `5` | no | Rows to keep from each source. |
|
|
131
|
+
| `sources` | array of source ids | no | Ask these sources alone. |
|
|
132
|
+
| `fan_out` | boolean, default `true` | no | Ask every source rather than stopping at the first that answers. |
|
|
302
133
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
134
|
+
**In return:** `results`, rows carrying `id`, which `get_recipe` takes;
|
|
135
|
+
`source` and `source_name` saying which site published the row; `title`; `url`;
|
|
136
|
+
`image_url`; and an `excerpt` where the source offers one. `per_source` gives one
|
|
137
|
+
report per site with its `status`, reading `answered` or `failed`, the `count` it
|
|
138
|
+
contributed, and its `reported_total` alongside `reported_total_means`, which
|
|
139
|
+
says what that number counts on that site. `order` says in words how the list was
|
|
140
|
+
built.
|
|
309
141
|
|
|
310
|
-
|
|
311
|
-
different things by them has made a mistake worth hearing about.
|
|
142
|
+
### `get_recipe`
|
|
312
143
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
144
|
+
Reads one recipe from any source, in one shape.
|
|
145
|
+
|
|
146
|
+
| Argument | Type | Required | What it does |
|
|
147
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| `id` | string, 1 to 500 characters | yes | The identifier a row carries, such as `marmiton:44078`. Two sources address a recipe by a bare number, so spell an id with its source. |
|
|
149
|
+
| `servings` | integer, 1 to 500 | no | Rescale the ingredients to this many servings. |
|
|
150
|
+
| `sections` | array of `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, default `["ingredients", "steps"]` | no | Which parts to return. |
|
|
151
|
+
| `max_steps` | integer, 1 to 100, default `20` | no | Steps to serve. |
|
|
152
|
+
| `max_step_chars` | integer, 80 to 4000, default `600` | no | Characters kept per step. |
|
|
153
|
+
|
|
154
|
+
**In return:** the recipe in the shape every source is rendered into, whichever
|
|
155
|
+
published it: its title, its address, its ingredients with each line's `scaling`,
|
|
156
|
+
its steps, and the sections asked for. A field one source publishes and another
|
|
157
|
+
has no notion of comes back absent rather than invented. `rest_minutes` carries a
|
|
158
|
+
resting time from a source that prints one apart, and is in no other time here.
|
|
159
|
+
`steps_as_one_block` says when a source published its method as one block of
|
|
160
|
+
prose rather than as steps. `withheld` names a part a source keeps for its
|
|
161
|
+
subscribers, which is a part the page has rather than a part that could not be
|
|
162
|
+
read. Raise `max_step_chars` when a step was cut mid-sentence.
|
|
316
163
|
|
|
317
164
|
### `compare_recipes`
|
|
318
165
|
|
|
319
|
-
|
|
320
|
-
| ---------------- | -------------------------------- | ----------------------------------------------- |
|
|
321
|
-
| `dish` | string | The dish, in any language a source publishes in |
|
|
322
|
-
| `servings` | integer, optional | Rescale every version to this many |
|
|
323
|
-
| `sections` | array, default `["ingredients"]` | As above |
|
|
324
|
-
| `max_steps` | integer, default 10 | Steps to return per version |
|
|
325
|
-
| `max_step_chars` | integer, default 600 | Characters kept per step |
|
|
166
|
+
Puts several versions of the same dish side by side.
|
|
326
167
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
168
|
+
| Argument | Type | Required | What it does |
|
|
169
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- |
|
|
170
|
+
| `dish` | string, 1 to 200 characters | yes | The dish to compare. |
|
|
171
|
+
| `servings` | integer, 1 to 500 | no | Rescale every version to this many servings. |
|
|
172
|
+
| `sections` | array of `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, default `["ingredients"]` | no | Which parts to return per version. |
|
|
173
|
+
| `max_steps` | integer, 1 to 100, default `10` | no | Steps to serve per version. |
|
|
174
|
+
| `max_step_chars` | integer, 80 to 4000, default `600` | no | Characters kept per step. |
|
|
175
|
+
| `sources` | array of source ids | no | Compare these sources alone. |
|
|
331
176
|
|
|
332
|
-
|
|
333
|
-
|
|
177
|
+
**In return:** `versions`, one recipe per source that answered, all rescaled to
|
|
178
|
+
the same number of servings so their quantities can be read against each other,
|
|
179
|
+
and `differences`, what separates them. `per_source` reports each site as a
|
|
180
|
+
search does.
|
|
334
181
|
|
|
335
|
-
|
|
336
|
-
search failed, it offered a row that could not be read, or it offered nothing
|
|
337
|
-
close enough to compare.
|
|
182
|
+
### `scale_ingredients`
|
|
338
183
|
|
|
339
|
-
|
|
184
|
+
Rescales any ingredient list, with no request to either site.
|
|
340
185
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
about the world, and a caller that cannot tell them apart will make the second
|
|
349
|
-
one.
|
|
350
|
-
- **No total is invented.** Each source's own number is reported with what it
|
|
351
|
-
counts, in that source's terms, and the counts are never summed.
|
|
352
|
-
- **No ranking across sources.** They share no scale, so rows interleave and the
|
|
353
|
-
answer says how the order was built.
|
|
354
|
-
- **No conversion between measuring systems.** A line keeps the units its source
|
|
355
|
-
published, and the source is named beside it.
|
|
356
|
-
- **A yield is repeated in the source's own words.** "4 à 6 personnes" and
|
|
357
|
-
"4 personnes" are different claims.
|
|
358
|
-
- **A field a source does not publish is `null`**, and a field nobody asked for
|
|
359
|
-
is listed in `sections_omitted`.
|
|
360
|
-
- **Text from a source cannot imitate this server.** Anything a source or a
|
|
361
|
-
caller wrote is put on a single line before it is rendered, with markdown
|
|
362
|
-
image syntax defused and any opening that looks like one of the server's own
|
|
363
|
-
markers indented. The structured payload keeps the text exactly as published.
|
|
186
|
+
| Argument | Type | Required | What it does |
|
|
187
|
+
| --------------- | ------------------------------------ | ---------- | ------------------------------------------ |
|
|
188
|
+
| `ingredients` | array of 1 to 200 lines | yes | The lines to rescale. |
|
|
189
|
+
| `factor` | number, up to 1000 | one of two | The multiplier to apply. |
|
|
190
|
+
| `from_servings` | integer, 1 to 500 | one of two | How many servings the list is written for. |
|
|
191
|
+
| `to_servings` | integer, 1 to 500 | one of two | How many servings are wanted. |
|
|
192
|
+
| `language` | `auto`, `fr` or `en`, default `auto` | no | How each line is read. |
|
|
364
193
|
|
|
365
|
-
|
|
194
|
+
Pass `factor`, or the `from_servings` and `to_servings` pair. `auto` reads each
|
|
195
|
+
line on its own, which is what a list holding both languages needs; naming a
|
|
196
|
+
language reads every line that way.
|
|
366
197
|
|
|
367
|
-
|
|
198
|
+
**In return:** the rescaled lines in the shape `get_recipe` returns, each with
|
|
199
|
+
its `scaling`.
|
|
368
200
|
|
|
369
|
-
|
|
370
|
-
stands, because a server that refuses to start over a typo is very hard to
|
|
371
|
-
diagnose from inside a host application.
|
|
201
|
+
## Rescaling the quantities
|
|
372
202
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
| `RECIPES_MIN_INTERVAL_MS` | `1000` | Minimum gap between two requests to one source. Floor 500 |
|
|
377
|
-
| `RECIPES_TIMEOUT_MS` | `20000` | Deadline for one request |
|
|
378
|
-
| `RECIPES_MAX_RETRIES` | `3` | Retries on rate limiting and transient failures |
|
|
379
|
-
| `RECIPES_CACHE_TTL_MS` | `900000` | In-memory cache lifetime. `0` turns it off |
|
|
380
|
-
| `RECIPES_CACHE_MAX_ENTRIES` | `200` | In-memory cache size |
|
|
381
|
-
| `RECIPES_LOG_LEVEL` | `error` | `silent`, `error`, `info`, `debug`. Logs go to stderr |
|
|
203
|
+
A quantity is stated in the unit that suits it, so a line can come back in a
|
|
204
|
+
different unit from the one the recipe used: 200 g multiplied by twenty reads
|
|
205
|
+
`4 kg`.
|
|
382
206
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
207
|
+
How finely an ingredient can be divided depends on what it is. A baguette can be
|
|
208
|
+
cut in two, in three or in four; an egg cannot be shared out. A quantity landing
|
|
209
|
+
between the two is rounded, and the rescaled recipe then departs a little from
|
|
210
|
+
the proportions of the original. The line carries `rounded`, and its note says
|
|
211
|
+
what was done.
|
|
386
212
|
|
|
387
|
-
|
|
213
|
+
The sources write their quantities in their own languages, and a line is read
|
|
214
|
+
in the language it was written in. The figures are this server's arithmetic, so
|
|
215
|
+
say they were recomputed when you show them.
|
|
388
216
|
|
|
389
|
-
##
|
|
217
|
+
## What an answer states about the sources
|
|
390
218
|
|
|
391
|
-
|
|
219
|
+
Every answer accounts for each source separately. A site that failed, one nobody
|
|
220
|
+
asked, and one that answered with nothing are three different things, and they
|
|
221
|
+
are reported as three. A total stays beside the source that published it, with
|
|
222
|
+
what that source counts when it says it: one site counts a whole category and the
|
|
223
|
+
other counts the rows it served.
|
|
392
224
|
|
|
393
|
-
|
|
394
|
-
import { RecipesClient } from "mcp-recipes/client";
|
|
225
|
+
## Configuration
|
|
395
226
|
|
|
396
|
-
|
|
397
|
-
const merged = await client.searchRecipes("carbonara", 3);
|
|
227
|
+
Every variable is optional. Set them in the `env` block of your client config.
|
|
398
228
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
229
|
+
| Variable | Default | What it does |
|
|
230
|
+
| --------------------------- | -------------------- | ------------------------------------------------------------------------------------ |
|
|
231
|
+
| `RECIPES_USER_AGENT` | the project identity | Names your application to both sites, with an address where a person can be reached. |
|
|
232
|
+
| `RECIPES_MIN_INTERVAL_MS` | `1000` | Gap between two requests to one site, from 500 to 60000. |
|
|
233
|
+
| `RECIPES_TIMEOUT_MS` | `20000` | Deadline for one request, from 1000 to 120000. |
|
|
234
|
+
| `RECIPES_MAX_RETRIES` | `3` | Attempts after a transient failure, from 0 to 8. |
|
|
235
|
+
| `RECIPES_CACHE_TTL_MS` | `900000` | How long an answer stays in memory, from 0 to 86400000. |
|
|
236
|
+
| `RECIPES_CACHE_MAX_ENTRIES` | `200` | Answers held in memory at once, from 1 to 5000. |
|
|
237
|
+
| `RECIPES_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`, written to stderr. |
|
|
403
238
|
|
|
404
|
-
|
|
405
|
-
|
|
239
|
+
A value outside its range falls back to the default, and the reason is written to
|
|
240
|
+
stderr.
|
|
406
241
|
|
|
407
|
-
|
|
408
|
-
// [{ text: "400 g de farine", language: "fr", scaling: "scaled", … },
|
|
409
|
-
// { text: "6 eggs", language: "en", scaling: "scaled", … }]
|
|
410
|
-
```
|
|
242
|
+
## Errors
|
|
411
243
|
|
|
412
|
-
|
|
413
|
-
|
|
244
|
+
Every failure carries one of six codes, a message, and where it helps a hint
|
|
245
|
+
naming the next move.
|
|
246
|
+
|
|
247
|
+
| Code | What happened | What to do |
|
|
248
|
+
| --------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
249
|
+
| `not_found` | A source answered, and holds no such recipe. | Check the identifier with `search_recipes`. |
|
|
250
|
+
| `invalid_input` | The arguments were refused before any request went out. | Read the message, which names the argument. |
|
|
251
|
+
| `rate_limited` | A source asked this client to slow down. | Wait, then call again with the same arguments. The recipe is still there. |
|
|
252
|
+
| `parse_failure` | A page loaded and the expected content was absent. | Report it at [the issue tracker](https://github.com/smeet666/mcp-recipes/issues). |
|
|
253
|
+
| `network_error` | The request did not complete. | Try again shortly. |
|
|
254
|
+
| `timeout` | The request passed its deadline. | Raise `RECIPES_TIMEOUT_MS`, or ask for fewer rows. |
|
|
255
|
+
|
|
256
|
+
A source that failed is reported per source rather than failing the whole answer,
|
|
257
|
+
so one silent site never hides the other.
|
|
258
|
+
|
|
259
|
+
## As a library
|
|
260
|
+
|
|
261
|
+
The layer reading the two sites is published on its own, with its pacing, its
|
|
262
|
+
cache and its errors, and with no protocol attached.
|
|
414
263
|
|
|
415
264
|
```ts
|
|
416
|
-
|
|
265
|
+
import { RecipesClient } from "mcp-recipes/client";
|
|
266
|
+
|
|
267
|
+
const client = new RecipesClient();
|
|
268
|
+
const read = await client.search({ query: "carbonara", limitPerSource: 3 });
|
|
269
|
+
console.log(read.data.results.length);
|
|
417
270
|
```
|
|
418
271
|
|
|
419
|
-
|
|
420
|
-
|
|
272
|
+
Each read answers `{ data, cached }`, and throws an error carrying one of the six
|
|
273
|
+
codes. Each site keeps its own pace, and the floor holds here as well.
|
|
421
274
|
|
|
422
|
-
|
|
275
|
+
## Pacing and attribution
|
|
423
276
|
|
|
424
|
-
|
|
277
|
+
Each site is paced on its own, one request at a time with at least a second
|
|
278
|
+
between two, and the floor of half a second holds however the server is
|
|
279
|
+
configured. Asking every site at once therefore costs each of them one request,
|
|
280
|
+
never two. The `User-Agent` always ends with the project identity and an address
|
|
281
|
+
where a person can be reached.
|
|
425
282
|
|
|
426
|
-
|
|
283
|
+
Every row carries the address of the recipe's own page and the name of the site
|
|
284
|
+
that published it. The Cookbook pages are published under
|
|
285
|
+
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), which asks that
|
|
286
|
+
what is built on them be shared under the same licence. Marmiton, Ptitchef, BBC
|
|
287
|
+
Good Food and Supertoinette state no terms on a recipe page, and their recipes
|
|
288
|
+
belong to those sites and to the cooks who wrote them. Silence is not a grant, so
|
|
289
|
+
credit the site and link the page you took a recipe from.
|
|
427
290
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
| `invalid_input` | The arguments could not produce a request |
|
|
432
|
-
| `rate_limited` | A source asked this client to slow down |
|
|
433
|
-
| `parse_failure` | An answer arrived in a shape this client cannot read |
|
|
434
|
-
| `network_error` | The request did not complete |
|
|
435
|
-
| `timeout` | The request exceeded its deadline |
|
|
291
|
+
A recipe BBC Good Food keeps for its subscribers comes back without its
|
|
292
|
+
ingredients and its method, named as a recipe held back, with the address of its
|
|
293
|
+
page. This server does not reconstruct what that site chose to sell.
|
|
436
294
|
|
|
437
|
-
|
|
295
|
+
Two figures the sites publish are not repeated here. A difficulty is a word each
|
|
296
|
+
site writes its own way, on no scale any of them publishes, so it sits on no axis
|
|
297
|
+
along which two versions could be put. A cost is a price in euros on one site and
|
|
298
|
+
a rank inside its own list on another, and one field holding both would invite
|
|
299
|
+
them to be compared.
|
|
438
300
|
|
|
439
|
-
|
|
440
|
-
|
|
301
|
+
This MCP server is an unofficial project, with no affiliation to any of the
|
|
302
|
+
sites it reads.
|
|
441
303
|
|
|
442
|
-
|
|
304
|
+
## Privacy
|
|
443
305
|
|
|
444
|
-
|
|
306
|
+
This server collects nothing about you and sends nothing to its author. It runs
|
|
307
|
+
on your machine, contacts `www.marmiton.org`, `api.wikimedia.org`,
|
|
308
|
+
`www.ptitchef.com`, `www.bbcgoodfood.com` and `www.supertoinette.com` and nothing
|
|
309
|
+
else, holds its answers in memory while it runs,
|
|
310
|
+
and writes nothing to disk. [PRIVACY.md](PRIVACY.md) states what a request
|
|
311
|
+
carries and which settings change any of it.
|
|
445
312
|
|
|
446
313
|
## Development
|
|
447
314
|
|
|
448
315
|
```bash
|
|
449
316
|
npm install
|
|
450
|
-
npm run
|
|
451
|
-
npm test
|
|
452
|
-
npm run
|
|
317
|
+
npm run build:fixtures
|
|
318
|
+
npm test
|
|
319
|
+
npm run check
|
|
453
320
|
```
|
|
454
321
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
322
|
+
Tests run against generated fixtures and make no network request. The live suite,
|
|
323
|
+
`npm run test:live`, makes one request per route and runs nightly against the
|
|
324
|
+
sites themselves.
|
|
458
325
|
|
|
459
|
-
|
|
460
|
-
RECIPES_LIVE=1 npm run test:live
|
|
461
|
-
```
|
|
326
|
+
## Contributing
|
|
462
327
|
|
|
463
|
-
|
|
464
|
-
|
|
328
|
+
Bugs, questions and ideas belong in
|
|
329
|
+
[the issue tracker](https://github.com/smeet666/mcp-recipes/issues). Pull
|
|
330
|
+
requests are welcome; opening an issue first helps agree on the shape of the
|
|
331
|
+
change. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
465
332
|
|
|
466
|
-
|
|
467
|
-
of its own identifiers, how to search it, and how to read one recipe off it.
|
|
468
|
-
Register it in `src/sources/registry.ts`. No tool, no merge and no error path
|
|
469
|
-
has a branch per source, so nothing else changes.
|
|
333
|
+
## License
|
|
470
334
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
`mcp-wikibooks-cookbook` for the Cookbook. Each keeps its own pacing, its own
|
|
474
|
-
cache and its own error taxonomy. Everything above the seam, including the whole
|
|
475
|
-
of the bilingual scaling, lives here.
|
|
335
|
+
MIT, see [LICENSE](LICENSE). The recipes belong to the sites that published them
|
|
336
|
+
and to their authors.
|
|
476
337
|
|
|
477
338
|
---
|
|
478
339
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
This server is MIT. What it returns is not.
|
|
482
|
-
|
|
483
|
-
Cookbook pages are published under **Creative Commons Attribution-Share Alike
|
|
484
|
-
4.0**, which requires attribution: every recipe carries its licence and its
|
|
485
|
-
link, and both have to travel with anything you repeat.
|
|
486
|
-
|
|
487
|
-
Marmiton states no terms of use on a recipe page. Silence is not permission.
|
|
488
|
-
Credit the source and link the page.
|
|
489
|
-
|
|
490
|
-
Every result carries a `url`. Use it.
|
|
491
|
-
|
|
492
|
-
---
|
|
493
|
-
|
|
494
|
-
---
|
|
340
|
+
<a name="mcp-recipes-français"></a>
|
|
495
341
|
|
|
496
342
|
# mcp-recipes (français)
|
|
497
343
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
344
|
+
_[English version](#mcp-recipes)_
|
|
345
|
+
|
|
346
|
+
Les recettes vivent sur beaucoup de sites, et chacun les écrit à sa façon : un
|
|
347
|
+
site de cuisine français publie en français, avec ses mesures et son idée de ce
|
|
348
|
+
qu'est une part, et un wiki de cuisine en anglais, avec des listes de matériel et
|
|
349
|
+
une prose pour lesquelles le premier n'a aucun champ. Poser une question à l'un
|
|
350
|
+
d'eux répond au sujet de l'un d'eux.
|
|
351
|
+
|
|
352
|
+
Ce serveur en lit cinq à la fois. Trois publient en français :
|
|
353
|
+
[Marmiton](https://www.marmiton.org), où des cuisiniers publient,
|
|
354
|
+
[Ptitchef](https://www.ptitchef.com), qui range ses recettes sous un arbre
|
|
355
|
+
d'ingrédients, et [Supertoinette](https://www.supertoinette.com), qui imprime un
|
|
356
|
+
temps de repos à part. Deux publient en anglais : le
|
|
357
|
+
[Cookbook des Wikibooks](https://en.wikibooks.org/wiki/Cookbook:Table_of_Contents),
|
|
358
|
+
écrit et entretenu à découvert, et
|
|
359
|
+
[BBC Good Food](https://www.bbcgoodfood.com), qui groupe une liste d'ingrédients
|
|
360
|
+
sous des intertitres. On peut chercher dans les cinq avec une seule question,
|
|
361
|
+
lire une recette de n'importe lequel sous une seule forme, mettre plusieurs
|
|
362
|
+
versions d'un même plat côte à côte, et adapter n'importe quelle liste
|
|
363
|
+
d'ingrédients. Aucune clé d'API, aucun compte.
|
|
506
364
|
|
|
507
|
-
|
|
365
|
+
## Installation
|
|
508
366
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
Marmiton est un site de recettes français, avec des notes de lecteurs et
|
|
512
|
-
plusieurs millions de visiteurs. Le Wikibooks Cookbook est un wiki anglophone
|
|
513
|
-
écrit par qui veut l'éditer, publié sous licence Creative Commons. Les deux
|
|
514
|
-
écrivent les mêmes plats différemment, et cette différence vaut d'être réunie au
|
|
515
|
-
même endroit.
|
|
516
|
-
|
|
517
|
-
Ce serveur :
|
|
518
|
-
|
|
519
|
-
- **cherche dans toutes les sources à la fois** et renvoie une seule liste,
|
|
520
|
-
chaque ligne nommant la source d'où elle vient ;
|
|
521
|
-
- **lit une recette** en entier et la recalcule pour le nombre de personnes
|
|
522
|
-
voulu ;
|
|
523
|
-
- **met à l'échelle une liste d'ingrédients** que vous avez déjà, hors ligne,
|
|
524
|
-
dans l'une ou l'autre langue, ou dans une liste qui mélange les deux ;
|
|
525
|
-
- **place un même plat côte à côte**, tel que chaque source l'écrit, toutes
|
|
526
|
-
ramenées au même nombre de parts pour que les listes se comparent.
|
|
527
|
-
|
|
528
|
-
### Ce qui rend les réponses utilisables
|
|
529
|
-
|
|
530
|
-
Multiplier chaque nombre par un facteur est juste arithmétiquement et inutile en
|
|
531
|
-
cuisine : cela donne « 2,4 oeufs » et « 0,67 pincée de sel » avec le même aplomb
|
|
532
|
-
que « 267 g de farine ». Ici, chaque quantité tombe là où un cuisinier peut la
|
|
533
|
-
suivre :
|
|
534
|
-
|
|
535
|
-
| La ligne dit | Fois six | Pourquoi |
|
|
536
|
-
| ------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
537
|
-
| `une pincée de sel` | `6 pincées de sel` | Une pincée garde la taille que la main lui donne ; c'est le compte qui porte la proportion |
|
|
538
|
-
| `1 dose (cup) de Mountain Dew` | `6 doses (cup) de Mountain Dew` | Une ligne française qui cite une mesure anglaise reste française |
|
|
539
|
-
| `2/3 d'un flacon de fleur d'oranger` | `4 flacons de fleur d'oranger` | Une part d'une chose devient un compte de ces choses |
|
|
540
|
-
| `1/4 litre de lait` | `1,5 l de lait` | Le français écrit ses décimales avec une virgule |
|
|
541
|
-
| `250 ml milk` | `1.5 l milk` | L'anglais avec un point, et les deux montent l'échelle métrique |
|
|
542
|
-
| `a capful of rum` | `6 capfuls rum` | Une mesure nommée d'après ce qui la contient est lue comme telle, répertoriée ou non |
|
|
543
|
-
|
|
544
|
-
### Ce qui se divise, et ce qui ne se divise pas
|
|
545
|
-
|
|
546
|
-
Une mesure se divise tant que la moitié de son contenu reste une quantité qu'une
|
|
547
|
-
cuisine sait prélever. C'est presque toujours le cas : ce que contient une
|
|
548
|
-
boîte, un bocal, un sachet ou une brique se verse, se pèse ou se
|
|
549
|
-
cuillère, donc une demi-boîte de tomates est une demi-boîte de tomates et le
|
|
550
|
-
reste se garde au frais. Une feuille de gélatine et une branche de thym se
|
|
551
|
-
coupent au couteau. Une poignée se divise en prenant moins.
|
|
552
|
-
|
|
553
|
-
L'œuf fait exception, avec le jaune et le blanc d'œuf pris à part : en prélever
|
|
554
|
-
la moitié demanderait de le battre et de le peser, ce qu'aucune recette ne
|
|
555
|
-
demande. Le clou de girofle et le zeste le rejoignent, faute de moitié que
|
|
556
|
-
quiconque prélève.
|
|
557
|
-
|
|
558
|
-
Quelques cas vont dans l'autre sens et se divisent plus loin, parce qu'ils
|
|
559
|
-
contiennent assez pour qu'un quart soit une portion : un pot, une bouteille, un
|
|
560
|
-
bottle, un block. Et un mot peut énoncer un nombre de choses plutôt qu'une
|
|
561
|
-
mesure, si bien que « 2 dozen mushrooms » réduits d'un quart reviennent en
|
|
562
|
-
**18 mushrooms**. Un blanc appartient à l'aliment que la ligne nomme : le blanc
|
|
563
|
-
d'œuf se compte entier, le blanc de poulet est une viande et se coupe en deux.
|
|
564
|
-
|
|
565
|
-
- `3 eggs` de six personnes à vingt-cinq donne **13 eggs**, jamais une demie.
|
|
566
|
-
- `1 can tomatoes` à quatre dixièmes donne **une demi-boîte**, avec la mention
|
|
567
|
-
que la ligne ne porte plus sa part de la recette.
|
|
568
|
-
- `2 tablespoons butter OR 30 g margarine` doublé met les **deux** branches à
|
|
569
|
-
l'échelle, et la ligne n'est jamais annoncée comme exacte : à quel point l'une
|
|
570
|
-
vaut l'autre, c'est la page qui l'affirme.
|
|
571
|
-
- `4 tablespoons` au dixième descend à **1.2 teaspoons**. Une mesure passe à une
|
|
572
|
-
unité plus petite _avant_ d'être arrondie, pour qu'une petite part survive.
|
|
573
|
-
- `1 lb 4 oz beef` doublé donne **2.5 lb beef**, et `1 kg 500 de farine` donne
|
|
574
|
-
**3 kg de farine**. Deux mesures d'une même échelle écrites côte à côte sont
|
|
575
|
-
une seule quantité, et la plus petite peut rester sous-entendue.
|
|
576
|
-
- `12 oz can tomatoes` revient telle que publiée, avec la mention que la mesure
|
|
577
|
-
donne ce que tient une boîte et que la ligne n'énonce aucun nombre de boîtes.
|
|
578
|
-
- `2 h de repos` revient telle que publiée. Un repos, une pousse ou une cuisson
|
|
579
|
-
durent autant pour une grande fournée que pour une petite : une durée n'est pas
|
|
580
|
-
une proportion.
|
|
581
|
-
- `quelques feuilles de basilic` revient telle que publiée, comme `a few basil
|
|
582
|
-
leaves` et comme la même ligne avec `plusieurs`. Un mot qui dit qu'il y en a
|
|
583
|
-
plusieurs n'énonce aucun compte, et tout chiffre placé derrière lui serait une
|
|
584
|
-
lecture de ce serveur plutôt qu'une quantité de la page.
|
|
585
|
-
|
|
586
|
-
Chaque ligne revient avec un champ `scaling` : `scaled` quand le calcul est tombé
|
|
587
|
-
juste, `rounded` quand quelque chose a dû bouger pour que la ligne reste
|
|
588
|
-
utilisable, `unscaled` quand la ligne ne porte rien à multiplier. Une ligne
|
|
589
|
-
arrondie dit depuis quelle valeur et dans quel sens.
|
|
367
|
+
**Installation en un clic**
|
|
590
368
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
## Installation
|
|
369
|
+
[](https://cursor.com/en/install-mcp?name=recipes&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1yZWNpcGVzIl19)
|
|
370
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=recipes&config=%7B%22name%22%3A%22recipes%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-recipes%22%5D%7D)
|
|
594
371
|
|
|
595
|
-
|
|
372
|
+
**Claude Code**
|
|
596
373
|
|
|
597
374
|
```bash
|
|
598
|
-
npx -y mcp-recipes
|
|
375
|
+
claude mcp add recipes -- npx -y mcp-recipes
|
|
599
376
|
```
|
|
600
377
|
|
|
601
|
-
|
|
378
|
+
**Claude Desktop, Cursor, et tout client au format de configuration standard**
|
|
602
379
|
|
|
603
380
|
```json
|
|
604
381
|
{
|
|
@@ -611,6 +388,9 @@ npx -y mcp-recipes
|
|
|
611
388
|
}
|
|
612
389
|
```
|
|
613
390
|
|
|
391
|
+
Node 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à
|
|
392
|
+
renseigner.
|
|
393
|
+
|
|
614
394
|
### Avec Docker
|
|
615
395
|
|
|
616
396
|
```json
|
|
@@ -618,271 +398,274 @@ npx -y mcp-recipes
|
|
|
618
398
|
"mcpServers": {
|
|
619
399
|
"recipes": {
|
|
620
400
|
"command": "docker",
|
|
621
|
-
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-recipes:
|
|
401
|
+
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-recipes:3.0.0"]
|
|
622
402
|
}
|
|
623
403
|
}
|
|
624
404
|
}
|
|
625
405
|
```
|
|
626
406
|
|
|
627
|
-
`-i` garde l'entrée standard ouverte, qui est le canal du protocole, et
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
407
|
+
`-i` garde l'entrée standard ouverte, qui est le canal du protocole, et `-t` est
|
|
408
|
+
omis parce qu'un TTY réécrit le flux. Le conteneur a besoin d'un accès HTTPS
|
|
409
|
+
sortant vers `www.marmiton.org`, `api.wikimedia.org`, `www.ptitchef.com`,
|
|
410
|
+
`www.bbcgoodfood.com` et `www.supertoinette.com`, et de
|
|
411
|
+
rien d'autre : aucun volume, aucun port, aucun identifiant.
|
|
631
412
|
|
|
632
|
-
|
|
413
|
+
### Bundle, sans npm
|
|
633
414
|
|
|
634
|
-
|
|
415
|
+
Téléchargez `mcp-recipes-3.0.0.mcpb` depuis
|
|
416
|
+
[la dernière publication](https://github.com/smeet666/mcp-recipes/releases/latest)
|
|
417
|
+
et ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et
|
|
418
|
+
sans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc
|
|
419
|
+
rien n'est téléchargé à l'installation.
|
|
420
|
+
|
|
421
|
+
## Ce qu'on peut demander
|
|
422
|
+
|
|
423
|
+
- « Trouve-moi des recettes de carbonara, d'où que tu puisses. »
|
|
424
|
+
- « Compare les versions française et anglaise de ce plat. »
|
|
425
|
+
- « Lis-moi la seconde pour huit personnes. »
|
|
426
|
+
- « Laquelle utilise de la crème ? »
|
|
427
|
+
- « Multiplie par 1,5 cette liste de mon carnet. »
|
|
428
|
+
|
|
429
|
+
Le chemin ordinaire va d'une recherche à une lecture : une ligne porte un `id`
|
|
430
|
+
qui nomme sa source, et `get_recipe` le reprend.
|
|
431
|
+
|
|
432
|
+
## Les sources
|
|
433
|
+
|
|
434
|
+
| Source | Site | Langue |
|
|
435
|
+
| --------------- | ----------------------- | -------- |
|
|
436
|
+
| `marmiton` | `www.marmiton.org` | français |
|
|
437
|
+
| `cookbook` | Cookbook Wikibooks | anglais |
|
|
438
|
+
| `ptitchef` | `www.ptitchef.com` | français |
|
|
439
|
+
| `goodfood` | `www.bbcgoodfood.com` | anglais |
|
|
440
|
+
| `supertoinette` | `www.supertoinette.com` | français |
|
|
441
|
+
|
|
442
|
+
L'`id` d'une ligne nomme sa source, donc un identifiant lu dans une réponse
|
|
443
|
+
retourne vers le bon site. **Les comptes ne sont jamais additionnés entre
|
|
444
|
+
sources**, et une source qui a échoué est rapportée comme ayant échoué plutôt que
|
|
445
|
+
comme n'ayant rien trouvé.
|
|
446
|
+
|
|
447
|
+
## Les outils
|
|
448
|
+
|
|
449
|
+
| Outil | Ce qu'il fait |
|
|
450
|
+
| ------------------- | ---------------------------------------------------------- |
|
|
451
|
+
| `search_recipes` | Cherche dans toutes les sources avec une seule question. |
|
|
452
|
+
| `get_recipe` | Lit une recette de l'une ou l'autre, sous une seule forme. |
|
|
453
|
+
| `compare_recipes` | Met plusieurs versions d'un même plat côte à côte. |
|
|
454
|
+
| `scale_ingredients` | Adapte n'importe quelle liste d'ingrédients, sans requête. |
|
|
635
455
|
|
|
636
456
|
### `search_recipes`
|
|
637
457
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
|
642
|
-
|
|
|
643
|
-
| `
|
|
644
|
-
| `
|
|
645
|
-
| `
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
Ces index répondent aux mots qu'on leur donne. L'un exige que chaque mot fourni
|
|
655
|
-
figure sur la même page : « comment faire un ceviche de truite à l'origan » en
|
|
656
|
-
revient vide alors que le corpus tient quatre recettes de ceviche. L'autre
|
|
657
|
-
classe sur les mots reçus : « comment faire » remonte tout ce qui les partage et
|
|
658
|
-
le plat sort de la liste. Dans les deux cas, le lecteur reçoit ce qui ressemble
|
|
659
|
-
à un constat sur un corpus alors que c'est un constat sur une formulation.
|
|
660
|
-
|
|
661
|
-
Une question devient donc une échelle ordonnée d'au plus trois formulations :
|
|
662
|
-
les mots tels quels, les mots qui nomment le plat une fois écartés ceux qui
|
|
663
|
-
encadrent la question, puis le premier mot du plat seul. Elles partent dans cet
|
|
664
|
-
ordre, une à la fois, et s'arrêtent dès qu'il y a assez de lignes : une requête
|
|
665
|
-
qui nomme déjà un plat coûte une requête. Ce qui revient est réuni et dédupliqué
|
|
666
|
-
sur l'identifiant qui nomme sa source. `per_source` liste chaque formulation, ce
|
|
667
|
-
qu'elle a rendu, et celles qui ont été retenues avec la raison. Rien n'est
|
|
668
|
-
traduit d'une langue de source vers l'autre : une traduction devinée est un mot
|
|
669
|
-
que personne n'a écrit, et une source qui publie dans une autre langue et ne
|
|
670
|
-
tient rien sous les mots écrits est une absence honnête.
|
|
671
|
-
|
|
672
|
-
**Une contrainte n'est pas un mot-clé.** « sans beurre », « gluten free » et
|
|
673
|
-
« pour 6 personnes » disent ce que la recette doit éviter ou servir. Aucune page
|
|
674
|
-
n'est nommée d'après ce qu'elle exclut, et le nombre de convives ne figure pas
|
|
675
|
-
dans un titre : chercher ces mots enterre le plat. Ils restent dans la
|
|
676
|
-
formulation envoyée en premier, où une source dont l'index les porte peut encore
|
|
677
|
-
les faire correspondre, et ils sont écartés des formulations dérivées. Les
|
|
678
|
-
laisser tomber en silence serait pire, puisque la recette trouvée peut contenir
|
|
679
|
-
exactement ce que la personne évitait : chaque contrainte écartée est donc
|
|
680
|
-
rappelée dans les notes, avec l'invitation à vérifier la liste d'ingrédients. Un
|
|
681
|
-
nombre de parts est renvoyé vers l'argument `servings`, qui est ce qui remet
|
|
682
|
-
réellement une recette à l'échelle.
|
|
683
|
-
|
|
684
|
-
Une contrainte se lit au rôle que jouent ses mots, pas à la tournure dans
|
|
685
|
-
laquelle ils sont écrits : une liste de tournures se troue au premier cas non
|
|
686
|
-
prévu. Trois rôles sortent un aliment du plat. Une négation, quel que soit le
|
|
687
|
-
mot qui la porte, puisque les négations forment une classe fermée de mots
|
|
688
|
-
grammaticaux et que « sans beurre », « no butter » et « pas de beurre » reposent
|
|
689
|
-
toutes sur l'une d'elles. Une phrase sur la personne plutôt que sur le plat,
|
|
690
|
-
comme « je suis allergique aux noix », rappelée comme une allergie parce que
|
|
691
|
-
c'est la contrainte dont l'échec relève de la santé. Et un régime nommé en un
|
|
692
|
-
mot, qui est une classe de recettes définie par ce qu'elle exclut et énonce donc
|
|
693
|
-
une exclusion sans qu'aucune négation soit écrite. Une question peut encore
|
|
694
|
-
formuler une contrainte que cette lecture ne saisit pas, et la réponse le dit
|
|
695
|
-
plutôt que de présenter les contraintes lues comme les seules présentes.
|
|
696
|
-
|
|
697
|
-
La portée d'une contrainte se lit dans la phrase. Un mot de liaison juste après
|
|
698
|
-
le marqueur dit que l'aliment le suit : « allergique aux noix », « allergic to
|
|
699
|
-
peanuts », « free of dairy ». Sans ce mot, l'aliment est celui sur lequel le
|
|
700
|
-
marqueur a été accolé : « peanut allergy », « lactose intolerant », « gluten
|
|
701
|
-
free ». Les deux ordres sont nécessaires, puisque les deux langues que publient
|
|
702
|
-
ces sources placent l'aliment de part et d'autre. Le nom d'un aliment s'arrête
|
|
703
|
-
là où plus rien ne le prolonge, si bien que « sans gluten gateau au chocolat »
|
|
704
|
-
écarte le gluten et cherche le gâteau. Quand un marqueur n'a d'aliment d'aucun
|
|
705
|
-
côté, la réponse dit qu'une contrainte a été énoncée et que son aliment n'a pas
|
|
706
|
-
été lu : nommer le mauvais mot cache un plat et laisse en même temps dans la
|
|
707
|
-
recherche l'aliment que l'on évite, et un allergène ne se devine pas.
|
|
708
|
-
|
|
709
|
-
Mettre `fan_out` à false envoie exactement les mots saisis. Les formulations non
|
|
710
|
-
envoyées sont quand même nommées, pour qu'un appelant voie ce que le réactiver
|
|
711
|
-
lui apporterait.
|
|
712
|
-
|
|
713
|
-
Les lignes alternent d'une source à l'autre. Aucune note ne les classe les unes
|
|
714
|
-
contre les autres, faute d'échelle commune : l'une porte des avis de lecteurs,
|
|
715
|
-
l'autre n'a par nature ni auteur ni note. La réponse dit comment l'ordre a été
|
|
716
|
-
construit.
|
|
717
|
-
|
|
718
|
-
`per_source` rapporte ce qu'a répondu chaque source, dans les termes de cette
|
|
719
|
-
source. Marmiton compte les lignes de l'unique page qu'il sert. Le Cookbook
|
|
720
|
-
n'annonce aucun total, donc une liste courte venant de lui ne prouve pas qu'il y
|
|
721
|
-
ait peu de choses. Ces nombres comptent des choses différentes et ne sont jamais
|
|
722
|
-
additionnés.
|
|
458
|
+
Cherche dans toutes les sources avec une seule question.
|
|
459
|
+
|
|
460
|
+
| Argument | Type | Requis | Ce qu'il fait |
|
|
461
|
+
| ------------------ | -------------------------------- | ------ | ----------------------------------------------------------------------- |
|
|
462
|
+
| `query` | chaîne, 1 à 200 caractères | oui | Le plat ou l'ingrédient cherché. |
|
|
463
|
+
| `limit_per_source` | entier, 1 à 25, défaut `5` | non | Lignes à garder de chaque source. |
|
|
464
|
+
| `sources` | tableau d'identifiants de source | non | N'interroger que ces sources. |
|
|
465
|
+
| `fan_out` | booléen, défaut `true` | non | Interroger chaque source plutôt que s'arrêter à la première qui répond. |
|
|
466
|
+
|
|
467
|
+
**En retour :** `results`, des lignes portant `id`, que `get_recipe` reprend ;
|
|
468
|
+
`source` et `source_name` qui disent quel site a publié la ligne ; `title` ;
|
|
469
|
+
`url` ; `image_url` ; et un `excerpt` là où la source en propose un.
|
|
470
|
+
`per_source` donne un rapport par site avec son `status`, valant `answered` ou
|
|
471
|
+
`failed`, le `count` qu'il a fourni, et son `reported_total` accompagné de
|
|
472
|
+
`reported_total_means`, qui dit ce que ce nombre compte sur ce site. `order` dit
|
|
473
|
+
en mots comment la liste a été bâtie.
|
|
723
474
|
|
|
724
475
|
### `get_recipe`
|
|
725
476
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
|
729
|
-
|
|
|
730
|
-
| `
|
|
731
|
-
| `
|
|
732
|
-
| `
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
qu'une source
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
477
|
+
Lit une recette de n'importe quelle source, sous une seule forme.
|
|
478
|
+
|
|
479
|
+
| Argument | Type | Requis | Ce qu'il fait |
|
|
480
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
481
|
+
| `id` | chaîne, 1 à 500 caractères | oui | L'identifiant d'une ligne, tel que `marmiton:44078`. Deux sources adressent une recette par un nombre nu : écrivez l'id avec sa source. |
|
|
482
|
+
| `servings` | entier, 1 à 500 | non | Adapte les ingrédients à ce nombre de parts. |
|
|
483
|
+
| `sections` | tableau de `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, défaut `["ingredients", "steps"]` | non | Les parties à rendre. |
|
|
484
|
+
| `max_steps` | entier, 1 à 100, défaut `20` | non | Étapes à servir. |
|
|
485
|
+
| `max_step_chars` | entier, 80 à 4000, défaut `600` | non | Caractères gardés par étape. |
|
|
486
|
+
|
|
487
|
+
**En retour :** la recette dans la forme où toutes les sources sont rendues, quelle
|
|
488
|
+
que soit celle qui l'a publiée : son titre, son adresse, ses ingrédients avec le
|
|
489
|
+
`scaling` de chaque ligne, ses étapes, et les parties demandées. Un champ qu'une
|
|
490
|
+
source publie et dont une autre n'a pas la notion revient absent plutôt
|
|
491
|
+
qu'inventé. `rest_minutes` porte le temps de repos d'une source qui l'imprime à
|
|
492
|
+
part, et il n'entre dans aucun autre temps rendu ici. `steps_as_one_block` dit
|
|
493
|
+
quand une source a publié sa méthode d'un seul bloc de prose plutôt qu'en étapes.
|
|
494
|
+
`withheld` nomme la partie qu'une source réserve à ses abonnés, qui est une
|
|
495
|
+
partie que la page porte et non une partie illisible. Augmentez `max_step_chars`
|
|
496
|
+
quand une étape a été coupée au milieu d'une phrase.
|
|
497
|
+
|
|
498
|
+
### `compare_recipes`
|
|
499
|
+
|
|
500
|
+
Met plusieurs versions d'un même plat côte à côte.
|
|
501
|
+
|
|
502
|
+
| Argument | Type | Requis | Ce qu'il fait |
|
|
503
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------ | ------ | ------------------------------------------- |
|
|
504
|
+
| `dish` | chaîne, 1 à 200 caractères | oui | Le plat à comparer. |
|
|
505
|
+
| `servings` | entier, 1 à 500 | non | Adapte chaque version à ce nombre de parts. |
|
|
506
|
+
| `sections` | tableau de `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, défaut `["ingredients"]` | non | Les parties à rendre par version. |
|
|
507
|
+
| `max_steps` | entier, 1 à 100, défaut `10` | non | Étapes à servir par version. |
|
|
508
|
+
| `max_step_chars` | entier, 80 à 4000, défaut `600` | non | Caractères gardés par étape. |
|
|
509
|
+
| `sources` | tableau d'ids de source | non | Ne compare que ces sources. |
|
|
510
|
+
|
|
511
|
+
**En retour :** `versions`, une recette par source ayant répondu, toutes adaptées
|
|
512
|
+
au même nombre de parts pour que leurs quantités se lisent l'une contre l'autre,
|
|
513
|
+
et `differences`, ce qui les sépare. `per_source` rapporte chaque site comme le
|
|
514
|
+
fait une recherche.
|
|
759
515
|
|
|
760
516
|
### `scale_ingredients`
|
|
761
517
|
|
|
762
|
-
|
|
763
|
-
source, d'un livre de cuisine sur une étagère, ou d'une photo de fiche.
|
|
518
|
+
Adapte n'importe quelle liste d'ingrédients, sans requête à l'un ou l'autre site.
|
|
764
519
|
|
|
765
|
-
| Argument
|
|
766
|
-
|
|
|
767
|
-
| `ingredients`
|
|
768
|
-
| `factor`
|
|
769
|
-
| `from_servings`
|
|
770
|
-
| `
|
|
520
|
+
| Argument | Type | Requis | Ce qu'il fait |
|
|
521
|
+
| --------------- | ----------------------------------- | ------------- | ----------------------------------------- |
|
|
522
|
+
| `ingredients` | tableau de 1 à 200 lignes | oui | Les lignes à adapter. |
|
|
523
|
+
| `factor` | nombre, jusqu'à 1000 | l'un des deux | Le multiplicateur à appliquer. |
|
|
524
|
+
| `from_servings` | entier, 1 à 500 | l'un des deux | Le nombre de parts de la liste d'origine. |
|
|
525
|
+
| `to_servings` | entier, 1 à 500 | l'un des deux | Le nombre de parts voulu. |
|
|
526
|
+
| `language` | `auto`, `fr` ou `en`, défaut `auto` | non | Comment chaque ligne est lue. |
|
|
771
527
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
528
|
+
Passez `factor`, ou le couple `from_servings` et `to_servings`. `auto` lit chaque
|
|
529
|
+
ligne pour elle-même, ce dont a besoin une liste portant les deux langues ;
|
|
530
|
+
nommer une langue lit toutes les lignes ainsi.
|
|
775
531
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
l'endroit où tombe le pluriel.
|
|
532
|
+
**En retour :** les lignes adaptées dans la forme que rend `get_recipe`, chacune
|
|
533
|
+
avec son `scaling`.
|
|
779
534
|
|
|
780
|
-
|
|
535
|
+
## L'adaptation des quantités
|
|
781
536
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
| `servings` | entier, optionnel | Ramener chaque version à ce nombre de parts |
|
|
786
|
-
| `sections` | tableau, défaut `["ingredients"]` | Comme ci-dessus |
|
|
787
|
-
| `max_steps` | entier, défaut 10 | Étapes renvoyées par version |
|
|
788
|
-
| `max_step_chars` | entier, défaut 600 | Caractères gardés par étape |
|
|
537
|
+
Une quantité est exprimée dans l'unité qui lui convient. Après adaptation, une
|
|
538
|
+
ligne peut donc apparaître dans une autre unité que celle de la recette : 200 g
|
|
539
|
+
multipliés par vingt donnent `4 kg`.
|
|
789
540
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
541
|
+
La finesse à laquelle un ingrédient se coupe dépend de sa nature. Une baguette se
|
|
542
|
+
coupe en deux, en trois ou en quatre ; un oeuf ne se partage pas. Une quantité
|
|
543
|
+
qui tombe entre les deux est donc arrondie, et la recette adaptée s'écarte alors
|
|
544
|
+
un peu des proportions de l'originale. La ligne porte `rounded`, et sa note dit
|
|
545
|
+
ce qui a été fait.
|
|
794
546
|
|
|
795
|
-
Les
|
|
796
|
-
|
|
797
|
-
|
|
547
|
+
Les sources écrivent leurs quantités dans leur propre langue, et une ligne
|
|
548
|
+
est lue dans la langue où elle a été écrite. Les chiffres sont l'arithmétique de
|
|
549
|
+
ce serveur, donc dites qu'ils ont été recalculés quand vous les montrez.
|
|
798
550
|
|
|
799
|
-
|
|
800
|
-
produite : sa recherche a échoué, elle a proposé une ligne illisible, ou elle
|
|
801
|
-
n'a rien proposé d'assez proche pour comparer.
|
|
551
|
+
## Ce qu'une réponse dit des sources
|
|
802
552
|
|
|
803
|
-
|
|
553
|
+
Chaque réponse rend compte de chaque source séparément. Un site qui a échoué, un
|
|
554
|
+
que personne n'a interrogé et un qui a répondu vide sont trois choses
|
|
555
|
+
différentes, et elles sont rapportées comme trois. Un total reste à côté de la
|
|
556
|
+
source qui l'a publié, avec ce que cette source compte en le disant : l'un compte
|
|
557
|
+
une catégorie entière et l'autre compte les lignes qu'il a servies.
|
|
804
558
|
|
|
805
|
-
##
|
|
806
|
-
|
|
807
|
-
Chacune de ces règles est tenue par un test qui la nomme.
|
|
808
|
-
|
|
809
|
-
- **Une source en échec est nommée comme une source en échec.** Une réponse qui
|
|
810
|
-
ne porte que les lignes de certaines sources ne dit rien de ce que contiennent
|
|
811
|
-
les autres.
|
|
812
|
-
- **Aucun total n'est inventé.** Le nombre de chaque source est rapporté avec ce
|
|
813
|
-
qu'il compte, dans les termes de cette source, et ces nombres ne sont jamais
|
|
814
|
-
additionnés.
|
|
815
|
-
- **Aucun classement entre les sources.** Elles n'ont pas d'échelle commune,
|
|
816
|
-
donc les lignes alternent et la réponse dit comment l'ordre a été construit.
|
|
817
|
-
- **Aucune conversion entre systèmes de mesure.** Une ligne garde les unités que
|
|
818
|
-
sa source a publiées, et la source est nommée à côté.
|
|
819
|
-
- **Un rendement est repris dans les mots de la source.** « 4 à 6 personnes » et
|
|
820
|
-
« 4 personnes » sont deux affirmations différentes.
|
|
821
|
-
- **Un champ qu'une source ne publie pas vaut `null`**, et un champ que personne
|
|
822
|
-
n'a demandé figure dans `sections_omitted`.
|
|
823
|
-
- **Le texte d'une source ne peut pas imiter ce serveur.** Tout ce qu'une source
|
|
824
|
-
ou un appelant a écrit tient sur une seule ligne avant affichage, la syntaxe
|
|
825
|
-
d'image markdown est désamorcée, et une ouverture ressemblant à un marqueur du
|
|
826
|
-
serveur est indentée. La charge structurée conserve le texte tel que publié.
|
|
559
|
+
## Configuration
|
|
827
560
|
|
|
828
|
-
|
|
561
|
+
Chaque variable est facultative. Elles se posent dans le bloc `env` de la
|
|
562
|
+
configuration du client.
|
|
829
563
|
|
|
830
|
-
|
|
564
|
+
| Variable | Défaut | Ce qu'elle fait |
|
|
565
|
+
| --------------------------- | -------------------- | ---------------------------------------------------------------------------------------- |
|
|
566
|
+
| `RECIPES_USER_AGENT` | l'identité du projet | Nomme votre application auprès des deux sites, avec une adresse où joindre une personne. |
|
|
567
|
+
| `RECIPES_MIN_INTERVAL_MS` | `1000` | Écart entre deux requêtes vers un même site, de 500 à 60000. |
|
|
568
|
+
| `RECIPES_TIMEOUT_MS` | `20000` | Délai d'une requête, de 1000 à 120000. |
|
|
569
|
+
| `RECIPES_MAX_RETRIES` | `3` | Tentatives après un échec passager, de 0 à 8. |
|
|
570
|
+
| `RECIPES_CACHE_TTL_MS` | `900000` | Durée pendant laquelle une réponse reste en mémoire, de 0 à 86400000. |
|
|
571
|
+
| `RECIPES_CACHE_MAX_ENTRIES` | `200` | Réponses gardées en mémoire à la fois, de 1 à 5000. |
|
|
572
|
+
| `RECIPES_LOG_LEVEL` | `error` | `silent`, `error`, `info` ou `debug`, écrit sur la sortie d'erreur. |
|
|
831
573
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
frappe est très difficile à diagnostiquer depuis l'application hôte.
|
|
574
|
+
Une valeur hors de sa plage retombe sur le défaut, et la raison est écrite sur la
|
|
575
|
+
sortie d'erreur.
|
|
835
576
|
|
|
836
|
-
|
|
837
|
-
| --------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
|
|
838
|
-
| `RECIPES_USER_AGENT` | — | Identifiez votre client. L'identité du projet est ajoutée, pour qu'une source puisse joindre un humain |
|
|
839
|
-
| `RECIPES_MIN_INTERVAL_MS` | `1000` | Écart minimal entre deux requêtes vers une même source. Plancher 500 |
|
|
840
|
-
| `RECIPES_TIMEOUT_MS` | `20000` | Délai maximal d'une requête |
|
|
841
|
-
| `RECIPES_MAX_RETRIES` | `3` | Réessais en cas de limitation ou d'échec passager |
|
|
842
|
-
| `RECIPES_CACHE_TTL_MS` | `900000` | Durée de vie du cache mémoire. `0` le désactive |
|
|
843
|
-
| `RECIPES_CACHE_MAX_ENTRIES` | `200` | Taille du cache mémoire |
|
|
844
|
-
| `RECIPES_LOG_LEVEL` | `error` | `silent`, `error`, `info`, `debug`. Sur stderr |
|
|
577
|
+
## Erreurs
|
|
845
578
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
plancher tient par quelque chemin que le réglage arrive.
|
|
579
|
+
Chaque échec porte un des six codes, un message, et quand cela aide une
|
|
580
|
+
indication du geste suivant.
|
|
849
581
|
|
|
850
|
-
|
|
582
|
+
| Code | Ce qui s'est passé | Que faire |
|
|
583
|
+
| --------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
584
|
+
| `not_found` | Une source a répondu, et n'a pas cette recette. | Vérifiez l'identifiant avec `search_recipes`. |
|
|
585
|
+
| `invalid_input` | Les arguments ont été refusés avant toute requête. | Lisez le message, qui nomme l'argument. |
|
|
586
|
+
| `rate_limited` | Une source demande à ce client de ralentir. | Attendez, puis rappelez avec les mêmes arguments. La recette est toujours là. |
|
|
587
|
+
| `parse_failure` | Une page a chargé et le contenu attendu est absent. | Signalez-le sur [le suivi d'incidents](https://github.com/smeet666/mcp-recipes/issues). |
|
|
588
|
+
| `network_error` | La requête n'a pas abouti. | Réessayez sous peu. |
|
|
589
|
+
| `timeout` | La requête a dépassé son délai. | Augmentez `RECIPES_TIMEOUT_MS`, ou demandez moins de lignes. |
|
|
851
590
|
|
|
852
|
-
|
|
591
|
+
Une source qui échoue est rapportée source par source plutôt que de faire échouer
|
|
592
|
+
toute la réponse, donc un site silencieux n'en cache jamais un autre.
|
|
853
593
|
|
|
854
|
-
|
|
594
|
+
## Comme bibliothèque
|
|
855
595
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
| `not_found` | Une source a répondu, et n'a pas cette recette |
|
|
859
|
-
| `invalid_input` | Les arguments ne permettaient pas de former une requête |
|
|
860
|
-
| `rate_limited` | Une source a demandé à ce client de ralentir |
|
|
861
|
-
| `parse_failure` | Une réponse est arrivée dans une forme illisible pour ce client |
|
|
862
|
-
| `network_error` | La requête n'a pas abouti |
|
|
863
|
-
| `timeout` | La requête a dépassé son délai |
|
|
596
|
+
La couche qui lit les deux sites est publiée seule, avec son rythme, son cache et
|
|
597
|
+
ses erreurs, sans protocole attaché.
|
|
864
598
|
|
|
865
|
-
|
|
866
|
-
|
|
599
|
+
```ts
|
|
600
|
+
import { RecipesClient } from "mcp-recipes/client";
|
|
867
601
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
602
|
+
const client = new RecipesClient();
|
|
603
|
+
const read = await client.search({ query: "carbonara", limitPerSource: 3 });
|
|
604
|
+
console.log(read.data.results.length);
|
|
605
|
+
```
|
|
871
606
|
|
|
872
|
-
|
|
873
|
-
|
|
607
|
+
Chaque lecture répond `{ data, cached }`, et lève une erreur portant un des six
|
|
608
|
+
codes. Chaque site garde son propre rythme, et le plancher tient également ici.
|
|
874
609
|
|
|
875
|
-
|
|
610
|
+
## Rythme et attribution
|
|
611
|
+
|
|
612
|
+
Chaque site est cadencé pour lui-même, une requête à la fois avec au moins une
|
|
613
|
+
seconde entre deux, et le plancher d'une demi-seconde tient quelle que soit la
|
|
614
|
+
configuration. Les interroger toutes à la fois coûte donc à chacune une requête,
|
|
615
|
+
jamais deux. Le `User-Agent` se termine toujours par l'identité du projet et une
|
|
616
|
+
adresse où joindre une personne.
|
|
617
|
+
|
|
618
|
+
Chaque ligne porte l'adresse de la page de la recette et le nom du site qui l'a
|
|
619
|
+
publiée. Les pages du Cookbook sont publiées sous
|
|
620
|
+
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.fr), qui
|
|
621
|
+
demande que ce qu'on bâtit dessus soit partagé sous la même licence. Marmiton,
|
|
622
|
+
Ptitchef, BBC Good Food et Supertoinette n'énoncent aucune condition sur une page
|
|
623
|
+
de recette, et leurs recettes appartiennent à ces sites et aux cuisiniers qui les
|
|
624
|
+
ont écrites. Le silence n'est pas une autorisation : créditez le site et liez la
|
|
625
|
+
page d'où vient la recette.
|
|
626
|
+
|
|
627
|
+
Une recette que BBC Good Food réserve à ses abonnés revient sans ses ingrédients
|
|
628
|
+
ni sa méthode, nommée comme une recette retenue, avec l'adresse de sa page. Ce
|
|
629
|
+
serveur ne reconstitue pas ce que ce site a choisi de vendre.
|
|
630
|
+
|
|
631
|
+
Deux chiffres que les sites publient ne sont pas repris ici. Une difficulté est
|
|
632
|
+
un mot que chaque site écrit à sa façon, sur aucune échelle publiée : elle ne
|
|
633
|
+
siège sur aucun axe le long duquel deux versions se compareraient. Un coût est un
|
|
634
|
+
prix en euros sur un site et un rang dans sa propre liste sur un autre, et un
|
|
635
|
+
seul champ portant les deux inviterait à les comparer.
|
|
636
|
+
|
|
637
|
+
Ce MCP est un projet non officiel, sans affiliation à aucun des sites qu'il
|
|
638
|
+
lit.
|
|
639
|
+
|
|
640
|
+
## Confidentialité
|
|
641
|
+
|
|
642
|
+
Ce serveur ne collecte rien sur vous et n'envoie rien à son auteur. Il tourne sur
|
|
643
|
+
votre machine, ne joint que `www.marmiton.org`, `api.wikimedia.org`,
|
|
644
|
+
`www.ptitchef.com`, `www.bbcgoodfood.com` et `www.supertoinette.com`, garde ses réponses en mémoire le temps qu'il tourne, et
|
|
645
|
+
n'écrit rien sur le disque. [PRIVACY.md](PRIVACY.md) dit ce qu'une requête
|
|
646
|
+
emporte et quels réglages changent cela.
|
|
647
|
+
|
|
648
|
+
## Développement
|
|
649
|
+
|
|
650
|
+
```bash
|
|
651
|
+
npm install
|
|
652
|
+
npm run build:fixtures
|
|
653
|
+
npm test
|
|
654
|
+
npm run check
|
|
655
|
+
```
|
|
876
656
|
|
|
877
|
-
|
|
657
|
+
Les tests s'exécutent sur des fixtures engendrées et n'émettent aucune requête.
|
|
658
|
+
La suite en direct, `npm run test:live`, émet une requête par route et tourne
|
|
659
|
+
chaque nuit contre les sites eux-mêmes.
|
|
878
660
|
|
|
879
|
-
|
|
661
|
+
## Contribuer
|
|
880
662
|
|
|
881
|
-
Les
|
|
882
|
-
|
|
883
|
-
|
|
663
|
+
Les anomalies, les questions et les idées ont leur place dans
|
|
664
|
+
[le suivi d'incidents](https://github.com/smeet666/mcp-recipes/issues). Les
|
|
665
|
+
propositions de modification sont bienvenues ; ouvrir un ticket d'abord aide à
|
|
666
|
+
s'accorder sur la forme du changement. Voir [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
884
667
|
|
|
885
|
-
|
|
886
|
-
silence n'est pas une autorisation. Créditez la source et liez la page.
|
|
668
|
+
## Licence
|
|
887
669
|
|
|
888
|
-
|
|
670
|
+
MIT, voir [LICENSE](LICENSE). Les recettes appartiennent aux sites qui les ont
|
|
671
|
+
publiées et à leurs auteurs.
|