openfoodfacts-mcp-server

v0.1.8 pre-1.0

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.

openfoodfacts.caseyjhand.com/mcp
claude mcp add --transport http openfoodfacts-mcp-server https://openfoodfacts.caseyjhand.com/mcp
codex mcp add openfoodfacts-mcp-server --url https://openfoodfacts.caseyjhand.com/mcp
{
  "mcpServers": {
    "openfoodfacts-mcp-server": {
      "url": "https://openfoodfacts.caseyjhand.com/mcp"
    }
  }
}
gemini mcp add --transport http openfoodfacts-mcp-server https://openfoodfacts.caseyjhand.com/mcp
{
  "mcpServers": {
    "openfoodfacts-mcp-server": {
      "command": "bunx",
      "args": [
        "mcp-remote",
        "https://openfoodfacts.caseyjhand.com/mcp"
      ]
    }
  }
}
{
  "mcpServers": {
    "openfoodfacts-mcp-server": {
      "type": "http",
      "url": "https://openfoodfacts.caseyjhand.com/mcp"
    }
  }
}
curl -X POST https://openfoodfacts.caseyjhand.com/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'

Tools

4

off_get_product

open-world

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), allergens, additives, computed scores (Nutri-Score a–e, NOVA 1–4, Green-Score), nutrition per 100g and per serving, categories, labels, packaging, origins, 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.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "off_get_product",
    "arguments": {
      "barcode": "<barcode>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/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.",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "product_name",
          "brands",
          "quantity",
          "ingredients_text",
          "ingredients",
          "allergens_tags",
          "additives_tags",
          "nutriscore_grade",
          "nova_group",
          "ecoscore_grade",
          "nutriments",
          "categories_tags",
          "labels_tags",
          "packaging_tags",
          "origins_tags",
          "image_url",
          "completeness",
          "data_quality_tags"
        ],
        "description": "A specific product field to include in the response."
      }
    }
  },
  "required": [
    "barcode"
  ],
  "additionalProperties": false
}
view source ↗

off_search_products

open-world

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). Tag filter values must be canonical tag IDs (e.g. "en:organic", "en:gluten-free") — use off_browse_taxonomy to resolve human terms to tag IDs. At least one search parameter is required. 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.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "off_search_products",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/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%\".",
      "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\". Fuzzy — partial matches may work.",
      "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"
    },
    "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"
    },
    "sort_by": {
      "description": "Sort order for searches without a text query. \"unique_scans_n\" surfaces the most-scanned products; omitting returns results in default order. Searches that include a text query are relevance-ranked and ignore this option.",
      "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.",
      "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
    }
  },
  "required": [
    "page",
    "page_size"
  ],
  "additionalProperties": false
}
view source ↗

off_compare_products

open-world

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. Scores carry regional formula caveats. Data under ODbL 1.0 — cite Open Food Facts in downstream use.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "off_compare_products",
    "arguments": {
      "barcodes": "<barcodes>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/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"
  ],
  "additionalProperties": false
}
view source ↗

off_browse_taxonomy

Browse and search the canonical tag vocabulary for Open Food Facts filter facets. Returns tag IDs and display names for use as filter values in off_search_products. Covers categories, labels/certifications, allergens, additives, countries, NOVA groups, and Nutri-Score grades. Tag IDs use the "en:" prefix convention (e.g. "en:organic", "en:gluten-free", "en:milk"). Use these tag IDs as filter values, not plain English terms.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "off_browse_taxonomy",
    "arguments": {
      "facet": "<facet>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/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\" return the complete fixed vocabularies."
    },
    "search": {
      "description": "Case-insensitive substring filter against tag ID or display name. Example: \"gluten\" returns en:gluten, en:no-gluten. Omit to list all entries for the facet (may be large for categories).",
      "type": "string"
    },
    "limit": {
      "default": 20,
      "description": "Maximum entries to return (1–100, default 20). The categories facet is broad; a search term narrows it to the relevant tags.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  },
  "required": [
    "facet",
    "limit"
  ],
  "additionalProperties": false
}
view source ↗