mcp-recipes 1.1.0 → 1.3.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 +270 -0
- package/README.md +174 -7
- package/dist/index.js +1237 -165
- package/dist/index.js.map +1 -1
- package/dist/recipe/scale.js +206 -40
- package/dist/recipe/scale.js.map +1 -1
- package/dist/sources/client.d.ts +180 -3
- package/dist/sources/client.js +667 -37
- package/dist/sources/client.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,276 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 1.3.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- A question asked in a sentence is sent in shorter wordings as well as in the
|
|
12
|
+
words it was written in. These indexes answer the words they are handed: one
|
|
13
|
+
requires every word given to appear on the same page, so "comment faire un
|
|
14
|
+
ceviche de truite à l'origan" came back empty from a corpus holding four
|
|
15
|
+
ceviche recipes, and the other ranks on the words received, so the words
|
|
16
|
+
framing a question pulled up whatever shared them and pushed the dish out of
|
|
17
|
+
the list. A question now becomes an ordered ladder of at most three wordings,
|
|
18
|
+
tried in order and stopped as soon as enough rows have come back, and the rows
|
|
19
|
+
are the union deduplicated on the id naming its source. `per_source` lists
|
|
20
|
+
every wording, what each returned, and the ones held back with the reason.
|
|
21
|
+
Nothing is translated between the sources' languages.
|
|
22
|
+
|
|
23
|
+
- `search_recipes` takes `fan_out`, on unless a caller turns it off, which sends
|
|
24
|
+
exactly the words typed and still names the wordings that were withheld.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- A condition a question sets is handled as a condition rather than as a word to
|
|
29
|
+
find. "sans beurre", "gluten free" and "pour 6 personnes" stay in the wording
|
|
30
|
+
sent first, where a source whose index carries them can match, and are set
|
|
31
|
+
aside from the derived wordings, since no page is named for what it leaves out
|
|
32
|
+
and a number of eaters is not printed in a title. Every condition set aside is
|
|
33
|
+
named back in the notes with a reminder to read the ingredient list, because a
|
|
34
|
+
recipe found this way can hold exactly what was being avoided. A number of
|
|
35
|
+
servings is pointed at the `servings` argument.
|
|
36
|
+
|
|
37
|
+
- A condition is read by the role its words play rather than by the turn of
|
|
38
|
+
phrase they are written in. Reading it off the phrasings "sans X", "without X"
|
|
39
|
+
and "X-free" left an allergen a person had declared to become a positive
|
|
40
|
+
search term: "je suis allergique aux noix" put "noix" into a derived wording,
|
|
41
|
+
which pulled a coconut cake to the second row of a lasagne search with nothing
|
|
42
|
+
said about nuts. An exclusion written "no butter" or "pas de beurre" raised no
|
|
43
|
+
warning at all, and a diet named in one word was not treated as a condition,
|
|
44
|
+
so "recette vegane au foie gras" returned foie gras at the first row unmarked.
|
|
45
|
+
Three roles are now read: a negation, whichever of the closed class of
|
|
46
|
+
negating words carries it; a sentence about the eater rather than the dish,
|
|
47
|
+
which is named back as an allergy because that is the condition whose failure
|
|
48
|
+
is a matter of health; and a diet named in one word. All three are set aside
|
|
49
|
+
from the derived wordings and named back in the notes. A question can still
|
|
50
|
+
phrase one in a way this reading misses, and the notes say so instead of
|
|
51
|
+
presenting the conditions read as the only ones present. The reading is made
|
|
52
|
+
once for the whole question, so the words set aside as a condition and the
|
|
53
|
+
words searched for as a dish can no longer disagree.
|
|
54
|
+
|
|
55
|
+
- A quantity written across two measures is read as one quantity. "1 lb 4 oz
|
|
56
|
+
beef" doubled came back as "2 lb 4 oz beef", a tenth short of the answer and
|
|
57
|
+
marked as the exact product, and "1 kg 500 de farine" came back as "2 kg de
|
|
58
|
+
500 de farine". Two measures off the same ladder written side by side, with
|
|
59
|
+
the second holding less than one of the first, are one amount: they give
|
|
60
|
+
"2.5 lb beef" and "3 kg de farine". The French form that leaves the smaller
|
|
61
|
+
unit unwritten is read the same way.
|
|
62
|
+
|
|
63
|
+
- A measure standing in front of a container is read as the size of one of them.
|
|
64
|
+
"12 oz can tomatoes" doubled came back as "1.5 lb can tomatoes", which asks
|
|
65
|
+
for a bigger tin where the recipe wants a second one. Lines of that shape come
|
|
66
|
+
back as published, with a note saying what the measure gives and that the line
|
|
67
|
+
states no count.
|
|
68
|
+
|
|
69
|
+
- A further quantity on a line is seen where the measure stands behind the word
|
|
70
|
+
introducing it. "150 g de sucre (soit 3/4 de tasse)" doubled came back as
|
|
71
|
+
"300 g de sucre (soit 3/4 de tasse)" marked as exact, so the line claimed
|
|
72
|
+
300 g was three quarters of a cup. A quantity left as published beside a mass
|
|
73
|
+
or a volume now shows on the line's own `scaling`, which no longer reads
|
|
74
|
+
`scaled`. A container's stated capacity is not one of those: "2 boîtes de
|
|
75
|
+
400 g" is exactly twice "1 boîte de 400 g" and stays `scaled`.
|
|
76
|
+
|
|
77
|
+
- A count read from a word that names no number is not reported as exact
|
|
78
|
+
arithmetic. "quelques feuilles de basilic" doubled gave six leaves marked
|
|
79
|
+
`scaled`, which put the weight of arithmetic behind a reading of "quelques".
|
|
80
|
+
The line comes back `rounded`, and the note says the figure is that reading
|
|
81
|
+
multiplied rather than a count the page gave.
|
|
82
|
+
|
|
83
|
+
- `compare_recipes` says when no version carries a word of the dish in its
|
|
84
|
+
title. One of these indexes answers almost any spelling with its closest row,
|
|
85
|
+
so a dish nobody publishes came back as a doughnut recipe presented as a
|
|
86
|
+
source's version of it, closing on an invitation to read it as what that
|
|
87
|
+
source publishes. That invitation is now withheld in exactly that case, and
|
|
88
|
+
the answer says the rows are candidates to check.
|
|
89
|
+
|
|
90
|
+
- A part of a recipe that comes back empty is reported as something this server
|
|
91
|
+
read nothing from rather than as something the page does not publish. Where
|
|
92
|
+
the page shows a sign of it anyway, a heading announcing that part or another
|
|
93
|
+
part of the recipe that was read, the answer states that this server failed to
|
|
94
|
+
read what the page carries. Where the page shows neither, the answer states
|
|
95
|
+
that a page publishing nothing and a layout this reader cannot follow are
|
|
96
|
+
indistinguishable from here. Both wordings close on the same warning: an empty
|
|
97
|
+
ingredient list is no evidence that an ingredient is absent from the dish.
|
|
98
|
+
This covers an empty method the same way. The note is only raised for a
|
|
99
|
+
section that was asked for.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- An allergy stated with the food before the marker is read. Scoping a condition
|
|
104
|
+
forwards alone reads the French order, "allergique aux noix", and misses the
|
|
105
|
+
English one, so "cookies peanut allergy" searched for the nut, returned peanut
|
|
106
|
+
butter and said nothing about an allergy; "pancakes, I am lactose intolerant"
|
|
107
|
+
did the same with lactose. A marker now takes its food from whichever side of
|
|
108
|
+
it the sentence put one: a joining word straight after it says the food
|
|
109
|
+
follows, and with no such word the food is the one the marker was written onto
|
|
110
|
+
the end of.
|
|
111
|
+
|
|
112
|
+
- An exclusion and the dish no longer swap places. "a dessert free of dairy"
|
|
113
|
+
reported the dessert as the thing to leave out and searched for the dairy.
|
|
114
|
+
"free" is read the same way as any other marker, so "free of dairy" leaves out
|
|
115
|
+
the dairy and "gluten free" leaves out the gluten.
|
|
116
|
+
|
|
117
|
+
- A condition ends at the food it names. A fixed span of words let a condition
|
|
118
|
+
written at the head of a question swallow the dish behind it, so "sans gluten
|
|
119
|
+
gateau au chocolat" reported a recipe free of "gluten gateau au". A food's
|
|
120
|
+
name now runs on only through a word that carries it into a second noun, as in
|
|
121
|
+
"farine de blé", and stops everywhere else.
|
|
122
|
+
|
|
123
|
+
- A marker with a food on neither side of it is reported as a condition whose
|
|
124
|
+
food was not read, rather than passing unmentioned or taking a word at random.
|
|
125
|
+
Naming the wrong word hides a dish and searches for the food being avoided at
|
|
126
|
+
the same time, so the answer states that a condition was set and that this
|
|
127
|
+
server did not read what it covers.
|
|
128
|
+
|
|
129
|
+
- The note that says a diet was not filtered on reaches the text block. Notes
|
|
130
|
+
are dropped from that block when an answer carries more than it has room for,
|
|
131
|
+
and which ones could go was decided by matching their wording, so the diet
|
|
132
|
+
note went with the rest and "recette vegane au foie gras" rendered pan-fried
|
|
133
|
+
foie gras at the first row with nothing said about the diet. A note now
|
|
134
|
+
carries whether the answer misleads without it, set where the note is written,
|
|
135
|
+
and the block gives up body text before it gives up such a note.
|
|
136
|
+
|
|
137
|
+
- A comparison confronts two versions only where both titles carry the dish.
|
|
138
|
+
Sharing one word of a name is what makes a row worth offering and not what
|
|
139
|
+
makes it the dish, so "biscuits and gravy" put a tin of Christmas biscuits
|
|
140
|
+
beside the American dish and reported their yields, their times and their
|
|
141
|
+
ingredient counts as the difference between two traditions. A version whose
|
|
142
|
+
title leaves a word of the dish unanswered is now named, with the words it
|
|
143
|
+
does not carry and an invitation to read it as a candidate to check, and
|
|
144
|
+
nothing is set side by side until every version answers to the same name.
|
|
145
|
+
|
|
146
|
+
- A bracket the line closes on is scaled with the amount it restates.
|
|
147
|
+
"150 g de sucre (soit 3/4 de tasse)" doubled came back with the bracket as
|
|
148
|
+
published, so the line claimed that 300 g is three quarters of a cup. A
|
|
149
|
+
closing bracket that reads entirely as measures now moves with the figure it
|
|
150
|
+
restates, and it is read through the word that introduces it and through the
|
|
151
|
+
partitive between the number and the measure, so "soit 3/4 de tasse" is the
|
|
152
|
+
same quantity as "0,75 tasse". A slash inside a bracket separates two
|
|
153
|
+
statements of one quantity, and one sitting between two digits belongs to the
|
|
154
|
+
fraction it writes.
|
|
155
|
+
|
|
156
|
+
- A capacity in brackets counts containers rather than measuring one out.
|
|
157
|
+
"1 (14 oz) can" doubled came back as "2 (1.75 lb) can", which asks for a tin
|
|
158
|
+
no shop sells and converts a quantity into another system on the way. A
|
|
159
|
+
measure inside a bracket standing beside a container is what one of them
|
|
160
|
+
holds: it goes back exactly as published, the count is what the factor moves,
|
|
161
|
+
the container agrees with that count, and the line says which figure is
|
|
162
|
+
whose.
|
|
163
|
+
|
|
164
|
+
- A bracket a French line closes on moves with the rest of the line.
|
|
165
|
+
"1 demi verre de lait (10 cl)" tripled instructed the reader to take the ten
|
|
166
|
+
centilitres as published while asking for three times the milk.
|
|
167
|
+
|
|
168
|
+
- A measure written in abbreviations keeps what stands behind it.
|
|
169
|
+
"2 c.à.s d'huile" doubled came back as "4 cuillères à soupe", with the oil
|
|
170
|
+
gone from the recipe, and "2 c.à.s + 1 c.à.c d'huile" came back as
|
|
171
|
+
"4 cuillères à soupe de c.à.c d'huile". Normalising an abbreviation spells it
|
|
172
|
+
as several words where the line writes one, and that difference was consuming
|
|
173
|
+
words of the ingredient; the words the line wrote are now consumed until they
|
|
174
|
+
match the measure. Two measures joined by a sign or a word saying they add up
|
|
175
|
+
are read as one quantity, as two measures written side by side already were.
|
|
176
|
+
|
|
177
|
+
- A mass keeps the precision the arithmetic gave it. "1234 g" doubled came back
|
|
178
|
+
as "2,47 kg" marked as the exact product, where the exact product is 2468 g:
|
|
179
|
+
a whole number of grams was being snapped to a multiple of five, and the
|
|
180
|
+
result was then read in kilos, where a hundredth of the unit is ten grams and
|
|
181
|
+
every gap looks small. A whole number is what a scale shows and is left where
|
|
182
|
+
the arithmetic put it, a quantity moves up to a bigger unit only where that
|
|
183
|
+
unit states it as precisely, and whether a value landed exactly is judged in
|
|
184
|
+
the finer of the two units.
|
|
185
|
+
|
|
186
|
+
- A mark standing in front of the amount is not the amount. A line opening on a
|
|
187
|
+
bullet, a dash or a picture of the food was answered as a line carrying no
|
|
188
|
+
quantity, so 200 g of cheese stayed 200 g in a doubled recipe with nothing
|
|
189
|
+
said, and the mark also silenced the measure the language is read from. Such
|
|
190
|
+
a mark is set aside for reading and put back for writing.
|
|
191
|
+
|
|
192
|
+
- A text block that runs out of room says what it left out. Sentences
|
|
193
|
+
qualifying an answer keep their room, which leaves the result lines as the
|
|
194
|
+
thing that gives way, and a list of 119 ingredients gave way to nothing at
|
|
195
|
+
all in silence, which reads as a recipe needing no ingredients. A list now
|
|
196
|
+
keeps its opening whatever the room allows, states how many of how many lines
|
|
197
|
+
are shown, and points at the structured output for the rest. A recipe's
|
|
198
|
+
ingredients and its method share what is left rather than the first of them
|
|
199
|
+
filling the block.
|
|
200
|
+
|
|
201
|
+
- A length of time is no longer multiplied like an ingredient. Ingredient lists
|
|
202
|
+
carry a rest, a proof or a marinade among the quantities, and "2 h de repos"
|
|
203
|
+
tripled came back as "6 h de repos". A figure followed by a measure of time,
|
|
204
|
+
in either language, is returned as published with a note saying that a rest
|
|
205
|
+
takes as long for a large batch as for a small one.
|
|
206
|
+
|
|
207
|
+
- A word that names no number no longer produces one. "quelques feuilles de
|
|
208
|
+
basilic" tripled came back as "9 feuilles de basilic", while "a few basil
|
|
209
|
+
leaves" and "plusieurs feuilles de basilic" were returned as published, so
|
|
210
|
+
three ways of writing the same vagueness got three answers. All three are read
|
|
211
|
+
the same way now: the line carries no count, so it is repeated as published
|
|
212
|
+
and flagged as carrying nothing to multiply.
|
|
213
|
+
|
|
214
|
+
- The factor stated is the factor applied. It was shortened to three decimals
|
|
215
|
+
while the arithmetic was not, so `factor: 0.0001` rendered "Scaled by 0" and
|
|
216
|
+
put a zero in the payload beside quantities that were not zero. Below three
|
|
217
|
+
decimals the significant digits are stated instead.
|
|
218
|
+
|
|
219
|
+
- One quantity gives one answer however the page spelled it. "1234 g de farine"
|
|
220
|
+
and "1 kg 234 de farine" doubled came back 32 g apart, one of them called
|
|
221
|
+
exact and the other rounded, because the kilo cannot state 2468 g in the two
|
|
222
|
+
decimals a kitchen reads. A measured amount now moves down to the unit that
|
|
223
|
+
states it exactly, the way it already moved up only to a unit that could hold
|
|
224
|
+
it, so both lines answer "2468 g de farine".
|
|
225
|
+
|
|
226
|
+
- `compare_recipes` states differences about the parts it returned. It compared
|
|
227
|
+
the times of versions whose times the same answer said were not requested and
|
|
228
|
+
not returned, leaving a reader with a claim they had no way to check; a
|
|
229
|
+
difference is now stated only about a section the call asked for. Each
|
|
230
|
+
version's yield is also written the way the rest of the answer writes one, as
|
|
231
|
+
published and with the factor and the number of servings it was rescaled for,
|
|
232
|
+
rather than as a bare published yield standing above a rescaled list.
|
|
233
|
+
|
|
234
|
+
- Four smaller repairs. A query carrying no letter and no digit is refused
|
|
235
|
+
rather than sent, since an index handed nothing to match on answers with the
|
|
236
|
+
page it shows by default and those rows came back as recipes for "!!!". A
|
|
237
|
+
failure raised while reading a source is restated in this server's own words,
|
|
238
|
+
with what the site said quoted and credited to it and with any advice it
|
|
239
|
+
carried dropped, rather than served as this server's own sentence pointing
|
|
240
|
+
somewhere this server cannot answer for. A figure glued to an ordinal ending
|
|
241
|
+
is a rank rather than an amount, so "1er choix de boeuf" doubled no longer
|
|
242
|
+
reads "2 er choix de boeuf". And a rewritten line keeps the case the page
|
|
243
|
+
used, "1 TOMATO" tripled giving "3 TOMATOES", while a French noun in -ou takes
|
|
244
|
+
the plural French gives it: three choux, not three chous.
|
|
245
|
+
|
|
246
|
+
## [1.2.0] - 2026-08-07
|
|
247
|
+
|
|
248
|
+
### Changed
|
|
249
|
+
|
|
250
|
+
- A mass stated behind the thing counted is read as the size of one of them.
|
|
251
|
+
"1 dinde de 3 kg" names one bird and says what it weighs, and multiplying the
|
|
252
|
+
count answered a dinner for twelve with a bird and a half. Lines of that shape
|
|
253
|
+
come back as the page published them, with a note saying the figure gives a
|
|
254
|
+
size and that more people means a bigger bird. A container keeps counting:
|
|
255
|
+
"2 boîtes de 400 g de tomates" states what one boîte holds and goes on to name
|
|
256
|
+
what is in it, and an equivalence such as "450 g (1 livre) de spaghetti"
|
|
257
|
+
restates one quantity twice and is scaled on both sides.
|
|
258
|
+
|
|
259
|
+
- A comparison tells apart the two moments a source can fail in. Searching and
|
|
260
|
+
then opening the version a search offered are two different readings, and a
|
|
261
|
+
source missing from a comparison was described as one whose search did not
|
|
262
|
+
answer whichever of the two had happened. Every row of `per_source` now
|
|
263
|
+
carries a `read` field saying what became of the version that source offered,
|
|
264
|
+
and the notes name the moment that failed.
|
|
265
|
+
|
|
266
|
+
- A search Marmiton answers with nothing is counted as an answer. Marmiton
|
|
267
|
+
serves a 404 on its results page when no recipe matches, and reading that as a
|
|
268
|
+
failed source put "Marmiton did not answer" in front of a reader for a search
|
|
269
|
+
Marmiton had answered. Every other failure stays a failure, so a site that
|
|
270
|
+
cannot be reached is never read as a site that holds nothing.
|
|
271
|
+
|
|
272
|
+
- A list of results carrying none of the words asked for says so. A source
|
|
273
|
+
ranking a title on the letters it opens with answers "chameau farci" with a
|
|
274
|
+
chapeau and a gâteau château. When no title returned carries a word of the
|
|
275
|
+
query, the answer names that, and the rows read as candidates to check.
|
|
276
|
+
|
|
7
277
|
## [1.1.0] - 2026-08-07
|
|
8
278
|
|
|
9
279
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# mcp-recipes
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/mcp-recipes)
|
|
4
|
+
[](https://github.com/smeet666/mcp-recipes/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-recipes)
|
|
7
|
+
[](https://glama.ai/mcp/servers/smeet666/mcp-recipes)
|
|
8
|
+
|
|
3
9
|
One question, several kitchens. An MCP server that asks every recipe source it
|
|
4
10
|
reads at the same time, merges what they say, and scales quantities in French
|
|
5
11
|
and English.
|
|
@@ -94,6 +100,17 @@ clou de girofle, and a count of those lands on a whole number.
|
|
|
94
100
|
other is the page's own claim.
|
|
95
101
|
- `4 tablespoons` at a tenth comes down to **1.2 teaspoons**. A measurement moves
|
|
96
102
|
to a smaller unit _before_ it is rounded, so a small share survives.
|
|
103
|
+
- `1 lb 4 oz beef` doubled gives **2.5 lb beef**, and `1 kg 500 de farine` gives
|
|
104
|
+
**3 kg de farine**. Two measures off one ladder written side by side are one
|
|
105
|
+
quantity, and the smaller one can be left unwritten.
|
|
106
|
+
- `12 oz can tomatoes` comes back as published, with a note saying the measure
|
|
107
|
+
gives what one tin holds and that the line states no count of them.
|
|
108
|
+
- `2 h de repos` comes back as published. A rest, a proof or a bake takes as long
|
|
109
|
+
for a large batch as for a small one, so a length of time is not a proportion.
|
|
110
|
+
- `quelques feuilles de basilic` comes back as published, as do `a few basil
|
|
111
|
+
leaves` and the same line with `plusieurs`. A word saying there are several
|
|
112
|
+
states no count, and any figure put behind it would be this server's reading
|
|
113
|
+
rather than the page's quantity.
|
|
97
114
|
|
|
98
115
|
Every line comes back with a `scaling` field: `scaled` when the arithmetic
|
|
99
116
|
landed on the exact product, `rounded` when something had to move for the line
|
|
@@ -145,12 +162,70 @@ one costs.
|
|
|
145
162
|
| `query` | string | A dish or an ingredient, in any language a source publishes in |
|
|
146
163
|
| `limit_per_source` | integer, 1 to 25, default 5 | Rows to take from each source |
|
|
147
164
|
| `sources` | array of source ids, optional | Left out, every source is asked |
|
|
165
|
+
| `fan_out` | boolean, default true | Whether shorter wordings derived from the question go out too |
|
|
148
166
|
|
|
149
167
|
The query goes to each source's own search as free text. There is no filtering:
|
|
150
168
|
a word naming a diet, a time or a calorie count matches only where that source's
|
|
151
169
|
index happens to carry it, and a question long enough to read like a request for
|
|
152
170
|
conditions gets a note saying so.
|
|
153
171
|
|
|
172
|
+
**A question asked in a sentence is also sent in shorter wordings.** These
|
|
173
|
+
indexes answer the words they are handed. One of them requires every word given
|
|
174
|
+
to appear on the same page, so "comment faire un ceviche de truite à l'origan"
|
|
175
|
+
comes back empty from a corpus holding four ceviche recipes; the other ranks on
|
|
176
|
+
the words it was handed, so "comment faire" pulls up whatever shares those words
|
|
177
|
+
and the dish falls out of the list. Both reach a reader looking like a statement
|
|
178
|
+
about a corpus when they are statements about a wording.
|
|
179
|
+
|
|
180
|
+
So a question is turned into an ordered ladder of at most three wordings: the
|
|
181
|
+
words as asked, the words naming the dish with the framing words set aside, and
|
|
182
|
+
the leading dish word on its own. They go out in that order, one at a time, and
|
|
183
|
+
stop as soon as enough rows have come back, so a query that already names a dish
|
|
184
|
+
costs one request. What comes back is unioned and deduplicated on the id naming
|
|
185
|
+
its source. `per_source` lists every wording, what each returned, and the ones
|
|
186
|
+
held back and why. Nothing is translated between the sources' languages: a
|
|
187
|
+
guessed translation is a word nobody wrote, and a source that publishes in
|
|
188
|
+
another language and holds nothing under the words written is an honest absence.
|
|
189
|
+
|
|
190
|
+
**A condition is not a keyword.** "sans beurre", "gluten free" and "pour 6
|
|
191
|
+
personnes" say what a recipe must avoid or serve. No page is named for what it
|
|
192
|
+
leaves out, and the number of people at a table is not printed in a title, so
|
|
193
|
+
searching for those words buries the dish. They stay in the wording sent first,
|
|
194
|
+
where a source whose index carries them can still match, and they are set aside
|
|
195
|
+
from the derived wordings. Dropping them quietly would be the worse move, since
|
|
196
|
+
a recipe found this way can hold exactly what the person was avoiding, so every
|
|
197
|
+
condition set aside is named back in the notes with a reminder to check the
|
|
198
|
+
ingredient list. A number of servings is pointed at the `servings` argument,
|
|
199
|
+
which is what actually rescales a recipe.
|
|
200
|
+
|
|
201
|
+
A condition is read by the role its words play rather than by the turn of phrase
|
|
202
|
+
they are written in, because a list of phrasings has a hole at the first one
|
|
203
|
+
nobody thought of. Three roles put a food out of a dish: a negation, whatever
|
|
204
|
+
word carries it, since negations are a closed class of function words and
|
|
205
|
+
"without butter", "no butter" and "pas de beurre" are all built on one; a
|
|
206
|
+
sentence about the eater rather than the dish, as in "je suis allergique aux
|
|
207
|
+
noix", which is named back as an allergy because that is the condition whose
|
|
208
|
+
failure is a matter of health; and a diet named in one word, which is a class of
|
|
209
|
+
recipes defined by what it leaves out and so states an exclusion with no
|
|
210
|
+
negation written anywhere. A question can still phrase one in a way this reading
|
|
211
|
+
does not catch, and the answer says so rather than presenting the ones it caught
|
|
212
|
+
as all there were.
|
|
213
|
+
|
|
214
|
+
Which words a condition covers is read off the sentence. A joining word straight
|
|
215
|
+
after the marker says its food follows, as in "allergique aux noix", "allergic
|
|
216
|
+
to peanuts" or "free of dairy"; with no such word, the food is the one the
|
|
217
|
+
marker was written onto the end of, as in "peanut allergy", "lactose intolerant"
|
|
218
|
+
or "gluten free". Both orders are needed, since the two languages these sources
|
|
219
|
+
publish in put the food on opposite sides. A food's name ends where nothing
|
|
220
|
+
carries it further, so "sans gluten gateau au chocolat" leaves out gluten and
|
|
221
|
+
looks for the cake. Where a marker has a food on neither side, the answer says a
|
|
222
|
+
condition was stated and that its food was not read: naming the wrong word hides
|
|
223
|
+
a dish and leaves the food being avoided in the search at the same time, and an
|
|
224
|
+
allergen is not something to guess at.
|
|
225
|
+
|
|
226
|
+
Setting `fan_out` to false sends exactly the words typed. The wordings that were
|
|
227
|
+
not sent are still named, so a caller can see what turning it back on would buy.
|
|
228
|
+
|
|
154
229
|
Rows are interleaved one source at a time. No score orders them against each
|
|
155
230
|
other, because the sources share none: one carries reader ratings and another
|
|
156
231
|
has no author and no rating by nature. The answer says how the order was built.
|
|
@@ -188,8 +263,17 @@ the lower end, and the answer gives the factor the upper end would have needed.
|
|
|
188
263
|
|
|
189
264
|
Some sources keep recipes and reference pages under one namespace, so a search
|
|
190
265
|
row can be a page _about_ a dish rather than a recipe _for_ it. Only reading the
|
|
191
|
-
page tells them apart
|
|
192
|
-
|
|
266
|
+
page tells them apart.
|
|
267
|
+
|
|
268
|
+
A part of a recipe this answer holds nothing for is never reported as an absence
|
|
269
|
+
on the page, because reading nothing is a fact about this server and only the
|
|
270
|
+
page can say whether there was anything to read. Where the page shows a sign of
|
|
271
|
+
the part all the same, a heading announcing it or another part of the recipe
|
|
272
|
+
that was read, the answer states that this server failed to read what the page
|
|
273
|
+
carries. Where the page shows neither, it states that the two cannot be told
|
|
274
|
+
apart from here. Either way an empty ingredient list is never evidence that an
|
|
275
|
+
ingredient is absent from the dish: someone avoiding butter has to open the page
|
|
276
|
+
before ruling it out.
|
|
193
277
|
|
|
194
278
|
### `scale_ingredients`
|
|
195
279
|
|
|
@@ -466,6 +550,18 @@ d'œuf se compte entier, le blanc de poulet est une viande et se coupe en deux.
|
|
|
466
550
|
vaut l'autre, c'est la page qui l'affirme.
|
|
467
551
|
- `4 tablespoons` au dixième descend à **1.2 teaspoons**. Une mesure passe à une
|
|
468
552
|
unité plus petite _avant_ d'être arrondie, pour qu'une petite part survive.
|
|
553
|
+
- `1 lb 4 oz beef` doublé donne **2.5 lb beef**, et `1 kg 500 de farine` donne
|
|
554
|
+
**3 kg de farine**. Deux mesures d'une même échelle écrites côte à côte sont
|
|
555
|
+
une seule quantité, et la plus petite peut rester sous-entendue.
|
|
556
|
+
- `12 oz can tomatoes` revient telle que publiée, avec la mention que la mesure
|
|
557
|
+
donne ce que tient une boîte et que la ligne n'énonce aucun nombre de boîtes.
|
|
558
|
+
- `2 h de repos` revient telle que publiée. Un repos, une pousse ou une cuisson
|
|
559
|
+
durent autant pour une grande fournée que pour une petite : une durée n'est pas
|
|
560
|
+
une proportion.
|
|
561
|
+
- `quelques feuilles de basilic` revient telle que publiée, comme `a few basil
|
|
562
|
+
leaves` et comme la même ligne avec `plusieurs`. Un mot qui dit qu'il y en a
|
|
563
|
+
plusieurs n'énonce aucun compte, et tout chiffre placé derrière lui serait une
|
|
564
|
+
lecture de ce serveur plutôt qu'une quantité de la page.
|
|
469
565
|
|
|
470
566
|
Chaque ligne revient avec un champ `scaling` : `scaled` quand le calcul est tombé
|
|
471
567
|
juste, `rounded` quand quelque chose a dû bouger pour que la ligne reste
|
|
@@ -504,17 +600,78 @@ npx -y mcp-recipes
|
|
|
504
600
|
Interroge toutes les sources en parallèle : les demander toutes coûte à peu près
|
|
505
601
|
ce que coûte en demander une.
|
|
506
602
|
|
|
507
|
-
| Argument | Type | Sens
|
|
508
|
-
| ------------------ | ------------------------------------------- |
|
|
509
|
-
| `query` | string | Un plat ou un ingrédient, dans une des langues des sources
|
|
510
|
-
| `limit_per_source` | entier, 1 à 25, défaut 5 | Lignes prises à chaque source
|
|
511
|
-
| `sources` | tableau d'identifiants de source, optionnel | Absent, toutes sont interrogées
|
|
603
|
+
| Argument | Type | Sens |
|
|
604
|
+
| ------------------ | ------------------------------------------- | ------------------------------------------------------------------- |
|
|
605
|
+
| `query` | string | Un plat ou un ingrédient, dans une des langues des sources |
|
|
606
|
+
| `limit_per_source` | entier, 1 à 25, défaut 5 | Lignes prises à chaque source |
|
|
607
|
+
| `sources` | tableau d'identifiants de source, optionnel | Absent, toutes sont interrogées |
|
|
608
|
+
| `fan_out` | booléen, défaut true | Envoyer aussi des formulations plus courtes dérivées de la question |
|
|
512
609
|
|
|
513
610
|
La requête part vers la recherche de chaque source en texte libre. Il n'y a
|
|
514
611
|
aucun filtrage : un mot désignant un régime, un temps ou un nombre de calories
|
|
515
612
|
ne compte que si l'index de la source le porte, et une question assez longue
|
|
516
613
|
pour ressembler à une demande de conditions reçoit une note qui le dit.
|
|
517
614
|
|
|
615
|
+
**Une question posée en phrase part aussi sous des formulations plus courtes.**
|
|
616
|
+
Ces index répondent aux mots qu'on leur donne. L'un exige que chaque mot fourni
|
|
617
|
+
figure sur la même page : « comment faire un ceviche de truite à l'origan » en
|
|
618
|
+
revient vide alors que le corpus tient quatre recettes de ceviche. L'autre
|
|
619
|
+
classe sur les mots reçus : « comment faire » remonte tout ce qui les partage et
|
|
620
|
+
le plat sort de la liste. Dans les deux cas, le lecteur reçoit ce qui ressemble
|
|
621
|
+
à un constat sur un corpus alors que c'est un constat sur une formulation.
|
|
622
|
+
|
|
623
|
+
Une question devient donc une échelle ordonnée d'au plus trois formulations :
|
|
624
|
+
les mots tels quels, les mots qui nomment le plat une fois écartés ceux qui
|
|
625
|
+
encadrent la question, puis le premier mot du plat seul. Elles partent dans cet
|
|
626
|
+
ordre, une à la fois, et s'arrêtent dès qu'il y a assez de lignes : une requête
|
|
627
|
+
qui nomme déjà un plat coûte une requête. Ce qui revient est réuni et dédupliqué
|
|
628
|
+
sur l'identifiant qui nomme sa source. `per_source` liste chaque formulation, ce
|
|
629
|
+
qu'elle a rendu, et celles qui ont été retenues avec la raison. Rien n'est
|
|
630
|
+
traduit d'une langue de source vers l'autre : une traduction devinée est un mot
|
|
631
|
+
que personne n'a écrit, et une source qui publie dans une autre langue et ne
|
|
632
|
+
tient rien sous les mots écrits est une absence honnête.
|
|
633
|
+
|
|
634
|
+
**Une contrainte n'est pas un mot-clé.** « sans beurre », « gluten free » et
|
|
635
|
+
« pour 6 personnes » disent ce que la recette doit éviter ou servir. Aucune page
|
|
636
|
+
n'est nommée d'après ce qu'elle exclut, et le nombre de convives ne figure pas
|
|
637
|
+
dans un titre : chercher ces mots enterre le plat. Ils restent dans la
|
|
638
|
+
formulation envoyée en premier, où une source dont l'index les porte peut encore
|
|
639
|
+
les faire correspondre, et ils sont écartés des formulations dérivées. Les
|
|
640
|
+
laisser tomber en silence serait pire, puisque la recette trouvée peut contenir
|
|
641
|
+
exactement ce que la personne évitait : chaque contrainte écartée est donc
|
|
642
|
+
rappelée dans les notes, avec l'invitation à vérifier la liste d'ingrédients. Un
|
|
643
|
+
nombre de parts est renvoyé vers l'argument `servings`, qui est ce qui remet
|
|
644
|
+
réellement une recette à l'échelle.
|
|
645
|
+
|
|
646
|
+
Une contrainte se lit au rôle que jouent ses mots, pas à la tournure dans
|
|
647
|
+
laquelle ils sont écrits : une liste de tournures se troue au premier cas non
|
|
648
|
+
prévu. Trois rôles sortent un aliment du plat. Une négation, quel que soit le
|
|
649
|
+
mot qui la porte, puisque les négations forment une classe fermée de mots
|
|
650
|
+
grammaticaux et que « sans beurre », « no butter » et « pas de beurre » reposent
|
|
651
|
+
toutes sur l'une d'elles. Une phrase sur la personne plutôt que sur le plat,
|
|
652
|
+
comme « je suis allergique aux noix », rappelée comme une allergie parce que
|
|
653
|
+
c'est la contrainte dont l'échec relève de la santé. Et un régime nommé en un
|
|
654
|
+
mot, qui est une classe de recettes définie par ce qu'elle exclut et énonce donc
|
|
655
|
+
une exclusion sans qu'aucune négation soit écrite. Une question peut encore
|
|
656
|
+
formuler une contrainte que cette lecture ne saisit pas, et la réponse le dit
|
|
657
|
+
plutôt que de présenter les contraintes lues comme les seules présentes.
|
|
658
|
+
|
|
659
|
+
La portée d'une contrainte se lit dans la phrase. Un mot de liaison juste après
|
|
660
|
+
le marqueur dit que l'aliment le suit : « allergique aux noix », « allergic to
|
|
661
|
+
peanuts », « free of dairy ». Sans ce mot, l'aliment est celui sur lequel le
|
|
662
|
+
marqueur a été accolé : « peanut allergy », « lactose intolerant », « gluten
|
|
663
|
+
free ». Les deux ordres sont nécessaires, puisque les deux langues que publient
|
|
664
|
+
ces sources placent l'aliment de part et d'autre. Le nom d'un aliment s'arrête
|
|
665
|
+
là où plus rien ne le prolonge, si bien que « sans gluten gateau au chocolat »
|
|
666
|
+
écarte le gluten et cherche le gâteau. Quand un marqueur n'a d'aliment d'aucun
|
|
667
|
+
côté, la réponse dit qu'une contrainte a été énoncée et que son aliment n'a pas
|
|
668
|
+
été lu : nommer le mauvais mot cache un plat et laisse en même temps dans la
|
|
669
|
+
recherche l'aliment que l'on évite, et un allergène ne se devine pas.
|
|
670
|
+
|
|
671
|
+
Mettre `fan_out` à false envoie exactement les mots saisis. Les formulations non
|
|
672
|
+
envoyées sont quand même nommées, pour qu'un appelant voie ce que le réactiver
|
|
673
|
+
lui apporterait.
|
|
674
|
+
|
|
518
675
|
Les lignes alternent d'une source à l'autre. Aucune note ne les classe les unes
|
|
519
676
|
contre les autres, faute d'échelle commune : l'une porte des avis de lecteurs,
|
|
520
677
|
l'autre n'a par nature ni auteur ni note. La réponse dit comment l'ordre a été
|
|
@@ -552,6 +709,16 @@ disant pourquoi. Une page qui annonce une fourchette, « 4 à 6 personnes », es
|
|
|
552
709
|
recalculée depuis la borne basse, et la réponse donne le facteur qu'aurait
|
|
553
710
|
demandé la borne haute.
|
|
554
711
|
|
|
712
|
+
Une partie de recette dont cette réponse ne tient rien n'est jamais rapportée
|
|
713
|
+
comme une absence sur la page : n'avoir rien lu est un fait sur ce serveur, et
|
|
714
|
+
seule la page peut dire s'il y avait quelque chose à lire. Quand la page en
|
|
715
|
+
montre malgré tout un signe, un titre de section qui l'annonce ou une autre
|
|
716
|
+
partie de la recette qui a été lue, la réponse dit que ce serveur n'a pas su
|
|
717
|
+
lire ce que la page porte. Quand la page n'en montre aucun, elle dit que les
|
|
718
|
+
deux cas sont indiscernables d'ici. Dans les deux cas, une liste d'ingrédients
|
|
719
|
+
vide ne prouve jamais qu'un ingrédient est absent du plat : qui évite le beurre
|
|
720
|
+
doit ouvrir la page avant de l'écarter.
|
|
721
|
+
|
|
555
722
|
### `scale_ingredients`
|
|
556
723
|
|
|
557
724
|
Hors ligne. Rien n'est téléchargé. La liste peut venir de n'importe quelle
|