Deux outils MCP pour qu'Hermes n'ait plus de scripts à écrire :
- hf_next_delivery() : prochaine box RÉELLEMENT sélectionnée (≈4 recettes,
pas le menu complet) + date/cutoff ; erreur stricte si introuvable
(jamais de repli propose). Saute les semaines PAUSED via next_delivery.
- hf_favorites() : recettes favorites du compte. Champ is_favorite ajouté
partout (hf_get_menu inclus).
Endpoints découverts (probe CDP) :
- sélection : GET /gw/my-deliveries/menu -> meals[].selection.quantity>0
- favoris : GET /gw/cfs/v2/favorites/recipe -> items[].object_id
(GET /gw/v1/carts/{week} renvoie 404 : pas la lecture de sélection.)
Images : URLs recettes CloudFront (502) réécrites vers
img.hellofresh.com/.../hellofresh_s3/... (hellofresh/images.py),
appliqué dans Recipe.summary() -> profite à tous les outils.
README : procédure de ré-auth CDP clarifiée (refresh tokens rotatifs,
backups inutiles, page /login, profil Chrome dédié).
Outils de re-découverte : tools/probe_selection.py, tools/probe_menu_capture.py
6.7 KiB
AntiCoco 🥥🚫
Serveur MCP qui donne accès à ton compte HelloFresh (France, hellofresh.fr) pour :
lire le menu de la semaine, exclure des ingrédients (la noix de coco en priorité),
proposer une shortlist de recettes, puis enregistrer ta sélection après confirmation.
Client MCP visé : Hermes (Nous Research), qui tourne sur le même homelab.
⚠️ HelloFresh n'a pas d'API publique. AntiCoco s'appuie sur l'API interne
gw/du site (non documentée, susceptible de changer) — usage strictement personnel.
État (validé de bout en bout, 2026-06)
✅ Boucle complète testée sur le vrai compte : lecture du menu (menus-service/menus →
détails batch recipes/recipes?ids=…), filtrage coco (4/85 détectées, faux positifs des tags
internes neutralisés), proposition classée, et écriture réelle réussie (PUT /v1/carts/{week},
HTTP 200) — sélection par index de course, ids de compte dérivés dynamiquement.
✅ Auth autonome (pur HTTP) : le token (30 min) est rafraîchi par un simple POST /gw/refresh
(le endpoint que la SPA appelle), sans navigateur. Le refresh_token roule par fenêtres de 60 j,
remises à zéro à chaque refresh → un homelab allumé en continu reste authentifié sans
intervention ni re-sync. Le navigateur headless ne sert plus que de filet de secours.
⚠️ La chaîne de refresh peut casser (homelab éteint trop longtemps, ou refresh_token consommé hors
_refresh_session) →POST /gw/refreshrenvoieinvalid_grantetauth_status()passe àlogged_in:false. Le seul remède est de refaire le login CDP (cf. ci-dessous). Restaurer un ancien.session/ne marche pas : les refresh_token sont rotatifs, les anciens sont morts.
⚠️ La connexion directe automatisée (Playwright/Chromium qui remplit le formulaire) est bloquée par l'anti-bot HelloFresh. La session se crée donc via attache CDP à ton vrai Chrome (
tools/attach_capture.py), où le login marche normalement.
Architecture
Hermes ──HTTP──▶ server.py (FastMCP, :9200/mcp)
├─ hellofresh/auth.py session storage_state + refresh HTTP /gw/refresh
├─ hellofresh/api.py httpx : menu, détails, deliveries, PUT cart
├─ hellofresh/filter.py exclusion (coco !) + scoring préférences
└─ config/ excludes.json · prefs.json · endpoints.json
Mise en route
1. Installer
pip install -r requirements.txt
playwright install chromium
cp .env.example .env
2. Créer (ou ré-créer) la session — login via TON Chrome, anti-bot contourné
C'est la seule méthode de login qui marche : un navigateur piloté par Playwright (même avec
channel=chrome) est bloqué par l'anti-bot HelloFresh. On lance donc le vrai Chrome en mode debug
et on s'y attache en CDP (lecture seule). Même procédure pour la 1ʳᵉ session et pour une
ré-authentification après invalid_grant.
# 1) Lance ton vrai Chrome en mode debug avec un PROFIL DÉDIÉ (Chrome ≥149 refuse le debug
# sur le profil par défaut). Ta fenêtre Chrome habituelle peut rester ouverte.
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--remote-debugging-port=9222 --user-data-dir="$HOME/.hf-chrome-debug" \
https://www.hellofresh.fr/my-account/deliveries/menu
# 2) Connecte-toi dans cette fenêtre (page de login = https://www.hellofresh.fr/login).
# 3) Pendant que Chrome reste ouvert et connecté, attache la capture :
python tools/attach_capture.py # capture trafic + exporte .session/storage_state.json
attach_capture doit tourner pendant que tu (re)charges la page menu, pour capter le trafic.
Il écrase .session/storage_state.json avec la session fraîche (cookies ~60 j) — ne pas
restaurer un vieux backup à la place (refresh_token rotatif = anciens morts). Vérifie ensuite :
python -c "from hellofresh import auth, json; print(auth.auth_status())" → logged_in: true.
config/endpoints.json est déjà rempli ; rejoue attach_capture si l'API change.
3. Tester en local (headless, comme le homelab)
python server.py # auth via storage_state, refresh token automatique
Déploiement homelab (Docker)
.session/ et .env ne sont jamais versionnés. Workflow :
# 1. Sur le Mac : générer la session (cf. « Mise en route » §2 → .session/storage_state.json)
# 2. Pousser le code
git add -A && git commit -m "..." && git push
# 3. Synchroniser la session vers le homelab (NON versionnée ; endpoints.json est dans git)
# storage_state.json suffit (le homelab tourne headless et rafraîchit le token tout seul).
scp .session/storage_state.json jerem@192.168.0.43:<path>/AntiCoco/.session/
# 4. Sur le homelab : déployer
ssh homelab
cd <path>/AntiCoco && git pull && docker compose up -d --build
Vérifier : curl -s http://127.0.0.1:9200/mcp (le serveur répond au handshake MCP).
Intégration Hermes
Enregistrer AntiCoco dans la config MCP de Hermes (côté homelab), URL
http://127.0.0.1:9200/mcp (transport streamable-http).
Si Hermes n'accepte que le stdio, changer la dernière ligne de
server.py(mcp.run(transport="stdio")) et lancer le serveur en sous-processus — le reste est identique.
Outils MCP exposés
| Outil | Rôle |
|---|---|
hf_auth_status() |
état de connexion |
hf_login() |
(re)connexion + capture token |
hf_list_weeks() |
semaines modifiables |
hf_next_delivery() |
prochaine box réellement sélectionnée (≈4 recettes) + date/cutoff, images servables — prêt Telegram |
hf_favorites() |
recettes favorites du compte (images servables) |
hf_get_menu(week) |
toutes les recettes, avec contains_excluded et is_favorite |
hf_propose(week, count=0) |
shortlist sans coco, classée par préférences |
hf_confirm_selection(week, recipe_ids) |
écrit la sélection (refuse la coco) |
hf_get_excludes() / hf_add_exclude(term) / hf_remove_exclude(term) |
gérer la liste d'exclusion |
Configuration
config/excludes.json— ingrédients bannis (matching insensible casse/accents). Coco déjà listée.config/prefs.json— mots-clésliked/dislikedpour classer les propositions.config/endpoints.json— URLs gateway réelles + transfo CDN images (image_cdn_*: les URLs recettes CloudFront…/0,0/image/Xrépondent en 502, réécrites versimg.hellofresh.com/…/hellofresh_s3/image/X). Sélection courante viamy-deliveries/menu, favoris viacfs/v2/favorites/recipe. Re-découverte :tools/probe_selection.py/tools/probe_menu_capture.py(attache CDP).