@Cyanheads/Openfoodfacts Server
FreeNot checkedLook up food products by barcode, search by ingredient or nutrition filter, compare products side-by-side, and browse the canonical tag vocabulary via MCP.
About
Look up food products by barcode, search by ingredient or nutrition filter, compare products side-by-side, and browse the canonical tag vocabulary via MCP.
README
@cyanheads/openfoodfacts-mcp-server
Look up food products by barcode, search by ingredient or nutrition filter, compare products side-by-side, and browse the canonical tag vocabulary via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://openfoodfacts.caseyjhand.com/mcp
Overview
Food product data from Open Food Facts, a crowd-sourced database of 3M+ packaged food products. Look up items by barcode, search by text and nutrition/allergen/label tags, compare products side-by-side, and resolve everyday terms to the canonical tag vocabulary from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
| Tool | Description |
|---|---|
off_get_product |
Fetch a packaged food product by barcode. Returns name, brand, quantity, ingredients, declared and trace allergens, additives, the vegan/vegetarian/palm-oil analysis, Nutri-Score, NOVA group, Green-Score, nutrition per 100g/serving, categories, labels, countries of sale, and data completeness. |
off_search_products |
Search by text query, structured tag filters (category, brand, label, allergen, additive, Nutri-Score grade, NOVA group, country), and numeric per-100 g nutrient thresholds. Returns summary rows with barcodes for follow-up lookups. |
off_compare_products |
Side-by-side nutrition and scoring comparison for 2–10 products by barcode. Returns a normalized table of energy, macros, salt, Nutri-Score, NOVA, and Green-Score. |
off_browse_taxonomy |
Resolve a human term to the canonical tag ID (categories, labels, allergens, additives, countries, NOVA groups, Nutri-Score grades) that off_search_products filters on, against the live Open Food Facts taxonomy. |
Capability reference
off_get_product tool
- Accepts 8–14 digit barcodes (EAN-13, EAN-8, UPC-A, UPC-E)
- Returns ingredients (raw text and parsed list with percent estimates, vegan/vegetarian flags), all 14 major allergens as tag IDs, E-number additives, Nutri-Score a–e, NOVA 1–4, Green-Score/Eco-Score, every nutrient Open Food Facts holds per 100g and per serving, the serving size those per-serving figures are measured against, categories/labels/packaging/origins/countries of sale as canonical tag IDs, front image URL, and data completeness score (0–1)
traces_tagscarries the "may contain" allergen warning separately from the declaredallergens_tags;["en:none"]is the label stating no traces, while an empty array means not yet entered — never trace-freeingredients_analysis_tagscarries the vegan, vegetarian, and palm-oil verdicts Open Food Facts computes itself, including its "maybe" states, rather than leaving the per-ingredient flags to be aggregated by the caller- Optional
fieldsparameter restricts the response to a subset (e.g., scores only, or nutrition only); a field that cannot be read on its own arrives with what it depends on —nutrimentsbrings the serving size its per-serving figures are measured against — andrequested_fieldsechoes the full set that was fetched - Open Food Facts is crowd-sourced — a missing field means "not yet entered by contributors," not that the attribute is absent from the actual product
- A barcode no contributor has recorded raises the
not_founderror carrying a recovery hint — it is never returned as an empty result
off_search_products tool
- Full-text
queryplus structured tag filters —categories_tag,brands_tag,labels_tag,allergens_tag,additives_tag,nutrition_grade(a–e),nova_group(1–4),countries_tag— and numericnutrient_filters, all combining as AND; all tag values are canonical IDs, resolved viaoff_browse_taxonomy(brands_tagmatches an exact slug, not free text) - Numeric
nutrient_filtersexpress per-100 g thresholds overenergy-kcal,fat,saturated-fat,carbohydrates,sugars,fiber,proteins,salt, andsodium— each a{ nutrient, operator, value }triple withlt/lte/gt/gte; pair two entries on one nutrient for a range. They AND with every other filter and are served by the text backend, so supplying one routes the search there even withoutquery additives_tagfilters only on searches carrying neitherquerynornutrient_filters— both route to a backend with no additives field, so the pairing is rejected up front rather than silently returning zero hits- Pagination via
page(1-based) andpage_size(1–50, default 20); text searches serve only the first 10,000 results (page * page_sizebeyond that is rejected), tag-only searches publish no window but refuse deep pages unpredictably totalis exact on tag-only searches; text searches stop counting at 10,000 and settotal_is_lower_bound: truewith the count rendered as10000+- The two paths read different indexes: a search carrying
queryis answered by a text index that lags the live database, and says so on both response surfaces; a tag-only search reads the live database. A recently contributed product can be missing from the first and present in the second sort_by(last_modified_t,unique_scans_n,created_t,popularity_key) orders newest or highest first on both paths; omitting it leaves text searches relevance-ranked- A page past the end of a result set is reported as an exhausted page naming the deepest page that holds products, not as a zero-match search — the broaden-the-filters guidance appears only when nothing matched
- Returns summary rows (barcode, name, brand, Nutri-Score, NOVA, categories) — chain to
off_get_productfor full label data; counts reflect contributed products, not the market - Own client-side budget of ~10 requests/min, kept well inside what Open Food Facts asks of clients
off_compare_products tool
- Accepts 2–10 barcodes, compared in the order provided
- Returns a normalized comparison table: energy (kcal/100g), fat, saturated fat, sugars, salt, protein, fiber, Nutri-Score, NOVA group, and Green-Score; missing nutrition data is preserved as
null, never imputed not_foundlists barcodes with no contributor record (not an error — the product may simply not be entered yet)failedlists barcodes whose fetch itself failed, with a per-barcode reason — kept separate fromnot_found, and a failed barcode never blocks the rows that did resolve
off_browse_taxonomy tool
- Facets
categories,labels,allergens,additives,countriesresolve live against the Open Food Facts taxonomy (case-insensitive substring match on tag ID, display name, or a common synonym — "shellfish" resolves toen:crustaceans); upstream tags are often plural, so pass the returnedidthrough unchanged nova_groupsandnutrition_gradesare closed vocabularies, returned complete, with bare"1"–"4"/"a"–"e"ids- Live lookups fall back to a small in-process sample when Open Food Facts is unreachable or the budget is spent, and say so rather than failing; omitting
searchreturns only that sample, since Open Food Facts can't enumerate a full facet — nototal_in_facetis reported for the open facets limitcontrols results (1–100, default 20); there is no offset or page — narrow the search term instead- Own client-side budget of ~10 requests/min, separate from the search budget
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 required — the identifying
User-Agentheader (required by OFF terms) is baked into the service layer - Token-bucket rate limiting per endpoint class: product reads (~15/min), search (~10/min), taxonomy resolution (~10/min). The product and search defaults are the per-IP ceilings Open Food Facts publishes; lower them on a shared outbound IP. Budgets count upstream requests, so a retried request spends its own slot and a budget exhausted mid-retry surfaces as
rate_limitedrather than sending. A local refusal says so — it never reports itself as an Open Food Facts rate limit - Automatic retry (4 attempts, 500ms base) for transient failures only — 5xx, timeouts, and 429 (honoring
Retry-After), with HTML error page detection for 503 during high load. A 4xx is never retried; the upstream's own explanation is surfaced instead - Nutriments normalized from raw hyphenated keys (
energy-kcal_100g) to underscore form — the_100gand_servingvariants of every nutrient on the record, with the macros as named fields and the rest in an open map that carries each nutrient's own unit (micronutrients are reported in grams, so calcium0.071is 71 mg) - Live tag resolution for
off_browse_taxonomyagainst the Open Food Facts taxonomy, merged behind a small in-process sample that covers offline operation and is authoritative for E-number lookups, which the upstream suggester answers poorly
Agent-friendly output:
- Per-serving nutrition always carries its denominator —
serving_sizeas printed plus the parsedserving_quantity/serving_quantity_unit, and an explicit note when Open Food Facts has recorded none - Computed scores (Nutri-Score, NOVA, Green-Score) returned as-is with regional caveat notes — not interpreted or normalized to health claims
- Graceful partial failure —
off_compare_productsreturns resolved rows even when others fail, splitting confirmed-missing barcodes intonot_foundand failed fetches intofailed - Every failure carries a declared
reasonand a recovery hint on both client surfaces — timeouts, upstream outages, upstream rejections, and rate limits each resolve to their own error code and their own next step
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/mcp
Prerequisites
- Bun v1.3.0 or higher (or Node.js v24+).
- No API key needed. The server sends an identifying
User-Agentto comply with Open Food Facts' terms of service — this is baked in and requires no configuration.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/openfoodfacts-mcp-server.git
- Navigate into the directory:
cd openfoodfacts-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# edit .env if you need to override rate limits or the base URL
Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
| Variable | Description | Default |
|---|---|---|
OFF_BASE_URL |
Open Food Facts API base URL. Override for local testing against a mock server. | https://world.openfoodfacts.org |
OFF_RATE_LIMIT_PRODUCT |
Product read rate limit (requests/min). Matches the 15 req/min/IP Open Food Facts documents for product reads. | 15 |
OFF_RATE_LIMIT_SEARCH |
Search rate limit (requests/min). | 10 |
OFF_RATE_LIMIT_TAXONOMY |
Taxonomy resolution rate limit (requests/min). A spent budget falls back to the offline sample rather than failing. | 10 |
MCP_TRANSPORT_TYPE |
Transport: stdio or http. |
stdio |
MCP_HTTP_PORT |
HTTP server port. | 3010 |
MCP_AUTH_MODE |
Auth mode: none, jwt, or oauth. |
none |
MCP_LOG_LEVEL |
Log level (debug, info, warning, error). |
info |
LOGS_DIR |
Log file directory (Node.js only). | <project-root>/logs |
OTEL_ENABLED |
Enable OpenTelemetry instrumentation. | false |
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-server
The 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 |
|---|---|
src/index.ts |
createApp() entry point — registers tools and inits services. |
src/config |
Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools |
Tool definitions (*.tool.ts). |
src/services/openfoodfacts |
Open Food Facts API client — HTTP, rate limiting, retry, field normalization. |
src/services/taxonomy |
Tag vocabulary service for off_browse_taxonomy — live resolution, offline sample, merge and fallback policy. |
tests/ |
Unit and integration tests mirroring src/. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools via the barrel in
src/mcp-server/tools/definitions/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Attribution
Open Food Facts data is released under the Open Database License (ODbL) 1.0. Downstream use must cite Open Food Facts.
Contributing
Bugs, feature requests, and documentation gaps all belong in an issue — see CONTRIBUTING.md for the forms, what makes a report actionable, and how to tell a server bug from a framework one. Vulnerabilities go through private disclosure, never a public issue.
Working on the code? Both gates must be green:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.
Installing @Cyanheads/Openfoodfacts Server
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/cyanheads/openfoodfacts-mcp-serverFAQ
Is @Cyanheads/Openfoodfacts Server MCP free?
Yes, @Cyanheads/Openfoodfacts Server MCP is free — one-click install via Unyly at no cost.
Does @Cyanheads/Openfoodfacts Server need an API key?
No, @Cyanheads/Openfoodfacts Server runs without API keys or environment variables.
Is @Cyanheads/Openfoodfacts Server hosted or self-hosted?
A hosted option is available: Unyly runs the server in the cloud, no local setup required.
How do I install @Cyanheads/Openfoodfacts Server in Claude Desktop, Claude Code or Cursor?
Open @Cyanheads/Openfoodfacts Server on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare @Cyanheads/Openfoodfacts Server with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
