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 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
+ [![npm](https://img.shields.io/npm/v/mcp-recipes.svg)](https://www.npmjs.com/package/mcp-recipes)
4
+ [![CI](https://github.com/smeet666/mcp-recipes/actions/workflows/ci.yml/badge.svg)](https://github.com/smeet666/mcp-recipes/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/mcp-recipes.svg)](LICENSE)
6
+ [![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-recipes)
7
+ [![Glama](https://glama.ai/mcp/servers/smeet666/mcp-recipes/badges/score.svg)](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, and this tool says when a page carries no ingredient
192
- list.
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