io.github.cyanheads/openfoodfacts-mcp-server
Barcode lookup, nutrition search, and product comparison for 3M+ crowd-sourced food products.
What can you do with io.github.cyanheads/openfoodfacts-mcp-server?
- No API key; the identifying
User-AgentOpen Food Facts' terms require is built in - Per-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 unit - Tag 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
- Per-serving nutrition carries its denominator:
serving_size,serving_quantity,serving_quantity_unit, and a note when none is recorded - Graceful partial failure:
off_compare_productsreturns resolved rows alongsidenot_foundandfailed - Explicit count bounds:
total_is_lower_bound,last_page, andomittedon searches
Install
Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).
claude mcp add --transport http openfoodfacts-mcp-server 'https://openfoodfacts.caseyjhand.com/mcp'
{
"mcpServers": {
"openfoodfacts-mcp-server": {
"type": "http",
"url": "https://openfoodfacts.caseyjhand.com/mcp"
}
}
}
@cyanheads/openfoodfacts-mcp-serverclaude mcp add openfoodfacts-mcp-server -e 'OFF_BASE_URL=https://world.openfoodfacts.org' -e 'OFF_RATE_LIMIT_PRODUCT=15' -e 'OFF_RATE_LIMIT_SEARCH=10' -e 'OFF_RATE_LIMIT_TAXONOMY=10' -e 'MCP_LOG_LEVEL=info' -- npx -y @cyanheads/openfoodfacts-mcp-server@0.4.0
{
"mcpServers": {
"openfoodfacts-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/openfoodfacts-mcp-server@0.4.0"
],
"env": {
"OFF_BASE_URL": "https://world.openfoodfacts.org",
"OFF_RATE_LIMIT_PRODUCT": "15",
"OFF_RATE_LIMIT_SEARCH": "10",
"OFF_RATE_LIMIT_TAXONOMY": "10",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Environment variables
OFF_BASE_URLOpen Food Facts API base URL override. Useful for testing against a mock server.OFF_RATE_LIMIT_PRODUCTProduct read rate limit (requests/min). Matches the 15 req/min/IP Open Food Facts documents for product reads.OFF_RATE_LIMIT_SEARCHSearch rate limit (requests/min).OFF_RATE_LIMIT_TAXONOMYTaxonomy resolution rate limit (requests/min). Applies to the live tag-vocabulary lookups off_browse_taxonomy makes, to tag-value resolution on text searches, and to checking allergen and trace exclusions on every search; a spent budget falls back to the offline sample, or to the tag value as normalized, rather than failing, except that an exclusion it could not check is refused as retryable.MCP_LOG_LEVELSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
@cyanheads/openfoodfacts-mcp-serverclaude mcp add openfoodfacts-mcp-server -e 'MCP_TRANSPORT_TYPE=<MCP_TRANSPORT_TYPE>' -e 'OFF_BASE_URL=https://world.openfoodfacts.org' -e 'OFF_RATE_LIMIT_PRODUCT=15' -e 'OFF_RATE_LIMIT_SEARCH=10' -e 'OFF_RATE_LIMIT_TAXONOMY=10' -e 'MCP_HTTP_HOST=127.0.0.1' -e 'MCP_HTTP_PORT=3010' -e 'MCP_HTTP_ENDPOINT_PATH=/mcp' -e 'MCP_AUTH_MODE=none' -e 'MCP_LOG_LEVEL=info' -- npx -y @cyanheads/openfoodfacts-mcp-server@0.4.0
{
"mcpServers": {
"openfoodfacts-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/openfoodfacts-mcp-server@0.4.0"
],
"env": {
"MCP_TRANSPORT_TYPE": "<MCP_TRANSPORT_TYPE>",
"OFF_BASE_URL": "https://world.openfoodfacts.org",
"OFF_RATE_LIMIT_PRODUCT": "15",
"OFF_RATE_LIMIT_SEARCH": "10",
"OFF_RATE_LIMIT_TAXONOMY": "10",
"MCP_HTTP_HOST": "127.0.0.1",
"MCP_HTTP_PORT": "3010",
"MCP_HTTP_ENDPOINT_PATH": "/mcp",
"MCP_AUTH_MODE": "none",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Environment variables
MCP_TRANSPORT_TYPESelects the HTTP transport.OFF_BASE_URLOpen Food Facts API base URL override. Useful for testing against a mock server.OFF_RATE_LIMIT_PRODUCTProduct read rate limit (requests/min). Matches the 15 req/min/IP Open Food Facts documents for product reads.OFF_RATE_LIMIT_SEARCHSearch rate limit (requests/min).OFF_RATE_LIMIT_TAXONOMYTaxonomy resolution rate limit (requests/min). Applies to the live tag-vocabulary lookups off_browse_taxonomy makes, to tag-value resolution on text searches, and to checking allergen and trace exclusions on every search; a spent budget falls back to the offline sample, or to the tag value as normalized, rather than failing, except that an exclusion it could not check is refused as retryable.MCP_HTTP_HOSTThe hostname for the HTTP server.MCP_HTTP_PORTThe port to run the HTTP server on.MCP_HTTP_ENDPOINT_PATHThe endpoint path for the MCP server.MCP_AUTH_MODEAuthentication mode to use: 'none', 'jwt', or 'oauth'.MCP_LOG_LEVELSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
Documentation
From the project's README on GitHub, refreshed weekly. Links open on GitHub.
Contents
@cyanheads/openfoodfacts-mcp-server
Look up food products by barcode, search by keyword, tag, allergen, or nutrition filter, compare products side-by-side, and browse the canonical tag vocabulary via MCP. STDIO or Streamable HTTP.
4 Tools
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 |
|---|---|
off_get_product | Fetch one product by barcode: ingredients, allergens and traces, additives, scores, nutrition, and tags |
off_search_products | Search by text, tag filters, allergen exclusions, and nutrient thresholds (per 100 g, or per 100 ml for liquids); returns summary rows with barcodes |
off_compare_products | 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 |
off_browse_taxonomy | Resolve a human term to the canonical tag ID that off_search_products filters on |
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 fetched- Returns 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 fetched - Rows 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 imputed not_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 paging- Returns
tags[]ofidandname, whereidgoes tooff_search_productsunchanged;nova_groupsandnutrition_gradescome back complete withtotal_in_facet - The 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 in - Per-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 unit - Tag 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 recorded - Graceful partial failure:
off_compare_productsreturns resolved rows alongsidenot_foundandfailed - Explicit count bounds:
total_is_lower_bound,last_page, andomittedon searches - Typed 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/mcp
Prerequisites
- 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.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
| Variable | Description | Default |
|---|---|---|
OFF_BASE_URL | Open Food Facts API base URL. | https://world.openfoodfacts.org |
OFF_RATE_LIMIT_PRODUCT | Product read budget (requests/min). The default is Open Food Facts' published per-IP limit; lower it on a shared outbound IP. | 15 |
OFF_RATE_LIMIT_SEARCH | Search budget (requests/min). The default is Open Food Facts' published per-IP limit. | 10 |
OFF_RATE_LIMIT_TAXONOMY | Taxonomy lookup budget (requests/min), shared by off_browse_taxonomy, tag canonicalization on text searches, and exclusion checks. | 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 |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
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:http - Run 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 — live resolution, offline sample, and tag-value canonicalization. |
src/utils | Markdown escaping for contributor-entered values in text output. |
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
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 test
License
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.
Frequently asked questions
What is io.github.cyanheads/openfoodfacts-mcp-server?
Barcode lookup, nutrition search, and product comparison for 3M+ crowd-sourced food products.
How do I install io.github.cyanheads/openfoodfacts-mcp-server?
It runs as a remote server. In Claude Code: claude mcp add --transport http openfoodfacts-mcp-server 'https://openfoodfacts.caseyjhand.com/mcp'. For other clients, paste the JSON configuration from this page into their MCP settings.
Does io.github.cyanheads/openfoodfacts-mcp-server need an API key?
Its registry entry lists no required key or secret. Some features may still need an account with the service it connects to.
Is io.github.cyanheads/openfoodfacts-mcp-server open source?
Yes. Its code is on GitHub under the Apache-2.0 license.
Who maintains io.github.cyanheads/openfoodfacts-mcp-server?
It is published by cyanheads. AI.tn lists it but does not maintain or endorse it.
From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.