Initial commit: Mealie MCP server

Ports a working Hermes agent skill to an MCP server. The skill's value was not
its Mealie endpoints but ~25 operational rules found by running imports against
the live instance; those are now code with tests rather than prompt text.

The server owns deterministic mechanics — auth, the non-default User-Agent
Cloudflare requires, the `extension` field on cover uploads, PATCH instead of
the 500-ing bulk-action endpoints, decimal-comma normalization, and restoring
Swedish display text after parsing. Language and taste judgement stay with the
model, fed by the four reference notes shipped as MCP resources.

verify_recipe runs the finished-import definition as code 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.

Home Assistant is optional and isolated; the Obsidian meal plan is out of scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 07:31:10 +02:00
commit e5c134b045
17 changed files with 1643 additions and 0 deletions
@@ -0,0 +1,65 @@
# External Mealie hostname via Cloudflare
## Symptom
Authenticated API calls to `https://recept.famfallman.com` can fail with:
- HTTP `403 Forbidden`
- response body: `error code: 1010`
- `Server: cloudflare` header
This is not the same as a bad Mealie token (`401`).
## Reproduction
Python `urllib` with its default user-agent may trigger the block:
```python
import os, urllib.request
base = 'https://recept.famfallman.com'
token = os.environ['MEALIE_API_TOKEN']
req = urllib.request.Request(
f'{base}/api/users/self',
headers={'Authorization': f'Bearer {token}'},
)
urllib.request.urlopen(req, timeout=30)
```
Observed failure pattern:
- default `Python-urllib/...` user-agent -> `403`
- explicit `User-Agent: Hermes-Debug/1.0` -> `200`
- `curl` with explicit user-agent -> `200`
## Working patterns
### curl
```bash
BASE_URL="${MEALIE_BASE_URL:-https://recept.famfallman.com}"
AUTH=(-H "Authorization: Bearer $MEALIE_API_TOKEN")
UA=(-H "User-Agent: Hermes-Debug/1.0")
curl -s "$BASE_URL/api/users/self" "${AUTH[@]}" "${UA[@]}"
```
### Python
```python
headers={
'Authorization': f'Bearer {token}',
'User-Agent': 'Hermes-Debug/1.0',
}
```
## Interpretation
If the same token works on the LAN URL and the external host returns Cloudflare `403/1010`, suspect edge/WAF bot filtering before suspecting Mealie auth, DNS, or the token.
## Import-response quirk discovered in the same session
`POST /api/recipes/create/url` may return a JSON string slug like:
```json
"grillat-laxpaket-med-sparris"
```
Do not assume the import response is a full object. Follow with:
```bash
curl -s "$BASE_URL/api/recipes/<slug>" "${AUTH[@]}" "${UA[@]}"
```
to verify title, image, and parsed ingredients.
@@ -0,0 +1,91 @@
# Fredrik + familjens smakprofil för Mealie-import
Den här profilen är tänkt att användas när nya recept ska väljas, prioriteras, importeras eller filtreras för Fredrik och familjen.
## Baslinje från nuvarande Mealie-bibliotek
Analysen bygger på cirka 198 recept i Mealie.
Tydliga mönster i befintliga recept:
- mycket **kyckling**, **lax**, **pasta**, **ris**, **grytor** och **ugnsrätter**
- stark dragning åt **medelhav/italienskt** och **asiatiskt**
- återkommande smaker: **citron**, **vitlök**, **parmesan**, **feta**, **örter**, **chili**, **kokos**, **curry**
- tydlig acceptans för både **vardagsmat** och **helgigare rätter**
- familjen verkar gilla rätter som är **krämiga**, **smakrikt kryddade**, **syrliga/friska** och ofta har tydlig sälta/umami
Exempel på representativa recept i biblioteket:
- Grönkålssoppa med grön curry
- Svamp- och salsicciapasta med tryffel
- Kycklingcurry med ris
- Citron- och örtris med halloumi
- Grillad lax med krämig citron- och spenatsås
- Asiatiska köttbullar i krämig kokos- och panengcurrysås
- Persisk biff/ärtgryta Khoresh Gheymeh
- Djoje Kebab saffransdoftande persiska kycklingspett
- Fesenjan - Kycklinggryta med valnötter och granatäpple
## Smaker att prioritera
Prioritera recept med en eller flera av dessa egenskaper:
### Proteiner
- kyckling
- lax och annan mild fisk
- lamm i rätt sammanhang
- nöt i gryta, kebab, spett eller färsrätter
- korv i smakrika vardagsrätter
- vegetariskt när det är tydligt smakdrivet, t.ex. halloumi, feta, örter, citron, svamp eller baljväxter
### Smakprofil
- syrligt och fräscht: citron, lime, granatäpple, yoghurt, örter
- varmt och kryddigt: curry, chili, saffran, spiskummin, paprika, vitlök
- runt och krämigt: kokosmjölk, yoghurt, ost, gräddiga eller sammetslena såser
- umami och sälta: parmesan, feta, brynt/rostad smak, tomat, lök
### Rätttyper
- grytor
- soppor
- ugnsrätter och gratänger
- risrätter
- pastarätter
- grillat/spett/kebab
- familjevänliga plock- eller brickrätter
## Persisk/iransk prioritering
Eftersom Carin/familjen vill ha in mer iranskt ska iranska recept ges **extra hög prioritet** även om de är underrepresenterade i nuvarande bibliotek.
### Särskilt önskvärda iranska rätter
- khoresh-rätter, t.ex. ghormeh sabzi, gheymeh, fesenjan
- kebab, koobideh, joojeh/djoojeh, barg
- risrätter/polo, särskilt med saffran, dill, bär eller tahdig
- ash och andra persiska soppor
- rätter med granatäpple, valnötter, saffran, torkad lime, sumak, mynta eller mycket örter
- vardagsvänliga iranska kyckling-, ris- och grytrecept
### Iranska recept ska gärna vara
- autentiska eller tydligt iranskinspirerade, inte bara "mellanöstern" i största allmänhet
- familjevänliga och möjliga att laga hemma utan alltför specialiserad restaurangutrustning
- smakrika men inte beroende av extrem hetta
- skrivna/importerade på svenska i Mealie
## Negativa signaler / lägre prioritet
Prioritera ned recept som är:
- torra och lågintensiva utan syra, örtighet eller krydddjup
- väldigt söta utan balans
- ultraprocessade/snackiga snarare än riktiga middagsrecept
- alltför amerikanska dessert-/fastfoodkopior om de inte är ovanligt bra
- starkt fokuserade på beige buffémat utan friskhet, örter, syra eller kryddkaraktär
## Praktisk rankingregel för importbevakning
När flera kandidater finns, prioritera i ungefär denna ordning:
1. iranskt/persiskt som verkar gott och trovärdigt
2. recept med kyckling, lax, gryta, ris, citron, örter, yoghurt, saffran, vitlök eller curry
3. familjevänliga vardagsrätter med tydlig smakprofil
4. vegetariska recept om de fortfarande känns smakrika och "riktiga" som middag
5. övrigt
## Instruktion för framtida importjobb/cron
När ett automatiskt jobb ska välja recept att importera:
- välj hellre **färre men mer träffsäkra** recept än många svaga
- favorisera svenska eller lättöversatta recept med tydliga ingredienser och steg
- om två recept verkar lika bra: välj det som är mer **iranskt**, mer **familjevänligt**, eller mer i linje med **kyckling/lax/gryta/ris/citron/örter**
- undvik dubletter mot befintliga Mealie-recept
- om iranska recept hittas: var mer generös med import även om exakt smakmatch är något osäkrare, eftersom biblioteket aktivt ska breddas åt det hållet
+44
View File
@@ -0,0 +1,44 @@
# Allrecipes post-import pitfalls
Den här referensen dokumenterar konkreta fel som observerats i live-körning mot Fredriks Mealie-instans och ska användas när recept importeras från Allrecipes eller andra engelskspråkiga receptsidor.
## Verifierade problem
### 1. `image` i API betyder inte nödvändigtvis att omslagsbilden syns i UI
- Ett recept kan ha ett icke-tomt `image`-fält och ändå sakna synlig omslagsbild i gränssnittet.
- Därför måste importflödet alltid köra ett explicit bildsteg efter import.
- För lokal upload på Fredriks Mealie-instans måste `PUT /api/recipes/<slug>/image` skicka med multipart-fältet `extension` (t.ex. `jpg` eller `webp`). Utan det kan uppladdningen fastna i validering och bilden blir inte korrekt sparad.
### 2. Bulk-actions för taggar/kategorier är opålitliga i denna instans
Följande endpoints gav 500-fel under verkliga körningar:
- `/api/recipes/bulk-actions/tag`
- `/api/recipes/bulk-actions/categorize`
Använd i stället direkt PATCH på receptets `tags` och `recipeCategory`.
### 3. Mealie-parsern kan förstöra svenska display-rader
Exempel på observerade fel:
- `4,7 dl basmatiris` blev något i stil med `4710 liter ...`
- parsern gav konstiga bråktecken, dubblerade enheter och skräptecken i `display`
- vissa parser-resultat gav märkliga `food.name`-värden för svenska rader
### 4. Svensk decimal-komma är känsligt
Parsern hanterar ofta struktur bättre om decimal-komma först normaliseras till punkt för parseranropet, men den normaliserade raden ska inte användas som slutlig `display` i Mealie.
## Rekommenderad motstrategi
1. Bevara alltid en canonical svensk ingrediensrad separat.
2. Använd parsern endast för `quantity`, `unit`, `food`.
3. Återställ den mänskliga svenska raden till `display` och `note` efter parse.
4. Om parsern ger `food.name` utan `id` och du behöver strukturen kvar efter PATCH, skapa/återanvänd riktig Mealie food först.
5. För Allrecipes: om scrape-bild inte blir stabil i UI, ladda ner hero-bilden och använd `PUT /api/recipes/<slug>/image`.
6. Om Köket.se- och YouTube-importer fortsätter att visa fungerande omslagsbilder medan Allrecipes-importer inte gör det, behandla det som ett källspecifikt Allrecipes-problem. Gå då direkt till explicit hero-bild-download + `PUT /api/recipes/<slug>/image` i stället för bredare generell felsökning.
7. Om parsern fortfarande gör visningen ful efter cleanup, välj läsbarhet före struktur för just de trasiga raderna: nollställ `quantity` / `unit` / `food` och spara rena svenska `display` / `note` i stället för att behålla dålig automatisk rendering.
## Definition av klar import
Ett importerat recept räknas inte som klart förrän:
- svensk titel/beskrivning/instruktioner är satta
- `recipeIngredient[].display` ser normala ut för människa
- parsern inte lämnar receptet i `Click Parse`-läge
- bild har satts eller verifierats explicit
- taggar/kategorier är applicerade eller medvetet hoppade över
+31
View File
@@ -0,0 +1,31 @@
# Köket-attribution efter Mealie-import
När ett recept importeras från `koket.se` följer inte receptmakare/program alltid med på ett synligt sätt i Mealie.
Verifierat arbetssätt:
1. Importera receptet via `POST /api/recipes/create/url`.
2. Läs källsidan och hämta explicit attribution från sidan själv:
- `Av: <namn>` → använd som `extras.author`
- `Från: <program>` → använd som `extras.source`
3. Läs tillbaka receptet via slug.
4. PATCH:a receptet så att attribution sparas både som metadata och synligt:
```json
{
"extras": {
"author": "Zeina Mourtada",
"source": "Kökets middag"
},
"description": "... Recept av Zeina Mourtada. Från Kökets middag."
}
```
Varför båda behövs:
- `extras.author` / `extras.source` är API-korrekt lagring.
- En kort text i `description` gör attributionen synlig i vanliga Mealie-vyer.
Regel:
- Gissa aldrig författare eller källa.
- Ta bara värden som uttryckligen står på källsidan.
- Verifiera efter PATCH att både `extras` och `description` faktiskt uppdaterades.