fredamn76 7d693ef429 Store nutrition per serving, and check the estimate before sending it
Recipes imported from a video caption have no nutrition data, so the values
have to be estimated. Mealie will accept anything: the fields are free-text
strings, a misspelt key is dropped silently, and a whole-recipe total looks
exactly like a per-serving one. The recipe page then renders whatever landed
as fact.

patch_recipe now validates a nutrition block first. Keys must be Mealie's own,
so a number cannot vanish into "carbs". Values are normalized to bare numbers,
matching how the library already stores them. Energy is checked against the
macros with the Atwater factors (4/9/4 kcal per gram) and refused if it is
more than 25% off, which is what catches an arithmetic slip.

Nutrition is per serving, and the text import left recipeServings at 0 --- it
set only the free-text recipeYield, so "4-6 personer" gave Mealie no number to
divide by or scale with. import_recipe_text now also sets recipeServings, from
the lower bound of a range, which is how this library already stores
"10-12 personer" (recipeServings 10). patch_recipe refuses nutrition while the
count is still missing, and accepts it when the same patch supplies it.

verify_recipe fails an import that has no nutrition, or has values without a
serving count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 14:52:54 +02:00
2026-07-31 07:31:10 +02:00
2026-07-31 07:31:10 +02:00
2026-07-31 10:35:34 +02:00

Mealie MCP Server

MCP server for Fredrik's Mealie instance. It exposes recipe search, suggestions, recipe details, ingredient scaling, imports, cover images, ingredient parsing, and a finished-import verification pass.

It grew out of a working Hermes agent skill. The valuable part of that skill was not the Mealie endpoints — it was roughly 25 operational rules discovered by running imports against the live instance and watching them fail. Those rules are the reason this server exists as code instead of prompt text.

What lives where

The split is deliberate:

The server owns (deterministic) The model owns (judgement)
Auth, and the non-default User-Agent Cloudflare requires Translating content to Swedish
The extension field on cover uploads Picking recipes against the taste profile
PATCH instead of the bulk-action endpoints Choosing a variant from an article page
Parsing, then restoring the Swedish display text Sensible categories and tags
Decimal-comma normalization before the parser Judging image quality

The token never leaves the server, so agents never handle a bearer credential.

Configuration

export MEALIE_API_TOKEN='...'                              # required
export MEALIE_BASE_URL='https://recept.famfallman.com'     # optional
export MEALIE_USER_AGENT='Mealie-MCP/0.1'                  # optional, must be non-default

export HA_BASE_URL='http://homeassistant.local:8123'       # optional, shopping list only
export HA_TOKEN='...'
export HA_SHOPPING_LIST_ENTITY='todo.ourgroceries_shoppinglista'

uv run mealie-mcp

Create the Mealie token at /user/profile/api-tokens.

Home Assistant is optional and isolated: without HA_BASE_URL and HA_TOKEN everything except shopping_list_add works normally.

Client integration

Hermes

Hermes runs this server over stdio. The credential stays in ~/.hermes/.env; config.yaml contains only environment placeholders.

hermes mcp add mealie \
  --command /home/fredrik/.local/bin/uv \
  --connect-timeout 60 \
  --env 'MEALIE_API_TOKEN=${MEALIE_API_TOKEN}' \
        'MEALIE_BASE_URL=${MEALIE_BASE_URL}' \
        'MEALIE_USER_AGENT=Mealie-MCP/0.1' \
  --args --directory /home/fredrik/.buzz/REPOS/mealie-mcp run mealie-mcp

hermes config set mcp_discovery_timeout 10
hermes mcp test mealie

The normal hermes chat client and the long-running gateway wait for MCP discovery. On the Hermes version used for the P1 acceptance test, hermes -z could snapshot its tools before a slower stdio server finished discovery; use the normal chat/gateway path for this server until that one-shot startup issue is fixed.

Buzz managed agent

Buzz's managed-agent harness accepts one per-agent MCP executable. Set the dedicated Recept agent's MCP command to this read-only launcher:

/home/fredrik/.buzz/REPOS/mealie-mcp/scripts/run-mealie-mcp-read-only-for-buzz

It contains search, read, scaling, organizer, duplicate-check, suggestion, and verification tools, but no import, patch, delete, ingredient-parse, image, or shopping-list tools. Switch to the full mealie-mcp entry point only after the separate write acceptance test has passed.

The launcher reads only MEALIE_API_TOKEN, MEALIE_BASE_URL, and MEALIE_USER_AGENT from ~/.hermes/.env, then starts the same stdio server with a clean environment. It does not place the token in the agent prompt, agent definition, command line, or Codex configuration.

Keep this configuration agent-specific. Adding Mealie globally to ~/.codex/config.toml would make the tools available to every Codex-based Buzz agent on the host, which is broader access than the private Recept agent needs. No HTTP transport is required while the agent and server run on the same machine.

Tools

Read

  • check_auth() — distinguishes a bad token (401) from a Cloudflare block (403/1010)
  • search_recipes(query, limit)
  • suggest_recipes(foods, limit, max_missing_foods)
  • get_recipe(slug_or_id)
  • list_organizers() — existing categories and tags, so imports reuse the vocabulary
  • find_by_source_url(url) — duplicate check on orgURL
  • scale_ingredients(slug_or_id, servings)

Import

  • import_recipe_url(url, check_duplicates=True)
  • import_recipe_text(text, source_url=None)
  • import_recipe_image(image_path)

Each returns an extraction report flagging Could not detect ... placeholders and campaign junk in the title. A response is not proof of a good import.

Write

  • patch_recipe(slug_or_id, patch)
  • delete_recipe(slug_or_id, confirm_slug) — exact-slug confirmation plus 404 read-back proof
  • parse_ingredients(slug_or_id)
  • set_cover_image(slug_or_id, source_url=None, image_path=None)
  • shopping_list_add(items)

Verify

  • verify_recipe(slug_or_id, image_verified=False, taxonomy_skipped_reason=None)

Writes are deliberately separate tools so a client can request confirmation.

verify_recipe

The one that keeps the rest honest. It runs the finished-import definition as code and returns pass/fail per check, so an agent cannot report success on a recipe with an English ingredient line, a missing cover, or ingredients still in the "Click Parse" state.

image_verified must be passed explicitly by a caller that actually ran an image step. A non-empty image field is only a warning: it does not prove the UI shows a cover.

Resources

The reference notes ship as MCP resources rather than prompt text:

  • mealie://reference/family-taste-profile
  • mealie://reference/import-pitfalls
  • mealie://reference/koket-attribution
  • mealie://reference/cloudflare-hostname

Prompt

import_pipeline(source) spells out the order — import → normalize → image → parse → verify — so no step gets skipped.

Out of scope

The Obsidian meal plan. That is file writing in a vault and belongs to the client's own file tools; folding it in would make this server two things at once.

Tests

uv run --extra dev python -m pytest

The suite covers the verified failure modes: Cloudflare 1010 vs 401, decimal-comma normalization, parser-mangled display text, the required extension field on uploads, the 500 on structured ingredient patches, and duplicate detection by orgURL.

S
Description
MCP server for Mealie recipe management + Home Assistant shopping list. Encodes verified operational rules from the Hermes mealie skill.
Readme 314 KiB
Languages
Python 97.6%
Shell 2.4%