@cyanheads/openfoodfacts-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/openfoodfacts-mcp-serverLook up the product with barcode 7622210289281"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Public Hosted Server: https://openfoodfacts.caseyjhand.com/mcp
Overview
Packaged-food data from Open Food Facts, a crowd-sourced database of 3M+ products. Look up items by barcode, search by text, tags, allergens, and nutrient thresholds, compare products side by side, and resolve everyday terms to the canonical tags the filters take.
Tools
Tool | Description |
| Fetch one product by barcode: ingredients, allergens and traces, additives, scores, nutrition, and tags |
| Search by text, tag filters, allergen exclusions, and nutrient thresholds (per 100 g, or per 100 ml for liquids); returns summary rows with barcodes |
| Compare up to 10 products per call by barcode on nutrition (per 100 g, or per 100 ml for liquids), Nutri-Score, NOVA, and Green-Score |
| Resolve a human term to the canonical tag ID that |
Related MCP server: openfoodfacts-mcp
Capability reference
off_get_product tool
barcodeis digits only, 4–40 digits after any leading zeros; optionalfieldstrims the response and pulls in what a field depends on (nutrimentsbrings the serving size), withrequested_fieldsechoing what was fetchedReturns ingredients (raw text plus a parsed tree up to three levels deep with
percent_estimate),allergens_tags,traces_tags,additives_tags,ingredients_analysis_tags(vegan, vegetarian, palm-oil verdicts),nutriscore_grade,nova_group,ecoscore_grade(Green-Score),nutriments(per serving, and per 100 g, or per 100 ml for liquids), category/label/packaging/origin/country tags,image_url, andcompleteness(0–1)traces_tags: ["en:none"]means the label states no traces, while an empty array means none entered yet; a barcode no contributor has recorded fails asnot_found
off_search_products tool
query(up to 24 words, all of which must match the name, generic name, categories, labels, or brand; punctuation separates words, somilk-chocolaterequires both) plus the tag filterscategories_tag,brands_tag,labels_tag(up to 10),allergens_tag,traces_tag,ingredients_analysis_tag,additives_tag,nutrition_grade,nova_group,countries_tag, theexclude_allergens/exclude_traceslists (up to 14 each), and up to 18nutrient_filters(per 100 g, or per 100 ml for liquids), all combined as AND; optionalsort_bypage_sizedefaults to 20 and is served at up to 50 per call; a larger value (up to 10,000) is served at 50 and flagged inpage_size_capped, andpagecounts pages of 50. Rows carrybarcode,product_name,brands,nutriscore_grade,nova_group,ecoscore_grade, andcategories_tags, plustotal,total_is_lower_bound,last_page,omitted, andtruncated(more matches beyond this page). Exclusions addexclusion_coverage, since products with no allergen data entered pass them;nutrient_filtersaddindexed_nutriments(the index values the filters checked, which can differ fromoff_get_product)queryornutrient_filterssearches a text index that lags the live database (flagged intext_index_snapshot), reaches only the first 10,000 results, and rejectsadditives_tagasadditives_filter_needs_tag_search; tag-only searches read the live database through page 10. A page past either limit fails aspage_out_of_range
off_compare_products tool
2–100
barcodes, one row each in input order; repeated barcodes are compared once, and those past the 10th distinct one are listed inunprocessedwithout being fetchedRows carry
found,nutriscore_grade,nova_group,ecoscore_grade, energy, fat, saturated fat, sugars, salt, protein, and fiber (per 100 g, or per 100 ml for liquids), andcompleteness; missing values stay absent, never imputednot_foundlists barcodes with no contributor record andfailedlists fetches that failed, each with areason; a failed barcode gets no row and never blocks the rest
off_browse_taxonomy tool
facetis one ofcategories,labels,allergens,additives,countries,nova_groups,nutrition_grades;searchmatches a substring of the tag ID, name, or a synonym ("shellfish" →en:crustaceans);limit(default 20) returns up to 100 per call (a value up to 200 still returns 100); a cut result says how many matched; no pagingReturns
tags[]ofidandname, whereidgoes tooff_search_productsunchanged;nova_groupsandnutrition_gradescome back complete withtotal_in_facetThe five open facets resolve against the live Open Food Facts taxonomy and fall back to a small offline sample, with a
notice, when it is unavailable; omittingsearchreturns only that sample
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Open Food Facts-specific:
No API key; the identifying
User-AgentOpen Food Facts' terms require is built inPer-endpoint request budgets (product 15/min, search 10/min, taxonomy 10/min); transient failures retry up to 4 attempts
Nutriments normalized from hyphenated keys (
energy-kcal_100g→energy_kcal_100g), withadditional_100g/additional_servingmaps that label each nutrient with its unitTag filters take canonical IDs (
en:organic,en:no-gluten); on text searches a case variant, synonym, or brand name is canonicalized first (US→en:united-states)Every field is contributor-entered, so a missing one means not yet recorded; Nutri-Score, NOVA, and Green-Score are Open Food Facts' own computed grades, returned as-is, and carry regional formula caveats
Agent-friendly output:
Per-serving nutrition carries its denominator:
serving_size,serving_quantity,serving_quantity_unit, and a note when none is recordedGraceful partial failure:
off_compare_productsreturns resolved rows alongsidenot_foundandfailedExplicit count bounds:
total_is_lower_bound,last_page, andomittedon searchesTyped failures: each carries a declared
reason(upstream_error,upstream_timeout,upstream_rejected,rate_limited, plus per-tool input reasons) and a recovery hint; an incomplete upstream response is a retryableupstream_error, never an empty result or a not-found
Getting started
Public Hosted Instance
A public instance is available at https://openfoodfacts.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"openfoodfacts-mcp-server": {
"type": "streamable-http",
"url": "https://openfoodfacts.caseyjhand.com/mcp"
}
}
}Self-Hosted / Local
No API key is required. Add the following to your MCP client configuration file.
{
"mcpServers": {
"openfoodfacts-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/openfoodfacts-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"openfoodfacts-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/openfoodfacts-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with Docker:
{
"mcpServers": {
"openfoodfacts-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/openfoodfacts-mcp-server:latest"
]
}
}
}For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
No API key or account. Open Food Facts asks clients to identify themselves, and the server's
User-Agentdoes so without configuration.
Installation
Clone the repository:
git clone https://github.com/cyanheads/openfoodfacts-mcp-server.gitNavigate into the directory:
cd openfoodfacts-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env if you need to override rate limits or the base URLConfiguration
Variable | Description | Default |
| Open Food Facts API base URL. |
|
| Product read budget (requests/min). The default is Open Food Facts' published per-IP limit; lower it on a shared outbound IP. |
|
| Search budget (requests/min). The default is Open Food Facts' published per-IP limit. |
|
| Taxonomy lookup budget (requests/min), shared by |
|
| Transport: |
|
| HTTP server port. |
|
| Auth mode: |
|
| Log level ( |
|
| Log each failed tool call's arguments and result, redacted by key name and capped at |
|
| Enable OpenTelemetry instrumentation. |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:httpRun checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t openfoodfacts-mcp-server .
docker run --rm -p 3010:3010 openfoodfacts-mcp-serverThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/openfoodfacts-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Open Food Facts API client — HTTP, rate limiting, retry, field normalization. |
| Tag vocabulary — live resolution, offline sample, and tag-value canonicalization. |
| Markdown escaping for contributor-entered values in text output. |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools via the barrel in
src/mcp-server/tools/definitions/index.tsWrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome — see CONTRIBUTING.md for the forms and what makes a report actionable. Report vulnerabilities through private disclosure, never a public issue. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details. Open Food Facts data is released under the Open Database License (ODbL) 1.0; downstream use must cite Open Food Facts.
This server cannot be deployed
Maintenance
Related MCP Connectors
Look up a packaged food by barcode, search and compare products, and check listed allergens.
USDA FoodData Central nutrient lookup, FDA recall watch, EU FMCG labelling via remote MCP
Unlock the power of food transparency with our Open Food Facts MCP server. Easily look up any food
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI assistants to access the Open Food Facts database to query detailed food product information, nutritional data, and environmental scores. Supports product lookup by barcode, smart search with filtering, nutritional analysis, product comparison, and dietary recommendations to help users make informed food choices.51MIT
- AlicenseAqualityCmaintenanceAn MCP server for the Open Food Facts API that allows users to search, read, and contribute to a global food database. It enables looking up nutrition data by barcode or name and managing product information through natural language.1048 npm3MIT
- AlicenseAqualityDmaintenanceMCP server for Open Food Facts, enabling food product lookup by barcode, search, nutrition facts, allergen checks, and eco-scores without an API key.8MIT
- AlicenseAqualityAmaintenanceProduct evaluation MCP server for US packaged food. Health scores, ingredient safety, regulatory flags, recall history, corporate ownership.21MIT