2026-07-31 07:31:10 +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
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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.
|
|
|
|
|
|
2026-07-31 10:35:34 +02:00
|
|
|
## Client integration
|
|
|
|
|
|
|
|
|
|
### Hermes
|
|
|
|
|
|
|
|
|
|
Hermes runs this server over stdio. The credential stays in
|
|
|
|
|
`~/.hermes/.env`; `config.yaml` contains only environment placeholders.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
/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.
|
|
|
|
|
|
2026-07-31 07:31:10 +02:00
|
|
|
## 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)`
|
2026-07-31 09:34:43 +02:00
|
|
|
- `delete_recipe(slug_or_id, confirm_slug)` — exact-slug confirmation plus 404 read-back proof
|
2026-07-31 07:31:10 +02:00
|
|
|
- `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
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-31 10:35:34 +02:00
|
|
|
uv run --extra dev python -m pytest
|
2026-07-31 07:31:10 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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`.
|