Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
6,000+ web scrapers for your AI agent, start free logo6,000+ web scrapers for your AI agent, start free

Apify gives your agent live web data: 6,000+ prebuilt scrapers and actors, MCP-ready. Sign up free with $5 in usage credits.

Try Apify free
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 48,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Local-first nutrition tracker MCP server for Hermes, enabling food, alias, recipe, and meal log management with SQLite persistence.

README.md

nutrition-mcp

A local-first nutrition tracker exposed as an HTTP MCP server for Hermes. SQLite is the source of truth for foods, recipes, and meal history; the agent does not need to memorize nutrition facts.

Data Model

The catalog separates a generic food from the products that can satisfy it:

  • Food type: a generic concept such as mozzarella, tuna, or pickles.
  • Food product: nutrition for a specific product or brand, always stored per 100 g with a usual portion name and weight.
  • Brand: stored on a product, such as Milbona.
  • Retailer: a searchable relationship, such as Lidl; one product may be linked to multiple retailers.
  • Alias: an exact phrase for either a product, recipe, or generic food type.
  • Recipe: a reusable set of foods and/or nested recipes with an optional measured yield.
  • Meal entry: an immutable nutrition snapshot plus quantity, grams, per-100g facts, recipe components, and recipe adjustments.

For example, a Mozzarella food type can contain several branded products. If the Milbona product is sold at Lidl and is the default:

  • mozzarella resolves to the default Milbona product.
  • milbona mozzarella resolves from its brand and food-type relationship.
  • lidl mozzarella resolves from its retailer and food-type relationship.
  • All three point to one concrete product for logging; other mozzarella products remain available by their own aliases or IDs.

Resolution is conservative. Exact product aliases are checked first, then generic aliases/defaults, then exact relationship phrases. Ambiguous relationship phrases fail instead of selecting an arbitrary product.

Product and food-type notes, aliases, brands, retailers, and names are searchable. Notes are useful for details such as low moisture, only sold at Lidl, or the jar with the green lid.

Nutrition Rules

Use add_food_product for all new packaged foods and ingredients. It requires:

  • all nutrition facts per 100 g;
  • usual_portion_grams;
  • usual_portion_name, such as 1 slice, 1 piece, or usual serving.

The older add_food tool remains available for backward compatibility and unusual serving-only data. audit_foods lists migrated or legacy foods that still lack a reliable weight or per-100g facts. Repair those with update_food_product.

Tracked nutrients are calories, protein, carbs, fat, fiber, sugars, saturated fat, and salt. salt_g means grams of salt from the nutrition label, not milligrams of sodium.

Historical entries always keep their stored snapshots when products, defaults, or recipes change later.

MCP Endpoint

  • URL: http://HOST:8765/mcp
  • Transport: Streamable HTTP through FastMCP
  • Health endpoints: GET / and GET /health

If MCP_TOKEN is set, every HTTP request must include:

Authorization: Bearer <token>

PUBLIC_HOSTS is a comma-separated list of hostnames or IP addresses accepted by MCP DNS-rebinding protection. Do not include a scheme or port.

Tools

Food products:

  • add_food_product, update_food_product, get_food, search_foods, list_foods
  • add_food, update_food for backward compatibility
  • add_alias, delete_food, audit_foods

Generic food catalog:

  • add_food_type, update_food_type, get_food_type, search_food_types
  • add_food_type_alias, assign_food_to_type, set_default_food
  • add_retailer, link_food_retailer, unlink_food_retailer, list_retailers

Recipes:

  • add_recipe, update_recipe, get_recipe, search_recipes, delete_recipe

Logging and history:

  • log_food, log_recipe
  • get_day, get_entries, get_weekly_report
  • update_entry, bulk_update_entries, delete_entry, finalize_day
  • list_aliases, health

Catalog Example

Create the generic type first:

{
  "name": "Mozzarella",
  "aliases": ["mozzarella", "mozz"],
  "notes": "Generic mozzarella used on pizza"
}

Then create a branded product with per-100g nutrition and a usual portion:

{
  "name": "Mozzarella",
  "brand": "Milbona",
  "food_type_alias": "mozzarella",
  "kcal_per_100g": 250,
  "protein_g_per_100g": 18.5,
  "carbs_g_per_100g": 2.0,
  "fat_g_per_100g": 19.0,
  "fiber_g_per_100g": 0,
  "sugars_g_per_100g": 1.0,
  "saturated_fat_g_per_100g": 13.0,
  "salt_g_per_100g": 0.6,
  "usual_portion_grams": 40,
  "usual_portion_name": "pizza portion",
  "aliases": ["milbona mozzarella"],
  "retailers": ["Lidl"],
  "make_default": true,
  "notes": "Low-moisture bag normally used for pizza"
}

After this, all of these log the same product:

{"alias": "mozzarella", "grams": 60}
{"alias": "milbona mozzarella", "grams": 60}
{"alias": "lidl mozzarella", "grams": 60}

Use set_default_food to change which branded product a generic alias resolves to. Existing meal entries are unchanged.

Recipes And Nested Recipes

Recipe items may target either a food or another recipe. A nested recipe must have a positive yield_grams, because the parent needs to know what fraction is used. Cycles are rejected.

