<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Mcp - Balise - arleo.eu</title><link>https://www.arleo.eu/tags/mcp/</link><description>Mcp - Balise - arleo.eu</description><generator>Hugo -- gohugo.io</generator><language>fr</language><lastBuildDate>Thu, 14 May 2026 09:42:22 +0200</lastBuildDate><atom:link href="https://www.arleo.eu/tags/mcp/" rel="self" type="application/rss+xml"/><item><title>Plugin Cloudflare hugo-mcp : purge ciblée plutôt que totale</title><link>https://www.arleo.eu/posts/hugo-mcp-plugin-cloudflare/</link><pubDate>Thu, 14 May 2026 09:42:22 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/hugo-mcp-plugin-cloudflare/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/hugo-mcp-plugin-cloudflare-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p>Le plugin Cloudflare de <a href="https://github.com/jmrGrav/hugo-mcp/releases/tag/v2.0.0" target="_blank" rel="noopener noreffer ">hugo-mcp v2.0</a> implémente 3 modes de purge cache (<code>full</code>, <code>partial</code>, <code>smart</code>). Le mode <code>partial</code> calcule les URLs liées à invalider (canonique + sitemap + RSS + listing + home) pour préserver 95% du cache CDN à chaque modification. Concrètement : 6 URLs purgées au lieu de tout vider. Ce post détaille le calcul, les pièges, et pourquoi <code>smart</code> est devenu le défaut.</p>]]></description></item><item><title>hugo-mcp v2.0 : un plugin-system Python en 200 lignes</title><link>https://www.arleo.eu/posts/hugo-mcp-plugins-architecture/</link><pubDate>Sat, 09 May 2026 23:23:49 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/hugo-mcp-plugins-architecture/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/hugo-mcp-plugins-architecture-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p><a href="https://github.com/jmrGrav/hugo-mcp/releases/tag/v2.0.0" target="_blank" rel="noopener noreffer ">hugo-mcp v2.0.0</a> introduit un <strong>plugin-system Python</strong> qui permet à n&rsquo;importe qui d&rsquo;ajouter des hooks après chaque <code>create_page</code> / <code>update_page</code> / <code>delete_page</code>. 200 lignes de code pour le coeur, 3 plugins de production fournis (IndexNow, Google Indexing, Cloudflare). Ce post explique le design, les arbitrages, la sécurité, et montre comment écrire son propre plugin en 5 minutes.</p>]]></description></item><item><title>Sprint sécurité MCP livré : v1.9.0, 10 chantiers, écosystème durci</title><link>https://www.arleo.eu/posts/sprint-securite-mcp-livre/</link><pubDate>Sat, 09 May 2026 18:42:54 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/sprint-securite-mcp-livre/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/sprint-securite-mcp-livre-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p>Le 9 mai 2026, j&rsquo;ai livré en une session marathon les 10 chantiers du sprint sécurité MCP que <a href="/posts/roadmap-sprint-securite-mcp/" rel="">j&rsquo;avais annoncé en début de journée</a>. <code>hugo-mcp</code> est désormais en <strong>v1.9.0</strong> (<a href="https://github.com/jmrGrav/hugo-mcp/releases/tag/v1.9.0" target="_blank" rel="noopener noreffer ">GitHub Release</a>), commit <code>1404f83</code> GPG-signé.</p>
<p>Voici le recap haute-volée + un zoom pédagogique sur 2 chantiers qui ont une vraie valeur en dehors de mon contexte précis : <strong>C2 token rotation</strong> et <strong>C6 TLS interne</strong>.</p>
<h2 id="recap-des-10-chantiers">Recap des 10 chantiers</h2>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>Chantier</th>
          <th>Implémentation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>C1</td>
          <td>Rate limiting</td>
          <td><code>slowapi</code>, 60 req/min par IP</td>
      </tr>
      <tr>
          <td>C2</td>
          <td>Token rotation</td>
          <td><code>tokens.json</code> + <code>token_mgr.py</code> CLI</td>
      </tr>
      <tr>
          <td>C3</td>
          <td>Audit logs JSON</td>
          <td><code>structlog</code>, événements machine-readable</td>
      </tr>
      <tr>
          <td>C4</td>
          <td>Pydantic v2 stricte</td>
          <td><code>CreatePageArgs</code> / <code>UpdatePageArgs</code> avec contraintes</td>
      </tr>
      <tr>
          <td>C5</td>
          <td>bcrypt cost-12</td>
          <td>Tokens hashés en stockage</td>
      </tr>
      <tr>
          <td>C6</td>
          <td>TLS NUC ↔ VM</td>
          <td>Cert EC P-256, uvicorn SSL, proxy vérifie le cert</td>
      </tr>
      <tr>
          <td>C7</td>
          <td>requirements.lock</td>
          <td>SHA-256 hashes via <code>pip-compile --generate-hashes</code></td>
      </tr>
      <tr>
          <td>C8</td>
          <td>Info disclosure</td>
          <td>Docs off, exception handler générique, <code>proxy_hide_header</code></td>
      </tr>
      <tr>
          <td>C9</td>
          <td>nginx WAF</td>
          <td>Enforcement POST + <code>application/json</code> sur <code>/mcp</code>, OWASP CRS actif</td>
      </tr>
      <tr>
          <td>C10</td>
          <td>Backup DR</td>
          <td><code>backup.sh</code> GPG-chiffré, rétention 30 jours</td>
      </tr>
  </tbody>
