Back to Browse

Bbc Goodfood MCP Server

Developer ToolsLow Risk10.0LocalNew
Free

Search BBC Good Food recipes, read one, and scale its ingredients. No API key.

About

Search BBC Good Food recipes, read one, and scale its ingredients. No API key.

Security Report

10.0
Low Risk10.0Low Risk

Valid MCP server (1 strong, 1 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry.

3 files analyzed · 1 issue found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

What You'll Need

Set these up before or after installing:

Identify your own client. This project's identifier stays appended, so the site can always reach a human.Optional

Environment variable: BGF_USER_AGENT

Minimum milliseconds between requests. Default 1500, floor 1000, ceiling 60000.Optional

Environment variable: BGF_MIN_INTERVAL_MS

Deadline for one request, in milliseconds. Default 20000.Optional

Environment variable: BGF_TIMEOUT_MS

Retries when the site is busy or asks for room. Default 3.Optional

Environment variable: BGF_MAX_RETRIES

In-memory cache lifetime, in milliseconds. Default 900000. Set 0 to turn it off.Optional

Environment variable: BGF_CACHE_TTL_MS

In-memory cache size. Default 200.Optional

Environment variable: BGF_CACHE_MAX_ENTRIES

silent, error, info or debug. Default error. Logs go to stderr.Optional

Environment variable: BGF_LOG_LEVEL

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-smeet666-mcp-bbc-goodfood": {
      "env": {
        "BGF_LOG_LEVEL": "your-bgf-log-level-here",
        "BGF_TIMEOUT_MS": "your-bgf-timeout-ms-here",
        "BGF_USER_AGENT": "your-bgf-user-agent-here",
        "BGF_MAX_RETRIES": "your-bgf-max-retries-here",
        "BGF_CACHE_TTL_MS": "your-bgf-cache-ttl-ms-here",
        "BGF_MIN_INTERVAL_MS": "your-bgf-min-interval-ms-here",
        "BGF_CACHE_MAX_ENTRIES": "your-bgf-cache-max-entries-here"
      },
      "args": [
        "-y",
        "mcp-bbc-goodfood"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

mcp-bbc-goodfood

npm CI license MCP Registry Glama Install in Cursor Install in VS Code

An MCP server that reads recipes on BBC Good Food. Read-only, no API key, no account.

Version française


Why it exists

BBC Good Food accepts any value on a search facet, and answers one it does not know with a total of zero. Ask for glutenfree instead of gluten-free and the site says nothing matches, with the same confidence it says 48 recipes match the correct spelling. A model that guesses a spelling gets a confident absence instead of a refusal.

This server publishes the vocabulary, so the question can be asked properly.

The tools

list_filters

Lists the axes a recipe search can be narrowed along, with the values each one takes and how many recipes carry them.

ArgumentTypeMeaning
querystring, optionalMeasure the counts inside this search. Leave it out for the site's whole listing.

The site counts its facets over the rows a search returns, so the two answer different questions. Within chicken, diet reports gluten-free 48. Across the whole listing it reports gluten-free 2810. Neither is comparable to the other, and the answer says which scope it measured.

{
  "query": "chicken",
  "filters": [
    {
      "name": "diet",
      "label": "Diets",
      "options": [{ "value": "gluten-free", "label": "Gluten-free", "count": 48 }],
      "option_count": 10
    }
  ],
  "filter_count": 9,
  "total_available": 363,
  "total_is_ceiling": false,
  "source": "BBC Good Food",
  "notes": ["…"]
}

search_recipes

Searches recipes and returns a listing. Every row carries the path of its page, which is what get_recipe reads.

ArgumentTypeMeaning
querystringA dish, an ingredient, a technique.
limit, pageinteger, optionalRows per page and which page. Defaults to 30 and 1.
sortstring, optionalHow the site orders the listing.
diet, cuisine, meal_type, difficultystring, optionalA value list_filters publishes.
max_total_minutes, max_calories, min_servings, min_ratingnumber, optionalA bound on the recipe.
exclude_premiumboolean, optionalLeave out what sits behind the site's subscription.

The site accepts any value on a facet and answers one it does not know with a total of zero, so a guessed spelling comes back as a confident absence rather than as a refusal. Call list_filters first.

get_recipe

Reads one recipe: its ingredients, its steps, its times, its rating and its nutrition.

ArgumentTypeMeaning
idstringThe page's own path, as a search_recipes row carries it.
servingsinteger, optionalPut the ingredients to this many people.

A recipe behind the site's subscription comes back with everything except its ingredients and its steps, and says so. The page carries them, which is exactly why the rule exists: reading past a wall the site put in front of its own readers would make this server the way around it.

scale_ingredients

Puts a list of ingredient lines to a different number of people, without reading anything on the site.

ArgumentTypeMeaning
ingredientsstring[]The lines to scale, as a recipe writes them.
factornumber, optionalWhat to multiply every quantity by.
from_servings, to_servingsinteger, optionalThe two ends of the change, instead of a factor.
{
  "factor": 0.5,
  "ingredients": [
    {
      "text": "100 g plain flour",
      "original": "200g plain flour",
      "scaling": "scaled",
      "amount": 100,
      "amount_max": null,
      "unit": "g"
    },
    {
      "text": "2 eggs",
      "original": "3 eggs",
      "scaling": "rounded",
      "amount": 2,
      "amount_max": null,
      "unit": null
    },
    {
      "text": "salt and pepper",
      "original": "salt and pepper",
      "scaling": "unscaled",
      "amount": null,
      "amount_max": null,
      "unit": null
    }
  ],
  "scaled_count": 1,
  "rounded_count": 1,
  "unscaled_count": 1,
  "source": "BBC Good Food",
  "notes": ["…"]
}

What the answers refuse to overstate

The published values are a shortlist. The site shows the ten most frequent values on an axis and accepts others it never lists: cuisine=mexican narrows a search to 306 recipes without appearing among the ten. The answer says the list is an excerpt rather than calling it the set of accepted values, because a server that called it that would refuse values that work.

A total can be a floor. The site serves at most 10 000 rows for one search and stops there. A total landing exactly on that figure was cut, so it states a floor rather than a count, and total_is_ceiling says so.

A count the site published nothing for is null, never 0. On a scale that starts at zero the two would be indistinguishable. A rating runs from one star to five, so a recipe nobody has rated comes back as null rather than as a recipe rated zero.

A recalculated quantity says that it was recalculated. The figures a scaled list carries are this server's arithmetic and not the site's, and every line says under scaling whether the arithmetic landed exactly or whether the figure moved to stay usable in a kitchen. Three eggs halved come back as two, because half an egg is not an amount a kitchen measures out; a cook told 2 deserves to know which of the two happened.

A quantity is said in the unit that states it exactly. Two grams divided by ten come back as 200 mg, and two hundred grams multiplied by twenty come back as 4 kg. A figure only moves up the ladder when the larger unit states it exactly: 1875 g reads 1.875 kg, and 7492.5 g stays in grams because 7.4925 kg would need a fourth decimal. A cook who cannot weigh the figure back is worse served by the shorter one.

A recipe whose page states no servings cannot be put to a number of people. The servings argument is then left without effect and a note says so, because the multiplication would have to start from a figure the site never wrote.

Install

npx mcp-bbc-goodfood

Claude Code

claude mcp add bbc-goodfood -- npx -y mcp-bbc-goodfood

Any MCP client

{
  "mcpServers": {
    "bbc-goodfood": {
      "command": "npx",
      "args": ["-y", "mcp-bbc-goodfood"]
    }
  }
}

Container

docker build -t mcp-bbc-goodfood .
docker run -i --rm mcp-bbc-goodfood

The container needs to reach www.bbcgoodfood.com and nothing else. It takes no credentials, because there are none to take.

Settings

Every setting is an environment variable, and none is required. A value outside its range is refused with a line on stderr and the default stands: a setting that cannot take effect says so rather than being quietly clamped.

VariableDefaultRange
BGF_USER_AGENTYour own identifier. This project's stays appended, so the site can reach a human.
BGF_MIN_INTERVAL_MS15001000 to 60000
BGF_TIMEOUT_MS200001000 to 120000
BGF_MAX_RETRIES30 to 8
BGF_CACHE_TTL_MS9000000 to 86400000, 0 turns storage off
BGF_CACHE_MAX_ENTRIES2001 to 5000
BGF_LOG_LEVELerrorsilent, error, info, debug

The pacing floor cannot be lowered from outside. The site is free to read and publishes no crawl delay, which is a reason to be careful rather than a licence to be fast.

As a library

The reading layer is published on its own, with its pacing, its storage and its error vocabulary and no protocol attached:

import { GoodFoodClient } from "mcp-bbc-goodfood/client";

Errors

Six codes and no more. A caller branches on the code that opens the message.

CodeWhat it means
not_foundThe site holds nothing at that address
invalid_inputThe arguments could not produce a request
rate_limitedThe site asked this client to slow down. It says nothing about whether anything matched
parse_failureAn answer arrived in a shape this server cannot read
network_errorThe request could not be completed
timeoutNo answer arrived within the deadline

Attribution

Recipes, titles and counts belong to BBC Good Food. Every answer carries the source, and a listing shown to a reader should credit the site and link the page.

Contributing

See CONTRIBUTING.md. Tests come first, coverage has a floor of 100%, and the rule everything follows is that the server never says anything the data does not carry.

Licensed under MIT.


mcp-bbc-goodfood (français)

Un serveur MCP qui lit les recettes de BBC Good Food. En lecture seule, sans clé d'API et sans compte.

Pourquoi il existe

BBC Good Food accepte n'importe quelle valeur sur une facette de recherche, et répond à une valeur qu'il ne connaît pas par un total de zéro. Demandez glutenfree au lieu de gluten-free et le site répond que rien ne correspond, avec l'assurance qu'il met à dire que 48 recettes correspondent à la bonne orthographe. Un modèle qui devine une orthographe reçoit une absence confiante plutôt qu'un refus.

Ce serveur publie le vocabulaire, pour que la question soit posable.

Les outils

list_filters

Publie les axes le long desquels une recherche de recettes se resserre, avec les valeurs que chacun prend et le nombre de recettes qui les portent.

ArgumentTypeSens
querychaîne, facultativeMesure les décomptes à l'intérieur de cette recherche. Sans elle, sur tout le catalogue.

Le site compte ses facettes sur les lignes que la recherche rend, si bien que les deux répondent à deux questions différentes. Dans chicken, diet annonce gluten-free 48. Sur tout le catalogue, il annonce gluten-free 2810. Aucun des deux ne se compare à l'autre, et la réponse dit dans quelle étendue elle a mesuré.

{
  "query": "chicken",
  "filters": [
    {
      "name": "diet",
      "label": "Diets",
      "options": [{ "value": "gluten-free", "label": "Gluten-free", "count": 48 }],
      "option_count": 10
    }
  ],
  "filter_count": 9,
  "total_available": 363,
  "total_is_ceiling": false,
  "source": "BBC Good Food",
  "notes": ["…"]
}

search_recipes

Cherche des recettes et rend une liste. Chaque ligne porte le chemin de sa page, qui est ce que get_recipe lit.

ArgumentTypeSens
querychaîneUn plat, un ingrédient, une technique.
limit, pageentier, facultatifLignes par page et quelle page. Par défaut 30 et 1.
sortchaîne, facultativeL'ordre dans lequel le site range la liste.
diet, cuisine, meal_type, difficultychaîne, facultativeUne valeur que list_filters publie.
max_total_minutes, max_calories, min_servings, min_ratingnombre, facultatifUne borne sur la recette.
exclude_premiumbooléen, facultatifLaisse de côté ce qui est derrière l'abonnement du site.

Le site accepte n'importe quelle valeur sur une facette et répond à une valeur qu'il ne connaît pas par un total de zéro : une orthographe devinée revient comme une absence assurée et non comme un refus. Appelle list_filters d'abord.

get_recipe

Lit une recette : ses ingrédients, ses étapes, ses temps, sa note et ses valeurs nutritionnelles.

ArgumentTypeSens
idchaîneLe chemin de la page, tel qu'une ligne de search_recipes le porte.
servingsentier, facultatifRemet les ingrédients à ce nombre de parts.

Une recette derrière l'abonnement du site revient avec tout sauf ses ingrédients et ses étapes, et le dit. La page les porte, et c'est exactement pourquoi la règle existe : lire au-delà d'un mur que le site a dressé devant ses propres lecteurs ferait de ce serveur le moyen de le contourner.

scale_ingredients

Remet une liste de lignes d'ingrédients à un autre nombre de personnes, sans rien lire sur le site.

ArgumentTypeSens
ingredientschaîne[]Les lignes à remettre à l'échelle, telles qu'une recette les écrit.
factornombre, facultatifCe par quoi multiplier chaque quantité.
from_servings, to_servingsentier, facultatifLes deux bouts du changement, à la place d'un facteur.
{
  "factor": 0.5,
  "ingredients": [
    {
      "text": "100 g plain flour",
      "original": "200g plain flour",
      "scaling": "scaled",
      "amount": 100,
      "amount_max": null,
      "unit": "g"
    },
    {
      "text": "2 eggs",
      "original": "3 eggs",
      "scaling": "rounded",
      "amount": 2,
      "amount_max": null,
      "unit": null
    },
    {
      "text": "salt and pepper",
      "original": "salt and pepper",
      "scaling": "unscaled",
      "amount": null,
      "amount_max": null,
      "unit": null
    }
  ],
  "scaled_count": 1,
  "rounded_count": 1,
  "unscaled_count": 1,
  "source": "BBC Good Food",
  "notes": ["…"]
}

Ce que les réponses refusent d'affirmer

Les valeurs publiées sont un extrait. Le site montre les dix valeurs les plus fréquentes d'un axe et en accepte d'autres qu'il ne liste jamais : cuisine=mexican resserre une recherche à 306 recettes sans figurer parmi les dix. La réponse dit que la liste est un extrait plutôt que de l'appeler l'ensemble des valeurs acceptées, parce qu'un serveur qui l'appellerait ainsi refuserait des valeurs qui fonctionnent.

Un total peut être un plancher. Le site sert au plus 10 000 lignes pour une recherche et s'arrête là. Un total qui tombe exactement sur ce chiffre a été coupé : il énonce un plancher et non un compte, et total_is_ceiling le dit.

Un décompte que le site n'a pas publié vaut null, jamais 0. Sur une échelle qui commence à zéro, les deux seraient indiscernables. Une note va d'une étoile à cinq, donc une recette que personne n'a notée revient à null plutôt qu'en recette notée zéro.

Une quantité recalculée dit qu'elle a été recalculée. Les chiffres d'une liste remise à l'échelle sont l'arithmétique de ce serveur et non celle du site, et chaque ligne dit sous scaling si le calcul est tombé juste ou si le chiffre a bougé pour rester utilisable en cuisine. Trois œufs divisés par deux reviennent à deux, parce qu'un demi-œuf n'est pas une quantité qu'une cuisine mesure ; un cuisinier à qui l'on annonce 2 mérite de savoir lequel des deux s'est produit.

Une quantité s'énonce dans l'unité qui la dit exactement. Deux grammes divisés par dix reviennent en 200 mg, et deux cents grammes multipliés par vingt reviennent en 4 kg. Un chiffre ne monte d'un échelon que si l'unité du dessus l'énonce exactement : 1875 g se lit 1,875 kg, et 7492,5 g reste en grammes parce que 7,4925 kg demanderait une quatrième décimale. Un cuisinier qui ne peut pas repeser le chiffre est plus mal servi par le plus court.

Une recette dont la page n'énonce aucun nombre de parts ne peut pas être remise à un nombre de personnes. L'argument servings reste alors sans effet et une note le dit, parce que la multiplication devrait partir d'un chiffre que le site n'a jamais écrit.

Installation

npx mcp-bbc-goodfood

Claude Code

claude mcp add bbc-goodfood -- npx -y mcp-bbc-goodfood

N'importe quel client MCP

{
  "mcpServers": {
    "bbc-goodfood": {
      "command": "npx",
      "args": ["-y", "mcp-bbc-goodfood"]
    }
  }
}

Conteneur

docker build -t mcp-bbc-goodfood .
docker run -i --rm mcp-bbc-goodfood

Le conteneur doit joindre www.bbcgoodfood.com et rien d'autre. Il ne prend aucun identifiant, puisqu'il n'y en a aucun à prendre.

Réglages

Chaque réglage est une variable d'environnement, et aucune n'est obligatoire. Une valeur hors bornes est refusée par une ligne sur stderr et le défaut tient : un réglage qui ne peut pas prendre effet le dit, plutôt que d'être borné en silence.

VariableDéfautBornes
BGF_USER_AGENTVotre identifiant. Celui du projet reste ajouté, pour que le site puisse joindre une personne.
BGF_MIN_INTERVAL_MS15001000 à 60000
BGF_TIMEOUT_MS200001000 à 120000
BGF_MAX_RETRIES30 à 8
BGF_CACHE_TTL_MS9000000 à 86400000, 0 éteint le stockage
BGF_CACHE_MAX_ENTRIES2001 à 5000
BGF_LOG_LEVELerrorsilent, error, info, debug

Le plancher du rythme ne peut pas être abaissé de l'extérieur. Le site est gratuit à lire et ne publie aucun délai d'exploration, ce qui est une raison d'être prudent plutôt qu'une licence d'aller vite.

Comme bibliothèque

La couche de lecture est publiée seule, avec son rythme, son stockage et sa taxonomie d'erreurs, sans protocole attaché :

import { GoodFoodClient } from "mcp-bbc-goodfood/client";

Erreurs

Six codes et pas un de plus. Un appelant branche sur le code qui ouvre le message.

CodeCe qu'il veut dire
not_foundLe site n'a rien à cette adresse
invalid_inputLes arguments ne pouvaient pas produire de requête
rate_limitedLe site a demandé de ralentir. Cela ne dit rien sur ce qui correspond
parse_failureUne réponse est arrivée dans une forme que ce serveur ne sait pas lire
network_errorLa requête n'a pas pu aboutir
timeoutAucune réponse n'est arrivée dans le délai

Attribution

Les recettes, les titres et les comptes appartiennent à BBC Good Food. Chaque réponse porte la source, et un listing montré à un lecteur doit créditer le site et lier la page.

Contribuer

Voir CONTRIBUTING.md. Les tests d'abord, un plancher de couverture à 100 %, et la règle qui gouverne tout : le serveur ne dit jamais quelque chose que la donnée ne porte pas.

Sous licence MIT.

Reviews

No reviews yet

Be the first to review this server!