Contenu

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

Le problème

Tu as un site Hugo. Tu veux pouvoir :

  1. Éditer le contenu via Claude.ai (publier un article, corriger une coquille, mettre à jour un draft) sans toucher à un terminal SSH.
  2. 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.

ZoneQui éditeVersionné dans Git ?
content/**/*.mdMCP exclusivement❌ NON (.gitignore)
layouts/, themes/, static/, hugo.toml, deploy.shGit 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 --hard cô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 blame sur un article pour voir qui a écrit quoi.
  • Pas de git log content/csp-nonce/index.fr.md pour 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

Diagram Diagram
┌────────────────────┐         ┌──────────────────────┐
│  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 :

  1. GitHub envoie un webhook POST sur https://mcp-hugo.arleo.eu/webhook/git
  2. L’endpoint valide le HMAC-SHA256 (secret partagé)
  3. Si OK, déclenche git fetch && git reset --hard origin/main && hugo --minify && rsync
  4. git reset --hard peut ê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 .md dans content/. 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.