Example dough recipe:

{
  "name": "Pizza dough batch",
  "aliases": ["pizza dough"],
  "yield_grams": 600,
  "ingredients": [
    {"alias": "flour", "grams": 400},
    {"alias": "olive oil", "grams": 20}
  ]
}

Use 165 g of that recipe inside a pizza:

{
  "name": "Tuna pizza",
  "aliases": ["tuna pizza"],
  "yield_grams": 285,
  "ingredients": [
    {"recipe_alias": "pizza dough", "grams": 165},
    {"alias": "tuna", "grams": 60},
    {"alias": "mozzarella", "grams": 60}
  ]
}

Recipe responses include direct_items and fully flattened ingredients. Logs snapshot the flattened components and source paths, so the complete history remains understandable after later recipe edits.

One-off recipe changes are normalized and stored in the entry:

{
  "alias": "tuna pizza",
  "adjustments": [
    {"alias": "mozzarella", "delta_grams": 20}
  ]
}

Each stored adjustment records the original request and before/after/delta quantities and grams. Updating the logged recipe quantity scales those snapshots consistently.

Correcting Entries

update_entry accepts either quantity or grams for one entry. bulk_update_entries applies several corrections atomically:

{
  "date": "2026-07-17",
  "updates": [
    {"entry_id": 104, "grams": 85},
    {"entry_id": 105, "scale_factor": 0.9},
    {"entry_id": 107, "quantity": 2, "note": "Corrected from photo"}
  ]
}

Each item may contain only one of quantity, grams, or scale_factor. If any correction is invalid, the entire request is rolled back.

Automatic Database Migration

At startup the server checks schema_meta. Schema 1-3 databases are upgraded automatically to schema 4 before MCP tools are registered.

The migration:

  1. Creates a consistent SQLite backup under /data/backups.
  2. Applies all schema and data changes in one transaction.
  3. Preserves food and recipe aliases, searchable notes, recipes, entries, and historical macro snapshots.
  4. Creates generic food types and portions for existing foods when the source data supports them.
  5. Validates row counts, historical totals, foreign keys, and PRAGMA quick_check.
  6. Updates the schema version only after validation succeeds.

Foods without a known serving weight are preserved in legacy mode as needs_review; the migration never invents grams or per-100g values. The /health result reports migration status and review counts.

Data Paths

Inside the container:

  • SQLite DB: /data/nutrition.db
  • Migration backups: /data/backups
  • Daily Markdown: /data/exports/daily/YYYY-MM-DD.md
  • CSV: /data/exports/csv/YYYY-MM-DD.csv
  • JSON: /data/exports/json/YYYY-MM-DD.json

CSV and JSON exports include grams, amount source, per-100g nutrition, and persistent recipe adjustments.

Local Development

py -3.12 -m venv .venv
.\.venv\Scripts\python -m pip install -e ".[test]"
.\.venv\Scripts\python -m pytest
.\.venv\Scripts\python -m app.main

Test the local endpoint:

curl -H "Authorization: Bearer change-me" http://localhost:8765/health

Docker Compose

The included compose file builds locally and persists the database in ./data:

docker compose up -d --build
docker compose logs -f nutrition-mcp

For Unraid, use a host path instead of the relative volume:

services:
  nutrition-mcp:
    image: ghcr.io/ispas-catalin/nutrition-mcp:0.5.0
    container_name: nutrition-mcp
    restart: unless-stopped
    ports:
      - "8765:8765"
    environment:
      DATA_DIR: /data
      TZ: Europe/Bucharest
      MCP_TOKEN: "replace-with-a-long-random-token"
      PUBLIC_HOSTS: "192.168.1.142,nutrition-mcp"
      HOST: 0.0.0.0
      PORT: 8765
    volumes:
      - /mnt/user/appdata/nutrition-mcp:/data

After replacing the token:

docker compose pull
docker compose up -d
curl -H "Authorization: Bearer YOUR_REAL_TOKEN" http://192.168.1.142:8765/health

Unraid Add Container

  • Name: nutrition-mcp
  • Repository: ghcr.io/ispas-catalin/nutrition-mcp:0.5.0
  • Network Type: bridge
  • Port: host 8765 to container 8765 TCP
  • Path: /mnt/user/appdata/nutrition-mcp to /data
  • TZ=Europe/Bucharest
  • MCP_TOKEN=<long random token>
  • PUBLIC_HOSTS=192.168.1.142,nutrition-mcp
  • WebUI: http://[IP]:[PORT:8765]/

Hermes MCP Config

mcp_servers:
  nutrition:
    url: "http://192.168.1.142:8765/mcp"
    headers:
      Authorization: "Bearer <REAL_MCP_TOKEN>"

Detailed agent behavior and call examples are in HERMES_AGENT_INSTRUCTIONS.md.

Security Notes

  • Use a strong token and keep port 8765 on the trusted LAN; do not expose it directly to the internet.
  • Keep /data private because it contains meal history and nutrition preferences.
  • No arbitrary SQL tool is exposed.
  • Deletes require exact IDs and refuse unsafe referenced records.
  • Back up /mnt/user/appdata/nutrition-mcp before major host changes even though schema migrations create their own database backup.

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Databases servers.