Stratégie 4 : séparer le contenu (MCP) de la structure (Git)

Le problème
Tu as un site Hugo. Tu veux pouvoir :
- Éditer le contenu via Claude.ai (publier un article, corriger une coquille, mettre à jour un draft) sans toucher à un terminal SSH.
- Versionner la structure (layouts, themes, hugo.toml, scripts de déploiement) dans Git, comme un dev sérieux.
Premier réflexe : « tout dans Git ». Les articles aussi. Le MCP commit, push, le webhook GitHub fait un rebuild. Propre, dans la philosophie GitOps.
Sauf que ça ne marche pas si bien. Voici pourquoi, et la solution simple que j’appelle la Stratégie 4.
Pourquoi « tout dans Git » casse en pratique
Imaginons que ton MCP fait un git commit à chaque create_page. Stratégie naïve, mais souvent proposée. Voici les problèmes :
Problème 1 — Conflit MCP ↔ Git
Tu push depuis ton laptop un nouveau layout (layouts/index.html modifié). Au même moment, le MCP est en train de commiter une nouvelle version d’un article. Race condition, le MCP peut faire un git pull --rebase qui échoue, ou pire, écraser ton commit local.
Problème 2 — Identité Git du MCP
Le MCP commit en tant que qui ? Avec quelle clé GPG ? Si tu as une politique « commits signés obligatoires », le MCP doit gérer une clé GPG, qu’il faut sécuriser, faire tourner, etc.
Problème 3 — Auto-commit indésirable
Tu fais un test, tu crées un article draft pour expérimenter, tu le supprimes. Mais le MCP a déjà commité. Maintenant tu as un commit “wip test” dans l’historique, à rebaser ou squasher manuellement.
Problème 4 — Réversibilité asymétrique
Un git revert côté repo n’a aucun effet sur les fichiers que le MCP a déjà créés. Tu te retrouves avec un repo et un état filesystem désynchronisés.
La Stratégie 4 : séparer les zones
L’idée : MCP et Git n’écrivent jamais sur les mêmes fichiers.
| Zone | Qui édite | Versionné dans Git ? |
|---|---|---|
content/**/*.md | MCP exclusivement | ❌ NON (.gitignore) |
layouts/, themes/, static/, hugo.toml, deploy.sh | Git push exclusivement | ✅ OUI |
Le .gitignore côté repo :
content/
public/
resources/Implications :
- Le MCP peut écrire dans
content/quand il veut. Aucun conflit Git possible. - Tu peux faire
git reset --hardcôté repo en confiance —content/reste intact. - Pas besoin que le MCP gère une identité Git.
- Pas de “commit pollution”.
Trade-off : pas de versioning du contenu
Tu perds le versioning Git du contenu. C’est une perte réelle :
- Pas de
git blamesur un article pour voir qui a écrit quoi. - Pas de
git log content/csp-nonce/index.fr.mdpour voir l’historique. - Pas de PR review pour les articles.
Mitigation : snapshots VM + backup content/ chiffré quotidien sur QNAP. Ça couvre la récupération en cas de désastre, mais pas le versioning fin (qui-a-changé-quoi-quand).
Pour mon homelab perso (un seul auteur, articles techniques, pas de workflow de validation éditoriale), c’est un trade-off acceptable. Pour un blog d’équipe avec 10 contributeurs, je reconsidérerais.
Architecture finale
┌────────────────────┐ ┌──────────────────────┐
│ Claude.ai client │ HTTPS │ hugo-mcp-proxy NUC │
│ (édition contenu) │ ──────► │ (mcp-oauth-proxy) │
└────────────────────┘ └──────────────────────┘
│
│ HTTP interne
▼
┌────────────────────────┐
│ Hugo MCP Server VM │
│ (FastAPI, 7 tools) │
│ │
│ ZONE MCP : │
│ /home/jm/hugo-site/ │
│ └── content/ │
└────────────────────────┘
┌────────────────────┐ ┌──────────────────────┐
│ Laptop dev │ SSH │ VM Hugo │
│ (édition layout) │ ──────► │ │
└────────────────────┘ │ ZONE GIT : │
│ /home/jm/hugo-site/ │
│ ├── layouts/ │
│ ├── themes/ │
│ ├── static/ │
│ ├── hugo.toml │
│ └── deploy.sh │
└──────────────────────┘Les deux zones partagent le même filesystem, mais aucun fichier en commun. Pas d’overlap, pas de conflit.
Webhook Git (optionnel mais utile)
Pour automatiser le rebuild quand tu push une nouvelle version de hugo.toml :
- GitHub envoie un webhook POST sur
https://mcp-hugo.arleo.eu/webhook/git - L’endpoint valide le HMAC-SHA256 (secret partagé)
- Si OK, déclenche
git fetch && git reset --hard origin/main && hugo --minify && rsync git reset --hardpeut être appelé en confiance — il ne touche jamais àcontent/(gitignored)
Le rate limiting est important : 1 rebuild/min max, pour éviter qu’un push spam (ou un attaquant qui réussit à signer un payload) ne sature le serveur.
Ce que ça permet
Concrètement, dans ma session de travail typique :
- J’écris un article via Claude.ai → MCP crée le fichier
.mddanscontent/. Hugo build, déploie, purge Cloudflare. 5 secondes total. - Je modifie un partial du thème via VS Code → SSH push direct ou
git push→ webhook → rebuild. 30 secondes.
Les deux flux sont indépendants. Si le MCP crash, je peux quand même push du code Git. Si Git tombe, le MCP continue de servir.
Variantes considérées et rejetées
Stratégie 1 — MCP commit chaque édition
Tous les problèmes listés ci-dessus. Rejetée pour V1, peut-être V2 si je passe à un workflow multi-auteur.
Stratégie 2 — Branches séparées
main pour la structure, content pour les articles. MCP push uniquement sur content, Git push uniquement sur main. Élégant mais complexe : il faut gérer les merges, les rebases, et l’état effectif sur prod devient difficile à raisonner.
Stratégie 3 — Submodule Git
content/ comme submodule pointant vers un autre repo. Le MCP commite dans le submodule. Trop de cérémonie pour un homelab.
Stratégie 4 — Zones séparées (celle-ci)
Simple. Robuste. Pas de cérémonie. Trade-off connu (pas de versioning fin du contenu) mais accepté pour mon usage.
Conclusion
Quand on combine deux outils qui veulent éditer les mêmes fichiers (MCP et Git), la solution n’est pas de les faire coopérer (synchroniser, locker, merger). C’est de les empêcher d’éditer les mêmes fichiers.
.gitignore content/ est 1 ligne de config qui résout 4 classes de problèmes. C’est de l’architecture en faisant moins, pas plus.
L’implémentation effective (webhook, IPAddressAllow GitHub ranges, HMAC) est documentée dans le brief webhook-git-rebuild archivé dans le repo hugo-mcp/docs/backlogs/. À déployer dans une prochaine session — pour l’instant, l’édition Git se fait en SSH direct, ce qui marche aussi.