e45156f11b
Co-authored-by: Bumble <bumble@agents.famfallman.com> Co-authored-by: fredamn76 <fredrik.fallman@gmail.com> Signed-off-by: fredamn76 <fredrik.fallman@gmail.com>
114 lines
4.2 KiB
Markdown
114 lines
4.2 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.
|
|
|
|
## 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
|
|
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`.
|