mcp-marmiton 2.0.0 → 2.1.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/README.md CHANGED
@@ -6,20 +6,29 @@
6
6
  [![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-marmiton)
7
7
  [![Glama](https://glama.ai/mcp/servers/smeet666/mcp-marmiton/badges/score.svg)](https://glama.ai/mcp/servers/smeet666/mcp-marmiton)
8
8
  [![M8ven](https://m8ven.ai/badge/mcp/smeet666-mcp-marmiton-1f9qvs?variant=verified)](https://m8ven.ai/mcp/smeet666-mcp-marmiton-1f9qvs)
9
+ [![LobeHub](https://lobehub.com/badge/mcp/smeet666-mcp-marmiton)](https://lobehub.com/mcp/smeet666-mcp-marmiton)
9
10
  [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=marmiton&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1tYXJtaXRvbiJdfQ%3D%3D)
10
11
  [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=marmiton&config=%7B%22name%22%3A%22marmiton%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-marmiton%22%5D%7D)
12
+
11
13
  <!-- m8ven-verify: 67179fe5844dc3b97dbd373300cca0e0 -->
12
14
 
13
- An [MCP](https://modelcontextprotocol.io) server for [Marmiton](https://www.marmiton.org),
14
- the French recipe site. Search recipes, read their ingredients and steps, and
15
- **rescale the quantities to any number of servings**. **No API key, no account, no
16
- configuration.**
15
+ [Marmiton](https://www.marmiton.org) is the largest French cooking site, where
16
+ home cooks have been publishing their recipes since 1999. Each one gives its
17
+ ingredients with their quantities, the steps to follow, the preparation and
18
+ cooking times, the number of servings it is written for, and the ratings the
19
+ people who made it left behind.
20
+
21
+ This server connects a chat client to that site. You can search its recipes by
22
+ dish or by ingredient, read one in full with its ingredients and its steps, and
23
+ **rescale the quantities to the number of people at your table**, with each line
24
+ saying whether the figure is exact or was moved to stay usable in a kitchen. It
25
+ needs no API key and no account.
17
26
 
18
- _(Version française plus bas / [French version below](#mcp-marmiton-français))_
27
+ _[Version française](#mcp-marmiton-français)_
19
28
 
20
29
  ---
21
30
 
22
- ## Quickstart
31
+ ## Install
23
32
 
24
33
  **One-click install**
25
34
 
@@ -45,6 +54,8 @@ claude mcp add marmiton -- npx -y mcp-marmiton
45
54
  }
46
55
  ```
47
56
 
57
+ Node 24 or later is required, and no environment variable has to be set.
58
+
48
59
  ### With Docker
49
60
 
50
61
  ```json
@@ -52,221 +63,219 @@ claude mcp add marmiton -- npx -y mcp-marmiton
52
63
  "mcpServers": {
53
64
  "marmiton": {
54
65
  "command": "docker",
55
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-marmiton:2.0.0"]
66
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-marmiton:2.1.0"]
56
67
  }
57
68
  }
58
69
  }
59
70
  ```
60
71
 
61
- `-i` keeps stdin open, which is where the protocol travels, and no `-t` is
62
- passed: a TTY rewrites the stream and breaks it. The container needs outbound
63
- HTTPS to `www.marmiton.org`, and nothing else: no volume, no port, no environment variable, no credential.
72
+ `-i` keeps stdin open, which is where the protocol travels, and `-t` is left out
73
+ because a TTY rewrites the stream. The container needs outbound HTTPS to
74
+ `www.marmiton.org`, and nothing else: no volume, no port, no credential.
75
+
76
+ ### Bundle, without npm
77
+
78
+ Download `mcp-marmiton-2.1.0.mcpb` from
79
+ [the latest release](https://github.com/smeet666/mcp-marmiton/releases/latest)
80
+ and open it. A client that supports MCP bundles installs it on its own, with no
81
+ npm and no configuration file to edit. The bundle carries its dependencies, so
82
+ nothing is fetched at install time.
64
83
 
65
- **Bundle, without npm**
84
+ ## What you can ask
66
85
 
67
- Download `mcp-marmiton-<version>.mcpb` from
68
- [the latest release](https://github.com/smeet666/mcp-marmiton/releases/latest) and open
69
- it. A client that supports MCP bundles installs it on its own, with no npm and
70
- no configuration file to edit. The bundle carries its dependencies, so nothing
71
- is fetched at install time.
86
+ - « Trouve-moi une recette de tarte aux pommes. »
87
+ - "Read me that recipe for six people instead of four."
88
+ - "What can I make with courgettes and chèvre?"
89
+ - "Here is a recipe I copied from a book, scale it by 1.5 for me."
90
+ - "How long does the second one take to cook?"
91
+
92
+ Marmiton is a French site, so its recipes are found in French: `tarte aux
93
+ pommes`, `poulet curry coco`. The ordinary path runs from a search to a reading:
94
+ `search_recipes` names an `id`, and `get_recipe` takes that id.
72
95
 
73
96
  ## Tools
74
97
 
75
- | Tool | What it does | Key parameters |
76
- | ------------------- | -------------------------------------- | ------------------------------------------------------- |
77
- | `search_recipes` | Finds recipes by dish or ingredient. | `query`, `limit` |
78
- | `get_recipe` | Reads one recipe, optionally rescaled. | `id`, `url`, `servings` |
79
- | `scale_ingredients` | Rescales any ingredient list, offline. | `ingredients`, `factor`, `from_servings`, `to_servings` |
80
-
81
- Search returns a Marmiton `id` for every result; `get_recipe` takes that id. That
82
- is the intended chain: search, then read.
83
-
84
- The server is **read-only**. It never posts anything to Marmiton.
85
-
86
- ### Scaling is the point
87
-
88
- Asking a language model to divide a recipe by 1.5 tends to produce "2.4 eggs" and
89
- "0.67 pinches of salt", stated with the same confidence as a correct number. This
90
- server does the arithmetic itself and, more importantly, **says what it could not
91
- compute**. Every ingredient comes back with a `scaling` flag:
92
-
93
- | Flag | Meaning | Example |
94
- | ---------- | -------------------------------------------------- | ---------------------------------------- |
95
- | `scaled` | The value is the product itself. | `3 oeufs` ×2 → `6 oeufs` |
96
- | `rounded` | The value had to be moved to stay usable. | `25 cl de lait` ×0.667 → `17 cl de lait` |
97
- | `unscaled` | Carries no quantity, so left exactly as published. | `sel`, `coriandre` |
98
-
99
- A count reaches an exact product as readily as a mass does: one pinch multiplied
100
- by six is six pinches, and that line is `scaled`. `rounded` is for a value that
101
- landed somewhere the arithmetic did not put it, a half egg taken to a whole one
102
- or 133.4 g written as 135 g.
103
-
104
- Two rules are enforced. Scaling a recipe **down never asks for more** than the
105
- original: half a sachet at factor 0.667 stays half a sachet, never a whole one.
106
- And scaling never **silently drops** an ingredient by rounding it to zero, which
107
- is why small amounts come back as fractions.
108
-
109
- **A counted thing is divided by what it holds, not by what holds it.** A boîte de
110
- tomates is poured and the rest kept, a sachet de sucre vanillé is split by eye, a
111
- feuille de gélatine is cut with scissors, a branche de thym is broken in two: all
112
- of those land on a half. Half an oeuf would have to be
113
- beaten and weighed, which is not an amount a recipe asks for, so a count of oeufs,
114
- jaunes or blancs d'oeufs lands on a whole number. A few things are decided by what
115
- they are: a clou de girofle and a zeste are counted whole, a pot and a bouteille
116
- hold enough for a quarter to be a portion, a tranche is cut off
117
- something larger and the board takes a corner off it in the same gesture, a
118
- gousse d'ail is split in two and no finer, a blanc de poulet is
119
- meat and halves, and a douzaine states a number of things rather than a measure
120
- of them, so `2 douzaines d'escargots` at three quarters comes back as
121
- `18 escargots`.
122
-
123
- **A thing counted on its own is divided by the size of one against what a recipe
124
- puts in.** Une crevette, une moule, une noisette, un grain de poivre, une baie de
125
- genièvre, un anis étoilé is already a portion: a recipe counts twelve of them
126
- and a smaller recipe puts one fewer in the pan, so they land on a whole number.
127
- Un gigot, une baguette, un camembert, un ananas, un oignon, une pastèque, une
128
- pintade, un poulet, un poireau sits at
129
- the other end of that comparison, asked for by the one or the two and shared out
130
- with a knife, so they go as far as the quarter. A cut carved off one of them
131
- stops at the half, une cuisse and une aile being the portion the knife already
132
- produced. Un jus stops at the half: half
133
- the jus of a citron is taken by squeezing half the fruit, and a quarter of one
134
- has to be poured out and measured back.
135
-
136
- Approximate measures are quantities too. A pinch, a handful, a bouchon or a
137
- ramequin has the size the cook gives it, and the recipe's proportion lives in how
138
- many are asked for, so the count is multiplied in whole units: `une pincée de
139
- bicarbonate de soude` from 6 servings to 25 comes back as `4 pincées de
140
- bicarbonate de soude`, carrying a note. Nothing is ever
141
- converted into grams or spoons, where published equivalences span a fourfold
142
- range; an order of magnitude belongs in a note, never in the quantity.
143
-
144
- Ranges are read as one quantity: "2 à 3 gousses" doubled reads "4 à 6 gousses",
145
- with `amount` holding the lower bound and `amountMax` the upper one. Where both
146
- ends land on the same amount, the line states that amount once and says so.
147
-
148
- A line that writes an article where a digit would go is read as one of the
149
- measure that follows it: `un bouchon de rhum` scaled sixfold is
150
- `6 bouchons de rhum`, and the note says which word the figure came from. A noun
151
- sitting between the amount and the `de` that introduces what is measured names a
152
- container or a gesture, which is how a measure the vocabulary has never met is
153
- still read. An article before a bare countable thing, as in `un oignon`, leaves
154
- the line as published.
155
-
156
- Each ingredient also carries `adjusted`, which says whether rounding moved the
157
- number away from the exact product.
158
-
159
- A shrinking line keeps the smallest share still worth measuring: a knife takes an
160
- oignon to a quarter, a boîte or a sachet goes to a half, an oeuf stops at one.
161
- Under that floor the amount is clamped up and **the line stops holding its share
162
- of the recipe**. Half a sachet of
163
- baking powder against three pots of flour comes out four times too strong that
164
- way, so the ingredient and the response both say it happened rather than leaving
165
- you to find out in the oven.
166
-
167
- `scale_ingredients` exposes the same logic **without any network request**, so it
168
- also works on a recipe pasted from somewhere else.
169
-
170
- ### Other things worth knowing
171
-
172
- **Marmiton is French.** Queries work best in French: `tarte aux pommes`,
173
- `poulet curry coco`.
174
-
175
- **One page of results.** Marmiton's robots.txt disallows paginating search
176
- results, so this server does not, and there is no page parameter. Narrow the query
177
- instead.
178
-
179
- **Nutrition is not rescaled.** The figures Marmiton publishes describe the recipe
180
- as written, and they are returned as such.
181
-
182
- **Structured data, not scraping.** Everything is read from the `schema.org`
183
- JSON-LD that Marmiton publishes for machines, so there are no CSS selectors to
184
- break when the site is redesigned.
98
+ | Tool | What it does |
99
+ | ------------------- | -------------------------------------------------------------- |
100
+ | `search_recipes` | Finds recipes by dish or by ingredient. |
101
+ | `get_recipe` | Reads one recipe, rescaled to a number of servings on request. |
102
+ | `scale_ingredients` | Rescales any ingredient list, with no request to the site. |
103
+
104
+ The server only reads. It publishes nothing to Marmiton.
105
+
106
+ ### `search_recipes`
107
+
108
+ Searches the recipes for a dish or an ingredient. Marmiton matches on the opening
109
+ letters of a word, so a query brings back what the site ranked for it, and the
110
+ answer says when the titles carry none of the words asked for.
111
+
112
+ | Argument | Type | Required | What it does |
113
+ | -------- | ------------------------------ | -------- | ------------------------------------------- |
114
+ | `query` | string, up to 200 characters | yes | What to search for, in French. |
115
+ | `limit` | integer, 1 to 30, default `10` | no | Recipes to serve from this page of results. |
116
+
117
+ **In return:** rows carrying `id`, which `get_recipe` takes; `title`; `url`; and
118
+ `image_url`, which is `null` for a recipe published without a picture. Alongside
119
+ come `result_count` and `total_available`, the recipes on this page before
120
+ `limit` was applied. Marmiton's robots.txt disallows paging through search
121
+ results, so one page is what a search reads: narrow the query to see other
122
+ recipes.
123
+
124
+ ### `get_recipe`
125
+
126
+ Reads one recipe in full, and rescales its quantities when a number of servings
127
+ is given.
128
+
129
+ | Argument | Type | Required | What it does |
130
+ | ---------- | ----------------------------- | ---------- | -------------------------------------------------------- |
131
+ | `id` | string of digits | one of two | The Marmiton recipe id, as `search_recipes` returned it. |
132
+ | `url` | a marmiton.org URL | one of two | The address of the recipe, used when `id` is absent. |
133
+ | `servings` | number, above 0 and up to 500 | no | Rescale the quantities to this many servings. |
134
+
135
+ **In return:** `title`, `url`, `ingredients`, `steps`, `prep_minutes`,
136
+ `cook_minutes`, `total_minutes`, `category`, `author`, `rating` and `nutrition`,
137
+ each `null` when the page publishes none. `yield` says what the recipe was
138
+ written for and what it was rescaled to: `original_count`, `original_text`,
139
+ `requested`, `unit` for what is being counted, and `factor` for the multiplier
140
+ applied. Every ingredient carries `original`, `text`, `amount`, `amountMax` for a
141
+ range, `unit`, and `scaling`, which reads `scaled`, `rounded` or `unscaled`. The
142
+ figures are this server's arithmetic, so say they were recomputed when you show
143
+ them. `nutrition` describes the recipe as published, at its own number of
144
+ servings.
145
+
146
+ ### `scale_ingredients`
147
+
148
+ Applies the same arithmetic to any list of ingredient lines, with no request to
149
+ the site, so it works on a recipe copied from a book or a family notebook.
150
+
151
+ | Argument | Type | Required | What it does |
152
+ | --------------- | ------------------------------------------ | ---------- | ------------------------------------------ |
153
+ | `ingredients` | array of 1 to 100 strings, up to 300 chars | yes | The lines to rescale, in French. |
154
+ | `factor` | number, above 0 and up to 100 | one of two | The multiplier to apply. |
155
+ | `from_servings` | number, above 0 and up to 500 | one of two | How many servings the list is written for. |
156
+ | `to_servings` | number, above 0 and up to 500 | one of two | How many servings are wanted. |
157
+
158
+ Pass `factor`, or the `from_servings` and `to_servings` pair.
159
+
160
+ **In return:** the `factor` used, the rescaled `ingredients` in the shape
161
+ `get_recipe` returns, and `scaled_count`, `rounded_count` and `unscaled_count`,
162
+ which count the lines whose value the rounding moved.
163
+
164
+ ## Scaling the quantities
165
+
166
+ Every ingredient comes back with a `scaling` flag saying what the rescaling could
167
+ do with its quantity.
168
+
169
+ | Flag | Meaning | Example |
170
+ | ---------- | ------------------------------------------------ | ---------------------------------------- |
171
+ | `scaled` | The value is the product itself. | `3 oeufs` ×2 → `6 oeufs` |
172
+ | `rounded` | The value was moved to stay usable. | `25 cl de lait` ×0.667 → `17 cl de lait` |
173
+ | `unscaled` | Carries no quantity, so left exactly as written. | `sel`, `coriandre` |
174
+
175
+ A quantity is stated in the unit that suits it, so a line can come back in a
176
+ different unit from the one the recipe used: 200 g multiplied by twenty reads
177
+ `4 kg`.
178
+
179
+ How finely an ingredient can be divided depends on what it is. A baguette can be
180
+ cut in two, in three or in four; an egg cannot be shared out. A quantity landing
181
+ between the two is rounded, and the rescaled recipe then departs a little from
182
+ the proportions of the original. The line carries `rounded`, and its `note` says
183
+ what was done.
185
184
 
186
185
  ## Configuration
187
186
 
188
- Every variable is optional. Set them in the `env` block of your MCP client config.
187
+ Every variable is optional. Set them in the `env` block of your client config.
189
188
 
190
- | Variable | Default | Purpose |
191
- | ---------------------------- | -------------------------------------- | -------------------------------------------------------------- |
192
- | `MARMITON_USER_AGENT` | `mcp-marmiton v<version> (<repo url>)` | User-Agent sent to Marmiton. |
193
- | `MARMITON_MIN_INTERVAL_MS` | `1000` | Minimum gap between requests. Values below 500 ms are ignored. |
194
- | `MARMITON_TIMEOUT_MS` | `15000` | Per-request timeout. |
195
- | `MARMITON_MAX_RETRIES` | `3` | Retries on rate limiting and transient errors. |
196
- | `MARMITON_CACHE_TTL_MS` | `900000` | In-memory cache lifetime (15 minutes). |
197
- | `MARMITON_CACHE_MAX_ENTRIES` | `200` | In-memory cache size. |
198
- | `MARMITON_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`. Logs go to stderr. |
189
+ | Variable | Default | What it does |
190
+ | ---------------------------- | -------------------- | --------------------------------------------------------------------------------------------------- |
191
+ | `MARMITON_USER_AGENT` | the project identity | Names your application to Marmiton, with an address where a person can be reached. |
192
+ | `MARMITON_MIN_INTERVAL_MS` | `1000` | Gap between two requests, from 500 to 60000. A figure under the floor is refused and this one used. |
193
+ | `MARMITON_TIMEOUT_MS` | `15000` | Deadline for one request, from 1000 to 120000. |
194
+ | `MARMITON_MAX_RETRIES` | `3` | Attempts after a transient failure, from 0 to 10. |
195
+ | `MARMITON_CACHE_TTL_MS` | `900000` | How long a page stays in memory, from 0 to 86400000. |
196
+ | `MARMITON_CACHE_MAX_ENTRIES` | `200` | Pages held in memory at once, from 0 to 10000. |
197
+ | `MARMITON_MAX_BODY_BYTES` | `8000000` | Largest page this reads, in bytes, from 100000 to 64000000. |
198
+ | `MARMITON_LOG_LEVEL` | `error` | `silent`, `error`, `info` or `debug`, written to stderr. |
199
199
 
200
- ## Troubleshooting
200
+ A value outside its range falls back to the default, and the reason is written to
201
+ stderr.
201
202
 
202
- **`rate_limited` errors.** Marmiton is throttling this client. The server already
203
- retries with backoff and slows itself down. Wait a moment and try again, and raise
204
- `MARMITON_MIN_INTERVAL_MS` if it persists. This never means the recipe is missing.
203
+ ## Errors
205
204
 
206
- **`parse_failure` errors.** Marmiton changed how it publishes its structured data
207
- and the server could not read the response. Please
208
- [open an issue](https://github.com/smeet666/mcp-marmiton/issues) with the recipe
209
- you asked for. The server reports this loudly rather than pretending it found
210
- nothing.
205
+ Every failure carries one of six codes, a message, and where it helps a hint
206
+ naming the next move.
211
207
 
212
- ## Development
208
+ | Code | What happened | What to do |
209
+ | --------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
210
+ | `not_found` | Marmiton answered, and holds no such recipe. | Check the id with `search_recipes`. |
211
+ | `invalid_input` | The arguments were refused before any request went out. | Read the message, which names the argument. |
212
+ | `rate_limited` | Marmiton asked this client to slow down. | Wait the number of seconds the hint names and call again with the same arguments. The recipe is still there. |
213
+ | `parse_failure` | The page loaded and the expected content was absent. | Report it at [the issue tracker](https://github.com/smeet666/mcp-marmiton/issues). |
214
+ | `network_error` | The request did not complete. | Try again shortly. |
215
+ | `timeout` | The request passed its deadline. | Raise `MARMITON_TIMEOUT_MS`. |
213
216
 
214
- ```bash
215
- npm install
216
- npm run build:fixtures # regenerate the HTML test fixtures
217
- npm test # unit tests, no network
218
- npm run typecheck
219
- npm run build
220
- MARMITON_LIVE=1 npm run test:live # hits the real site, excluded from CI
221
- npm run inspector # explore the tools in the MCP Inspector
217
+ ## As a library
218
+
219
+ The layer reading Marmiton is published on its own, with its pacing, its cache
220
+ and its errors, and with no protocol attached.
221
+
222
+ ```ts
223
+ import { MarmitonClient } from "mcp-marmiton/client";
224
+
225
+ const client = new MarmitonClient();
226
+ const { data, cached } = await client.getRecipe({ id: "18257" });
227
+ console.log(data.title, data.ingredients.length, cached);
222
228
  ```
223
229
 
224
- Fixtures are generated, not captured: they reproduce Marmiton's JSON-LD shape with
225
- invented recipes, so the tests are deterministic and no Marmiton content lives in
226
- this repository.
230
+ `search` and `getRecipe` each answer `{ data, cached }`, and throw an error
231
+ carrying one of the six codes. The floor between two requests holds here as well.
227
232
 
228
- The scraping layer (`src/marmiton`, `src/recipe`) does not import the MCP SDK and
229
- is published separately as `mcp-marmiton/client`, so it can be used as a plain
230
- library.
233
+ ## Pacing and attribution
231
234
 
232
- ## Recipes, copyright and cooking
235
+ Requests go out one at a time with a minimum gap between them, and that floor
236
+ holds however the server is configured. The `User-Agent` always ends with the
237
+ project identity and an address where a person can be reached. Everything is read
238
+ from the `schema.org` JSON-LD Marmiton publishes for machines, and the paths its
239
+ robots.txt disallows are left alone.
233
240
 
234
- Ingredient lists and cooking steps are facts and procedures. The descriptive prose
235
- an author writes around them is their work, and this server does not return it:
236
- you get what you need to cook, plus a link to the original page.
241
+ Every result carries the title and the address of the recipe, and `get_recipe`
242
+ carries the author when the page names one, along with `attribution`, the title
243
+ and the address written as one line.
237
244
 
238
- This server is a client. It reads the structured data Marmiton publishes for
239
- machines, on demand, one request at a time, in response to an explicit request
240
- from you or your assistant. It does not crawl the site, does not build a recipe
241
- database, and writes nothing to disk. It honours Marmiton's robots.txt, including
242
- the rule against paginating search results.
245
+ The recipes belong to Marmiton and to the cooks who wrote them. This MCP server
246
+ is an unofficial project, with no affiliation to Marmiton.
243
247
 
244
- Every result carries the recipe title and its source URL. If you display or reuse
245
- anything this server returns, keep that attribution and link back to Marmiton.
248
+ ## Privacy
246
249
 
247
- Rescaled quantities are computed, and cooking is not arithmetic: baking in
248
- particular does not always scale linearly. Read them as a helpful starting point
249
- and use your judgement.
250
+ This server collects nothing about you and sends nothing to its author. It runs
251
+ on your machine, contacts `www.marmiton.org` and nothing else, holds its answers in memory
252
+ while it runs, and writes nothing to disk.
253
+ [PRIVACY.md](PRIVACY.md) states what a request carries and which settings change
254
+ any of it.
250
255
 
251
- This is an unofficial project, with no affiliation to or endorsement by Marmiton.
256
+ ## Development
252
257
 
253
- ## Contributing
258
+ ```bash
259
+ npm install
260
+ npm run build:fixtures
261
+ npm test
262
+ npm run check
263
+ ```
254
264
 
255
- Bugs, questions and ideas all belong in
256
- [the issue tracker](https://github.com/smeet666/mcp-marmiton/issues). Pull requests
257
- are welcome; please open an issue first so we can agree on what the right
258
- answer is before you write it. [CONTRIBUTING.md](CONTRIBUTING.md) has the
259
- detail, and [SECURITY.md](SECURITY.md) covers anything exploitable.
265
+ Tests run against generated fixtures and make no network request. The live suite,
266
+ `npm run test:live`, makes one request per route and runs nightly against the
267
+ site itself.
260
268
 
261
- ## Support
269
+ ## Contributing
262
270
 
263
- These servers are free and stay free. If one of them saved you an afternoon,
264
- you can [buy me a coffee](https://buymeacoffee.com/smeet666).
271
+ Bugs, questions and ideas belong in
272
+ [the issue tracker](https://github.com/smeet666/mcp-marmiton/issues). Pull
273
+ requests are welcome; opening an issue first helps agree on the shape of the
274
+ change. See [CONTRIBUTING.md](CONTRIBUTING.md).
265
275
 
266
276
  ## License
267
277
 
268
- MIT. See [LICENSE](./LICENSE). The license covers this source code only, not the
269
- recipes retrieved through it.
278
+ MIT, see [LICENSE](LICENSE). The recipes belong to Marmiton and to their authors.
270
279
 
271
280
  ---
272
281
 
@@ -274,11 +283,21 @@ recipes retrieved through it.
274
283
 
275
284
  # mcp-marmiton (français)
276
285
 
277
- Un serveur [MCP](https://modelcontextprotocol.io) pour [Marmiton](https://www.marmiton.org).
278
- Cherchez des recettes, lisez leurs ingrédients et leurs étapes, et **adaptez les
279
- quantités au nombre de convives**. **Sans clé d'API, sans compte, sans configuration.**
286
+ _[English version](#mcp-marmiton)_
280
287
 
281
- ## Démarrage rapide
288
+ [Marmiton](https://www.marmiton.org) est le plus grand site de cuisine français,
289
+ où des cuisiniers publient leurs recettes depuis 1999. Chacune donne ses
290
+ ingrédients avec leurs quantités, les étapes à suivre, les temps de préparation
291
+ et de cuisson, le nombre de parts pour lequel elle est écrite, et les notes
292
+ laissées par ceux qui l'ont faite.
293
+
294
+ Ce serveur relie un client de conversation à ce site. On peut y chercher des
295
+ recettes par plat ou par ingrédient, en lire une en entier avec ses ingrédients
296
+ et ses étapes, et **adapter les quantités au nombre de convives**, chaque ligne
297
+ disant si le chiffre est exact ou s'il a été déplacé pour rester utilisable en
298
+ cuisine. Aucune clé d'API, aucun compte.
299
+
300
+ ## Installation
282
301
 
283
302
  **Installation en un clic**
284
303
 
@@ -291,7 +310,7 @@ quantités au nombre de convives**. **Sans clé d'API, sans compte, sans configu
291
310
  claude mcp add marmiton -- npx -y mcp-marmiton
292
311
  ```
293
312
 
294
- **Claude Desktop, Cursor, et tout client utilisant le format standard**
313
+ **Claude Desktop, Cursor, et tout client au format de configuration standard**
295
314
 
296
315
  ```json
297
316
  {
@@ -304,6 +323,9 @@ claude mcp add marmiton -- npx -y mcp-marmiton
304
323
  }
305
324
  ```
306
325
 
326
+ Node 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à
327
+ renseigner.
328
+
307
329
  ### Avec Docker
308
330
 
309
331
  ```json
@@ -311,196 +333,220 @@ claude mcp add marmiton -- npx -y mcp-marmiton
311
333
  "mcpServers": {
312
334
  "marmiton": {
313
335
  "command": "docker",
314
- "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-marmiton:2.0.0"]
336
+ "args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-marmiton:2.1.0"]
315
337
  }
316
338
  }
317
339
  }
318
340
  ```
319
341
 
320
- `-i` garde l'entrée standard ouverte, qui est le canal du protocole, et aucun
321
- `-t` n'est passé : un terminal réécrit le flux et le casse. Le conteneur a besoin
322
- d'un accès HTTPS sortant vers `www.marmiton.org`, et de rien d'autre :
323
- aucun volume, aucun port, aucune variable d'environnement, aucun identifiant.
324
-
325
- **Bundle, sans npm**
326
-
327
- Téléchargez `mcp-marmiton-<version>.mcpb` depuis
328
- [la dernière release](https://github.com/smeet666/mcp-marmiton/releases/latest) et
329
- ouvrez-le. Un client compatible avec les bundles MCP l'installe seul, sans npm
330
- ni fichier de configuration à modifier. Le bundle embarque ses dépendances,
331
- donc rien n'est téléchargé à l'installation.
332
-
333
- ## Outils
334
-
335
- | Outil | Rôle | Paramètres principaux |
336
- | ------------------- | ------------------------------------------- | ------------------------------------------------------- |
337
- | `search_recipes` | Trouve des recettes par plat ou ingrédient. | `query`, `limit` |
338
- | `get_recipe` | Lit une recette, avec adaptation possible. | `id`, `url`, `servings` |
339
- | `scale_ingredients` | Adapte n'importe quelle liste, hors ligne. | `ingredients`, `factor`, `from_servings`, `to_servings` |
340
-
341
- La recherche renvoie un `id` Marmiton pour chaque résultat, et `get_recipe` prend
342
- cet id. C'est l'enchaînement prévu : chercher, puis lire.
343
-
344
- Le serveur est en **lecture seule**. Il ne publie jamais rien sur Marmiton.
345
-
346
- ### L'adaptation des quantités est le cœur du sujet
347
-
348
- Demander à un modèle de diviser une recette par 1,5 produit volontiers « 2,4 œufs »
349
- et « 0,67 pincée de sel », énoncés avec le même aplomb qu'un chiffre juste. Ce
350
- serveur fait le calcul lui-même et, surtout, **signale ce qu'il n'a pas pu
351
- calculer**. Chaque ingrédient porte un indicateur `scaling` :
352
-
353
- | Indicateur | Sens | Exemple |
354
- | ---------- | ---------------------------------------------------- | ---------------------------------------- |
355
- | `scaled` | La valeur est le produit exact. | `3 oeufs` ×2 → `6 oeufs` |
356
- | `rounded` | La valeur a dû être déplacée pour rester utilisable. | `25 cl de lait` ×0,667 → `17 cl de lait` |
357
- | `unscaled` | Ne porte aucune quantité, laissé tel que publié. | `sel`, `coriandre` |
358
-
359
- Un décompte tombe juste aussi bien qu'une masse : une pincée multipliée par six
360
- fait six pincées, et cette ligne est `scaled`. `rounded` désigne une valeur posée
361
- ailleurs que là où le calcul la mettait, un demi-œuf ramené à un œuf entier ou
362
- 133,4 g écrits 135 g.
363
-
364
- Deux règles sont garanties. Réduire une recette **ne demande jamais davantage** que
365
- l'originale : un demi-sachet au facteur 0,667 reste un demi-sachet, jamais un
366
- sachet entier. Et l'adaptation ne **supprime jamais** un ingrédient en l'arrondissant
367
- à zéro, d'où les fractions pour les petites quantités.
368
-
369
- **Un dénombrable se divise selon son contenu, pas selon son emballage.** Une boîte
370
- de tomates se verse et le reste se garde, un sachet de sucre vanillé se partage à
371
- l'œil, une feuille de gélatine se coupe aux ciseaux, une branche de thym se casse
372
- en deux : tous tombent sur une demie. Un demi-œuf, lui,
373
- demanderait de le battre et de le peser, ce qu'aucune recette ne demande : un
374
- nombre d'œufs, de jaunes ou de blancs d'œufs tombe sur un entier. Quelques cas se
375
- décident sur la nature de la chose : un clou de girofle et un zeste se comptent
376
- entiers, un pot et une bouteille en contiennent assez pour qu'un quart soit une
377
- portion, une tranche est déjà taillée dans plus grand et la planche lui reprend
378
- un coin du même geste, une gousse d'ail se coupe en deux et pas plus fin, un
379
- blanc de poulet est une viande et
380
- se coupe en deux, et une douzaine
381
- énonce un nombre de choses plutôt qu'une mesure, si bien que
382
- « 2 douzaines d'escargots » réduites d'un quart reviennent en « 18 escargots ».
383
-
384
- Les mesures approximatives sont des quantités elles aussi. Une pincée, une
385
- poignée, un bouchon ou un ramequin ont la taille que le cuisinier leur donne,
386
- et la proportion de la recette tient au nombre demandé : ce nombre est donc
387
- multiplié, en unités entières. « une pincée de bicarbonate de soude » de 6 à 25
388
- parts revient en « 4 pincées de bicarbonate de soude », accompagnée d'une
389
- note. Aucune conversion en grammes ni en cuillères : les
390
- équivalences publiées varient du simple au quadruple, et un ordre de grandeur a
391
- sa place dans une note, jamais dans la quantité.
392
-
393
- Les fourchettes sont lues comme une seule quantité : « 2 à 3 gousses » doublé
394
- donne « 4 à 6 gousses », `amount` portant la borne basse et `amountMax` la borne
395
- haute. Quand les deux bornes tombent sur la même valeur, la ligne énonce cette
396
- valeur une fois et le signale.
397
-
398
- Une ligne qui écrit un article là où irait un chiffre est lue comme une unité de
399
- la mesure qui suit : « un bouchon de rhum » multiplié par six donne « 6 bouchons
400
- de rhum », et la note indique de quel mot vient le chiffre. Un nom placé entre la
401
- quantité et le « de » qui introduit ce qui est mesuré désigne un contenant ou un
402
- geste, ce qui permet de lire une mesure absente du vocabulaire. Un article devant
403
- une chose dénombrable seule, comme « un oignon », laisse la ligne telle que
404
- publiée.
405
-
406
- Chaque ingrédient porte aussi `adjusted`, qui dit si l'arrondi a déplacé le
407
- nombre par rapport au produit exact.
408
-
409
- Une ligne qui rétrécit garde la plus petite part qui vaille encore la peine : le
410
- couteau mène un oignon au quart, une boîte ou un sachet s'arrêtent à la demie, un
411
- œuf s'arrête à l'unité. Sous ce plancher, la quantité est remontée et **la ligne
412
- cesse de tenir sa part de la recette**. Un demi-sachet
413
- de levure face à trois pots de farine ressort ainsi quatre fois trop dosé, donc
414
- l'ingrédient et la réponse le disent, plutôt que de vous le laisser découvrir au
415
- four.
416
-
417
- `scale_ingredients` expose la même logique **sans aucune requête réseau**, ce qui
418
- permet aussi d'adapter une recette venue d'ailleurs.
419
-
420
- ### Autres points utiles
421
-
422
- **Marmiton est un site français.** Les requêtes fonctionnent mieux en français :
423
- `tarte aux pommes`, `poulet curry coco`.
424
-
425
- **Une seule page de résultats.** Le robots.txt de Marmiton interdit de paginer les
426
- résultats de recherche, donc ce serveur ne le fait pas et n'expose aucun paramètre
427
- de page. Affinez plutôt la requête.
428
-
429
- **La nutrition n'est pas recalculée.** Les valeurs publiées par Marmiton décrivent
430
- la recette telle quelle, et sont restituées comme telles.
431
-
432
- **Données structurées, pas de scraping.** Tout est lu dans le JSON-LD `schema.org`
433
- que Marmiton publie à destination des machines, donc aucun sélecteur CSS ne peut
434
- casser lors d'une refonte du site.
342
+ `-i` garde l'entrée standard ouverte, qui est le canal du protocole, et `-t` est
343
+ omis parce qu'un TTY réécrit le flux. Le conteneur a besoin d'un accès HTTPS
344
+ sortant vers `www.marmiton.org`, et de rien d'autre : aucun volume, aucun port,
345
+ aucun identifiant.
346
+
347
+ ### Bundle, sans npm
348
+
349
+ Téléchargez `mcp-marmiton-2.1.0.mcpb` depuis
350
+ [la dernière publication](https://github.com/smeet666/mcp-marmiton/releases/latest)
351
+ et ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et
352
+ sans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc
353
+ rien n'est téléchargé à l'installation.
354
+
355
+ ## Ce qu'on peut demander
356
+
357
+ - « Trouve-moi une recette de tarte aux pommes. »
358
+ - « Lis-moi cette recette pour six personnes au lieu de quatre. »
359
+ - « Qu'est-ce que je peux faire avec des courgettes et du chèvre ? »
360
+ - « Voici une recette recopiée d'un livre, multiplie-la par 1,5. »
361
+ - « Combien de temps de cuisson pour la deuxième ? »
362
+
363
+ Marmiton est un site français, donc ses recettes se trouvent en français : `tarte
364
+ aux pommes`, `poulet curry coco`. Le chemin ordinaire va d'une recherche à une
365
+ lecture : `search_recipes` nomme un `id`, et `get_recipe` reprend cet
366
+ identifiant.
367
+
368
+ ## Les outils
369
+
370
+ | Outil | Ce qu'il fait |
371
+ | ------------------- | ------------------------------------------------------------------ |
372
+ | `search_recipes` | Trouve des recettes par plat ou par ingrédient. |
373
+ | `get_recipe` | Lit une recette, adaptée à un nombre de parts sur demande. |
374
+ | `scale_ingredients` | Adapte n'importe quelle liste d'ingrédients, sans requête au site. |
375
+
376
+ Le serveur ne fait que lire. Il ne publie rien sur Marmiton.
377
+
378
+ ### `search_recipes`
379
+
380
+ Cherche des recettes par plat ou par ingrédient. Marmiton fait correspondre les
381
+ premières lettres d'un mot, donc une requête ramène ce que le site a classé pour
382
+ elle, et la réponse signale quand les titres ne portent aucun des mots demandés.
383
+
384
+ | Argument | Type | Requis | Ce qu'il fait |
385
+ | -------- | ------------------------------ | ------ | ------------------------------------------------- |
386
+ | `query` | chaîne, jusqu'à 200 caractères | oui | Ce qu'on cherche, en français. |
387
+ | `limit` | entier, 1 à 30, défaut `10` | non | Recettes à servir depuis cette page de résultats. |
388
+
389
+ **En retour :** des lignes portant `id`, que `get_recipe` reprend ; `title` ;
390
+ `url` ; et `image_url`, `null` pour une recette publiée sans photo. Viennent
391
+ aussi `result_count` et `total_available`, les recettes de cette page avant
392
+ l'application de `limit`. Le robots.txt de Marmiton interdit de paginer les
393
+ résultats de recherche, donc une recherche lit une page : resserrez la requête
394
+ pour voir d'autres recettes.
395
+
396
+ ### `get_recipe`
397
+
398
+ Lit une recette en entier, et adapte ses quantités quand un nombre de parts est
399
+ donné.
400
+
401
+ | Argument | Type | Requis | Ce qu'il fait |
402
+ | ---------- | -------------------------------- | ------------- | -------------------------------------------------- |
403
+ | `id` | chaîne de chiffres | l'un des deux | L'identifiant Marmiton rendu par `search_recipes`. |
404
+ | `url` | une adresse marmiton.org | l'un des deux | L'adresse de la recette, utilisée à défaut d'`id`. |
405
+ | `servings` | nombre, au-delà de 0 jusqu'à 500 | non | Adapte les quantités à ce nombre de parts. |
406
+
407
+ **En retour :** `title`, `url`, `ingredients`, `steps`, `prep_minutes`,
408
+ `cook_minutes`, `total_minutes`, `category`, `author`, `rating` et `nutrition`,
409
+ chacun `null` quand la page n'en publie pas. `yield` dit pour quoi la recette est
410
+ écrite et vers quoi elle a été adaptée : `original_count`, `original_text`,
411
+ `requested`, `unit` pour ce qui est compté, et `factor` pour le multiplicateur
412
+ appliqué. Chaque ingrédient porte `original`, `text`, `amount`, `amountMax` pour
413
+ un intervalle, `unit`, et `scaling`, qui vaut `scaled`, `rounded` ou `unscaled`.
414
+ Les chiffres sont l'arithmétique de ce serveur, donc dites qu'ils ont été
415
+ recalculés quand vous les montrez. `nutrition` décrit la recette telle que
416
+ publiée, pour son propre nombre de parts.
417
+
418
+ ### `scale_ingredients`
419
+
420
+ Applique la même arithmétique à n'importe quelle liste d'ingrédients, sans
421
+ requête au site, donc sur une recette recopiée d'un livre ou d'un carnet de
422
+ famille.
423
+
424
+ | Argument | Type | Requis | Ce qu'il fait |
425
+ | --------------- | -------------------------------------------------- | ------------- | --------------------------------------------------- |
426
+ | `ingredients` | tableau de 1 à 100 chaînes, jusqu'à 300 caractères | oui | Les lignes à adapter, en français. |
427
+ | `factor` | nombre, au-delà de 0 jusqu'à 100 | l'un des deux | Le multiplicateur à appliquer. |
428
+ | `from_servings` | nombre, au-delà de 0 jusqu'à 500 | l'un des deux | Le nombre de parts pour lequel la liste est écrite. |
429
+ | `to_servings` | nombre, au-delà de 0 jusqu'à 500 | l'un des deux | Le nombre de parts voulu. |
430
+
431
+ Passez `factor`, ou le couple `from_servings` et `to_servings`.
432
+
433
+ **En retour :** le `factor` employé, les `ingredients` adaptés dans la forme que
434
+ rend `get_recipe`, et `scaled_count`, `rounded_count` et `unscaled_count`, qui
435
+ comptent les lignes dont l'arrondi a déplacé la valeur.
436
+
437
+ ## L'adaptation des quantités
438
+
439
+ Chaque ingrédient revient avec un drapeau `scaling` qui dit ce que l'adaptation a
440
+ pu faire de sa quantité.
441
+
442
+ | Drapeau | Ce que cela veut dire | Exemple |
443
+ | ---------- | ------------------------------------------------ | ---------------------------------------- |
444
+ | `scaled` | La valeur est le produit lui-même. | `3 oeufs` ×2 → `6 oeufs` |
445
+ | `rounded` | La valeur a été déplacée pour rester utilisable. | `25 cl de lait` ×0,667 → `17 cl de lait` |
446
+ | `unscaled` | Ne porte aucune quantité, laissée telle quelle. | `sel`, `coriandre` |
447
+
448
+ Une quantité est exprimée dans l'unité qui lui convient. Après adaptation, une
449
+ ligne peut donc apparaître dans une autre unité que celle de la recette : 200 g
450
+ multipliés par vingt donnent `4 kg`.
451
+
452
+ La finesse à laquelle un ingrédient se coupe dépend de sa nature. Une baguette se
453
+ coupe en deux, en trois ou en quatre ; un oeuf ne se partage pas. Une quantité
454
+ qui tombe entre les deux est donc arrondie, et la recette adaptée s'écarte alors
455
+ un peu des proportions de l'originale. La ligne porte `rounded`, et sa `note` dit
456
+ ce qui a été fait.
435
457
 
436
458
  ## Configuration
437
459
 
438
- Toutes les variables sont optionnelles, à déclarer dans le bloc `env` de votre client.
460
+ Chaque variable est facultative. Elles se posent dans le bloc `env` de la
461
+ configuration du client.
439
462
 
440
- | Variable | Défaut | Rôle |
441
- | ---------------------------- | ------------------------------------------ | ----------------------------------------------------------------- |
442
- | `MARMITON_USER_AGENT` | `mcp-marmiton v<version> (<url du dépôt>)` | User-Agent envoyé à Marmiton. |
443
- | `MARMITON_MIN_INTERVAL_MS` | `1000` | Écart minimal entre requêtes. Sous 500 ms, la valeur est ignorée. |
444
- | `MARMITON_TIMEOUT_MS` | `15000` | Délai d'attente par requête. |
445
- | `MARMITON_MAX_RETRIES` | `3` | Tentatives en cas de limitation ou d'erreur passagère. |
446
- | `MARMITON_CACHE_TTL_MS` | `900000` | Durée de vie du cache mémoire (15 minutes). |
447
- | `MARMITON_CACHE_MAX_ENTRIES` | `200` | Taille du cache mémoire. |
448
- | `MARMITON_LOG_LEVEL` | `error` | `silent`, `error`, `info` ou `debug`. Logs sur stderr. |
463
+ | Variable | Défaut | Ce qu'elle fait |
464
+ | ---------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |
465
+ | `MARMITON_USER_AGENT` | l'identité du projet | Nomme votre application auprès de Marmiton, avec une adresse où joindre une personne. |
466
+ | `MARMITON_MIN_INTERVAL_MS` | `1000` | Écart entre deux requêtes, de 500 à 60000. Une valeur sous le plancher est refusée au profit de celle-ci. |
467
+ | `MARMITON_TIMEOUT_MS` | `15000` | Délai d'une requête, de 1000 à 120000. |
468
+ | `MARMITON_MAX_RETRIES` | `3` | Tentatives après un échec passager, de 0 à 10. |
469
+ | `MARMITON_CACHE_TTL_MS` | `900000` | Durée pendant laquelle une page reste en mémoire, de 0 à 86400000. |
470
+ | `MARMITON_CACHE_MAX_ENTRIES` | `200` | Pages gardées en mémoire à la fois, de 0 à 10000. |
471
+ | `MARMITON_MAX_BODY_BYTES` | `8000000` | Plus grande page lue, en octets, de 100000 à 64000000. |
472
+ | `MARMITON_LOG_LEVEL` | `error` | `silent`, `error`, `info` ou `debug`, écrit sur la sortie d'erreur. |
449
473
 
450
- ## Dépannage
474
+ Une valeur hors de sa plage retombe sur le défaut, et la raison est écrite sur la
475
+ sortie d'erreur.
451
476
 
452
- **Erreurs `rate_limited`.** Marmiton limite ce client. Le serveur réessaie déjà avec
453
- backoff et ralentit tout seul. Patientez un instant, et augmentez
454
- `MARMITON_MIN_INTERVAL_MS` si cela persiste. Cela ne signifie jamais que la recette
455
- est absente.
477
+ ## Erreurs
456
478
 
457
- **Erreurs `parse_failure`.** Marmiton a changé la façon dont il publie ses données
458
- structurées et le serveur n'a pas su lire la réponse. Merci d'[ouvrir une issue](https://github.com/smeet666/mcp-marmiton/issues)
459
- en indiquant la recette demandée. Le serveur signale ce cas au lieu de faire comme
460
- s'il n'avait rien trouvé.
479
+ Chaque échec porte un des six codes, un message, et quand cela aide une
480
+ indication du geste suivant.
461
481
 
462
- ## Développement
482
+ | Code | Ce qui s'est passé | Que faire |
483
+ | --------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
484
+ | `not_found` | Marmiton a répondu, et n'a pas cette recette. | Vérifiez l'identifiant avec `search_recipes`. |
485
+ | `invalid_input` | Les arguments ont été refusés avant toute requête. | Lisez le message, qui nomme l'argument. |
486
+ | `rate_limited` | Marmiton demande à ce client de ralentir. | Attendez les secondes indiquées et rappelez avec les mêmes arguments. La recette est toujours là. |
487
+ | `parse_failure` | La page a chargé et le contenu attendu est absent. | Signalez-le sur [le suivi d'incidents](https://github.com/smeet666/mcp-marmiton/issues). |
488
+ | `network_error` | La requête n'a pas abouti. | Réessayez sous peu. |
489
+ | `timeout` | La requête a dépassé son délai. | Augmentez `MARMITON_TIMEOUT_MS`. |
463
490
 
464
- ```bash
465
- npm install
466
- npm run build:fixtures # régénère les fixtures HTML de test
467
- npm test # tests unitaires, sans réseau
468
- npm run typecheck
469
- npm run build
470
- MARMITON_LIVE=1 npm run test:live # touche le vrai site, exclu de la CI
471
- npm run inspector # explorer les outils dans le MCP Inspector
491
+ ## Comme bibliothèque
492
+
493
+ La couche qui lit Marmiton est publiée seule, avec son rythme, son cache et ses
494
+ erreurs, sans protocole attaché.
495
+
496
+ ```ts
497
+ import { MarmitonClient } from "mcp-marmiton/client";
498
+
499
+ const client = new MarmitonClient();
500
+ const { data, cached } = await client.getRecipe({ id: "18257" });
501
+ console.log(data.title, data.ingredients.length, cached);
472
502
  ```
473
503
 
474
- Les fixtures sont générées, pas capturées : elles reproduisent la forme du JSON-LD
475
- de Marmiton avec des recettes inventées, ce qui rend les tests déterministes et
476
- évite de stocker du contenu Marmiton dans ce dépôt.
504
+ `search` et `getRecipe` répondent chacun `{ data, cached }`, et lèvent une erreur
505
+ portant un des six codes. Le plancher entre deux requêtes tient également ici.
477
506
 
478
- La couche d'accès (`src/marmiton`, `src/recipe`) n'importe pas le SDK MCP et est
479
- publiée séparément sous `mcp-marmiton/client`, utilisable comme simple bibliothèque.
507
+ ## Rythme et attribution
480
508
 
481
- ## Recettes, droits d'auteur et cuisine
509
+ Les requêtes partent une à une avec un écart minimal entre elles, et ce plancher
510
+ tient quelle que soit la configuration. Le `User-Agent` se termine toujours par
511
+ l'identité du projet et une adresse où joindre une personne. Tout est lu dans le
512
+ JSON-LD `schema.org` que Marmiton publie pour les machines, et les chemins que
513
+ son robots.txt interdit sont laissés tranquilles.
482
514
 
483
- Les listes d'ingrédients et les étapes de préparation sont des faits et des procédés.
484
- La prose descriptive qu'un auteur rédige autour relève de sa création, et ce serveur
485
- ne la renvoie pas : vous obtenez de quoi cuisiner, plus un lien vers la page d'origine.
515
+ Chaque résultat porte le titre et l'adresse de la recette, et `get_recipe` porte
516
+ l'auteur quand la page le nomme, ainsi qu'`attribution`, le titre et l'adresse
517
+ écrits en une ligne.
486
518
 
487
- Ce serveur est un client. Il lit les données structurées que Marmiton publie pour
488
- les machines, à la demande, une requête à la fois, en réponse à une demande explicite
489
- de votre part ou de celle de votre assistant. Il ne parcourt pas le site, ne constitue
490
- aucune base de recettes et n'écrit rien sur le disque. Il respecte le robots.txt de
491
- Marmiton, y compris l'interdiction de paginer les résultats de recherche.
519
+ Les recettes appartiennent à Marmiton et aux cuisiniers qui les ont écrites.
520
+ Ce MCP est un projet non officiel, sans affiliation à Marmiton.
521
+
522
+ ## Confidentialité
523
+
524
+ Ce serveur ne collecte rien sur vous et n'envoie rien à son auteur. Il tourne sur
525
+ votre machine, ne joint que `www.marmiton.org`, garde ses réponses en mémoire le temps qu'il
526
+ tourne, et n'écrit rien sur le disque. [PRIVACY.md](PRIVACY.md) dit ce qu'une
527
+ requête emporte et quels réglages changent cela.
528
+
529
+ ## Développement
530
+
531
+ ```bash
532
+ npm install
533
+ npm run build:fixtures
534
+ npm test
535
+ npm run check
536
+ ```
492
537
 
493
- Chaque résultat porte le titre de la recette et son URL source. Si vous affichez ou
494
- réutilisez ce que renvoie ce serveur, conservez cette attribution et le lien vers
495
- Marmiton.
538
+ Les tests s'exécutent sur des fixtures engendrées et n'émettent aucune requête.
539
+ La suite en direct, `npm run test:live`, émet une requête par route et tourne
540
+ chaque nuit contre le site lui-même.
496
541
 
497
- Les quantités adaptées sont calculées, et la cuisine n'est pas de l'arithmétique :
498
- la pâtisserie en particulier ne se met pas toujours à l'échelle linéairement.
499
- Prenez-les comme un point de départ commode et gardez votre jugement.
542
+ ## Contribuer
500
543
 
501
- Projet non officiel, sans affiliation à Marmiton ni approbation de sa part.
544
+ Les anomalies, les questions et les idées ont leur place dans
545
+ [le suivi d'incidents](https://github.com/smeet666/mcp-marmiton/issues). Les
546
+ propositions de modification sont bienvenues ; ouvrir un ticket d'abord aide à
547
+ s'accorder sur la forme du changement. Voir [CONTRIBUTING.md](CONTRIBUTING.md).
502
548
 
503
549
  ## Licence
504
550
 
505
- MIT, voir [LICENSE](./LICENSE). La licence couvre uniquement le code source, pas les
506
- recettes récupérées par son intermédiaire.
551
+ MIT, voir [LICENSE](LICENSE). Les recettes appartiennent à Marmiton et à leurs
552
+ auteurs.