{
  "markdown": "# mcp-recipes\n\n[![npm](https://img.shields.io/npm/v/mcp-recipes.svg)](https://www.npmjs.com/package/mcp-recipes)\n[![CI](https://github.com/smeet666/mcp-recipes/actions/workflows/ci.yml/badge.svg)](https://github.com/smeet666/mcp-recipes/actions/workflows/ci.yml)\n[![license](https://img.shields.io/npm/l/mcp-recipes.svg)](./LICENSE)\n[![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-recipes)\n[![Glama](https://glama.ai/mcp/servers/smeet666/mcp-recipes/badges/score.svg)](https://glama.ai/mcp/servers/smeet666/mcp-recipes)\n[![M8ven](https://m8ven.ai/badge/mcp/smeet666-mcp-recipes-1o0x5l?variant=verified)](https://m8ven.ai/mcp/smeet666-mcp-recipes-1o0x5l)\n[![LobeHub](https://lobehub.com/badge/mcp/smeet666-mcp-recipes)](https://lobehub.com/mcp/smeet666-mcp-recipes)\n[![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=recipes&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1yZWNpcGVzIl19)\n[![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=recipes&config=%7B%22name%22%3A%22recipes%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-recipes%22%5D%7D)\n\nRecipes live on many sites, and each one writes them its own way: a French\ncooking site publishes in French, with its own measures and its own idea of what\na serving is, and a wiki cookbook in English, with equipment lists and prose the\nfirst has no field for. Asking a question of one of them answers about one of\nthem.\n\nThis server reads six. Three publish in French,\n[Marmiton](https://www.marmiton.org), [Ptitchef](https://www.ptitchef.com) and\n[Supertoinette](https://www.supertoinette.com); two in English, the\n[Wikibooks Cookbook](https://en.wikibooks.org/wiki/Cookbook:Table_of_Contents)\nand [BBC Good Food](https://www.bbcgoodfood.com); and one in Spanish,\n[Pequerecetas](https://www.pequerecetas.com). You can search them all with\none question, read a recipe from any of them in one shape, put several versions\nof the same dish side by side, and rescale any ingredient list. It needs no API\nkey and no account.\n\n_[Version française](#mcp-recipes-français)_\n\n---\n\n## Install\n\n**One-click install**\n\n[![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=recipes&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1yZWNpcGVzIl19)\n[![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=recipes&config=%7B%22name%22%3A%22recipes%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-recipes%22%5D%7D)\n\n**Claude Code**\n\n```bash\nclaude mcp add recipes -- npx -y mcp-recipes\n```\n\n**Claude Desktop, Cursor, and any client using the standard config format**\n\n```json\n{\n  \"mcpServers\": {\n    \"recipes\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"mcp-recipes\"]\n    }\n  }\n}\n```\n\nNode 24 or later is required, and no environment variable has to be set.\n\n### With Docker\n\n```json\n{\n  \"mcpServers\": {\n    \"recipes\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"ghcr.io/smeet666/mcp-recipes:4.0.0\"]\n    }\n  }\n}\n```\n\n`-i` keeps stdin open, which is where the protocol travels, and `-t` is left out\nbecause a TTY rewrites the stream. The container needs outbound HTTPS to\n`www.marmiton.org`, `api.wikimedia.org`, `www.ptitchef.com`,\n`www.bbcgoodfood.com`, `www.supertoinette.com` and `www.pequerecetas.com`, and\nnothing else: no volume, no port, no credential.\n\n### Bundle, without npm\n\nDownload `mcp-recipes-4.0.0.mcpb` from\n[the latest release](https://github.com/smeet666/mcp-recipes/releases/latest) and\nopen it. A client that supports MCP bundles installs it on its own, with no npm\nand no configuration file to edit. The bundle carries its dependencies, so\nnothing is fetched at install time.\n\n## What you can ask\n\n- \"Find me carbonara recipes, from wherever you can.\"\n- \"Compare the French and English versions of that dish.\"\n- \"Read the second one for eight people.\"\n- \"Which of them uses cream?\"\n- \"Scale this list from my notebook by 1.5.\"\n\nThe ordinary path runs from a search to a reading: a row carries an `id` naming\nits source, and `get_recipe` takes it.\n\n## The sources\n\n| Source          | Site                    | Language |\n| --------------- | ----------------------- | -------- |\n| `marmiton`      | `www.marmiton.org`      | French   |\n| `cookbook`      | Wikibooks Cookbook      | English  |\n| `ptitchef`      | `www.ptitchef.com`      | French   |\n| `goodfood`      | `www.bbcgoodfood.com`   | English  |\n| `supertoinette` | `www.supertoinette.com` | French   |\n| `pequerecetas`  | `www.pequerecetas.com`  | Spanish  |\n\nA row's `id` names its source, so an identifier read from one answer goes back to\nthe right site. **Counts are never added across sources**, and a source that\nfailed is reported as having failed rather than as having found nothing.\n\nEach site is read at its own pace: two of them ask for three seconds between\nrequests, and a setting posted for all of them can only make this server more\npatient than the slowest asks for.\n\nTwo of these sites file something other than a recipe at the address a recipe\nlives at. The Wikibooks Cookbook keeps pages about an ingredient beside the\nrecipes using it, and Pequerecetas publishes articles that gather recipes. A\nsearch says so, and `get_recipe` says what it read off the page.\n\n## Tools\n\n| Tool                | What it does                                               |\n| ------------------- | ---------------------------------------------------------- |\n| `search_recipes`    | Searches every source with one question.                   |\n| `get_recipe`        | Reads one recipe from any source, in one shape.            |\n| `compare_recipes`   | Puts several versions of the same dish side by side.       |\n| `scale_ingredients` | Rescales any ingredient list, with no request to any site. |\n\n### `search_recipes`\n\nSearches every source with one question.\n\n| Argument           | Type                          | Required | What it does                                                     |\n| ------------------ | ----------------------------- | -------- | ---------------------------------------------------------------- |\n| `query`            | string, 1 to 200 characters   | yes      | The dish or the ingredient to look for.                          |\n| `limit_per_source` | integer, 1 to 25, default `5` | no       | Rows to keep from each source.                                   |\n| `sources`          | array of source ids           | no       | Ask these sources alone.                                         |\n| `fan_out`          | boolean, default `true`       | no       | Ask every source rather than stopping at the first that answers. |\n\n**In return:** `results`, rows carrying `id`, which `get_recipe` takes;\n`source` and `source_name` saying which site published the row; `title`; `url`;\n`image_url`; and an `excerpt` where the source offers one. `per_source` gives one\nreport per site with its `status`, reading `answered` or `failed`, the `count` it\ncontributed, and its `reported_total` alongside `reported_total_means`, which\nsays what that number counts on that site. `names_the_dish` says how many of that\nsite's rows carry the dish in their title, out of `count`: a search index answers\nthe words it is handed, so a site can offer rows and none of them be the dish.\n`order` says in words how the list was built.\n\n### `get_recipe`\n\nReads one recipe from any source, in one shape.\n\n| Argument         | Type                                                                                                           | Required | What it does                                                                                                                           |\n| ---------------- | -------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`             | string, 1 to 500 characters                                                                                    | yes      | The identifier a row carries, such as `marmiton:44078`. Two sources address a recipe by a bare number, so spell an id with its source. |\n| `servings`       | integer, 1 to 500                                                                                              | no       | Rescale the ingredients to this many servings.                                                                                         |\n| `sections`       | array of `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, default `[\"ingredients\", \"steps\"]` | no       | Which parts to return.                                                                                                                 |\n| `max_steps`      | integer, 1 to 100, default `20`                                                                                | no       | Steps to serve.                                                                                                                        |\n| `max_gathered`   | integer, 1 to 500, default `30`                                                                                | no       | Recipes and headings to return from an address that gathers recipes.                                                                   |\n| `max_step_chars` | integer, 80 to 4000, default `600`                                                                             | no       | Characters kept per step.                                                                                                              |\n\n**In return:** `kind` says what the address held. A `recipe` answer carries\n`recipe` and no `collection`; a `collection` answer carries `collection` and no\n`recipe`, and is an article gathering other recipes, with the `headings` it is\nbuilt from and the `recipes` it points at, each of them readable with\n`get_recipe`.\n\nA recipe comes in the shape every source is rendered into, whichever\npublished it: its title, its address, its ingredients with each line's `scaling`\nand `is_equipment`,\nits steps, and the sections asked for. A field one source publishes and another\nhas no notion of comes back absent rather than invented. `rest_minutes` carries a\nresting time from a source that prints one apart, and is in no other time here.\n`steps_as_one_block` says when a source published its method as one block of\nprose rather than as steps. `withheld` names a part a source keeps for its\nsubscribers, which is a part the page has rather than a part that could not be\nread. `scaling_summary` counts the lines four ways, and the four add up to the\nlist. Raise `max_step_chars` when a step was cut mid-sentence.\n\n### `compare_recipes`\n\nPuts several versions of the same dish side by side.\n\n| Argument         | Type                                                                                                  | Required | What it does                                 |\n| ---------------- | ----------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- |\n| `dish`           | string, 1 to 200 characters                                                                           | yes      | The dish to compare.                         |\n| `servings`       | integer, 1 to 500                                                                                     | no       | Rescale every version to this many servings. |\n| `sections`       | array of `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, default `[\"ingredients\"]` | no       | Which parts to return per version.           |\n| `max_steps`      | integer, 1 to 100, default `10`                                                                       | no       | Steps to serve per version.                  |\n| `max_step_chars` | integer, 80 to 4000, default `600`                                                                    | no       | Characters kept per step.                    |\n| `sources`        | array of source ids                                                                                   | no       | Compare these sources alone.                 |\n\n**In return:** `versions`, one recipe per source that answered, all rescaled to\nthe same number of servings so their quantities can be read against each other,\nand `differences`, what separates them. `per_source` reports each site as a\nsearch does.\n\n### `scale_ingredients`\n\nRescales any ingredient list, with no request to any site.\n\n| Argument        | Type                                       | Required   | What it does                               |\n| --------------- | ------------------------------------------ | ---------- | ------------------------------------------ |\n| `ingredients`   | array of 1 to 200 lines                    | yes        | The lines to rescale.                      |\n| `factor`        | number, up to 1000                         | one of two | The multiplier to apply.                   |\n| `from_servings` | integer, 1 to 500                          | one of two | How many servings the list is written for. |\n| `to_servings`   | integer, 1 to 500                          | one of two | How many servings are wanted.              |\n| `language`      | `auto`, `fr`, `en` or `es`, default `auto` | no         | How each line is read.                     |\n\nPass `factor`, or the `from_servings` and `to_servings` pair. `auto` reads each\nline on its own, which is what a list holding more than one language needs; naming a\nlanguage reads every line that way.\n\n**In return:** the rescaled lines in the shape `get_recipe` returns, each with\nits `scaling`.\n\n## Rescaling the quantities\n\nA quantity is stated in the unit that suits it, so a line can come back in a\ndifferent unit from the one the recipe used: 200 g multiplied by twenty reads\n`4 kg`.\n\nHow finely an ingredient can be divided depends on what it is. A baguette can be\ncut in two, in three or in four; an egg cannot be shared out. A quantity landing\nbetween the two is rounded, and the rescaled recipe then departs a little from\nthe proportions of the original. The line carries `rounded`, and its note says\nwhat was done.\n\nThe sources write their quantities in their own languages, and a line is read\nin the language it was written in. The figures are this server's arithmetic, so\nsay they were recomputed when you show them.\n\n## What an answer states about the sources\n\nEvery answer accounts for each source separately. A site that failed, one nobody\nasked, and one that answered with nothing are three different things, and they\nare reported as three. A total stays beside the source that published it, with\nwhat that source counts when it says it: one site counts a whole category, another\ncounts the rows it served, and a third publishes no total at all.\n\n## Configuration\n\nEvery variable is optional. Set them in the `env` block of your client config.\n\n| Variable                    | Default              | What it does                                                                        |\n| --------------------------- | -------------------- | ----------------------------------------------------------------------------------- |\n| `RECIPES_USER_AGENT`        | the project identity | Names your application to the sites, with an address where a person can be reached. |\n| `RECIPES_MIN_INTERVAL_MS`   | `1000`               | Gap between two requests to one site, from 500 to 60000.                            |\n| `RECIPES_TIMEOUT_MS`        | `20000`              | Deadline for one request, from 1000 to 120000.                                      |\n| `RECIPES_MAX_RETRIES`       | `3`                  | Attempts after a transient failure, from 0 to 8.                                    |\n| `RECIPES_CACHE_TTL_MS`      | `900000`             | How long an answer stays in memory, from 0 to 86400000.                             |\n| `RECIPES_CACHE_MAX_ENTRIES` | `200`                | Answers held in memory at once, from 1 to 5000.                                     |\n| `RECIPES_LOG_LEVEL`         | `error`              | `silent`, `error`, `info` or `debug`, written to stderr.                            |\n\nA value outside its range falls back to the default, and the reason is written to\nstderr.\n\n## Errors\n\nEvery failure carries one of six codes, a message, and where it helps a hint\nnaming the next move.\n\n| Code            | What happened                                           | What to do                                                                        |\n| --------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- |\n| `not_found`     | A source answered, and holds no such recipe.            | Check the identifier with `search_recipes`.                                       |\n| `invalid_input` | The arguments were refused before any request went out. | Read the message, which names the argument.                                       |\n| `rate_limited`  | A source asked this client to slow down.                | Wait, then call again with the same arguments. The recipe is still there.         |\n| `parse_failure` | A page loaded and the expected content was absent.      | Report it at [the issue tracker](https://github.com/smeet666/mcp-recipes/issues). |\n| `network_error` | The request did not complete.                           | Try again shortly.                                                                |\n| `timeout`       | The request passed its deadline.                        | Raise `RECIPES_TIMEOUT_MS`, or ask for fewer rows.                                |\n\nA source that failed is reported per source rather than failing the whole answer,\nso one silent site never hides the other.\n\n## As a library\n\nThe layer reading the sites is published on its own, with its pacing, its cache\nand its errors, and with no protocol attached.\n\n```ts\nimport { RecipesClient } from \"mcp-recipes/client\";\n\nconst client = new RecipesClient();\nconst { rows, reports } = await client.searchRecipes(\"carbonara\", 3);\nconsole.log(\n  rows.length,\n  reports.map((report) => report.status),\n);\n\nconst { recipe } = await client.getRecipe(rows[0].id);\n```\n\n`searchRecipes(query, limitPerSource, sources?, options?)` answers\n`{ rows, reports }`: one report per source, saying whether it answered and what\nits own count measured, so a source that failed is never read as a source that\nholds nothing. `getRecipe(id)` answers `{ recipe, cached, read }`, and throws an\nerror carrying one of the six codes. `client.profiles` lists the sources the\nbuild registers.\n\nThe scaler is published separately at `mcp-recipes/scale`, and works offline on\nany list:\n\n```ts\nimport { scaleIngredients } from \"mcp-recipes/scale\";\n\nscaleIngredients([\"200 g de harina\", \"4 oeufs\", \"1 cup milk\"], { factor: 2 });\n```\n\nEach site keeps its own pace, and the floors hold here as well.\n\n## Pacing and attribution\n\nEach site is paced on its own, one request at a time with at least a second\nbetween two, and the floor of half a second holds however the server is\nconfigured. Two of the sites ask for more, three seconds between two requests,\nand they get it: a setting posted for every source can raise a site's spacing\nand never lower it. Asking every site at once therefore costs each of them one request,\nnever two. The `User-Agent` always ends with the project identity and an address\nwhere a person can be reached.\n\nEvery row carries the address of the recipe's own page and the name of the site\nthat published it. The Cookbook pages are published under\n[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), which asks that\nwhat is built on them be shared under the same licence. Marmiton, Ptitchef, BBC\nGood Food, Supertoinette and Pequerecetas state no terms on a recipe page, and their recipes\nbelong to those sites and to the cooks who wrote them. Silence is not a grant, so\ncredit the site and link the page you took a recipe from.\n\nA recipe BBC Good Food keeps for its subscribers comes back without its\ningredients and its method, named as a recipe held back, with the address of its\npage. This server does not reconstruct what that site chose to sell.\n\nTwo figures the sites publish are not repeated here. A difficulty is a word each\nsite writes its own way, on no scale any of them publishes, so it sits on no axis\nalong which two versions could be put. A cost is a price in euros on one site and\na rank inside its own list on another, and one field holding both would invite\nthem to be compared.\n\nThis MCP server is an unofficial project, with no affiliation to any of the\nsites it reads.\n\n## Privacy\n\nThis server collects nothing about you and sends nothing to its author. It runs\non your machine, contacts `www.marmiton.org`, `api.wikimedia.org`,\n`www.ptitchef.com`, `www.bbcgoodfood.com`, `www.supertoinette.com` and\n`www.pequerecetas.com` and nothing\nelse, holds its answers in memory while it runs,\nand writes nothing to disk. [PRIVACY.md](PRIVACY.md) states what a request\ncarries and which settings change any of it.\n\n## Development\n\n```bash\nnpm install\nnpm run build:fixtures\nnpm test\nnpm run check\n```\n\nTests run against generated fixtures and make no network request. The live suite,\n`npm run test:live`, makes one request per route and runs nightly against the\nsites themselves.\n\n## Contributing\n\nBugs, questions and ideas belong in\n[the issue tracker](https://github.com/smeet666/mcp-recipes/issues). Pull\nrequests are welcome; opening an issue first helps agree on the shape of the\nchange. See [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nMIT, see [LICENSE](LICENSE). The recipes belong to the sites that published them\nand to their authors.\n\n---\n\n<a name=\"mcp-recipes-français\"></a>\n\n# mcp-recipes (français)\n\n_[English version](#mcp-recipes)_\n\nLes recettes vivent sur beaucoup de sites, et chacun les écrit à sa façon : un\nsite de cuisine français publie en français, avec ses mesures et son idée de ce\nqu'est une part, et un wiki de cuisine en anglais, avec des listes de matériel et\nune prose pour lesquelles le premier n'a aucun champ. Poser une question à l'un\nd'eux répond au sujet de l'un d'eux.\n\nCe serveur en lit six. Trois publient en français,\n[Marmiton](https://www.marmiton.org), [Ptitchef](https://www.ptitchef.com) et\n[Supertoinette](https://www.supertoinette.com) ; deux en anglais, le\n[Cookbook des Wikibooks](https://en.wikibooks.org/wiki/Cookbook:Table_of_Contents)\net [BBC Good Food](https://www.bbcgoodfood.com) ; un en espagnol,\n[Pequerecetas](https://www.pequerecetas.com). On peut chercher dans les six\navec une seule question, lire une recette de n'importe lequel sous une seule\nforme, mettre plusieurs versions d'un même plat côte à côte, et adapter\nn'importe quelle liste d'ingrédients. Aucune clé d'API, aucun compte.\n\n## Installation\n\n**Installation en un clic**\n\n[![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=recipes&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1yZWNpcGVzIl19)\n[![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=recipes&config=%7B%22name%22%3A%22recipes%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-recipes%22%5D%7D)\n\n**Claude Code**\n\n```bash\nclaude mcp add recipes -- npx -y mcp-recipes\n```\n\n**Claude Desktop, Cursor, et tout client au format de configuration standard**\n\n```json\n{\n  \"mcpServers\": {\n    \"recipes\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"mcp-recipes\"]\n    }\n  }\n}\n```\n\nNode 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à\nrenseigner.\n\n### Avec Docker\n\n```json\n{\n  \"mcpServers\": {\n    \"recipes\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"ghcr.io/smeet666/mcp-recipes:4.0.0\"]\n    }\n  }\n}\n```\n\n`-i` garde l'entrée standard ouverte, qui est le canal du protocole, et `-t` est\nomis parce qu'un TTY réécrit le flux. Le conteneur a besoin d'un accès HTTPS\nsortant vers `www.marmiton.org`, `api.wikimedia.org`, `www.ptitchef.com`,\n`www.bbcgoodfood.com`, `www.supertoinette.com` et `www.pequerecetas.com`, et de\nrien d'autre : aucun volume, aucun port, aucun identifiant.\n\n### Bundle, sans npm\n\nTéléchargez `mcp-recipes-4.0.0.mcpb` depuis\n[la dernière publication](https://github.com/smeet666/mcp-recipes/releases/latest)\net ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et\nsans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc\nrien n'est téléchargé à l'installation.\n\n## Ce qu'on peut demander\n\n- « Trouve-moi des recettes de carbonara, d'où que tu puisses. »\n- « Compare les versions française et anglaise de ce plat. »\n- « Lis-moi la seconde pour huit personnes. »\n- « Laquelle utilise de la crème ? »\n- « Multiplie par 1,5 cette liste de mon carnet. »\n\nLe chemin ordinaire va d'une recherche à une lecture : une ligne porte un `id`\nqui nomme sa source, et `get_recipe` le reprend.\n\n## Les sources\n\n| Source          | Site                    | Langue   |\n| --------------- | ----------------------- | -------- |\n| `marmiton`      | `www.marmiton.org`      | français |\n| `cookbook`      | Cookbook Wikibooks      | anglais  |\n| `ptitchef`      | `www.ptitchef.com`      | français |\n| `goodfood`      | `www.bbcgoodfood.com`   | anglais  |\n| `supertoinette` | `www.supertoinette.com` | français |\n| `pequerecetas`  | `www.pequerecetas.com`  | espagnol |\n\nL'`id` d'une ligne nomme sa source, donc un identifiant lu dans une réponse\nretourne vers le bon site. **Les comptes ne sont jamais additionnés entre\nsources**, et une source qui a échoué est rapportée comme ayant échoué plutôt que\ncomme n'ayant rien trouvé.\n\nChaque site est lu à son propre rythme : deux d'entre eux demandent trois\nsecondes entre deux requêtes, et un réglage posé pour tous ne peut que rendre ce\nserveur plus patient que ce que le plus lent demande.\n\nDeux de ces sites rangent autre chose qu'une recette à l'adresse où vit une\nrecette. Le Cookbook des Wikibooks garde des pages sur un ingrédient à côté des\nrecettes qui l'emploient, et Pequerecetas publie des articles qui rassemblent\ndes recettes. Une recherche le dit, et `get_recipe` dit ce qu'il a lu sur la\npage.\n\n## Les outils\n\n| Outil               | Ce qu'il fait                                                     |\n| ------------------- | ----------------------------------------------------------------- |\n| `search_recipes`    | Cherche dans toutes les sources avec une seule question.          |\n| `get_recipe`        | Lit une recette de n'importe quelle source, sous une seule forme. |\n| `compare_recipes`   | Met plusieurs versions d'un même plat côte à côte.                |\n| `scale_ingredients` | Adapte n'importe quelle liste d'ingrédients, sans requête.        |\n\n### `search_recipes`\n\nCherche dans toutes les sources avec une seule question.\n\n| Argument           | Type                             | Requis | Ce qu'il fait                                                           |\n| ------------------ | -------------------------------- | ------ | ----------------------------------------------------------------------- |\n| `query`            | chaîne, 1 à 200 caractères       | oui    | Le plat ou l'ingrédient cherché.                                        |\n| `limit_per_source` | entier, 1 à 25, défaut `5`       | non    | Lignes à garder de chaque source.                                       |\n| `sources`          | tableau d'identifiants de source | non    | N'interroger que ces sources.                                           |\n| `fan_out`          | booléen, défaut `true`           | non    | Interroger chaque source plutôt que s'arrêter à la première qui répond. |\n\n**En retour :** `results`, des lignes portant `id`, que `get_recipe` reprend ;\n`source` et `source_name` qui disent quel site a publié la ligne ; `title` ;\n`url` ; `image_url` ; et un `excerpt` là où la source en propose un.\n`per_source` donne un rapport par site avec son `status`, valant `answered` ou\n`failed`, le `count` qu'il a fourni, et son `reported_total` accompagné de\n`reported_total_means`, qui dit ce que ce nombre compte sur ce site.\n`names_the_dish` dit combien des lignes de ce site portent le plat dans leur\ntitre, sur `count` : un index de recherche répond aux mots qu'on lui tend, donc\nun site peut rendre des lignes dont aucune n'est le plat. `order` dit en mots\ncomment la liste a été bâtie.\n\n### `get_recipe`\n\nLit une recette de n'importe quelle source, sous une seule forme.\n\n| Argument         | Type                                                                                                            | Requis | Ce qu'il fait                                                                                                                           |\n| ---------------- | --------------------------------------------------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`             | chaîne, 1 à 500 caractères                                                                                      | oui    | L'identifiant d'une ligne, tel que `marmiton:44078`. Deux sources adressent une recette par un nombre nu : écrivez l'id avec sa source. |\n| `servings`       | entier, 1 à 500                                                                                                 | non    | Adapte les ingrédients à ce nombre de parts.                                                                                            |\n| `sections`       | tableau de `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, défaut `[\"ingredients\", \"steps\"]` | non    | Les parties à rendre.                                                                                                                   |\n| `max_gathered`   | entier, 1 à 500, défaut `30`                                                                                    | non    | Recettes et intertitres rendus pour une adresse qui rassemble des recettes.                                                             |\n| `max_steps`      | entier, 1 à 100, défaut `20`                                                                                    | non    | Étapes à servir.                                                                                                                        |\n| `max_step_chars` | entier, 80 à 4000, défaut `600`                                                                                 | non    | Caractères gardés par étape.                                                                                                            |\n\n**En retour :** `kind` dit ce que l'adresse portait. Une réponse `recipe` porte\n`recipe` et pas de `collection` ; une réponse `collection` porte `collection` et\npas de `recipe`, et c'est un article rassemblant d'autres recettes, avec les\n`headings` dont il est bâti et les `recipes` vers lesquelles il pointe, chacune\nlisible par `get_recipe`.\n\nUne recette vient dans la forme où toutes les sources sont rendues, quelle\nque soit celle qui l'a publiée : son titre, son adresse, ses ingrédients avec le\n`scaling` et l'`is_equipment` de chaque ligne, ses étapes, et les parties\ndemandées. Un champ qu'une\nsource publie et dont une autre n'a pas la notion revient absent plutôt\nqu'inventé. `rest_minutes` porte le temps de repos d'une source qui l'imprime à\npart, et il n'entre dans aucun autre temps rendu ici. `steps_as_one_block` dit\nquand une source a publié sa méthode d'un seul bloc de prose plutôt qu'en étapes.\n`scaling_summary` compte les lignes de quatre façons, et les quatre font le\ntotal de la liste. `withheld` nomme la partie qu'une source réserve à ses abonnés, qui est une\npartie que la page porte et non une partie illisible. Augmentez `max_step_chars`\nquand une étape a été coupée au milieu d'une phrase.\n\n### `compare_recipes`\n\nMet plusieurs versions d'un même plat côte à côte.\n\n| Argument         | Type                                                                                                   | Requis | Ce qu'il fait                               |\n| ---------------- | ------------------------------------------------------------------------------------------------------ | ------ | ------------------------------------------- |\n| `dish`           | chaîne, 1 à 200 caractères                                                                             | oui    | Le plat à comparer.                         |\n| `servings`       | entier, 1 à 500                                                                                        | non    | Adapte chaque version à ce nombre de parts. |\n| `sections`       | tableau de `ingredients`, `steps`, `times`, `nutrition`, `tips`, `equipment`, défaut `[\"ingredients\"]` | non    | Les parties à rendre par version.           |\n| `max_steps`      | entier, 1 à 100, défaut `10`                                                                           | non    | Étapes à servir par version.                |\n| `max_step_chars` | entier, 80 à 4000, défaut `600`                                                                        | non    | Caractères gardés par étape.                |\n| `sources`        | tableau d'ids de source                                                                                | non    | Ne compare que ces sources.                 |\n\n**En retour :** `versions`, une recette par source ayant répondu, toutes adaptées\nau même nombre de parts pour que leurs quantités se lisent l'une contre l'autre,\net `differences`, ce qui les sépare. `per_source` rapporte chaque site comme le\nfait une recherche.\n\n### `scale_ingredients`\n\nAdapte n'importe quelle liste d'ingrédients, sans requête à aucun site.\n\n| Argument        | Type                                      | Requis        | Ce qu'il fait                             |\n| --------------- | ----------------------------------------- | ------------- | ----------------------------------------- |\n| `ingredients`   | tableau de 1 à 200 lignes                 | oui           | Les lignes à adapter.                     |\n| `factor`        | nombre, jusqu'à 1000                      | l'un des deux | Le multiplicateur à appliquer.            |\n| `from_servings` | entier, 1 à 500                           | l'un des deux | Le nombre de parts de la liste d'origine. |\n| `to_servings`   | entier, 1 à 500                           | l'un des deux | Le nombre de parts voulu.                 |\n| `language`      | `auto`, `fr`, `en` ou `es`, défaut `auto` | non           | Comment chaque ligne est lue.             |\n\nPassez `factor`, ou le couple `from_servings` et `to_servings`. `auto` lit chaque\nligne pour elle-même, ce dont a besoin une liste portant plusieurs langues ;\nnommer une langue lit toutes les lignes ainsi.\n\n**En retour :** les lignes adaptées dans la forme que rend `get_recipe`, chacune\navec son `scaling` et son `is_equipment`, vrai pour une ligne qui nomme un outil\net qu'on laisse telle quelle. `scaled_count`, `rounded_count`, `unscaled_count`\net `equipment_count` font le total des lignes envoyées.\n\n## L'adaptation des quantités\n\nUne quantité est exprimée dans l'unité qui lui convient. Après adaptation, une\nligne peut donc apparaître dans une autre unité que celle de la recette : 200 g\nmultipliés par vingt donnent `4 kg`.\n\nLa finesse à laquelle un ingrédient se coupe dépend de sa nature. Une baguette se\ncoupe en deux, en trois ou en quatre ; un oeuf ne se partage pas. Une quantité\nqui tombe entre les deux est donc arrondie, et la recette adaptée s'écarte alors\nun peu des proportions de l'originale. La ligne porte `rounded`, et sa note dit\nce qui a été fait.\n\nLes sources écrivent leurs quantités dans leur propre langue, et une ligne\nest lue dans la langue où elle a été écrite. Les chiffres sont l'arithmétique de\nce serveur, donc dites qu'ils ont été recalculés quand vous les montrez.\n\n## Ce qu'une réponse dit des sources\n\nChaque réponse rend compte de chaque source séparément. Un site qui a échoué, un\nque personne n'a interrogé et un qui a répondu vide sont trois choses\ndifférentes, et elles sont rapportées comme trois. Un total reste à côté de la\nsource qui l'a publié, avec ce que cette source compte en le disant : l'un compte\nune catégorie entière, un autre compte les lignes qu'il a servies, et un\ntroisième ne publie aucun total.\n\n## Configuration\n\nChaque variable est facultative. Elles se posent dans le bloc `env` de la\nconfiguration du client.\n\n| Variable                    | Défaut               | Ce qu'elle fait                                                                     |\n| --------------------------- | -------------------- | ----------------------------------------------------------------------------------- |\n| `RECIPES_USER_AGENT`        | l'identité du projet | Nomme votre application auprès des sites, avec une adresse où joindre une personne. |\n| `RECIPES_MIN_INTERVAL_MS`   | `1000`               | Écart entre deux requêtes vers un même site, de 500 à 60000.                        |\n| `RECIPES_TIMEOUT_MS`        | `20000`              | Délai d'une requête, de 1000 à 120000.                                              |\n| `RECIPES_MAX_RETRIES`       | `3`                  | Tentatives après un échec passager, de 0 à 8.                                       |\n| `RECIPES_CACHE_TTL_MS`      | `900000`             | Durée pendant laquelle une réponse reste en mémoire, de 0 à 86400000.               |\n| `RECIPES_CACHE_MAX_ENTRIES` | `200`                | Réponses gardées en mémoire à la fois, de 1 à 5000.                                 |\n| `RECIPES_LOG_LEVEL`         | `error`              | `silent`, `error`, `info` ou `debug`, écrit sur la sortie d'erreur.                 |\n\nUne valeur hors de sa plage retombe sur le défaut, et la raison est écrite sur la\nsortie d'erreur.\n\n## Erreurs\n\nChaque échec porte un des six codes, un message, et quand cela aide une\nindication du geste suivant.\n\n| Code            | Ce qui s'est passé                                  | Que faire                                                                               |\n| --------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------- |\n| `not_found`     | Une source a répondu, et n'a pas cette recette.     | Vérifiez l'identifiant avec `search_recipes`.                                           |\n| `invalid_input` | Les arguments ont été refusés avant toute requête.  | Lisez le message, qui nomme l'argument.                                                 |\n| `rate_limited`  | Une source demande à ce client de ralentir.         | Attendez, puis rappelez avec les mêmes arguments. La recette est toujours là.           |\n| `parse_failure` | Une page a chargé et le contenu attendu est absent. | Signalez-le sur [le suivi d'incidents](https://github.com/smeet666/mcp-recipes/issues). |\n| `network_error` | La requête n'a pas abouti.                          | Réessayez sous peu.                                                                     |\n| `timeout`       | La requête a dépassé son délai.                     | Augmentez `RECIPES_TIMEOUT_MS`, ou demandez moins de lignes.                            |\n\nUne source qui échoue est rapportée source par source plutôt que de faire échouer\ntoute la réponse, donc un site silencieux n'en cache jamais un autre.\n\n## Comme bibliothèque\n\nLa couche qui lit les sites est publiée seule, avec son rythme, son cache et\nses erreurs, sans protocole attaché.\n\n```ts\nimport { RecipesClient } from \"mcp-recipes/client\";\n\nconst client = new RecipesClient();\nconst { rows, reports } = await client.searchRecipes(\"carbonara\", 3);\nconsole.log(\n  rows.length,\n  reports.map((report) => report.status),\n);\n\nconst { recipe } = await client.getRecipe(rows[0].id);\n```\n\n`searchRecipes(query, limitPerSource, sources?, options?)` répond\n`{ rows, reports }` : un rapport par source, disant si elle a répondu et ce que\nson propre compte mesure, pour qu'une source en échec ne se lise jamais comme une\nsource qui ne détient rien. `getRecipe(id)` répond `{ recipe, cached, read }`, et\nlève une erreur portant un des six codes. `client.profiles` énumère les sources\nque cette construction enregistre.\n\nLa mise à l'échelle est publiée à part, sous `mcp-recipes/scale`, et travaille\nhors ligne sur n'importe quelle liste :\n\n```ts\nimport { scaleIngredients } from \"mcp-recipes/scale\";\n\nscaleIngredients([\"200 g de harina\", \"4 oeufs\", \"1 cup milk\"], { factor: 2 });\n```\n\nChaque site garde son propre rythme, et les planchers tiennent également ici.\n\n## Rythme et attribution\n\nChaque site est cadencé pour lui-même, une requête à la fois avec au moins une\nseconde entre deux, et le plancher d'une demi-seconde tient quelle que soit la\nconfiguration. Deux des sites en demandent davantage, trois secondes entre deux\nrequêtes, et ils l'obtiennent : un réglage posé pour toutes les sources peut\nélargir l'écart d'un site, jamais le réduire. Les interroger toutes à la fois coûte donc à chacune une requête,\njamais deux. Le `User-Agent` se termine toujours par l'identité du projet et une\nadresse où joindre une personne.\n\nChaque ligne porte l'adresse de la page de la recette et le nom du site qui l'a\npubliée. Les pages du Cookbook sont publiées sous\n[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.fr), qui\ndemande que ce qu'on bâtit dessus soit partagé sous la même licence. Marmiton,\nPtitchef, BBC Good Food, Supertoinette et Pequerecetas n'énoncent aucune condition sur une page\nde recette, et leurs recettes appartiennent à ces sites et aux cuisiniers qui les\nont écrites. Le silence n'est pas une autorisation : créditez le site et liez la\npage d'où vient la recette.\n\nUne recette que BBC Good Food réserve à ses abonnés revient sans ses ingrédients\nni sa méthode, nommée comme une recette retenue, avec l'adresse de sa page. Ce\nserveur ne reconstitue pas ce que ce site a choisi de vendre.\n\nDeux chiffres que les sites publient ne sont pas repris ici. Une difficulté est\nun mot que chaque site écrit à sa façon, sur aucune échelle publiée : elle ne\nsiège sur aucun axe le long duquel deux versions se compareraient. Un coût est un\nprix en euros sur un site et un rang dans sa propre liste sur un autre, et un\nseul champ portant les deux inviterait à les comparer.\n\nCe MCP est un projet non officiel, sans affiliation à aucun des sites qu'il\nlit.\n\n## Confidentialité\n\nCe serveur ne collecte rien sur vous et n'envoie rien à son auteur. Il tourne sur\nvotre machine, ne joint que `www.marmiton.org`, `api.wikimedia.org`,\n`www.ptitchef.com`, `www.bbcgoodfood.com`, `www.supertoinette.com` et\n`www.pequerecetas.com`, garde ses réponses en mémoire le temps qu'il tourne, et\nn'écrit rien sur le disque. [PRIVACY.md](PRIVACY.md) dit ce qu'une requête\nemporte et quels réglages changent cela.\n\n## Développement\n\n```bash\nnpm install\nnpm run build:fixtures\nnpm test\nnpm run check\n```\n\nLes tests s'exécutent sur des fixtures engendrées et n'émettent aucune requête.\nLa suite en direct, `npm run test:live`, émet une requête par route et tourne\nchaque nuit contre les sites eux-mêmes.\n\n## Contribuer\n\nLes anomalies, les questions et les idées ont leur place dans\n[le suivi d'incidents](https://github.com/smeet666/mcp-recipes/issues). Les\npropositions de modification sont bienvenues ; ouvrir un ticket d'abord aide à\ns'accorder sur la forme du changement. Voir [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Licence\n\nMIT, voir [LICENSE](LICENSE). Les recettes appartiennent aux sites qui les ont\npubliées et à leurs auteurs.\n",
  "bytes": 44433,
  "sha": "86ac3ec8daccd4ace15b1e3564e363cab617b859ce799d80248e5abf3212a236",
  "repo_slug": "smeet666/mcp-recipes",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_smeet666_mcp_recipes_a68986a3/readme"
}