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:
@@ -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.
|
||||
Reference in New Issue
Block a user