</table>
<p>Détails complets dans le <a href="https://github.com/jmrGrav/hugo-mcp/blob/main/CHANGELOG.md" target="_blank" rel="noopener noreffer ">CHANGELOG v1.9.0</a> et le commit <a href="https://github.com/jmrGrav/hugo-mcp/commit/1404f83" target="_blank" rel="noopener noreffer ">1404f83</a>.</p>]]></description></item><item><title>Roadmap : Sprint sécurité MCP — 4 domaines, 10 chantiers (LIVRÉ ✅)</title><link>https://www.arleo.eu/posts/roadmap-sprint-securite-mcp/</link><pubDate>Sat, 09 May 2026 12:47:59 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/roadmap-sprint-securite-mcp/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/roadmap-sprint-securite-mcp-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="statut---livré--sprint-clos-le-9-mai-2026">Statut : ✅ LIVRÉ — sprint clos le 9 mai 2026</h2>
<p><strong>Mise à jour 9 mai 2026 (fin de session)</strong> : les 10 chantiers sont livrés en une session marathon, et <strong>les 3 releases coordonnées sont publiées</strong> (<code>hugo-mcp</code> v1.9.0, <code>mcp-oauth-proxy</code> v2.1.0, <code>mcp-installer</code> v1.3.0). Tout l&rsquo;écosystème MCP est durci dans la même journée. Recap technique complet : <a href="/posts/sprint-securite-mcp-livre/" rel="">Sprint sécurité MCP livré : v1.9.0, 10 chantiers, écosystème durci</a>.</p>
<p>Cette page reste publiée à fins d&rsquo;archive — pour montrer la trajectoire d&rsquo;un sprint annoncé puis tenu. Tout le contenu original ci-dessous est préservé.</p>
<hr>
<h2 id="statut-original-avant-livraison">Statut original (avant livraison)</h2>
<p>Cette page documentait publiquement un sprint de hardening en cours sur l&rsquo;infrastructure MCP d&rsquo;arleo.eu. Pour des raisons de sécurité, les détails précis des chantiers n&rsquo;étaient pas exposés tant que les correctifs n&rsquo;étaient pas livrés (philosophie &ldquo;sec-first&rdquo; : ne pas publier de roadmap d&rsquo;attaque).</p>
<h2 id="pourquoi-cette-transparence">Pourquoi cette transparence</h2>
<p>J&rsquo;ai débattu de publier ou non cette page. Arguments pour la transparence :</p>
<ul>
<li><strong>Engagement public</strong> = pression saine sur soi-même pour livrer</li>
<li><strong>Documentation</strong> d&rsquo;un homelab dont l&rsquo;objectif est apprendre et partager</li>
<li><strong>Honnêteté</strong> vis-à-vis des lecteurs qui consomment les autres articles techniques</li>
</ul>
<p>Arguments contre :</p>
<ul>
<li><strong>Roadmap d&rsquo;attaque</strong> : si je liste précisément ce qui n&rsquo;est pas encore protégé, je donne des indices à un attaquant patient</li>
<li><strong>Pression artificielle</strong> : annoncer un sprint puis ne pas livrer = pire que ne rien annoncer</li>
</ul>
<p>Compromis adopté : publier la <strong>direction</strong> et les <strong>domaines de hardening</strong> sans préciser ce qui est faible aujourd&rsquo;hui. Détail technique précis publié à la livraison.</p>
<h2 id="domaines-couverts-par-le-sprint">Domaines couverts par le sprint</h2>
<p>Le sprint couvrait 10 chantiers regroupés en 4 domaines :</p>
<h3 id="1-hardening-applicatif-fastapi--pydantic">1. Hardening applicatif (FastAPI + Pydantic)</h3>
<p>Renforcement des couches d&rsquo;entrée du MCP : validation stricte des inputs, gestion d&rsquo;erreurs unifiée, pas de leak d&rsquo;information dans les responses (stack traces, paths internes, versions de libs).</p>
<h3 id="2-authentification-et-tokens">2. Authentification et tokens</h3>
<p>Refonte de la gestion des tokens d&rsquo;accès au MCP : durée de vie, rotation, révocation, hashing en stockage. Permettre de couper l&rsquo;accès d&rsquo;un client compromis sans redéployer le service.</p>
<h3 id="3-observabilité-et-audit">3. Observabilité et audit</h3>
<p>Intégration de logs structurés JSON ingérés dans BetterStack via Vector. Chaque appel MCP doit produire un événement traçable : qui, quoi, quand, durée, statut. Permet la détection d&rsquo;anomalies en quasi-temps réel.</p>
<h3 id="4-infrastructure-et-résilience">4. Infrastructure et résilience</h3>
<p>TLS pour le trafic interne entre le NUC et la VM, règles ModSec dédiées au path <code>/mcp</code>, runbook de récupération en cas de désastre (vol de tokens, compromise serveur, perte de données).</p>
<h2 id="ce-qui-était-déjà-en-place-avant-le-sprint">Ce qui était déjà en place avant le sprint</h2>
<p>Pour ne pas créer de fausse impression, voici les couches <strong>déjà en place</strong> sur l&rsquo;infrastructure avant le sprint (donc hors scope) :</p>
<ul>
<li>nginx + TLS 1.3 obligatoire (Mozilla Modern config)</li>
<li>Cloudflare WAF + Bot Management + IP whitelist</li>
<li>ModSecurity + OWASP CRS 4.x sur tous les vhosts</li>
<li>CrowdSec en mode WAF + bouncer Cloudflare</li>
<li>systemd hardening niveau 1.7 (cf. <a href="/posts/hardening-systemd-mcp/" rel="">hardening systemd</a>)</li>
<li>HMAC validation sur les webhooks</li>
<li>Frontmatter validation Pydantic (1 chantier sur 4 dans la catégorie validation)</li>
</ul>
<p>Le sprint visait à <strong>compléter</strong> cette base, pas à la remplacer.</p>
<h2 id="méthode">Méthode</h2>
<p>Pour chaque chantier :</p>
<ol>
<li>Implémenté en local + tests unitaires</li>
<li>Validé sur <code>mcp-test-vm</code> (VM de pré-prod)</li>
<li>Déployé sur prod uniquement après tests réussis</li>
<li>Audit log structuré JSON pour traçabilité du déploiement</li>
<li>Article post-mortem ou write-up technique publié <strong>après</strong> livraison</li>
</ol>
<p>Aucun déploiement direct prod sans étape pré-prod.</p>
<h2 id="releases-publiées">Releases publiées</h2>]]></description></item><item><title>Roadmap : Webhook Git → rebuild Hugo automatique</title><link>https://www.arleo.eu/posts/roadmap-webhook-git-rebuild/</link><pubDate>Sat, 09 May 2026 12:47:17 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/roadmap-webhook-git-rebuild/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/roadmap-webhook-git-rebuild-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="statut---backlog--pas-encore-implémenté">Statut : 🗂️ BACKLOG — pas encore implémenté</h2>
<p>Cette page documente une intention d&rsquo;architecture qui sera implémentée dans une prochaine itération. Le code n&rsquo;est pas encore en prod.</p>
<h2 id="contexte">Contexte</h2>
<p>Dans la <a href="/posts/strategie-4-mcp-vs-git/" rel="">Stratégie 4 (séparation MCP / Git)</a>, j&rsquo;ai expliqué pourquoi <code>content/</code> est dans <code>.gitignore</code> côté repo arleo.eu : pour qu&rsquo;aucun conflit ne soit possible entre l&rsquo;écriture MCP et l&rsquo;écriture Git.</p>
<p>Concrètement, ça veut dire que quand je push depuis VS Code une nouvelle version de <code>layouts/</code>, <code>themes/</code>, <code>static/</code>, <code>hugo.toml</code>, ou <code>deploy.sh</code>, <strong>rien ne se passe automatiquement</strong> côté serveur. Je dois SSH dans la VM Hugo et faire <code>git pull &amp;&amp; hugo --minify &amp;&amp; rsync</code> à la main.</p>
<p>C&rsquo;est pas critique (push de structure = ~1× par semaine), mais c&rsquo;est de la friction inutile. Donc : webhook GitHub → rebuild auto.</p>
<h2 id="architecture-cible">Architecture cible</h2>]]></description></item><item><title>Post-mortem : Cloudflare Bot Management bloquait les webhooks MCP</title><link>https://www.arleo.eu/posts/postmortem-cf-bot-blocking-mcp/</link><pubDate>Sat, 09 May 2026 12:46:28 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/postmortem-cf-bot-blocking-mcp/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/postmortem-cf-bot-blocking-mcp-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="le-symptôme">Le symptôme</h2>
<p>Je viens de finaliser un endpoint webhook dans le <code>hugo-mcp-proxy</code> qui recevra des notifications de GitHub à chaque push sur le repo <code>arleo.eu</code>. Implementation propre : HMAC-SHA256, rate limiting, IPAddressAllow GitHub ranges côté systemd.</p>
<p>Test fonctionnel depuis un client externe :</p>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ curl -X POST https://mcp-hugo.arleo.eu/webhook/test <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -d <span class="s1">&#39;{&#34;test&#34;: true}&#39;</span></span></span></code></pre></div></div>
<p>Réponse : <strong>403 Forbidden</strong>.</p>
<p>Curieux. Le service tourne, l&rsquo;IP source de mon test est dans le whitelist, le HMAC est correct. Pourquoi 403 ?</p>
<h2 id="investigation-côté-serveur">Investigation côté serveur</h2>
<p>Logs nginx côté NUC :</p>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>$ sudo tail -100 /var/log/nginx/mcp-hugo.access.log | grep webhook</code></pre></div>
<p>Vide. Aucune requête n&rsquo;arrive sur nginx.</p>
<p>Logs <code>mcp-oauth-proxy</code> :</p>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>$ sudo journalctl -u mcp-oauth-proxy -n 100 | grep webhook</code></pre></div>
<p>Vide aussi. La requête n&rsquo;arrive pas jusqu&rsquo;au service.</p>
<p>Soit elle est bloquée par firewall avant nginx (CrowdSec ou ufw), soit en amont par Cloudflare.</p>
<h2 id="la-vérité-côté-cloudflare">La vérité côté Cloudflare</h2>]]></description></item><item><title>Post-mortem : mcp-installer régénérait les tokens à chaque relance</title><link>https://www.arleo.eu/posts/postmortem-mcp-installer-idempotence/</link><pubDate>Sat, 09 May 2026 12:45:36 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/postmortem-mcp-installer-idempotence/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/postmortem-mcp-installer-idempotence-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="le-bug">Le bug</h2>
<p><code>mcp-installer</code> est un script bash que j&rsquo;ai écrit pour automatiser l&rsquo;installation du <code>mcp-oauth-proxy</code> (FastAPI + nginx + systemd) sur un nouveau host. Workflow standard : clone, run, c&rsquo;est tout.</p>
<p>Sauf qu&rsquo;en relançant le script sur un host <strong>déjà installé</strong> (par exemple pour mettre à jour la version), j&rsquo;ai découvert un bug d&rsquo;idempotence : tous les secrets étaient régénérés.</p>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo ./install.sh
</span></span><span class="line"><span class="cl"><span class="o">[</span>+<span class="o">]</span> Generating MCP_TOKEN...
</span></span><span class="line"><span class="cl"><span class="o">[</span>+<span class="o">]</span> Generating CLIENT_ID...
</span></span><span class="line"><span class="cl"><span class="o">[</span>+<span class="o">]</span> Generating CLIENT_SECRET...
</span></span><span class="line"><span class="cl"><span class="o">[</span>+<span class="o">]</span> Generating TOKEN_SECRET...
</span></span><span class="line"><span class="cl"><span class="o">[</span>+<span class="o">]</span> Writing /etc/mcp-oauth-proxy/.env...</span></span></code></pre></div></div>
<p>Si tu as déjà un <code>.env</code> avec des tokens en service, l&rsquo;installer les <strong>écrase</strong>. Tous les clients OAuth déjà enregistrés (Claude.ai dans mon cas) se retrouvent avec des credentials invalides. La connexion casse.</p>
<h2 id="pourquoi-cest-arrivé">Pourquoi c&rsquo;est arrivé</h2>
<p>Le script utilisait cette logique :</p>
<div class="code-block code-line-numbers" data-start="0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">MCP_TOKEN</span><span class="o">=</span><span class="k">$(</span>openssl rand -hex 32<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">CLIENT_ID</span><span class="o">=</span><span class="k">$(</span>openssl rand -hex 16<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">CLIENT_SECRET</span><span class="o">=</span><span class="k">$(</span>openssl rand -hex 32<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">TOKEN_SECRET</span><span class="o">=</span><span class="k">$(</span>openssl rand -hex 32<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">cat &gt; /etc/mcp-oauth-proxy/.env <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="cl"><span class="s">MCP_TOKEN=$MCP_TOKEN
</span></span></span><span class="line"><span class="cl"><span class="s">CLIENT_ID=$CLIENT_ID
</span></span></span><span class="line"><span class="cl"><span class="s">CLIENT_SECRET=$CLIENT_SECRET
</span></span></span><span class="line"><span class="cl"><span class="s">TOKEN_SECRET=$TOKEN_SECRET
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre></div></div>
<p>Le pattern « génère puis écris » est correct pour une première install. Mais en relance, il ignore complètement le <code>.env</code> existant.</p>
<p>C&rsquo;est l&rsquo;erreur classique : l&rsquo;auteur (moi) a testé le script <strong>uniquement sur un host fresh</strong>. Le mode &ldquo;réinstall sur host existant&rdquo; n&rsquo;a jamais été testé.</p>
<h2 id="reproduction-du-bug">Reproduction du bug</h2>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo ./install.sh
</span></span><span class="line"><span class="cl"><span class="o">[</span>OK<span class="o">]</span> Installation <span class="nb">complete</span>
</span></span><span class="line"><span class="cl">$ cat /etc/mcp-oauth-proxy/.env <span class="p">|</span> head -1
</span></span><span class="line"><span class="cl"><span class="nv">MCP_TOKEN</span><span class="o">=</span>a7f3e9d2c4b8a1...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ sudo ./install.sh
</span></span><span class="line"><span class="cl"><span class="o">[</span>OK<span class="o">]</span> Installation <span class="nb">complete</span>
</span></span><span class="line"><span class="cl">$ cat /etc/mcp-oauth-proxy/.env <span class="p">|</span> head -1
</span></span><span class="line"><span class="cl"><span class="nv">MCP_TOKEN</span><span class="o">=</span>8c2f6a1b9e4d7c...   ← DIFFÉRENT</span></span></code></pre></div></div>
<p>Test reproductible en 30 secondes. C&rsquo;est presque comique que je ne l&rsquo;aie pas vu plus tôt.</p>
<h2 id="la-leçon--idempotent-est-un-contrat-pas-un-espoir">La leçon : &ldquo;idempotent&rdquo; est un contrat, pas un espoir</h2>
<p>Un script d&rsquo;installation doit être <strong>idempotent par défaut</strong>. C&rsquo;est-à-dire : <code>./install.sh</code> 1 fois, 2 fois, 5 fois → le système est dans le même état stable.</p>
<p>J&rsquo;avais ce contrat <strong>dans ma tête</strong>. Je ne l&rsquo;avais pas dans <strong>le code</strong>.</p>
<h2 id="le-fix--pre-flight-checks--force-flag-explicite">Le fix : pre-flight checks + force flag explicite</h2>]]></description></item><item><title>Post-mortem : 3 timeouts MCP — IPAddressDeny + Cloudflare + NFS</title><link>https://www.arleo.eu/posts/postmortem-mcp-timeouts-cloudflare/</link><pubDate>Sat, 09 May 2026 12:44:44 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/postmortem-mcp-timeouts-cloudflare/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/postmortem-mcp-timeouts-cloudflare-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="contexte">Contexte</h2>
<p>J&rsquo;ai mis en production un Hugo MCP Server (FastAPI, 7 tools) qui me permet d&rsquo;éditer arleo.eu depuis Claude.ai. Architecture : <code>claude.ai → mcp-oauth-proxy NUC → hugo-mcp-proxy NUC → MCP server VM</code>.</p>]]></description></item><item><title>Stratégie 4 : séparer le contenu (MCP) de la structure (Git)</title><link>https://www.arleo.eu/posts/strategie-4-mcp-vs-git/</link><pubDate>Sat, 09 May 2026 12:40:58 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/strategie-4-mcp-vs-git/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/strategie-4-mcp-vs-git-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="le-problème">Le problème</h2>
<p>Tu as un site Hugo. Tu veux pouvoir :</p>
<ol>
<li><strong>Éditer le contenu via Claude.ai</strong> (publier un article, corriger une coquille, mettre à jour un draft) sans toucher à un terminal SSH.</li>
<li><strong>Versionner la structure</strong> (layouts, themes, hugo.toml, scripts de déploiement) dans Git, comme un dev sérieux.</li>
</ol>
<p>Premier réflexe : « tout dans Git ». Les articles aussi. Le MCP commit, push, le webhook GitHub fait un rebuild. Propre, dans la philosophie GitOps.</p>
<p>Sauf que ça ne marche pas si bien. Voici pourquoi, et la solution simple que j&rsquo;appelle la <strong>Stratégie 4</strong>.</p>
<h2 id="pourquoi--tout-dans-git--casse-en-pratique">Pourquoi « tout dans Git » casse en pratique</h2>
<p>Imaginons que ton MCP fait un <code>git commit</code> à chaque <code>create_page</code>. Stratégie naïve, mais souvent proposée. Voici les problèmes :</p>
<h3 id="problème-1--conflit-mcp--git">Problème 1 — Conflit MCP ↔ Git</h3>
<p>Tu push depuis ton laptop un nouveau layout (<code>layouts/index.html</code> modifié). Au même moment, le MCP est en train de commiter une nouvelle version d&rsquo;un article. Race condition, le MCP peut faire un <code>git pull --rebase</code> qui échoue, ou pire, écraser ton commit local.</p>
<h3 id="problème-2--identité-git-du-mcp">Problème 2 — Identité Git du MCP</h3>
<p>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&rsquo;il faut sécuriser, faire tourner, etc.</p>
<h3 id="problème-3--auto-commit-indésirable">Problème 3 — Auto-commit indésirable</h3>
<p>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 &ldquo;wip test&rdquo; dans l&rsquo;historique, à rebaser ou squasher manuellement.</p>
<h3 id="problème-4--réversibilité-asymétrique">Problème 4 — Réversibilité asymétrique</h3>
<p>Un <code>git revert</code> côté repo n&rsquo;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.</p>
<h2 id="la-stratégie-4--séparer-les-zones">La Stratégie 4 : séparer les zones</h2>
<p>L&rsquo;idée : <strong>MCP et Git n&rsquo;écrivent jamais sur les mêmes fichiers</strong>.</p>
<table>
  <thead>
      <tr>
          <th>Zone</th>
          <th>Qui édite</th>
          <th>Versionné dans Git ?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>content/**/*.md</code></td>
          <td><strong>MCP exclusivement</strong></td>
          <td>❌ NON (<code>.gitignore</code>)</td>
      </tr>
      <tr>
          <td><code>layouts/</code>, <code>themes/</code>, <code>static/</code>, <code>hugo.toml</code>, <code>deploy.sh</code></td>
          <td><strong>Git push exclusivement</strong></td>
          <td>✅ OUI</td>
      </tr>
  </tbody>
