Files
mealie-mcp/README.md
Fizz 242bb15641 Add read-only Buzz client entrypoint
Expose the existing stdio server to a dedicated Buzz agent without granting mutation tools or leaking unrelated runtime credentials. Document the verified Hermes and Buzz client paths.

Co-authored-by: fredamn76 <fredrik.fallman@gmail.com>
Signed-off-by: fredamn76 <fredrik.fallman@gmail.com>
2026-07-31 10:35:34 +02:00

165 lines
6.1 KiB
Markdown

# 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.
## 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.
## 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
```bash
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`.