{"name":"io.github.cyanheads/openfoodfacts-mcp-server","slug":"cyanheads-openfoodfacts-mcp-server","title":null,"description":"Barcode lookup, nutrition search, and product comparison for 3M+ crowd-sourced food products.","url":"https://mcp.market/server/cyanheads-openfoodfacts-mcp-server","rating":null,"grade":"A","score":93,"certified":false,"status":"active","category":"ecommerce","tags":["ecommerce","search"],"presence":{"score":34,"stars":1,"forks":0,"downloads_week":135,"last_push_at":"2026-09-17T18:33:48.000Z","license":"Apache-2.0"},"uptime":{"percent":100,"checks":1,"ok":1,"last_checked_at":"2026-09-19T17:30:21.671Z","last_ok_at":"2026-09-19T17:30:21.671Z","latency_ms":706},"claimed":false,"transport":"mixed","callable_via_gateway":true,"default_price_micros":0,"repository":"https://github.com/cyanheads/openfoodfacts-mcp-server","website":null,"version":"0.3.4","remotes":[{"type":"streamable-http","url":"https://openfoodfacts.caseyjhand.com/mcp"}],"packages":[{"registryType":"npm","registryBaseUrl":"https://registry.npmjs.org","identifier":"@cyanheads/openfoodfacts-mcp-server","version":"0.3.4","runtimeHint":"bun","transport":{"type":"stdio"},"packageArguments":[{"value":"run","type":"positional"},{"value":"start:stdio","type":"positional"}],"environmentVariables":[{"description":"Open Food Facts API base URL override. Useful for testing against a mock server.","format":"string","default":"https://world.openfoodfacts.org","name":"OFF_BASE_URL"},{"description":"Product read rate limit (requests/min). Matches the 15 req/min/IP Open Food Facts documents for product reads.","format":"string","default":"15","name":"OFF_RATE_LIMIT_PRODUCT"},{"description":"Search rate limit (requests/min).","format":"string","default":"10","name":"OFF_RATE_LIMIT_SEARCH"},{"description":"Taxonomy resolution rate limit (requests/min). Applies to the live tag-vocabulary lookups off_browse_taxonomy makes; a spent budget falls back to the offline sample rather than failing.","format":"string","default":"10","name":"OFF_RATE_LIMIT_TAXONOMY"},{"description":"Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').","format":"string","default":"info","name":"MCP_LOG_LEVEL"}]},{"registryType":"npm","registryBaseUrl":"https://registry.npmjs.org","identifier":"@cyanheads/openfoodfacts-mcp-server","version":"0.3.4","runtimeHint":"bun","transport":{"type":"streamable-http","url":"http://localhost:3010/mcp"},"packageArguments":[{"value":"run","type":"positional"},{"value":"start:http","type":"positional"}],"environmentVariables":[{"description":"Open Food Facts API base URL override. Useful for testing against a mock server.","format":"string","default":"https://world.openfoodfacts.org","name":"OFF_BASE_URL"},{"description":"Product read rate limit (requests/min). Matches the 15 req/min/IP Open Food Facts documents for product reads.","format":"string","default":"15","name":"OFF_RATE_LIMIT_PRODUCT"},{"description":"Search rate limit (requests/min).","format":"string","default":"10","name":"OFF_RATE_LIMIT_SEARCH"},{"description":"Taxonomy resolution rate limit (requests/min). Applies to the live tag-vocabulary lookups off_browse_taxonomy makes; a spent budget falls back to the offline sample rather than failing.","format":"string","default":"10","name":"OFF_RATE_LIMIT_TAXONOMY"},{"description":"The hostname for the HTTP server.","format":"string","default":"127.0.0.1","name":"MCP_HTTP_HOST"},{"description":"The port to run the HTTP server on.","format":"string","default":"3010","name":"MCP_HTTP_PORT"},{"description":"The endpoint path for the MCP server.","format":"string","default":"/mcp","name":"MCP_HTTP_ENDPOINT_PATH"},{"description":"Authentication mode to use: 'none', 'jwt', or 'oauth'.","format":"string","default":"none","name":"MCP_AUTH_MODE"},{"description":"Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').","format":"string","default":"info","name":"MCP_LOG_LEVEL"}]}],"tools":[{"name":"off_browse_taxonomy","description":"Resolve a human term to the canonical Open Food Facts tag ID that off_search_products filters on. Covers categories, labels/certifications, allergens, additives, countries, NOVA groups, and Nutri-Score grades. Pass a search term to resolve against the Open Food Facts vocabulary, which holds tens of thousands of tags; omitting it returns only a small reference list for each facet except NOVA groups and Nutri-Score grades, which are complete. Most tag IDs use the \"en:\" prefix (e.g. \"en:organic\", \"en:no-gluten\", \"en:crustaceans\"); NOVA groups return bare digits \"1\"-\"4\" and Nutri-Score grades bare letters \"a\"-\"e\". Pass the id through to off_search_products exactly as returned. Category tags are frequently plural (\"kombucha\" resolves to \"en:kombuchas\"), so use the returned id rather than constructing one.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"facet":{"type":"string","enum":["categories","labels","allergens","additives","countries","nova_groups","nutrition_grades"],"description":"\"categories\" covers food categories (en:cheeses, en:breakfast-cereals). \"labels\" covers certifications (en:organic, en:fair-trade). \"allergens\" covers declared allergens (en:milk, en:gluten). \"additives\" covers E-numbers (en:e322). \"countries\" covers country-of-sale tags (en:france). \"nova_groups\" and \"nutrition_grades\" are closed vocabularies returned complete; the other five are resolved against the Open Food Facts taxonomy."},"search":{"description":"Term to resolve. Matched case-insensitively as a substring of the tag ID, the display name, or a common synonym of either (\"shellfish\" resolves to en:crustaceans, \"gluten free\" to en:no-gluten). A single word works best (\"hummus\", not \"hummus dip\"). Omit only to see a small reference list — Open Food Facts cannot list the full vocabulary without a term, so an unfiltered call is not a view of the full facet.","type":"string"},"limit":{"default":20,"description":"Maximum entries to return (1–100, default 20). There is no offset or page input: Open Food Facts returns only the first `limit` matches for a term and offers no cursor, so narrow the search term rather than paging.","type":"integer","minimum":1,"maximum":100}},"required":["facet"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"off_compare_products","description":"Side-by-side nutrition and scoring comparison for 2–10 products by barcode. Returns a normalized table of energy (kcal/100g), fat, saturated fat, sugars, salt, protein, fiber, Nutri-Score, NOVA group, and Green-Score. Designed for \"which of these cereals is healthiest?\" or \"compare these pasta brands\" workflows. Missing nutrition data for any product is preserved as absent — comparisons are not imputed. A batch is not all-or-nothing: barcodes that resolve are returned even when others fail, with confirmed-missing barcodes listed in not_found and failed fetches listed separately in failed. Scores carry regional formula caveats. Data under ODbL 1.0 — cite Open Food Facts in downstream use.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"barcodes":{"minItems":2,"maxItems":10,"type":"array","items":{"type":"string","pattern":"^\\d{8,14}$","description":"EAN-13 or UPC barcode (8–14 digits)."},"description":"2–10 barcodes to compare, returned as one row each in input order. Example: [\"3017620422003\", \"7622210100146\"]."}},"required":["barcodes"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"off_get_product","description":"Fetch a packaged food product by barcode (EAN-13 or UPC) from Open Food Facts. Returns the product name, brand, quantity, ingredients (raw text and parsed list), declared allergens, trace allergens the label warns about, additives, the product-level vegan/vegetarian/palm-oil analysis, computed scores (Nutri-Score a–e, NOVA 1–4, Green-Score), nutrition per 100g and per serving, categories, labels, packaging, origins, countries of sale, image URL, and data completeness. Open Food Facts is a crowd-sourced database — a missing field means \"not yet entered by contributors,\" not that the attribute is absent from the actual product. Computed scores carry regional formula caveats and are indicators, not absolute rankings. Data is under ODbL 1.0 — cite Open Food Facts in downstream use.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"barcode":{"type":"string","pattern":"^\\d{8,14}$","description":"EAN-13 or UPC barcode (8–14 digits). The primary key for Open Food Facts. Example: \"3017620422003\" (Nutella FR)."},"fields":{"description":"Subset of fields to return. Omitting returns all standard fields. Use to reduce payload when only scores or ingredients are needed. A field that cannot be read on its own arrives with what it depends on: nutriments brings serving_size, serving_quantity, and serving_quantity_unit so per-serving figures carry their denominator, and serving_quantity_unit brings the quantity it describes. requested_fields echoes the full set that was fetched.","type":"array","items":{"type":"string","enum":["product_name","brands","quantity","ingredients_text","ingredients","allergens_tags","traces_tags","additives_tags","ingredients_analysis_tags","nutriscore_grade","nova_group","ecoscore_grade","nutriments","serving_size","serving_quantity","serving_quantity_unit","categories_tags","labels_tags","packaging_tags","origins_tags","countries_tags","image_url","completeness","data_quality_tags"],"description":"A specific product field to include in the response."}}},"required":["barcode"],"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}},{"name":"off_search_products","description":"Search Open Food Facts by full-text query, structured tag filters, or both at once. Returns a summary list with barcodes, product names, brands, Nutri-Score, NOVA group, and categories — enough for triage and selection, not full label data. Use off_get_product on the returned barcodes for complete details. A text query and tag filters combine: results match the query text and satisfy every filter provided (e.g. query \"dark chocolate\" with labels_tag \"en:organic\" and countries_tag \"en:france\" returns organic chocolate sold in France); numeric nutrient_filters express per-100 g thresholds such as sugars below 8 g and combine the same way; additives_tag is the one exception, filtering only on searches carrying neither query nor nutrient_filters. Tag filter values must be canonical tag IDs (e.g. \"en:organic\", \"en:no-gluten\") — use off_browse_taxonomy to resolve human terms to tag IDs. At least one search parameter is required. The two paths read different indexes: a search carrying query is answered by the text index, a snapshot that lags the live database, while a tag-only search reads the live database and is current — so a recently contributed product can be missing from a text search and present in the same search without query. Data is crowd-sourced; result count reflects contributed products, not all products in the market. Data under ODbL 1.0 — cite Open Food Facts in downstream use.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"query":{"description":"Full-text search term across product names, brands, and ingredients. Combines with any tag filters — results match this text and satisfy the filters. Example: \"dark chocolate 70%\". Supplying it routes the search to the text index, a snapshot that lags the live Open Food Facts database; drop it to run the same tag filters against the current data.","type":"string"},"categories_tag":{"description":"Canonical category tag ID. Example: \"en:breakfast-cereals\", \"en:cheeses\". Use off_browse_taxonomy with facet=\"categories\" to discover valid values.","type":"string"},"brands_tag":{"description":"Brand slug (lowercased, hyphenated). Example: \"nutella\", \"kelloggs\". Matched exactly against the normalized slug — a partial or misspelled slug matches nothing rather than falling back to a near match, so put open-ended brand wording in query instead.","type":"string"},"labels_tag":{"description":"Canonical label/certification tag ID. Example: \"en:organic\", \"en:fair-trade\", \"en:no-gluten\". Use off_browse_taxonomy with facet=\"labels\".","type":"string"},"allergens_tag":{"description":"Canonical allergen tag ID. Example: \"en:milk\", \"en:gluten\". Use off_browse_taxonomy with facet=\"allergens\". Selects products that declare this allergen; it cannot select allergen-free products, because a product with no allergen tags may simply have none entered yet.","type":"string"},"additives_tag":{"description":"Canonical additive (E-number) tag ID. Example: \"en:e322\", \"en:e330\". Use off_browse_taxonomy with facet=\"additives\". Available only on searches carrying neither query nor nutrient_filters — both route to a backend with no additives field, so combining them is rejected instead of silently returning nothing.","type":"string"},"nutrition_grade":{"description":"Filter by Nutri-Score grade. \"a\" is highest nutritional quality, \"e\" is lowest. Products without a score are excluded.","type":"string","enum":["a","b","c","d","e"]},"nova_group":{"description":"Filter by NOVA food processing class. \"1\"=unprocessed/minimally processed, \"4\"=ultra-processed. Products without a NOVA score are excluded.","type":"string","enum":["1","2","3","4"]},"countries_tag":{"description":"Canonical country tag ID. Example: \"en:france\", \"en:united-states\". Filters to products sold in that country.","type":"string"},"nutrient_filters":{"description":"Numeric constraints on nutrient values per 100 g, combined as AND with each other and with every other filter. Pair two entries on the same nutrient to express a range (e.g. sugars gte 2 and sugars lte 8). Served only by the text backend, so supplying one routes the search there even without query — it then reads the lagging text index and is subject to the 10,000-result page window, and additives_tag cannot be combined with it. Per-serving and prepared-product values are not searchable.","maxItems":18,"type":"array","items":{"type":"object","properties":{"nutrient":{"type":"string","enum":["energy-kcal","fat","saturated-fat","carbohydrates","sugars","fiber","proteins","salt","sodium"],"description":"Nutrient to constrain, measured per 100 g. Energy is kilocalories; every other value is grams per 100 g."},"operator":{"type":"string","enum":["lt","lte","gt","gte"],"description":"Comparison against value: \"lt\" below, \"lte\" at or below, \"gt\" above, \"gte\" at or above."},"value":{"type":"number","minimum":0,"description":"Threshold to compare against, in the nutrient's per-100 g unit."}},"required":["nutrient","operator","value"],"description":"One numeric constraint on a per-100 g nutrient value."}},"sort_by":{"description":"Sort order, applied on every search. Each value orders newest or highest first: \"unique_scans_n\" surfaces the most-scanned products, \"last_modified_t\" and \"created_t\" the most recently updated and newest records, \"popularity_key\" the most popular. Omitting it leaves text searches relevance-ranked and tag-only searches in the default order.","type":"string","enum":["last_modified_t","unique_scans_n","created_t","popularity_key"]},"page":{"default":1,"description":"Page number (1-based). Use with page_size to paginate results. Searches that include a text query serve only the first 10,000 results, so page * page_size must stay at or below 10,000 — a deeper request is rejected rather than sent. Tag-only searches have no published window, but Open Food Facts refuses deep pages unpredictably; narrowing the filters is more reliable than paging far in.","type":"integer","minimum":1,"maximum":9007199254740991},"page_size":{"default":20,"description":"Results per page (1–50, default 20). Keep low for initial exploration; increase for comparison workflows.","type":"integer","minimum":1,"maximum":50}},"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false}}],"scan":{"score":93,"grade":"A","scanned_at":"2026-09-19T19:23:31.877Z","report":{"scannerVersion":"0.1.5","scannedAt":"2026-09-19T19:23:31.791Z","components":{"code":{"score":25,"max":25,"notes":["24 source files scanned","24 source files scanned"]},"reliability":{"score":20,"max":20,"notes":["remote reachable in 1056ms"]},"poisoning":{"score":15,"max":15,"notes":["4 tool descriptions checked"]},"auth":{"score":10,"max":15,"notes":["open endpoint, read-only tools"]},"maintenance":{"score":15,"max":15,"notes":["last push 2 days ago"]},"identity":{"score":8,"max":10,"notes":["registry namespace matches repository owner","GitHub account older than a year"]}},"findings":[],"inputs":{"probes":[{"url":"https://openfoodfacts.caseyjhand.com/mcp","reachable":true,"authRequired":false,"latencyMs":1056,"serverInfo":{"name":"openfoodfacts-mcp-server","version":"0.3.4"}}],"packages":[{"registryType":"npm","identifier":"@cyanheads/openfoodfacts-mcp-server","version":"0.3.4","found":true,"license":"Apache-2.0","hasInstallScripts":false,"dependencyCount":3,"publishedAt":"2026-09-17T18:35:12.934Z","repositoryUrl":"git+https://github.com/cyanheads/openfoodfacts-mcp-server.git","weeklyDownloads":135},{"registryType":"npm","identifier":"@cyanheads/openfoodfacts-mcp-server","version":"0.3.4","found":true,"license":"Apache-2.0","hasInstallScripts":false,"dependencyCount":3,"publishedAt":"2026-09-17T18:35:12.934Z","repositoryUrl":"git+https://github.com/cyanheads/openfoodfacts-mcp-server.git"}],"repo":{"found":true,"owner":"cyanheads","repo":"openfoodfacts-mcp-server","archived":false,"pushedAt":"2026-09-17T18:33:48Z","stars":1,"forks":0,"openIssues":2,"ownerType":"User","ownerAvatarUrl":"https://avatars.githubusercontent.com/u/10339515?v=4","ownerCreatedAt":"2014-12-29T13:01:12Z","license":"Apache-2.0"},"icon":{"url":"https://avatars.githubusercontent.com/u/10339515?v=4&s=128","source":"registry"},"presence":{"stars":1,"forks":0,"downloadsWeek":135,"license":"Apache-2.0","lastPushAt":"2026-09-17T18:33:48.000Z","score":34}}}},"grade_history":[],"reviews":[]}