</table>
<p>Le <code>.gitignore</code> côté repo :</p>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>content/
public/
resources/</code></pre></div>
<p>Implications :</p>
<ul>
<li>Le MCP peut écrire dans <code>content/</code> quand il veut. Aucun conflit Git possible.</li>
<li>Tu peux faire <code>git reset --hard</code> côté repo en confiance — <code>content/</code> reste intact.</li>
<li>Pas besoin que le MCP gère une identité Git.</li>
<li>Pas de &ldquo;commit pollution&rdquo;.</li>
</ul>
<h2 id="trade-off--pas-de-versioning-du-contenu">Trade-off : pas de versioning du contenu</h2>
<p>Tu perds le versioning Git du contenu. C&rsquo;est une perte réelle :</p>
<ul>
<li>Pas de <code>git blame</code> sur un article pour voir qui a écrit quoi.</li>
<li>Pas de <code>git log content/csp-nonce/index.fr.md</code> pour voir l&rsquo;historique.</li>
<li>Pas de PR review pour les articles.</li>
</ul>
<p><strong>Mitigation</strong> : snapshots VM + backup <code>content/</code> chiffré quotidien sur QNAP. Ça couvre la <strong>récupération en cas de désastre</strong>, mais pas le versioning fin (qui-a-changé-quoi-quand).</p>
<p>Pour mon homelab perso (un seul auteur, articles techniques, pas de workflow de validation éditoriale), c&rsquo;est un trade-off acceptable. Pour un blog d&rsquo;équipe avec 10 contributeurs, je reconsidérerais.</p>
<h2 id="architecture-finale">Architecture finale</h2>]]></description></item><item><title>Hugo MCP Server : connecter Claude.ai à un site Hugo statique</title><link>https://www.arleo.eu/posts/hugo-mcp-server/</link><pubDate>Sun, 03 May 2026 19:00:00 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/posts/hugo-mcp-server/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/hugo-mcp-server-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-en-bref">⚡ En bref</h2>
<p>Connecter Claude.ai à un site <strong>Hugo</strong> hébergé dans une VM KVM en 30 minutes : un serveur FastAPI expose 6 outils MCP (lire, créer, modifier, supprimer des pages, rebuilder le site) via JSON-RPC 2.0, un proxy OAuth réutilise l&rsquo;infrastructure existante, et chaque modification déclenche automatiquement un rebuild Hugo + une purge du cache Cloudflare.</p>
<p>Le code est disponible sur GitHub :</p>
<ul>
<li>🔌 Serveur MCP Hugo : <a href="https://github.com/jmrGrav/hugo-mcp" target="_blank" rel="noopener noreffer ">jmrGrav/hugo-mcp</a></li>
<li>🔐 Proxy OAuth : <a href="https://github.com/jmrGrav/mcp-oauth-proxy" target="_blank" rel="noopener noreffer ">jmrGrav/mcp-oauth-proxy</a></li>
</ul>
<h2 id="-pourquoi">🧠 Pourquoi</h2>
<p>Le protocole <a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreffer ">MCP (Model Context Protocol)</a> d&rsquo;Anthropic permet à Claude.ai de se connecter à des sources de données externes via des outils standardisés. Contrairement à Grav CMS qui est dynamique (PHP), Hugo génère du HTML statique — ce qui rend la gestion de contenu via MCP encore plus puissante : chaque modification est compilée et déployée instantanément.</p>]]></description></item></channel></rss>