<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julien Landuré</title>
    <description>The latest articles on DEV Community by Julien Landuré (@jlandure).</description>
    <link>https://dev.to/jlandure</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192673%2Fa76bdc95-3cd3-4bd5-b177-01f02dabcdb3.jpg</url>
      <title>DEV Community: Julien Landuré</title>
      <link>https://dev.to/jlandure</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jlandure"/>
    <language>en</language>
    <item>
      <title>GitHub Universe 2026 : pourquoi et comment adopter le harnais Copilot!</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 03 Nov 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/techtown-fr/github-universe-2026-pourquoi-et-comment-adopter-le-harnais-copilot-2d76</link>
      <guid>https://dev.to/techtown-fr/github-universe-2026-pourquoi-et-comment-adopter-le-harnais-copilot-2d76</guid>
      <description>&lt;p&gt;Le 28 octobre 2026, GitHub a dévoilé ses annonces de &lt;strong&gt;GitHub Universe 2026&lt;/strong&gt; , et l’équipe VS Code a sorti le même jour la version 1.144 de l’éditeur. La liste est longue : Copilot Projects dans l’app GitHub Copilot, automations dans le cloud, pull requests empilées, nouvelles protections de sécurité…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6dknvltiqoyklvdo58n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6dknvltiqoyklvdo58n.webp" alt="Visuel officiel de GitHub Universe 2026 : le mot UNIVERSE ’26 en grandes lettres noires, le logo GitHub et l’Octocat de profil suivi de silhouettes colorées" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://github.blog/news-insights/company-news/github-universe-2026-evolving-every-layer-of-the-stack/" rel="noopener noreferrer"&gt;GitHub Blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pour les développeurs qui utilisent des agents dans VS Code, le changement le plus structurant est aussi le plus discret : le &lt;strong&gt;harnais&lt;/strong&gt;. Depuis l’automne 2026, les nouvelles sessions d’agent tournent par défaut sur le harnais Copilot, construit sur le Copilot SDK, à la place du harnais Local historique de VS Code.&lt;/p&gt;

&lt;p&gt;Dans cet article, nous allons voir pourquoi adopter le harnais Copilot, ce qu’il faut adapter pour basculer, puis quatre nouveautés à tester tout de suite : Copilot Fusion, Compare Models, la génération d’images dans le chat et l’option &lt;code&gt;--attach&lt;/code&gt; de GitHub CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  📌 Un harnais, c’est quoi ?
&lt;/h2&gt;

&lt;p&gt;La documentation VS Code donne une définition simple : un harnais d’agent est la couche logicielle qui fait tourner une session d’agent. Il relie le modèle au contexte et aux outils, pilote la boucle de l’agent et garde l’état de la session.&lt;/p&gt;

&lt;p&gt;Changer de modèle ne change donc pas le harnais. Nous pouvons utiliser le même modèle avec deux harnais différents et obtenir des comportements différents, parce que les outils, les permissions et les customisations disponibles ne sont pas les mêmes.&lt;/p&gt;

&lt;p&gt;VS Code propose plusieurs harnais dans le contrôle &lt;strong&gt;Session Target&lt;/strong&gt;  :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot&lt;/strong&gt;  : construit sur le &lt;a href="https://github.com/github/copilot-sdk" rel="noopener noreferrer"&gt;Copilot SDK&lt;/a&gt;, il tourne dans l’ &lt;strong&gt;Agent Host&lt;/strong&gt; , un processus séparé des fenêtres de l’éditeur ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local&lt;/strong&gt;  : le harnais historique de VS Code, qui tourne dans l’extension host de la fenêtre ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; et &lt;strong&gt;Codex&lt;/strong&gt;  : les harnais des fournisseurs, eux aussi accessibles via l’Agent Host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le bilan publié par l’équipe VS Code résume l’histoire : l’équipe a fusionné le harnais Local de VS Code et celui de Copilot CLI pour créer le nouveau harnais Copilot, désormais le harnais par défaut des nouvelles sessions, dans l’éditeur comme dans la fenêtre Agents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9z8irjyiwtwn3cr2293s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9z8irjyiwtwn3cr2293s.webp" alt="Contrôle Session Target de VS Code ouvert sur le harnais Copilot, avec les choix Copilot, Local, Claude, Codex et Cloud, et l’infobulle qui décrit le Copilot SDK et l’Agent Host" width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://code.visualstudio.com/blogs/2026/10/28/vscode-wrapped" rel="noopener noreferrer"&gt;VS Code Wrapped&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 🤖 Pourquoi adopter le harnais Copilot
&lt;/h2&gt;

&lt;p&gt;Le harnais Local est en fin de vie. Dans les &lt;a href="https://code.visualstudio.com/updates/v1_143" rel="noopener noreferrer"&gt;notes de version de VS Code 1.143&lt;/a&gt;, l’équipe VS Code présente les réglages qui permettent de rester sur Local comme temporaires, le temps que Local soit retiré (« These controls are temporary while Local is retired. »). Les nouveautés comme la fenêtre Agents ou Compare Models ne sont d’ailleurs pas disponibles avec Local.&lt;/p&gt;

&lt;p&gt;La recommandation de la &lt;a href="https://code.visualstudio.com/docs/agents/run/agent-harnesses" rel="noopener noreferrer"&gt;documentation des harnais&lt;/a&gt; est claire : commencer par le harnais Copilot pour le code et les tâches d’agent du quotidien, et ne choisir un autre harnais que pour un besoin précis d’outils, de customisations ou d’exécution. Voici les raisons concrètes de suivre ce conseil.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un seul runtime pour toutes les surfaces
&lt;/h3&gt;

&lt;p&gt;Le harnais Copilot repose sur le même runtime que Copilot CLI et l’app GitHub Copilot. Les notes de version de VS Code 1.144 le résument en une phrase : « Write agent customizations once and use them consistently across all these experiences. »&lt;/p&gt;

&lt;p&gt;Concrètement, une skill, un custom agent ou une configuration MCP rangés dans les dossiers natifs de Copilot (&lt;code&gt;.github/&lt;/code&gt;, &lt;code&gt;.agents/&lt;/code&gt;, &lt;code&gt;~/.copilot/&lt;/code&gt;) sont lus par VS Code comme par Copilot CLI. Les sessions Cloud ne supportent d’ailleurs que ce harnais.&lt;/p&gt;

&lt;h3&gt;
  
  
  Une orchestration qui survit à la fenêtre
&lt;/h3&gt;

&lt;p&gt;Avec le harnais Local, fermer la fenêtre de l’éditeur arrête le travail de l’agent. Avec le harnais Copilot, l’Agent Host gère les sessions et donne accès aux harnais, aux modèles, aux fichiers et aux terminaux. Le travail continue en arrière-plan, y compris sur une machine distante via Remote SSH ou les dev tunnels.&lt;/p&gt;

&lt;p&gt;La même session reste synchronisée entre la vue Chat et la &lt;strong&gt;fenêtre Agents&lt;/strong&gt; , qui permet de piloter plusieurs agents sur plusieurs workspaces. Le harnais Local, lui, n’est pas disponible dans la fenêtre Agents. Or c’est là qu’arrivent les nouveautés comme Compare Models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Des garde-fous appliqués
&lt;/h3&gt;

&lt;p&gt;La documentation est explicite : le harnais Local n’applique pas les règles de sandbox et de permissions propres au runtime. Le harnais Copilot les applique, avec le mode &lt;strong&gt;Assisted permissions&lt;/strong&gt; (disponibilité générale dans VS Code 1.144) qui évalue le risque des actions de l’agent.&lt;/p&gt;

&lt;p&gt;À noter : un runtime partagé ne veut pas dire des outils identiques partout. La documentation précise que le harnais Copilot n’expose pas tous les outils intégrés de VS Code ni ceux fournis par les extensions. Ces derniers ne suivent pas une session reprise dans Copilot CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. ⚙️ Ce qu’il faut adapter pour basculer
&lt;/h2&gt;

&lt;p&gt;Le harnais se choisit dans le contrôle &lt;strong&gt;Session Target&lt;/strong&gt; , avant d’envoyer le premier prompt. Le harnais actif s’affiche en préfixe du titre du chat.&lt;/p&gt;

&lt;p&gt;La migration des customisations est guidée : avec Copilot comme Session Target, la commande &lt;strong&gt;Chat: Open Customizations&lt;/strong&gt; ouvre un onglet &lt;strong&gt;Migrations&lt;/strong&gt; qui propose une conversion automatique, une conversion assistée par l’agent ou une revue manuelle. Le &lt;a href="https://code.visualstudio.com/docs/agent-customization/migrate-customizations" rel="noopener noreferrer"&gt;guide de migration&lt;/a&gt; détaille les cas particuliers. Voici les principaux points à adapter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Harnais Local&lt;/th&gt;
&lt;th&gt;Harnais Copilot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sous-agent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runSubagent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lire un fichier&lt;/td&gt;
&lt;td&gt;&lt;code&gt;read_file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;view&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lancer une commande&lt;/td&gt;
&lt;td&gt;&lt;code&gt;run_in_terminal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bash&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt files &lt;code&gt;*.prompt.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Oui&lt;/td&gt;
&lt;td&gt;Non : à convertir en skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.vscode/mcp.json&lt;/code&gt;, &lt;code&gt;${input:...}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.mcp.json&lt;/code&gt;, &lt;code&gt;~/.copilot/mcp-config.json&lt;/code&gt;, &lt;code&gt;${VAR}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hooks&lt;/td&gt;
&lt;td&gt;Contrat Local&lt;/td&gt;
&lt;td&gt;Contrat Copilot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Les prompt files deviennent des skills
&lt;/h3&gt;

&lt;p&gt;Le harnais Copilot ne lit pas les fichiers &lt;code&gt;*.prompt.md&lt;/code&gt;. La conversion en skills est proposée par l’onglet Migrations. Attention, elle ne conserve pas les champs &lt;code&gt;agent&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt; ni &lt;code&gt;tools&lt;/code&gt; du prompt file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Les serveurs MCP changent de fichier et de syntaxe
&lt;/h3&gt;

&lt;p&gt;Le harnais Copilot lit &lt;code&gt;.mcp.json&lt;/code&gt; à la racine du workspace et &lt;code&gt;~/.copilot/mcp-config.json&lt;/code&gt; pour l’utilisateur, avec une clé racine &lt;code&gt;mcpServers&lt;/code&gt;. Les secrets passent par des variables d’environnement &lt;code&gt;${VAR}&lt;/code&gt; au lieu des &lt;code&gt;${input:...}&lt;/code&gt; de VS Code.&lt;/p&gt;

&lt;p&gt;Un serveur dont la configuration utilise un &lt;code&gt;${input:...}&lt;/code&gt; secret n’est pas migré automatiquement : il n’est pas transmis au harnais Copilot et ses tools disparaissent de la session. Les serveurs protégés par un token saisi via un input secret sont donc à traiter en priorité. Pour la question du filtrage des tools MCP, j’avais détaillé les options dans un article précédent 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://techtown.fr/blog/gouvernance-mcp-entreprise-filtrer-tools-agents-de-code/" rel="noopener noreferrer"&gt;https://techtown.fr/blog/gouvernance-mcp-entreprise-filtrer-tools-agents-de-code/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Les hooks changent de contrat
&lt;/h3&gt;

&lt;p&gt;Les hooks du harnais Copilot suivent le contrat Copilot (&lt;code&gt;version: 1&lt;/code&gt;, clés en camelCase). Un hook écrit pour le harnais Local doit être relu avant d’être réutilisé.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 🧠 Copilot Fusion prend la place d’Auto
&lt;/h2&gt;

&lt;p&gt;Dans le sélecteur de modèles, &lt;strong&gt;Copilot Fusion&lt;/strong&gt; remplace l’option Auto là où il est disponible. Il est en disponibilité générale depuis le 28 octobre 2026, sur tous les plans Copilot, dans VS Code, l’app GitHub Copilot, Copilot CLI et les IDE JetBrains.&lt;/p&gt;

&lt;p&gt;Fusion combine la sélection automatique de modèle d’Auto et l’orchestration multi-modèles du projet HydraFusion. C’est un routeur et un orchestrateur : pour chaque prompt, il choisit le modèle ou la combinaison de modèles, l’effort de raisonnement et le pattern d’exécution adaptés à la tâche.&lt;/p&gt;

&lt;p&gt;Nous ne choisissons que le niveau :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard&lt;/strong&gt; pour le travail courant ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High&lt;/strong&gt; pour une tâche de code importante et bien délimitée, comme un bug complexe ou une modification sur plusieurs fichiers ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max&lt;/strong&gt; pour les problèmes qui demandent le raisonnement le plus poussé.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frxgaa8niaix21bkbcyvd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frxgaa8niaix21bkbcyvd.webp" alt="Sélecteur de modèles de GitHub Copilot avec Copilot Fusion activé et ses trois niveaux d’effort Standard, High et Max, Max étant sélectionné" width="799" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://github.blog/changelog/2026-10-28-route-and-orchestrate-models-with-github-copilot-fusion/" rel="noopener noreferrer"&gt;changelog GitHub Copilot Fusion&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fusion décide ensuite du pattern pour chaque prompt :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single&lt;/strong&gt;  : un seul modèle traite la tâche ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascade&lt;/strong&gt;  : un modèle efficace propose une solution, et un juge l’accepte ou la confie à un modèle plus puissant ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critique&lt;/strong&gt;  : un modèle rédige, d’autres modèles critiquent en parallèle, puis le premier révise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Côté coût, chaque modèle utilisé est facturé à son tarif standard, avec une remise de 30 % pour les plans payants jusqu’au 30 novembre 2026. Dans VS Code, les niveaux High et Max sont réservés aux plans Pro+, Max, Business et Enterprise. Côté résultats, &lt;a href="https://github.blog/changelog/2026-10-28-route-and-orchestrate-models-with-github-copilot-fusion/" rel="noopener noreferrer"&gt;GitHub annonce&lt;/a&gt; que Fusion Max obtient 60 points sur l’Artificial Analysis Coding Agent Index, à deux points de Claude Code avec Opus 5.5 Max, pour un coût moyen par tâche environ deux fois plus faible et environ 40 % de temps en moins.&lt;/p&gt;

&lt;p&gt;À noter : les administrateurs gardent la main. Fusion n’utilise que les modèles autorisés par les politiques de l’entreprise et de l’organisation, et un modèle peut être exclu de Fusion sans être désactivé ailleurs.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. ⚖️ Compare Models dans la fenêtre Agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Compare Models&lt;/strong&gt; (en preview) envoie un même prompt à plusieurs modèles, ou à plusieurs harnais, en parallèle, puis affiche les résultats côte à côte. Il est disponible uniquement dans la fenêtre Agents, avec le paramètre &lt;code&gt;sessions.chat.compareAgents.enabled&lt;/code&gt;, activé par défaut pour les nouvelles sessions éligibles dans VS Code 1.144.&lt;/p&gt;

&lt;p&gt;L’idée est simple :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dans le sélecteur de modèles, choisir &lt;strong&gt;Compare Models&lt;/strong&gt; (ou &lt;code&gt;⌥⌘M&lt;/code&gt; sur macOS, &lt;code&gt;Ctrl+Alt+M&lt;/code&gt; sur Windows et Linux).&lt;/li&gt;
&lt;li&gt;Sélectionner au moins deux tentatives, en mélangeant si besoin les harnais Copilot, Claude et Codex, avec un niveau de raisonnement différent pour chacune.&lt;/li&gt;
&lt;li&gt;Lancer le prompt : chaque tentative tourne dans sa propre session, avec son worktree.&lt;/li&gt;
&lt;li&gt;Optionnellement, laisser un modèle &lt;strong&gt;Judge&lt;/strong&gt; recommander une solution, avec un tableau du temps et des tokens consommés.&lt;/li&gt;
&lt;li&gt;Lancer &lt;strong&gt;Synthesize&lt;/strong&gt; pour repartir de la tentative recommandée en y intégrant les meilleures idées des autres.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pratique pour se faire sa propre idée sur ses vraies tâches, en complément des benchmarks. La comparaison n’est pas disponible dans les Dev Containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 🎨 Générer des images dans le chat
&lt;/h2&gt;

&lt;p&gt;Copilot peut maintenant générer et modifier des images en langage naturel, en pleine tâche de code ou dans une nouvelle demande. L’aperçu s’affiche dans le chat et l’image est enregistrée directement dans le workspace. Le modèle de raisonnement sélectionné appelle un outil dédié, basé sur GPT Image 2.5 Flare.&lt;/p&gt;

&lt;p&gt;La fonctionnalité est en preview publique dans VS Code, l’app GitHub Copilot et Copilot CLI, pour les plans Copilot Pro, Pro+, Business et Enterprise. Quelques réglages à connaître :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;la génération est facturée à part, en tokens d’image qui comptent dans les crédits IA du plan ;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chat.copilot.imageGeneration.watermark&lt;/code&gt; à &lt;code&gt;true&lt;/code&gt; ajoute un filigrane « Made with AI », désactivé par défaut ;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chat.copilot.imageGeneration.model&lt;/code&gt; à &lt;code&gt;off&lt;/code&gt; désactive la fonctionnalité, et les administrateurs peuvent la couper via les politiques de modèles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. 💻 Joindre images et vidéos avec &lt;code&gt;gh --attach&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Côté terminal, GitHub CLI accepte une option &lt;code&gt;--attach&lt;/code&gt; depuis la version 2.99.0, mise en avant dans le récapitulatif de Universe. Elle téléverse une image ou une vidéo locale et insère le lien dans le corps d’une issue, d’une pull request ou d’un commentaire.&lt;/p&gt;

&lt;p&gt;Elle fonctionne avec &lt;code&gt;gh issue create&lt;/code&gt;, &lt;code&gt;gh issue edit&lt;/code&gt;, &lt;code&gt;gh issue comment&lt;/code&gt;, &lt;code&gt;gh pr create&lt;/code&gt;, &lt;code&gt;gh pr edit&lt;/code&gt; et &lt;code&gt;gh pr comment&lt;/code&gt;. L’option est répétable, et le texte alternatif d’une image se place après un &lt;code&gt;#&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh issue create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Erreur à la connexion"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--attach&lt;/span&gt; &lt;span class="s2"&gt;"./login.png#Message d'erreur sur la page de connexion"&lt;/span&gt;

gh &lt;span class="nb"&gt;pr &lt;/span&gt;comment 13 &lt;span class="nt"&gt;--attach&lt;/span&gt; ./before.png &lt;span class="nt"&gt;--attach&lt;/span&gt; ./after.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si le corps de l’issue, de la pull request ou du commentaire référence déjà un fichier local (&lt;code&gt;![alt](./login.png)&lt;/code&gt;) et que ce même fichier est passé avec &lt;code&gt;--attach&lt;/code&gt;, GitHub CLI remplace le chemin local par l’URL téléversée.&lt;/p&gt;

&lt;p&gt;Quelques limites à connaître :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;formats PNG, JPEG, GIF, WebP, SVG, MP4, MOV et WebM ;&lt;/li&gt;
&lt;li&gt;10 Mo pour les images, 10 Mo pour les vidéos sur un plan Free et 100 Mo sur un plan payant ;&lt;/li&gt;
&lt;li&gt;accès en écriture au dépôt requis ;&lt;/li&gt;
&lt;li&gt;GitHub Enterprise Server non supporté dans la première version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💪 ProTip : combinée à un agent qui fait des captures d’écran, l’option permet de produire des pull requests avec un avant/après visuel, sans passer par le navigateur.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Bonus : les autres annonces à suivre
&lt;/h2&gt;

&lt;p&gt;Universe 2026 ne se limite pas à VS Code. Quelques annonces à garder en tête :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot Projects&lt;/strong&gt; (preview publique) : dans l’app GitHub Copilot, un objectif et son contexte (dépôts, maquettes, fils Slack, notes de réunion) deviennent des tâches coordonnées sur un ou plusieurs dépôts ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Computer use&lt;/strong&gt; (preview publique sur macOS et Windows) : Copilot peut piloter des applications de bureau, après approbation ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copilot automations&lt;/strong&gt;  : des workflows IA déclenchés à la demande, sur un calendrier ou sur un événement, qui tournent dans le cloud ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull requests empilées&lt;/strong&gt;  : découper une grosse modification en PR liées, relues une à une ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copilot code review Max&lt;/strong&gt;  : un nouveau niveau de revue, à côté de Lite et Balanced ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protection push par IA&lt;/strong&gt;  : détection des secrets non structurés, comme les mots de passe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Côté VS Code, la version 1.144 permet aussi de lancer des automations dans le cloud depuis la fenêtre Agents (Cloud Automations, en preview). Elles tournent sur l’infrastructure GitHub, même ordinateur éteint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcb0ncnxpmn6ck5ykozrz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcb0ncnxpmn6ck5ykozrz.webp" alt="Fenêtre New automation de VS Code : une automation Issue Triage qui lance la commande /triage-issue dans le cloud, déclenchée quand une issue est assignée" width="600" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://code.visualstudio.com/updates/v1_144" rel="noopener noreferrer"&gt;notes de version de VS Code 1.144&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Le &lt;a href="https://github.com/events/universe/recap?locale=fr-fr" rel="noopener noreferrer"&gt;récapitulatif officiel&lt;/a&gt; et l’&lt;a href="https://github.blog/news-insights/company-news/github-universe-2026-evolving-every-layer-of-the-stack/" rel="noopener noreferrer"&gt;article de Jay Parikh&lt;/a&gt; donnent la vue d’ensemble.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;Compare Models, la fenêtre Agents et les sessions Cloud s’appuient sur le harnais Copilot et l’Agent Host. Rester sur le harnais Local, c’est s’en priver et garder des agents qui s’arrêtent avec la fenêtre, sans les règles de sandbox et de permissions du runtime.&lt;/p&gt;

&lt;p&gt;Le passage demande un peu de travail sur les customisations : noms de tools, prompt files, serveurs MCP avec secrets et hooks. Mon conseil : basculer la Session Target sur Copilot, lancer l’onglet Migrations, traiter les serveurs MCP en premier, puis tester Fusion et Compare Models sur une vraie tâche de votre backlog.&lt;/p&gt;

&lt;p&gt;Pour revoir toute l’année côté éditeur, le bilan &lt;a href="https://code.visualstudio.com/blogs/2026/10/28/vscode-wrapped" rel="noopener noreferrer"&gt;VS Code Wrapped : GitHub Universe 2026&lt;/a&gt; retrace le chemin parcouru depuis Universe 2025 : Agent Host, harnais Copilot, fenêtre Agents, Fusion, Compare Models, canvases et génération d’images.&lt;/p&gt;

</description>
      <category>devaugmenté</category>
      <category>événements</category>
    </item>
    <item>
      <title>Filtrer les tools MCP de vos agents avec AgentGateway.dev</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 27 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/techtown-fr/filtrer-les-tools-mcp-de-vos-agents-avec-agentgatewaydev-goh</link>
      <guid>https://dev.to/techtown-fr/filtrer-les-tools-mcp-de-vos-agents-avec-agentgatewaydev-goh</guid>
      <description>&lt;p&gt;Dans un &lt;a href="https://techtown.fr/blog/gouvernance-mcp-entreprise-filtrer-tools-agents-de-code/" rel="noopener noreferrer"&gt;précédent article&lt;/a&gt;, nous avons comparé trois façons de choisir les tools MCP auxquels nos agents de code ont accès. La gateway MCP était la solution la plus fiable : la règle est appliquée côté serveur, au même endroit pour tous les agents.&lt;/p&gt;

&lt;p&gt;Restait à voir comment la mettre en place. Bonne nouvelle : avec agentgateway, un fichier YAML et une image Docker suffisent.&lt;/p&gt;

&lt;p&gt;Dans cet article, nous allons présenter le projet et son histoire, puis monter pas à pas une gateway qui donne accès à Context7 et filtre les tools d’un serveur MCP Atlassian.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Le use case
&lt;/h2&gt;

&lt;p&gt;Nos agents de code (GitHub Copilot, Claude Code, Cursor) utilisent deux serveurs MCP (Model Context Protocol) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context7&lt;/strong&gt; , un serveur distant qui fournit de la documentation à jour sur les librairies. Il expose deux tools, sans authentification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atlassian&lt;/strong&gt; , via le serveur open source &lt;a href="https://github.com/sooperset/mcp-atlassian" rel="noopener noreferrer"&gt;mcp-atlassian&lt;/a&gt;, pour Jira et Confluence. Il expose 97 tools, dont une quarantaine qui modifient des données : &lt;code&gt;jira_create_issue&lt;/code&gt;, &lt;code&gt;confluence_delete_page&lt;/code&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous voulons trois choses :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;accéder à Context7 en passant par la gateway ;&lt;/li&gt;
&lt;li&gt;ne garder qu’une liste de tools Atlassian en lecture seule ;&lt;/li&gt;
&lt;li&gt;que chaque utilisateur garde son propre token Atlassian, sans aucun secret côté serveur.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffhihkdlkiaa63d66we3r.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffhihkdlkiaa63d66we3r.webp" alt="Schéma : les agents de code passent par agentgateway, qui filtre les tools de Context7 et d’Atlassian" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tous les agents passent par la même gateway, qui applique la liste blanche des tools et journalise chaque appel.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📌 agentgateway en bref
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://agentgateway.dev" rel="noopener noreferrer"&gt;agentgateway&lt;/a&gt; est un proxy open source pensé pour le trafic des agents IA. Il couvre trois familles de trafic avec un seul binaire :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM Gateway&lt;/strong&gt;  : router les appels vers OpenAI, Claude, Gemini ou un modèle auto-hébergé, avec la gestion des clés d’API et le basculement automatique (failover) d’un modèle à l’autre ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP et A2A&lt;/strong&gt;  : un support natif du Model Context Protocol et du protocole A2A (Agent-to-Agent), le trafic entre agents ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APIs et services&lt;/strong&gt;  : le trafic HTTP, gRPC et TCP classique, observable par défaut, avec mTLS et OIDC intégrés.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notre use case n’utilise que le pilier MCP, mais la même gateway peut ensuite porter le trafic LLM et API de l’équipe, avec la même configuration et les mêmes logs.&lt;/p&gt;

&lt;p&gt;GitHub : &lt;a href="https://github.com/agentgateway/agentgateway" rel="noopener noreferrer"&gt;agentgateway/agentgateway&lt;/a&gt;, plus de 5 600 stars en octobre 2026&lt;/p&gt;

&lt;p&gt;Documentation : &lt;a href="https://agentgateway.dev/docs/standalone/latest/" rel="noopener noreferrer"&gt;agentgateway.dev/docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour notre use case, quatre fonctionnalités nous intéressent :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;la fédération MCP&lt;/strong&gt;  : une route par serveur MCP, distant ou local ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;le filtrage des tools&lt;/strong&gt; avec des règles CEL (Common Expression Language), une syntaxe d’expressions simple ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;la manipulation des en-têtes&lt;/strong&gt; , pour décider quel token part vers quel serveur ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;l’observabilité&lt;/strong&gt;  : logs JSON, métriques Prometheus, traces OpenTelemetry et une UI web.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le projet est écrit en Rust et publié sous licence Apache 2.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  ℹ️ Un peu d’histoire
&lt;/h2&gt;

&lt;p&gt;Le projet est jeune, mais il a déjà fait du chemin :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mars 2025&lt;/strong&gt;  : Solo.io crée agentgateway, un proxy pensé dès le départ pour MCP et A2A ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;25 août 2025&lt;/strong&gt;  : la &lt;a href="https://linuxfoundation.org/press/linux-foundation-welcomes-agentgateway-project-to-accelerate-ai-agent-adoption-while-maintaining-security-observability-and-governance" rel="noopener noreferrer"&gt;Linux Foundation accueille le projet&lt;/a&gt;, annoncé à l’Open Source Summit Europe à Amsterdam ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fin 2025&lt;/strong&gt;  : la Linux Foundation lance l’Agentic AI Foundation (&lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;AAIF&lt;/a&gt;) avec MCP d’Anthropic, goose de Block et le standard AGENTS.md ;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 juin 2026&lt;/strong&gt;  : &lt;a href="https://www.solo.io/blog/agentgateway-joins-aaif-as-an-open-gateway-for-agentic-ai-infrastructure" rel="noopener noreferrer"&gt;agentgateway rejoint l’AAIF&lt;/a&gt; et devient son quatrième projet hébergé.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Selon Solo.io, le projet comptait en juin 2026 plus de 300 contributeurs actifs venant de plus de 60 organisations. C’est un point important pour une brique d’infrastructure : la gouvernance est neutre et le projet ne dépend pas d’un seul éditeur.&lt;/p&gt;

&lt;p&gt;À noter : nos tests ont été faits avec la version 1.5.0 (août 2026). La version 1.6.0 est sortie le 2 octobre 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ 1. Lancer la gateway avec Docker Compose
&lt;/h2&gt;

&lt;p&gt;Commençons par le fichier &lt;code&gt;docker-compose.yaml&lt;/code&gt;. Il démarre deux conteneurs : le serveur mcp-atlassian et la gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mcp-atlassian&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/sooperset/mcp-atlassian:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt; &lt;span class="c1"&gt;# gateway : routes MCP&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4001:4001"&lt;/span&gt; &lt;span class="c1"&gt;# gateway : UI&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TRANSPORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;streamable-http&lt;/span&gt;
      &lt;span class="na"&gt;HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
      &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000"&lt;/span&gt;
      &lt;span class="na"&gt;STATELESS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;JIRA_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://votre-site.atlassian.net/&lt;/span&gt;
      &lt;span class="na"&gt;CONFLUENCE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://votre-site.atlassian.net/wiki&lt;/span&gt;
      &lt;span class="na"&gt;ATLASSIAN_EXTERNAL_AUTH_ENABLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;READ_ONLY_MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;

  &lt;span class="na"&gt;agentgateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cr.agentgateway.dev/agentgateway:v1.5.0&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/config/config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;network_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service:mcp-atlassian"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./config:/config:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quelques points méritent une explication :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;network_mode: "service:mcp-atlassian"&lt;/code&gt; place les deux conteneurs dans le même réseau. La gateway joint mcp-atlassian sur &lt;code&gt;localhost:8000&lt;/code&gt;, exactement comme un sidecar sur Cloud Run. La même config sert donc en local et dans le Cloud.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HOST: 127.0.0.1&lt;/code&gt; : mcp-atlassian n’écoute que sur localhost. Seule la gateway peut l’appeler.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ATLASSIAN_EXTERNAL_AUTH_ENABLE: "true"&lt;/code&gt; indique à mcp-atlassian que chaque requête apporte son propre token. Sans ce réglage, le serveur ne trouve aucun identifiant global et n’expose aucun tool.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;READ_ONLY_MODE: "true"&lt;/code&gt; ajoute une seconde barrière, en plus du filtrage de la gateway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il n’y a aucun secret dans ce fichier.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ 2. Exposer Context7 derrière la gateway
&lt;/h2&gt;

&lt;p&gt;Passons au fichier &lt;code&gt;config/config.yaml&lt;/code&gt;. Il déclare d’abord le port de la gateway et celui de l’UI :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;gateways&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4001&lt;/span&gt;

&lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gateways&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ajoutons ensuite une première route, pour Context7 :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;context7&lt;/span&gt;
  &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pathPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/context7/mcp&lt;/span&gt;
  &lt;span class="na"&gt;policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mcpAuthorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.tool.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;==&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"resolve-library-id"'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.tool.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;==&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"query-docs"'&lt;/span&gt;
  &lt;span class="na"&gt;backends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;context7&lt;/span&gt;
        &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://mcp.context7.com/mcp&lt;/span&gt;
        &lt;span class="na"&gt;policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;authorization&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La route reçoit les requêtes MCP sur &lt;code&gt;/context7/mcp&lt;/code&gt; et les relaie vers le serveur distant de Context7. Le bloc &lt;code&gt;requestHeaderModifier&lt;/code&gt; retire l’en-tête &lt;code&gt;Authorization&lt;/code&gt; : le token Atlassian d’un utilisateur ne doit jamais partir vers un service externe.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ 3. Filtrer les tools Atlassian
&lt;/h2&gt;

&lt;p&gt;La seconde route suit le même modèle, avec une liste blanche plus longue :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlassian&lt;/span&gt;
  &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pathPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/atlassian/mcp&lt;/span&gt;
  &lt;span class="na"&gt;policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mcpAuthorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.tool.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;["jira_get_issue",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_search",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_project_issues",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_transitions",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_all_projects",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_agile_boards",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_board_issues",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_sprints_from_board",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_sprint_issues",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_user_profile"]'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.tool.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;["confluence_search",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"confluence_get_page",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"confluence_get_page_children",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"confluence_get_space_page_tree",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"confluence_get_comments",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"confluence_get_labels"]'&lt;/span&gt;
  &lt;span class="na"&gt;backends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlassian&lt;/span&gt;
        &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8000/mcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ici, pas de &lt;code&gt;requestHeaderModifier&lt;/code&gt; : l’en-tête &lt;code&gt;Authorization&lt;/code&gt; de l’utilisateur doit arriver intact jusqu’à mcp-atlassian.&lt;/p&gt;

&lt;p&gt;Lançons la stack :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voici ce que nous avons constaté :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tools/list&lt;/code&gt; ne renvoie que les 16 tools de la liste blanche, sur les 97 du serveur ;&lt;/li&gt;
&lt;li&gt;un appel direct à &lt;code&gt;jira_create_issue&lt;/code&gt; est rejeté par la gateway, même forcé à la main avec &lt;code&gt;curl&lt;/code&gt; ;&lt;/li&gt;
&lt;li&gt;une requête sans token reçoit une erreur 401 de mcp-atlassian ;&lt;/li&gt;
&lt;li&gt;avec un token valide, &lt;code&gt;jira_search&lt;/code&gt; et &lt;code&gt;confluence_search&lt;/code&gt; renvoient bien les données de l’utilisateur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La gateway filtre les tools sans rien savoir d’Atlassian. Elle lit simplement les messages MCP qui passent.&lt;/p&gt;

&lt;p&gt;💪 ProTip : gardez une route par serveur MCP. agentgateway sait aussi regrouper plusieurs serveurs derrière une seule URL, mais les noms des tools reçoivent alors un préfixe (&lt;code&gt;atlassian_jira_search&lt;/code&gt;) et il devient plus difficile de contrôler quel token part vers quel serveur.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 4. Brancher un agent de code
&lt;/h2&gt;

&lt;p&gt;Côté poste, il n’y a plus rien à installer. Chaque utilisateur prépare son en-tête à partir de son email et de son token API Atlassian :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s:%s'&lt;/span&gt; &lt;span class="s1"&gt;'prenom.nom@exemple.fr'&lt;/span&gt; &lt;span class="s1"&gt;'VOTRE_TOKEN_API'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avec Claude Code, une seule commande suffit :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude mcp add --transport http atlassian-gw http://localhost:8080/atlassian/mcp --header "Authorization: Basic &amp;lt;BASE64&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avec Cursor, dans &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"atlassian-gw"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/atlassian/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Basic &amp;lt;BASE64&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context7-gw"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/context7/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L’agent voit alors les 16 tools autorisés. Si nous lui demandons de créer un ticket Jira, il répond qu’il n’a pas de tool pour ça.&lt;/p&gt;

&lt;p&gt;⚡️ ProTip : utilisez un token API classique d’Atlassian. Les tokens « avec scopes » ne fonctionnent pas avec l’URL du site (&lt;code&gt;votre-site.atlassian.net&lt;/code&gt;) : ils passent obligatoirement par &lt;code&gt;https://api.atlassian.com/ex/{jira|confluence}/&amp;lt;cloudId&amp;gt;&lt;/code&gt;. Nous avons perdu un peu de temps sur ce 401 avant de comprendre.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;En quelques dizaines de lignes de YAML, agentgateway nous donne une gateway MCP qui :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expose Context7 et Atlassian derrière une seule URL ;&lt;/li&gt;
&lt;li&gt;filtre les tools côté serveur, pour tous les agents à la fois ;&lt;/li&gt;
&lt;li&gt;relaie le token de chaque utilisateur, sans secret à stocker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le projet est simple à démarrer et sa gouvernance est neutre depuis son arrivée à l’AAIF. Pour une équipe qui veut encadrer les serveurs MCP de ses agents de code, c’est une bonne première brique, en local avec Docker Compose puis sur Cloud Run.&lt;/p&gt;

&lt;p&gt;Le code complet de ce test tient en deux fichiers : &lt;code&gt;docker-compose.yaml&lt;/code&gt; et &lt;code&gt;config/config.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Bonus : aller plus loin avec l’observabilité
&lt;/h2&gt;

&lt;p&gt;Une gateway voit passer tous les appels de tools. C’est l’endroit idéal pour mesurer l’usage réel des serveurs MCP.&lt;/p&gt;

&lt;p&gt;Ajoutons une politique de logs d’accès en tête de &lt;code&gt;config/config.yaml&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json&lt;/span&gt;

&lt;span class="na"&gt;frontendPolicies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessLog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.methodName&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;==&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"tools/call"'&lt;/span&gt;
    &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mcp_target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.tool.target'&lt;/span&gt;
      &lt;span class="na"&gt;mcp_tool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.tool.name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chaque appel de tool produit maintenant une ligne JSON avec le serveur et le tool utilisés :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"default/context7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"http.status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"mcp.method.name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"mcp_target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"context7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"mcp_tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"resolve-library-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2274ms"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un peu de &lt;code&gt;jq&lt;/code&gt; suffit pour compter les appels par serveur et par tool. Voici le résultat après quelques tests :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP TOOL APPELS ERREURS
atlassian confluence_search 3 0
atlassian jira_create_issue 3 3
atlassian jira_get_issue 3 0
context7 resolve-library-id 3 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les trois erreurs sur &lt;code&gt;jira_create_issue&lt;/code&gt; sont les appels refusés par la gateway.&lt;/p&gt;

&lt;p&gt;En local, &lt;code&gt;jq&lt;/code&gt; suffit. En production, trois options s’offrent à nous selon les outils de l’équipe :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Les logs, sans rien installer.&lt;/strong&gt; Sur Cloud Run, tout ce que la gateway écrit arrive automatiquement dans Cloud Logging. Le même tableau peut alors se construire avec une requête SQL dans Log Analytics, sur les champs &lt;code&gt;mcp_target&lt;/code&gt; et &lt;code&gt;mcp_tool&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un compteur Prometheus.&lt;/strong&gt; agentgateway publie le compteur &lt;code&gt;agentgateway_mcp_requests_total&lt;/code&gt; (nombre de requêtes MCP par serveur et par tool) sur le port 15020. Prometheus le collecte et Grafana en fait un tableau de bord.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Des traces OpenTelemetry.&lt;/strong&gt; Elles suivent chaque appel de bout en bout, de l’agent jusqu’au serveur MCP, avec le temps passé à chaque étape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;À noter : la colonne ERREURS ne compte que les appels bloqués par la gateway, comme &lt;code&gt;jira_create_issue&lt;/code&gt; ici. En version 1.5.0, si l’appel passe la gateway mais échoue chez Atlassian (un token invalide, par exemple), il est compté comme réussi.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Envie de maîtriser MCP ?
&lt;/h2&gt;

&lt;p&gt;Pour comprendre MCP de l’intérieur, notre &lt;a href="https://techtown.fr/formations/mcp/" rel="noopener noreferrer"&gt;formation Model Context Protocol (MCP)&lt;/a&gt; couvre en une journée l’architecture client-serveur, les tools, resources et prompts, la création de serveurs MCP en Python et en Java, puis l’intégration avec des agents IA et le déploiement dans le Cloud. La journée alterne théorie et ateliers pratiques.&lt;/p&gt;

&lt;p&gt;👉 Et vous, quels serveurs MCP mettriez-vous en premier derrière une gateway ?&lt;/p&gt;

</description>
      <category>devaugmenté</category>
      <category>agentsia</category>
    </item>
    <item>
      <title>Certificats TLS : 200 jours en 2026, 47 jours en 2029, êtes-vous prêts ?</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Thu, 22 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/techtown-fr/certificats-tls-200-jours-en-2026-47-jours-en-2029-etes-vous-prets--5289</link>
      <guid>https://dev.to/techtown-fr/certificats-tls-200-jours-en-2026-47-jours-en-2029-etes-vous-prets--5289</guid>
      <description>&lt;p&gt;La durée de vie des certificats TLS baisse depuis des années, et le mouvement s’accélère. Depuis le 15 mars 2026, un certificat public ne peut plus dépasser 200 jours. En 2029, ce sera 47 jours.&lt;/p&gt;

&lt;p&gt;J’ai récemment rencontré l’effet de bord le moins attendu de ces règles : un outil de développement qui change de bibliothèque TLS et une registry MCP interne qui devient brusquement « injoignable » sur macOS. Rien n’avait changé côté serveur.&lt;/p&gt;

&lt;p&gt;Dans cet article, je vous propose de voir le calendrier officiel et ce qu’il implique pour la validation des domaines (et donc pour votre DNS). Je vous expliquerai ensuite pourquoi Copilot CLI a cassé une registry MCP interne, comment vérifier vos certificats, et ce qui pourrait encore bouger côté Windows et Linux.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sfcddp2mhwskymmxa0c.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sfcddp2mhwskymmxa0c.webp" alt="Durée maximale des certificats TLS publics par date d’émission : 39 mois en 2015, 825 jours en 2018, 398 jours en 2020, 200 jours en 2026, 100 jours en 2027 et 47 jours en 2029, avec la limite de 825 jours imposée par macOS" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📅 1. Le calendrier du CA/Browser Forum
&lt;/h2&gt;

&lt;p&gt;📌 Le CA/Browser Forum réunit les autorités de certification (CA) et les éditeurs de navigateurs et de systèmes. Il publie les &lt;a href="https://github.com/cabforum/servercert/blob/main/docs/BR.md" rel="noopener noreferrer"&gt;Baseline Requirements&lt;/a&gt;, les règles que doivent respecter tous les certificats TLS publics.&lt;/p&gt;

&lt;p&gt;En avril 2025, le &lt;a href="https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/" rel="noopener noreferrer"&gt;ballot SC-081v3&lt;/a&gt;, proposé par Apple, a été adopté sans aucun vote contre. Côté éditeurs, Apple, Google, Microsoft et Mozilla ont tous voté pour. Le ballot réduit la durée maximale des certificats en trois étapes (section 6.3.2 des Baseline Requirements) :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Certificat émis à partir du&lt;/th&gt;
&lt;th&gt;Émis avant le&lt;/th&gt;
&lt;th&gt;Durée maximale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;15 mars 2026&lt;/td&gt;
&lt;td&gt;398 jours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 mars 2026&lt;/td&gt;
&lt;td&gt;15 mars 2027&lt;/td&gt;
&lt;td&gt;200 jours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 mars 2027&lt;/td&gt;
&lt;td&gt;15 mars 2029&lt;/td&gt;
&lt;td&gt;100 jours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 mars 2029&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;47 jours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;À noter : le texte compte un jour comme 86 400 secondes, et toute fraction de seconde en plus compte comme un jour entier. Les Baseline Requirements recommandent donc de ne pas émettre pour la durée maximale (397, 199, 99 ou 46 jours au lieu de 398, 200, 100 ou 47).&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 L’enjeu DNS : la validation des domaines
&lt;/h3&gt;

&lt;p&gt;Le ballot réduit aussi la durée pendant laquelle une CA peut réutiliser une preuve de contrôle du domaine (DCV, Domain Control Validation) :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;À partir du&lt;/th&gt;
&lt;th&gt;Réutilisation maximale de la validation du domaine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avant le 15 mars 2026&lt;/td&gt;
&lt;td&gt;398 jours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 mars 2026&lt;/td&gt;
&lt;td&gt;200 jours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 mars 2027&lt;/td&gt;
&lt;td&gt;100 jours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 mars 2029&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10 jours&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Avec 10 jours de réutilisation et des certificats de 47 jours, la preuve de contrôle du domaine devra être refaite à presque chaque renouvellement. Si vous validez vos domaines avec un enregistrement DNS (le challenge &lt;code&gt;DNS-01&lt;/code&gt; du protocole ACME, obligatoire pour les certificats wildcard), votre DNS doit pouvoir être modifié automatiquement, par API. Un enregistrement &lt;code&gt;TXT&lt;/code&gt; ajouté à la main une fois par an ne tiendra plus.&lt;/p&gt;

&lt;p&gt;Let’s Encrypt anticipe : ses certificats durent 90 jours par défaut, des certificats de 6 jours sont déjà disponibles en option, et la durée maximale passera à 45 jours en février 2028 (&lt;a href="https://letsencrypt.org/docs/cert-lifetimes/" rel="noopener noreferrer"&gt;plan officiel&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  🍏 2. Apple n’attend pas : la règle des 825 jours
&lt;/h2&gt;

&lt;p&gt;Ces règles visent les certificats publics. Une PKI interne n’est pas soumise au CA/Browser Forum, mais macOS lui impose quand même ses propres limites.&lt;/p&gt;

&lt;p&gt;Depuis macOS 10.15 et iOS 13, le système lui-même refuse les certificats serveur TLS qui ne respectent pas &lt;a href="https://support.apple.com/fr-fr/103769" rel="noopener noreferrer"&gt;les exigences d’Apple&lt;/a&gt; : clés RSA d’au moins 2048 bits, signature SHA-2, nom DNS dans le SAN (Subject Alternative Name), &lt;code&gt;serverAuth&lt;/code&gt; dans l’EKU (Extended Key Usage) et &lt;strong&gt;une validité de 825 jours maximum&lt;/strong&gt; pour les certificats émis après le 1er juillet 2019. Pour un certificat rattaché à une racine préinstallée par Apple, la limite est de &lt;a href="https://support.apple.com/fr-fr/102028" rel="noopener noreferrer"&gt;398 jours&lt;/a&gt; depuis le 1er septembre 2020.&lt;/p&gt;

&lt;p&gt;La règle des 825 jours s’applique aussi aux certificats émis par une PKI d’entreprise. Apple ne l’écrit pas noir sur blanc, mais c’est ce que j’ai constaté sur macOS 26. Un certificat interne valable 3 ans fonctionne dans Chrome, avec &lt;code&gt;curl&lt;/code&gt; ou avec Node, et échoue dans Safari et dans toutes les apps qui utilisent le vérificateur du Mac.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 3. Quand Copilot CLI change de bibliothèque TLS
&lt;/h2&gt;

&lt;p&gt;C’est exactement ce que j’ai vécu avec &lt;a href="https://github.com/github/copilot-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Le contexte : une organisation qui impose une registry MCP (Model Context Protocol) interne, avec la politique « Registry only ». Copilot CLI interroge cette registry pour vérifier que chaque serveur MCP configuré est autorisé.&lt;/p&gt;

&lt;p&gt;Avec la version 1.0.78, tous les serveurs MCP personnalisés ont été bloqués d’un coup sur macOS :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;! 3 MCP servers were blocked by policy: '&amp;lt;server-a&amp;gt;', '&amp;lt;server-b&amp;gt;', '&amp;lt;server-c&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les logs de debug montrent la vraie cause :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ERROR] failed to verify TLS certificate: invalid peer certificate: Other(OtherError("“*.&amp;lt;domain&amp;gt;” certificate is not standards compliant: -67901"))
        log.target=rustls_platform_verifier::verification::apple
[ERROR] MCP server "&amp;lt;server-id&amp;gt;" filtered: Registry https://&amp;lt;registry&amp;gt;/ was unreachable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le code &lt;code&gt;-67901&lt;/code&gt; correspond à &lt;code&gt;errSecCertificateValidityPeriodTooLong&lt;/code&gt; dans &lt;code&gt;Security.framework&lt;/code&gt;. Le message parle de certificat « not standards compliant », mais le problème est uniquement sa durée : 1095 jours, émis en janvier 2026 pour 3 ans.&lt;/p&gt;

&lt;p&gt;Ce qui a changé, c’est le client. Avant la 1.0.78, la vérification de la registry passait par Node, qui vérifie les certificats avec OpenSSL et n’impose aucune durée maximale. Depuis, elle passe par le runtime Rust de Copilot CLI (&lt;code&gt;reqwest&lt;/code&gt; + &lt;code&gt;rustls-platform-verifier&lt;/code&gt;), qui délègue la vérification au système. Sur macOS, c’est donc Apple qui décide, avec sa règle des 825 jours. Comme la registry semble injoignable, Copilot CLI bloque par précaution tous les serveurs MCP qu’elle n’a pas pu vérifier.&lt;/p&gt;

&lt;p&gt;Le diagnostic est piégeux, parce que tous les tests habituels passent :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;Vérificateur&lt;/th&gt;
&lt;th&gt;Résultat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copilot CLI &amp;lt; 1.0.78&lt;/td&gt;
&lt;td&gt;OpenSSL (Node)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot CLI ≥ 1.0.78 sur macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Security.framework&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌ &lt;code&gt;-67901&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safari&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Security.framework&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chrome, &lt;code&gt;curl&lt;/code&gt;, Node&lt;/td&gt;
&lt;td&gt;Leur propre vérificateur ou OpenSSL&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Et mes collègues sous Windows ? Ils ne sont pas concernés : sur Windows, &lt;code&gt;rustls-platform-verifier&lt;/code&gt; passe par CryptoAPI, qui n’impose pas de durée maximale.&lt;/p&gt;

&lt;p&gt;J’ai documenté le problème dans &lt;a href="https://github.com/github/copilot-cli/issues/4364" rel="noopener noreferrer"&gt;l’issue #4364&lt;/a&gt;. Il n’est pas isolé : &lt;a href="https://github.com/github/copilot-cli/issues/5085" rel="noopener noreferrer"&gt;l’issue #5085&lt;/a&gt; montre qu’à partir de la 1.0.93, davantage de requêtes passent par le runtime natif, avec une autre régression TLS sur macOS quand Copilot CLI tourne dans une sandbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 4. Vérifiez vos certificats, surtout internes
&lt;/h2&gt;

&lt;p&gt;Pour savoir si un certificat passera sur macOS, &lt;code&gt;curl&lt;/code&gt; ne suffit pas : il faut interroger &lt;code&gt;Security.framework&lt;/code&gt;. J’ai donc créé un petit outil open source : &lt;a href="https://github.com/jlandure/macos-tls-cert-check" rel="noopener noreferrer"&gt;jlandure/macos-tls-cert-check&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Il contient deux scripts qui affichent un verdict PASS ou FAIL, sans rien installer :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;check-tls.sh&lt;/code&gt; sur macOS, qui vérifie le certificat avec le vérificateur d’Apple ;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check-tls.ps1&lt;/code&gt; sur Windows, qui vérifie avec le vérificateur de Windows et prédit le résultat macOS à partir des règles d’Apple.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sur macOS, une seule commande suffit :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/jlandure/macos-tls-cert-check.git
&lt;span class="nb"&gt;cd &lt;/span&gt;macos-tls-cert-check
./scripts/check-tls.sh github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le script affiche aussi où se situe le certificat par rapport aux limites de durée :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Target: github.com:443
Certificate: CN=github.com
Issuer: C=GB, O=Sectigo Limited, CN=Sectigo Public Server Authentication CA DV E36
Validity: 2026-09-01 -&amp;gt; 2026-11-29 (89 days, 38 days left)

Lifetime limits (informative):
  Apple, private root CA 825 days OK
  Apple, Apple-trusted root CA 398 days OK
  CA/B Forum, public CA at issue date 200 days OK
  CA/B Forum, public CA from 2029-03-15 47 days OVER

macOS verifier (Security.framework): PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pour notre registry interne, le verdict est clair :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Validity: 2026-01-22 -&amp;gt; 2029-01-21 (1095 days, 822 days left)

macOS verifier (Security.framework): FAIL
  FAILED (-67901): “*.&amp;lt;domain&amp;gt;” certificate is not standards compliant
  -67901 errSecCertificateValidityPeriodTooLong: lifetime above 825 days (398 for an Apple-trusted root).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depuis un poste Windows, la commande équivalente est :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Bypass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\scripts\check-tls.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-HostName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;internal.example.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les scripts renvoient &lt;code&gt;0&lt;/code&gt; pour PASS, &lt;code&gt;1&lt;/code&gt; pour FAIL et &lt;code&gt;2&lt;/code&gt; si la connexion échoue. On peut donc les brancher en CI ou dans une sonde de supervision.&lt;/p&gt;

&lt;p&gt;💪 ProTip : &lt;a href="https://badssl.com/" rel="noopener noreferrer"&gt;badssl.com&lt;/a&gt; héberge des certificats volontairement défectueux (expiré, mauvais hôte, racine inconnue…). C’est pratique pour voir comment un outil réagit avant de le pointer vers votre infrastructure.&lt;/p&gt;

&lt;p&gt;Côté PKI interne, quelques réflexes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faire l’inventaire des certificats serveur internes et de leur durée ;&lt;/li&gt;
&lt;li&gt;émettre pour 825 jours maximum si des Mac doivent s’y connecter, et viser plutôt 398 jours ou moins ;&lt;/li&gt;
&lt;li&gt;automatiser le renouvellement (ACME interne, cert-manager sur Kubernetes…) avant que les durées ne baissent encore ;&lt;/li&gt;
&lt;li&gt;tester avec le vérificateur de chaque système, pas seulement avec &lt;code&gt;curl&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🪟 5. Et Windows, Linux, les autres bibliothèques ?
&lt;/h2&gt;

&lt;p&gt;En octobre 2026, Apple est la seule plateforme qui impose une durée maximale dans le système lui-même. Les autres s’appuient sur leurs programmes de certificats racines, qui obligent les CA publiques à suivre les Baseline Requirements :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/fr-fr/security/trusted-root/program-requirements" rel="noopener noreferrer"&gt;Microsoft Trusted Root Program&lt;/a&gt; pour Windows : je n’ai trouvé aucune documentation d’un contrôle de durée côté client ;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mozilla.org/en-US/about/governance/policies/security-group/certs/policy/" rel="noopener noreferrer"&gt;Mozilla Root Store Policy&lt;/a&gt; pour la plupart des distributions Linux, dont le magasin de certificats commun repose sur la liste de Mozilla (&lt;a href="https://docs.redhat.com/fr/documentation/red_hat_enterprise_linux/10/html/securing_networks/using-shared-system-certificates" rel="noopener noreferrer"&gt;doc Red Hat&lt;/a&gt;) ;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/certificate_lifetimes.md" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;, qui refuse lui-même les certificats publics de plus de 398 jours depuis Chrome 85, mais exempte les autorités configurées localement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Personne n’a annoncé de date pour un contrôle des 200, 100 ou 47 jours côté client. Le précédent existe pourtant : en 2020, Apple dans ses systèmes et Chrome dans son navigateur ont appliqué eux-mêmes la limite de 398 jours.&lt;/p&gt;

&lt;p&gt;L’histoire de Copilot CLI montre un second risque, plus discret. Vos certificats peuvent devenir invalides sans qu’aucune règle ne change, simplement parce qu’un outil change de bibliothèque TLS. De plus en plus d’outils passent d’OpenSSL au vérificateur du système, par exemple avec &lt;code&gt;rustls-platform-verifier&lt;/code&gt; en Rust. Le jour où vos outils, ou Windows et Linux, se mettront à appliquer ces limites, mieux vaut que vos certificats internes soient déjà en règle et renouvelés automatiquement.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;Les durées des certificats publics sont fixées jusqu’en 2029 : 200 jours depuis mars 2026, 100 jours en mars 2027, 47 jours en mars 2029, avec une validation de domaine à refaire tous les 10 jours. Sans renouvellement automatique, ça ne tiendra pas, et un DNS pilotable par API devient indispensable pour les certificats wildcard.&lt;/p&gt;

&lt;p&gt;Pour les certificats internes, la limite immédiate est celle d’Apple : 825 jours. Si vous ne devez faire qu’une chose cette semaine, lancez &lt;a href="https://github.com/jlandure/macos-tls-cert-check" rel="noopener noreferrer"&gt;&lt;code&gt;check-tls.sh&lt;/code&gt;&lt;/a&gt; sur vos services internes depuis un Mac.&lt;/p&gt;

&lt;p&gt;👉 Et vous, combien de temps durent les certificats de votre PKI interne ?&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Pour aller plus loin
&lt;/h2&gt;

&lt;p&gt;CA/Browser Forum :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/cabforum/servercert/blob/main/docs/BR.md" rel="noopener noreferrer"&gt;Baseline Requirements, section 6.3.2&lt;/a&gt; : durée maximale des certificats publics&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/" rel="noopener noreferrer"&gt;Ballot SC-081v3&lt;/a&gt; : le calendrier 2026-2029 et le vote&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apple :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://support.apple.com/fr-fr/103769" rel="noopener noreferrer"&gt;Exigences pour les certificats approuvés dans iOS 13 et macOS 10.15&lt;/a&gt; : la règle des 825 jours&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.apple.com/fr-fr/102028" rel="noopener noreferrer"&gt;Limites à venir des certificats approuvés&lt;/a&gt; : la règle des 398 jours&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/apple/apple-root-program/blob/main/policy.md" rel="noopener noreferrer"&gt;Apple Root Program Policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft, Mozilla et Linux :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/fr-fr/security/trusted-root/program-requirements" rel="noopener noreferrer"&gt;Exigences du Microsoft Trusted Root Program&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mozilla.org/en-US/about/governance/policies/security-group/certs/policy/" rel="noopener noreferrer"&gt;Mozilla Root Store Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.mozilla.org/security/2020/07/09/reducing-tls-certificate-lifespans-to-398-days/" rel="noopener noreferrer"&gt;Mozilla : passage des certificats TLS à 398 jours&lt;/a&gt; (2020)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.redhat.com/fr/documentation/red_hat_enterprise_linux/10/html/securing_networks/using-shared-system-certificates" rel="noopener noreferrer"&gt;Red Hat : certificats système partagés&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chrome et Let’s Encrypt :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/certificate_lifetimes.md" rel="noopener noreferrer"&gt;Chromium : durée des certificats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/cert-lifetimes/" rel="noopener noreferrer"&gt;Let’s Encrypt : durée des certificats, raisons et calendrier&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le cas Copilot CLI :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/copilot-cli/issues/4364" rel="noopener noreferrer"&gt;Issue #4364&lt;/a&gt; : registry MCP rejetée sur macOS (&lt;code&gt;-67901&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/copilot-cli/issues/5085" rel="noopener noreferrer"&gt;Issue #5085&lt;/a&gt; : régression TLS dans les sandbox macOS depuis la 1.0.93&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jlandure/macos-tls-cert-check/blob/main/COPILOT.md" rel="noopener noreferrer"&gt;Le diagnostic complet&lt;/a&gt; dans le dépôt de l’outil&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎓 Aller plus loin avec GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Vous utilisez déjà GitHub Copilot au quotidien et voulez passer aux usages avancés ? En une journée, la &lt;strong&gt;&lt;a href="https://techtown.fr/formations/coder-avec-github-copilot-avance/" rel="noopener noreferrer"&gt;formation Coder avec GitHub Copilot - Avancé de TechTown&lt;/a&gt;&lt;/strong&gt; couvre :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;les workflows complexes et l’automatisation avec GitHub Copilot ;&lt;/li&gt;
&lt;li&gt;l’intégration dans les pipelines CI/CD ;&lt;/li&gt;
&lt;li&gt;l’optimisation du code legacy, des performances et de la sécurité.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devaugmenté</category>
    </item>
    <item>
      <title>Google Antigravity &amp; Gemini 4 Argon: How to Load Your Global ~/.agents Skills</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/gde/google-antigravity-gemini-4-argon-how-to-load-your-global-agents-skills-2cek</link>
      <guid>https://dev.to/gde/google-antigravity-gemini-4-argon-how-to-load-your-global-agents-skills-2cek</guid>
      <description>&lt;p&gt;Reuse your global Agent Skills in Google Antigravity with Gemini 4 Argon using a single symbolic link, and clean up your legacy Gemini configuration.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills specification&lt;/a&gt; has quickly become the standard way to package reusable instructions, scripts, and references for coding agents. Across tools listed on the &lt;a href="https://agentskills.io/clients" rel="noopener noreferrer"&gt;Agent Skills clients page&lt;/a&gt; such as Claude Code, Cursor, Gemini CLI, and OpenAI Codex, the community has converged on a shared directory convention: &lt;code&gt;.agents/skills/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you test Gemini 4 Argon inside Google Antigravity (AGY), project-level skills inside &lt;code&gt;&amp;lt;workspace&amp;gt;/.agents/skills/&lt;/code&gt; work out of the box. Global skills stored in &lt;code&gt;~/.agents/skills/&lt;/code&gt;, however, do not appear at startup.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how the &lt;code&gt;.agents/skills/&lt;/code&gt; convention works, why our first configuration attempt with &lt;code&gt;skills.json&lt;/code&gt; failed in Antigravity, how to fix global skill discovery with a symbolic link, and how to clean up your &lt;code&gt;~/.gemini&lt;/code&gt; directory when migrating from Gemini CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 1. How the Agent Skills standard organizes your skills
&lt;/h2&gt;

&lt;p&gt;An &lt;a href="https://agentskills.io/" rel="noopener noreferrer"&gt;Agent Skill&lt;/a&gt; is a directory containing a &lt;code&gt;SKILL.md&lt;/code&gt; file (with &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; in YAML frontmatter, followed by Markdown instructions) and optional subdirectories like &lt;code&gt;scripts/&lt;/code&gt;, &lt;code&gt;references/&lt;/code&gt;, and &lt;code&gt;assets/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.agents/skills/
├── ai-seo/
│ ├── SKILL.md
│ └── references/
├── blog-post-writer/
│ ├── SKILL.md
│ └── references/
└── humanizer/
    └── SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of loading every instruction file into the prompt at once, compatible agents use progressive disclosure in three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At session start, the agent reads only the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; of each skill (around 50 to 100 tokens per skill).&lt;/li&gt;
&lt;li&gt;When your prompt matches a skill’s description, or when you invoke it explicitly with &lt;code&gt;/skill-name&lt;/code&gt;, the agent loads the full &lt;code&gt;SKILL.md&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;The agent reads supporting files in &lt;code&gt;references/&lt;/code&gt; or runs helper scripts in &lt;code&gt;scripts/&lt;/code&gt; only when needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For cross-client interoperability, the &lt;a href="https://agentskills.io/client-implementation/adding-skills-support" rel="noopener noreferrer"&gt;Agent Skills implementation guide&lt;/a&gt; recommends scanning two &lt;code&gt;.agents/skills/&lt;/code&gt; locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;project&amp;gt;/.agents/skills/&lt;/code&gt; for project-specific skills shared through Git.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.agents/skills/&lt;/code&gt; for user-level skills shared across all projects on your machine (including skills installed with &lt;code&gt;npx skills&lt;/code&gt; and tracked in &lt;code&gt;~/.agents/.skill-lock.json&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;According to the &lt;a href="https://antigravity.google/docs/skills" rel="noopener noreferrer"&gt;Google Antigravity Skills documentation&lt;/a&gt;, Antigravity scans &lt;code&gt;&amp;lt;workspace-root&amp;gt;/.agents/skills/&lt;/code&gt; at the workspace level (with backward compatibility for &lt;code&gt;.agent/skills/&lt;/code&gt;), whereas its global scope is set to &lt;code&gt;~/.gemini/config/skills/&lt;/code&gt; (unlike Gemini CLI, which already supports &lt;code&gt;~/.agents/skills/&lt;/code&gt; as a global alias).&lt;/p&gt;

&lt;h2&gt;
  
  
  ❌ 2. Attempt #1: Why &lt;code&gt;~/.gemini/config/skills.json&lt;/code&gt; fails
&lt;/h2&gt;

&lt;p&gt;If you check Antigravity’s built-in customization documentation (&lt;code&gt;agy-customizations&lt;/code&gt;), you will find a JSON configuration mechanism called &lt;code&gt;skills.json&lt;/code&gt; designed to register skills stored outside default locations.&lt;/p&gt;

&lt;p&gt;Our first try was to create &lt;code&gt;~/.gemini/config/skills.json&lt;/code&gt; pointing to &lt;code&gt;~/.agents/skills&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/.agents/skills"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restarting Antigravity and opening a new conversation, none of the skills from &lt;code&gt;~/.agents/skills&lt;/code&gt; were loaded.&lt;/p&gt;

&lt;p&gt;The reason is a subtle rule in Antigravity’s customization loader:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Config files are also honored on explicitly configured customization paths […] when the configured path points directly at a config file (e.g. &lt;code&gt;path/to/skills.json&lt;/code&gt;). A configured directory keeps its regular one-level scan; a manifest inside it is not expanded.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inside a project repository, &lt;code&gt;&amp;lt;workspace&amp;gt;/.agents/skills.json&lt;/code&gt; is explicitly checked and expanded.&lt;/li&gt;
&lt;li&gt;Inside the global &lt;code&gt;~/.gemini/config/&lt;/code&gt; directory, Antigravity performs a direct one-level directory scan on &lt;code&gt;~/.gemini/config/skills/&lt;/code&gt; and &lt;code&gt;~/.gemini/config/plugins/&lt;/code&gt;. It ignores &lt;code&gt;~/.gemini/config/skills.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚡️ 3. Attempt #2: Link &lt;code&gt;~/.gemini/config/skills&lt;/code&gt; to &lt;code&gt;~/.agents/skills&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Since Antigravity expects global standalone skills inside &lt;code&gt;~/.gemini/config/skills/&amp;lt;skill-name&amp;gt;/SKILL.md&lt;/code&gt;, the cleanest fix is a symbolic link from &lt;code&gt;~/.gemini/config/skills&lt;/code&gt; to &lt;code&gt;~/.agents/skills&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before creating the symlink, check if you already have an existing &lt;code&gt;~/.gemini/config/skills.json&lt;/code&gt; file or a &lt;code&gt;~/.gemini/config/skills/&lt;/code&gt; folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you created &lt;code&gt;~/.gemini/config/skills.json&lt;/code&gt; during your own tests and it only points to &lt;code&gt;~/.agents/skills&lt;/code&gt;, you can remove it (&lt;code&gt;rm -f ~/.gemini/config/skills.json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If your &lt;code&gt;~/.gemini/config/skills.json&lt;/code&gt; listed other custom directories, move that &lt;code&gt;skills.json&lt;/code&gt; into your project’s &lt;code&gt;&amp;lt;workspace&amp;gt;/.agents/skills.json&lt;/code&gt; (where Antigravity does expand it), or symlink those individual skill folders inside &lt;code&gt;~/.agents/skills/&lt;/code&gt; so everything stays in one place.&lt;/li&gt;
&lt;li&gt;If you already have a non-empty &lt;code&gt;~/.gemini/config/skills/&lt;/code&gt; directory, move its skill subfolders into &lt;code&gt;~/.agents/skills/&lt;/code&gt; first, then remove the empty directory (&lt;code&gt;mv ~/.gemini/config/skills/* ~/.agents/skills/ &amp;amp;&amp;amp; rmdir ~/.gemini/config/skills&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then create the symbolic link in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Point Antigravity's global skills folder to ~/.agents/skills&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; ~/.agents/skills ~/.gemini/config/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that the symbolic link points to your &lt;code&gt;.agents/skills&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt; ~/.gemini/config/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lrwxr-xr-x 1 user staff 26 Oct 13 11:05 /Users/user/.gemini/config/skills -&amp;gt; /Users/user/.agents/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload Antigravity. Your &lt;code&gt;~/.agents/skills&lt;/code&gt; catalog is now injected at startup, and you can trigger any skill directly in chat with &lt;code&gt;/skill-name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl9euelajirzl44vb3577.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl9euelajirzl44vb3577.webp" alt="Google Antigravity chat prompt with Gemini 4 Argon High selected and / for actions" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💪 ProTip: Watch out for nested plugin symlinks inside &lt;code&gt;~/.agents/skills/&lt;/code&gt;. Antigravity scans &lt;code&gt;~/.gemini/config/skills/&lt;/code&gt; only one level deep (&lt;code&gt;skills/&amp;lt;skill-name&amp;gt;/SKILL.md&lt;/code&gt;). If &lt;code&gt;~/.agents/skills/my-plugin&lt;/code&gt; is a symlink to a plugin repository where the skill file is nested at &lt;code&gt;my-plugin/skills/my-skill/SKILL.md&lt;/code&gt;, Antigravity will skip it. Point your symlink directly to the folder that immediately contains &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧹 4. Bonus: Clean up &lt;code&gt;~/.gemini&lt;/code&gt; when coming from Gemini CLI
&lt;/h2&gt;

&lt;p&gt;Because Google Antigravity and Gemini CLI both store state under &lt;code&gt;~/.gemini&lt;/code&gt;, that directory often accumulates old folders over time.&lt;/p&gt;

&lt;p&gt;First, a warning: do not delete &lt;code&gt;~/.gemini&lt;/code&gt;! Antigravity stores its active data there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.gemini/antigravity/&lt;/code&gt; holds your conversation history (&lt;code&gt;conversations/&lt;/code&gt;, &lt;code&gt;conversation_summaries.db&lt;/code&gt;), generated artifacts (&lt;code&gt;brain/&lt;/code&gt;), and internal binaries.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.gemini/config/&lt;/code&gt; holds your active Antigravity configuration (&lt;code&gt;config.json&lt;/code&gt;), Model Context Protocol (MCP) server definitions (&lt;code&gt;mcp_config.json&lt;/code&gt;), installed &lt;code&gt;plugins/&lt;/code&gt;, and your new &lt;code&gt;skills&lt;/code&gt; symlink.&lt;/li&gt;
&lt;li&gt;Root files such as &lt;code&gt;oauth_creds.json&lt;/code&gt;, &lt;code&gt;google_accounts.json&lt;/code&gt;, &lt;code&gt;settings.json&lt;/code&gt;, and &lt;code&gt;GEMINI.md&lt;/code&gt; are still used if you run Gemini CLI in the terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, you can inspect &lt;code&gt;~/.gemini&lt;/code&gt; and remove obsolete migration backups and temporary folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt; ~/.gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what you can safely clean up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;~/.gemini/antigravity-backup/&lt;/code&gt; and &lt;code&gt;~/.gemini/antigravity-ide/&lt;/code&gt;: migration snapshots created when upgrading from earlier builds. Once you have confirmed your conversations and settings work in Antigravity, you can delete both folders.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.gemini/tmp/&lt;/code&gt;: leftover temporary session directories.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.gemini/skills/&lt;/code&gt;: an older root-level skills folder sometimes populated by Gemini CLI extensions (for example, GCP Data Cloud skills listed in &lt;code&gt;.datacloud_skills_manifest&lt;/code&gt;). Antigravity does not scan &lt;code&gt;~/.gemini/skills/&lt;/code&gt;. If you have custom skills stored there, move them into &lt;code&gt;~/.agents/skills/&lt;/code&gt; first, then remove the obsolete folder.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove obsolete migration backups and temporary directories&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.gemini/antigravity-backup &lt;span class="se"&gt;\&lt;/span&gt;
       ~/.gemini/antigravity-ide &lt;span class="se"&gt;\&lt;/span&gt;
       ~/.gemini/tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Before removing &lt;code&gt;~/.gemini/skills&lt;/code&gt;, run &lt;code&gt;ls -al ~/.gemini/skills&lt;/code&gt; to make sure you do not lose any custom skill you created for Gemini CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;Making your &lt;code&gt;~/.agents/skills&lt;/code&gt; setup work with Google Antigravity and Gemini 4 Argon takes a single symbolic link once you know how its global loader operates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep your shared skills in &lt;code&gt;~/.agents/skills/&lt;/code&gt; so they work across all &lt;a href="https://agentskills.io/clients" rel="noopener noreferrer"&gt;Agent Skills compatible clients&lt;/a&gt; and CLI tools.&lt;/li&gt;
&lt;li&gt;Skip &lt;code&gt;~/.gemini/config/skills.json&lt;/code&gt;, which is not expanded in the global configuration directory.&lt;/li&gt;
&lt;li&gt;Link &lt;code&gt;~/.gemini/config/skills&lt;/code&gt; to &lt;code&gt;~/.agents/skills&lt;/code&gt; with &lt;code&gt;ln -s ~/.agents/skills ~/.gemini/config/skills&lt;/code&gt; and reload Antigravity.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;~/.gemini/antigravity/&lt;/code&gt; and &lt;code&gt;~/.gemini/config/&lt;/code&gt;, and clean up legacy migration folders inside &lt;code&gt;~/.gemini/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Give it a try in your terminal and type &lt;code&gt;/&lt;/code&gt; in your next Antigravity chat session to see all your global skills in action!&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Go further with Antigravity
&lt;/h2&gt;

&lt;p&gt;Want to master autonomous coding agents in your daily workflow? In one day, the &lt;strong&gt;&lt;a href="https://techtown.fr/formations/coder-avec-antigravity/" rel="noopener noreferrer"&gt;Code with Antigravity training by TechTown&lt;/a&gt;&lt;/strong&gt; covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;discovering Antigravity and the agentic paradigm;&lt;/li&gt;
&lt;li&gt;delegating development tasks to autonomous agents;&lt;/li&gt;
&lt;li&gt;validating agent work via Artifacts;&lt;/li&gt;
&lt;li&gt;customizing behavior with Rules, Skills, and MCP servers.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devaugmenté</category>
      <category>agentsia</category>
    </item>
    <item>
      <title>Opus génère ma vidéo de motion design en 20 minutes</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/techtown-fr/opus-genere-ma-video-de-motion-design-en-20-minutes-254n</link>
      <guid>https://dev.to/techtown-fr/opus-genere-ma-video-de-motion-design-en-20-minutes-254n</guid>
      <description>&lt;p&gt;Tout le monde teste des prompts pour qu’Opus 5.5 génère une vidéo. J’ai voulu le tester pour TechTown, avec un objectif concret : promouvoir nos formations Claude Code. À vrai dire, je suis bluffé par le résultat ⭐️&lt;/p&gt;

&lt;p&gt;En une vingtaine de minutes, Claude Code m’a livré un showreel de 21 secondes, en 1080p à 60 images par seconde, avec sa propre bande-son. Je n’ai ouvert aucun logiciel de montage.&lt;/p&gt;

&lt;p&gt;Dans cet article, nous allons voir le but de cette vidéo, le principe du prompt, puis ce que Claude a réellement fait sous le capot. Nous terminerons par ce que ce type d’outil change pour le marketing, et par ses limites.&lt;/p&gt;


  


&lt;p&gt;&lt;em&gt;Le showreel de 21 secondes généré par Claude Code avec Opus 5.5. La vidéo est aussi à voir &lt;a href="https://www.linkedin.com/posts/jlandure_tout-le-monde-teste-des-prompts-pour-que-ugcPost-7515341522557534208-Blpi/" rel="noopener noreferrer"&gt;sur LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Le but : une vidéo pour nos formations Claude Code
&lt;/h2&gt;

&lt;p&gt;Nous animons une formation Claude Code par mois, et nous avons déjà formé 175 personnes. Pour donner envie à d’autres équipes de se lancer, il nous fallait un format court et visuel, facile à partager sur LinkedIn.&lt;/p&gt;

&lt;p&gt;Habituellement, ce genre de vidéo passe par un motion designer et plusieurs allers-retours. Je voulais savoir si un agent de code pouvait tenir ce rôle, avec un brief aussi court que celui que j’enverrais à un prestataire.&lt;/p&gt;

&lt;h2&gt;
  
  
  💬 Le prompt
&lt;/h2&gt;

&lt;p&gt;Voici le prompt complet, envoyé tel quel à Claude Code :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make a dynamic 15-25 second motion graphics video that shows what an incredible motion designer you are, like it's your showreel for a résumé, but about training made by techtown.fr -- go all out.
highlight : formation Claude Code.
Info : https://techtown.fr/formations/
Use /techtown-brand-guidelines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le prompt ne dit rien de la technique : ni outil, ni format de fichier, ni découpage en scènes. Il donne en revanche tout ce qu’un bon brief créatif contient. Nous retrouvons d’ailleurs la méthode des 3C (Contexte, Clarté, Contraintes) que nous enseignons en formation :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;le contexte : un showreel de motion designer, sur les formations TechTown ;&lt;/li&gt;
&lt;li&gt;la clarté : un sujet à mettre en avant, la formation Claude Code ;&lt;/li&gt;
&lt;li&gt;les contraintes : une durée entre 15 et 25 secondes et la charte graphique TechTown.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L’URL de la page formations donne à Claude une source de vérité. Il a récupéré lui-même les 16 formations, leurs catégories et leurs durées, au lieu d’inventer un catalogue.&lt;/p&gt;

&lt;p&gt;Le skill &lt;code&gt;/techtown-brand-guidelines&lt;/code&gt; contient notre charte : la police Poppins, le bleu &lt;code&gt;#1C62ED&lt;/code&gt;, les règles du logo, le style des boutons. Claude le charge avant de dessiner quoi que ce soit, et chaque couleur de la vidéo en vient.&lt;/p&gt;

&lt;p&gt;Enfin, « like it’s your showreel » et « go all out » fixent le niveau d’exigence. Ces quelques mots autorisent Claude à investir du temps dans les détails : flou de mouvement, sound design, transitions travaillées.&lt;/p&gt;

&lt;p&gt;💪 ProTip: écrivez votre prompt comme un brief à un professionnel. Un rôle, un objectif, des sources et des contraintes donnent de meilleurs résultats qu’une liste d’instructions techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Sous le capot : comment Claude a travaillé
&lt;/h2&gt;

&lt;p&gt;C’est la partie qui m’a le plus impressionné. Claude ne dispose d’aucun outil vidéo dédié. Il a donc écrit la vidéo sous forme de code, puis l’a rendue avec des outils déjà installés sur mon Mac.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Rassembler la matière
&lt;/h3&gt;

&lt;p&gt;Claude a d’abord chargé le skill de charte graphique. Il a ensuite téléchargé la &lt;a href="https://techtown.fr/formations/" rel="noopener noreferrer"&gt;page formations&lt;/a&gt; avec &lt;code&gt;curl&lt;/code&gt; pour en extraire le texte, puis récupéré le logo SVG officiel du site. Le tracé de ce logo est réutilisé tel quel dans l’animation finale.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Choisir une technique : la vidéo comme une fonction du temps
&lt;/h3&gt;

&lt;p&gt;L’animation est une page HTML avec un &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; de 1920×1080 pixels, dessiné en JavaScript avec l’API Canvas 2D. Le principe est simple : chaque image est une fonction pure du temps. La fonction &lt;code&gt;drawFrame(t)&lt;/code&gt; dessine exactement ce que l’on doit voir à &lt;code&gt;t&lt;/code&gt; secondes, et la même valeur de &lt;code&gt;t&lt;/code&gt; donne toujours la même image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drawScene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// le point bleu du logo&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// le manifeste typographique&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;8.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// le catalogue des formations&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;s4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// le terminal Claude Code&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;18.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;s5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// les chiffres clés&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;s6&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// le logo et l'appel à l'action&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce déterminisme permet d’exporter la vidéo image par image, sans dépendre de la vitesse de la machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Construire un storyboard calé sur le rythme
&lt;/h3&gt;

&lt;p&gt;Claude a découpé la vidéo en six scènes, toutes calées sur un tempo de 120 BPM. Chaque coupe tombe sur un temps de la musique.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxuaqndistxgw8mkahyd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxuaqndistxgw8mkahyd.webp" alt="Six images extraites du showreel : le point bleu, le manifeste typographique, le catalogue des 16 formations, le terminal Claude Code, le compteur 175+ et le logo final" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Les six scènes du showreel, de la genèse du point bleu jusqu’au logo TechTown.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;La scène la plus longue, de 8,5 à 15,5 secondes, est consacrée à Claude Code. Une carte du catalogue « Coder avec Claude Code » se détache, s’agrandit et devient un terminal. On y voit taper &lt;code&gt;claude&lt;/code&gt;, puis la demande « Forme mon équipe à Claude Code ». L’agent active ensuite le Plan Mode, génère un &lt;code&gt;CLAUDE.md&lt;/code&gt;, connecte un serveur MCP et lance des sous-agents. Le terminal se range ensuite sur le côté pour présenter les deux niveaux de formation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Les techniques d’un motion designer
&lt;/h3&gt;

&lt;p&gt;Claude a appliqué les techniques d’un vrai showreel :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;des courbes d’accélération (&lt;code&gt;outExpo&lt;/code&gt;, &lt;code&gt;inOutCubic&lt;/code&gt;, &lt;code&gt;outBack&lt;/code&gt;) pour chaque mouvement ;&lt;/li&gt;
&lt;li&gt;des textes révélés par masque, et un effet de texte « décodé » où des glyphes aléatoires se résolvent de gauche à droite ;&lt;/li&gt;
&lt;li&gt;un compteur à rouleaux pour le nombre de formations ;&lt;/li&gt;
&lt;li&gt;des secousses de caméra synchronisées avec les impacts sonores ;&lt;/li&gt;
&lt;li&gt;un grain, un vignettage et un habillage de type caméra (repères d’angle, timecode, barre de progression).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le flou de mouvement est le détail le plus technique. Pour chaque image, Claude dessine six sous-images réparties sur une demi-durée d’image, puis les moyenne. C’est l’équivalent d’un obturateur à 180° en cinéma.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;subs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;shutter&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;FPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;drawScene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalAlpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// moyenne glissante&lt;/span&gt;
  &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuzgnokz6tow2fnky0zt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuzgnokz6tow2fnky0zt.webp" alt="La carte Coder avec Claude Code se transforme en terminal, avec un flou de mouvement visible sur les contours" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;La transition entre le catalogue et le terminal : le flou de mouvement vient de la moyenne de six sous-images.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Le son, synthétisé de A à Z
&lt;/h3&gt;

&lt;p&gt;La bande-son ne réutilise aucun sample. Un second script Node.js, &lt;code&gt;audio.mjs&lt;/code&gt;, calcule l’onde sonore échantillon par échantillon : 48 000 valeurs par seconde, en stéréo.&lt;/p&gt;

&lt;p&gt;Chaque instrument est une petite fonction mathématique. La grosse caisse est une sinusoïde dont la fréquence chute très vite. Les charleys et les claps sont du bruit blanc filtré. La basse et les nappes sont des ondes en dents de scie filtrées, sur une grille d’accords la mineur, fa, do, sol. Le script ajoute des montées, des impacts et des souffles pour les transitions.&lt;/p&gt;

&lt;p&gt;Côté mixage, la musique baisse à chaque coup de grosse caisse (le fameux &lt;em&gt;sidechain&lt;/em&gt; de la musique électronique), et une réverbération inspirée de Freeverb donne de l’espace au tout. Le script écrit enfin un fichier WAV.&lt;/p&gt;

&lt;p&gt;La synchronisation repose sur les mêmes horaires que l’animation. Il y a même un clic de clavier par caractère tapé dans le terminal, calculé avec la même formule que l’effet de frappe.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Le rendu : Chrome headless et ffmpeg
&lt;/h3&gt;

&lt;p&gt;Pour transformer la page en vidéo, Claude a écrit un petit serveur Node.js, sans aucune dépendance. Il sert la page et enregistre chaque image PNG que le navigateur lui envoie. Google Chrome, lancé en mode headless, calcule alors les 1 260 images de la vidéo :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"&lt;/span&gt; &lt;span class="nt"&gt;--headless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;new &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"http://localhost:8123/?render&amp;amp;subs=6"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ffmpeg assemble ensuite les images et le son :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 60 &lt;span class="nt"&gt;-i&lt;/span&gt; frames/%05d.png &lt;span class="nt"&gt;-i&lt;/span&gt; audio.wav &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 16 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 256k &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart techtown-showreel-formations.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Au total, le projet représente environ 1 000 lignes de code réparties dans quatre fichiers : la page HTML, l’animation, la bande-son et le serveur de rendu.&lt;/p&gt;

&lt;p&gt;À noter : je pensais que Claude utiliserait le serveur MCP Playwright pour piloter le navigateur. Il n’était pas disponible dans la session. Claude s’est adapté en lançant directement Chrome headless, sans installer le moindre paquet.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Se relire avant de livrer
&lt;/h3&gt;

&lt;p&gt;Avant le rendu final, Claude a vérifié son travail dans le navigateur intégré à l’application Claude. Il a ajouté à sa page un mode « planche contact » (&lt;code&gt;?sheet=0.3,0.9,1.3,...&lt;/code&gt;) qui affiche seize vignettes de la vidéo sur un seul écran, puis il a contrôlé certaines images en plein écran.&lt;/p&gt;

&lt;p&gt;Cette relecture lui a permis de corriger deux défauts avant le rendu : un vignettage trop fort qui grisait les textes en bord d’écran, et une liste à puces trop serrée dans les cartes de formation. Après le rendu, il a encore relu quelques images exportées pour vérifier le flou de mouvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔁 Une correction, trois secondes re-rendues
&lt;/h2&gt;

&lt;p&gt;La première version contenait une erreur de fond. La scène des chiffres clés reprenait la préparation aux certifications Google Cloud, présente sur la page formations. Dans une vidéo consacrée à Claude Code, ce mélange entre Google Cloud et Anthropic n’avait pas de sens.&lt;/p&gt;

&lt;p&gt;Je l’ai signalé en une phrase, avec une capture d’écran : « change ce slide. Tu mélanges GCP et Anthropic. Tu peux juste dire 175+ formés cette année avec une courbe par exemple. »&lt;/p&gt;

&lt;p&gt;Claude a réécrit la scène avec un compteur 175+ et une courbe de progression qui se dessine mois après mois. La forme de la courbe est illustrative : seul le total de 175 personnes formées est un chiffre réel. Il n’a ensuite re-rendu que les images concernées, autour de la plage de 15,5 à 18 secondes, avant de réencoder la vidéo. Le reste du film n’a pas bougé.&lt;/p&gt;

&lt;p&gt;Claude avait bien trouvé l’information sur notre site, sans voir qu’elle n’avait pas sa place dans ce message. La relecture humaine des faits reste indispensable.&lt;/p&gt;

&lt;h2&gt;
  
  
  📣 Ce que ça change pour le marketing
&lt;/h2&gt;

&lt;p&gt;Ce test m’a fait voir la vidéo marketing autrement.&lt;/p&gt;

&lt;p&gt;Le délai entre l’idée et la vidéo se compte en minutes. Le brief tient en quatre lignes, et le premier rendu arrive dans la même session. Il devient possible de tester une idée de vidéo avant même d’en parler à une agence.&lt;/p&gt;

&lt;p&gt;La vidéo devient du code. Elle se versionne dans Git, se relit dans une pull request et se reproduit à l’identique. Pour changer un chiffre, il suffit de modifier une ligne et de relancer le rendu de quelques secondes.&lt;/p&gt;

&lt;p&gt;Les déclinaisons deviennent peu coûteuses. Une fois l’animation écrite, nous pouvons demander une version par formation, une version en anglais ou un format vertical pour les stories, sans repartir de zéro.&lt;/p&gt;

&lt;p&gt;La charte graphique s’applique d’elle-même. En la décrivant une fois dans un skill, l’équipe marketing la rend disponible pour chaque prompt. Chaque vidéo, page ou visuel généré en hérite.&lt;/p&gt;

&lt;p&gt;Le contenu reste aligné avec la réalité. Claude lit le site et les données réelles au lieu de recopier un texte figé. Dans notre cas, les 16 formations affichées dans la vidéo sont celles du catalogue.&lt;/p&gt;

&lt;p&gt;Il y a aussi des limites, et il faut les garder en tête :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;les faits doivent être relus par un humain, comme l’a montré l’erreur sur les certifications ;&lt;/li&gt;
&lt;li&gt;les marques tierces demandent de la vigilance : la vidéo reproduit l’interface de Claude Code et une couleur de la charte d’Anthropic, ce qui mérite une vérification avant une diffusion large ;&lt;/li&gt;
&lt;li&gt;le son synthétisé fonctionne bien pour un format court, mais ne remplace pas un compositeur ni une voix off ;&lt;/li&gt;
&lt;li&gt;pour une campagne haut de gamme, un motion designer garde l’avantage sur la direction artistique.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour du contenu fréquent, court et cohérent avec la marque, en revanche, l’écart avec un agent de code devient très faible.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;Avec un brief de quatre lignes, Opus 5.5 et Claude Code ont produit une vidéo de motion design complète : storyboard, animation, sound design et rendu final. Sous le capot, rien de magique : du Canvas 2D, du Node.js, Chrome headless et ffmpeg, orchestrés par un agent qui sait aussi relire son propre travail.&lt;/p&gt;

&lt;p&gt;Mon takeaway : rédigez vos prompts comme des briefs créatifs, donnez une source de vérité et une charte sous forme de skill, puis gardez la main sur la relecture des faits.&lt;/p&gt;

&lt;p&gt;👉 Et vous, quel contenu marketing confieriez-vous à un agent de code ?&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Aller plus loin avec Claude Code
&lt;/h2&gt;

&lt;p&gt;Vous voulez apprendre à travailler ainsi avec un agent de code ? En une journée, la &lt;strong&gt;&lt;a href="https://techtown.fr/formations/coder-avec-claude-debutant/" rel="noopener noreferrer"&gt;formation Coder avec Claude Code - Débutant de TechTown&lt;/a&gt;&lt;/strong&gt; couvre :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;l’installation et la configuration de Claude Code ;&lt;/li&gt;
&lt;li&gt;les commandes essentielles et la méthode des 3C pour formuler des prompts ;&lt;/li&gt;
&lt;li&gt;la personnalisation de l’environnement de travail (CLAUDE.md, permissions, Plan Mode) ;&lt;/li&gt;
&lt;li&gt;l’intégration de Claude dans un workflow Git professionnel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quelques retours de participants :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;« Sujet passionnant, formateur passionné. »&lt;/p&gt;

&lt;p&gt;« Cette formation m’a donné les bases de la bonne utilisation de Claude pour mon métier de dev. Je me sens prêt à l’utiliser quotidiennement et je connais la bonne direction pour progresser en autonomie sur cet outil. »&lt;/p&gt;

&lt;p&gt;« J’ai apprécié le fait que l’on puisse approfondir certains sujets lors des questionnements ainsi que de voir des cas pratiques et des démos et TPs. »&lt;/p&gt;

&lt;p&gt;« Bonne introduction, sujets pertinents et dans un ordre accessible. Explications théoriques claires et en accord avec la suite des TP. »&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devaugmenté</category>
      <category>ia</category>
    </item>
    <item>
      <title>Gouvernance MCP : qui choisit les tools de vos agents de code ?</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 29 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/techtown-fr/gouvernance-mcp-qui-choisit-les-tools-de-vos-agents-de-code--be4</link>
      <guid>https://dev.to/techtown-fr/gouvernance-mcp-qui-choisit-les-tools-de-vos-agents-de-code--be4</guid>
      <description>&lt;p&gt;Les agents de code comme GitHub Copilot, Claude Code ou Cursor se branchent sur des &lt;strong&gt;serveurs MCP&lt;/strong&gt; (Model Context Protocol). Ces serveurs leur apportent des &lt;em&gt;tools&lt;/em&gt; : chercher un ticket Jira, lire une page Confluence, ouvrir une pull request…&lt;/p&gt;

&lt;p&gt;C’est très pratique. Mais pour une entreprise, une question arrive vite : &lt;strong&gt;qui décide de ce que l’agent a le droit de faire ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dans cet article, nous allons partir d’un cas concret : Jira, Confluence et Context7 placés derrière une gateway MCP. Nous verrons les trois besoins de gouvernance, puis les trois solutions possibles aujourd’hui, avec leurs limites.&lt;/p&gt;

&lt;h2&gt;
  
  
  📌 Les trois besoins de gouvernance
&lt;/h2&gt;

&lt;p&gt;Côté entreprise, nous voulons trois choses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A. Choisir les serveurs MCP autorisés.&lt;/strong&gt; Pas n’importe quel serveur trouvé sur Internet, mais une liste validée. Elle est publiée par une &lt;strong&gt;registry MCP&lt;/strong&gt; interne, ou imposée par des &lt;strong&gt;managed settings&lt;/strong&gt; , c’est-à-dire des réglages déployés par l’entreprise sur les postes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. Préférer les serveurs MCP distants officiels.&lt;/strong&gt; Un serveur &lt;em&gt;remote&lt;/em&gt; hébergé par l’éditeur (GitHub, Atlassian…) apporte :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rien à installer sur les postes : ni &lt;code&gt;docker run&lt;/code&gt;, ni &lt;code&gt;npx&lt;/code&gt; ;&lt;/li&gt;
&lt;li&gt;des mises à jour gérées par l’éditeur ;&lt;/li&gt;
&lt;li&gt;une simple URL à déclarer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;C. Filtrer les tools proposés par ces serveurs.&lt;/strong&gt; Un serveur d’éditeur expose souvent &lt;strong&gt;des dizaines de tools&lt;/strong&gt; , dont beaucoup en écriture. Le serveur open source &lt;a href="https://github.com/sooperset/mcp-atlassian" rel="noopener noreferrer"&gt;mcp-atlassian&lt;/a&gt;, que nous utilisons dans notre test, en expose 97, dont une quarantaine qui modifient des données : &lt;code&gt;jira_create_issue&lt;/code&gt;, &lt;code&gt;confluence_delete_page&lt;/code&gt;… Nous voulons n’en garder qu’une partie, par exemple la lecture seule.&lt;/p&gt;

&lt;p&gt;A et B sont bien outillés aujourd’hui. &lt;strong&gt;C est le vrai problème.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Rappel : registry et managed settings pour le besoin A
&lt;/h3&gt;

&lt;p&gt;Deux mécanismes permettent déjà de choisir les serveurs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Côté Copilot&lt;/strong&gt; , une politique d’organisation GitHub impose à VS Code une registry MCP interne qui liste les serveurs approuvés. C’est ce que nous faisons. Une entrée de registry ressemble à ceci :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.upstash/context7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"remotes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"streamable-http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.context7.com/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chaque entrée décrit un serveur et la façon de s’y connecter. Ici, Context7 est déclaré comme serveur distant, sans installation locale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Côté Claude Code&lt;/strong&gt; , nous faisons de même avec des managed settings. L’entreprise dépose un fichier de réglages, dans &lt;code&gt;/Library/Application Support/ClaudeCode/&lt;/code&gt; sur macOS (&lt;a href="https://code.claude.com/docs/en/managed-mcp" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowedMcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"serverUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.context7.com/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"serverUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-gateway.example.com/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deniedMcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"serverUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://untrusted.example.com/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La liste de refus est évaluée en premier. Ensuite, seuls les serveurs présents dans la liste d’autorisation peuvent être chargés.&lt;/p&gt;

&lt;p&gt;À noter : ces deux mécanismes filtrent &lt;strong&gt;des serveurs&lt;/strong&gt; , pas &lt;strong&gt;des tools&lt;/strong&gt;. Une fois un serveur autorisé, tous ses tools arrivent chez l’agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  1️⃣ Solution 1 : le filtrage proposé par l’éditeur
&lt;/h2&gt;

&lt;p&gt;Certains éditeurs permettent de choisir les tools… &lt;strong&gt;dans la configuration du client&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Le serveur MCP distant de GitHub lit par exemple des en-têtes HTTP maison, décrits dans sa &lt;a href="https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.githubcopilot.com/mcp/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"X-MCP-Toolsets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repos,issues"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"X-MCP-Tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_file_contents,issue_read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"X-MCP-Readonly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;X-MCP-Toolsets&lt;/code&gt; et &lt;code&gt;X-MCP-Tools&lt;/code&gt; limitent les tools exposés, et &lt;code&gt;X-MCP-Readonly&lt;/code&gt; ne garde que les tools de lecture.&lt;/p&gt;

&lt;p&gt;Autre exemple : quand nous installons nous-mêmes mcp-atlassian, nous le configurons par variables d’environnement :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;READ_ONLY_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ENABLED_TOOLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;jira_search,confluence_search &lt;span class="se"&gt;\&lt;/span&gt;
  ...image-mcp-atlassian...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pourquoi cette approche est fragile
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;C’est de la configuration client.&lt;/strong&gt; Le réglage vit dans le &lt;code&gt;mcp.json&lt;/code&gt; de chaque utilisateur. Il suffit de retirer &lt;code&gt;X-MCP-Readonly&lt;/code&gt; ou &lt;code&gt;READ_ONLY_MODE&lt;/code&gt; pour retrouver tous les tools. Ce n’est pas une barrière, c’est une préférence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Le serveur accepte toujours tout.&lt;/strong&gt; Avec le même token, un autre client, ou un simple &lt;code&gt;curl&lt;/code&gt;, obtient l’ensemble des tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chaque éditeur a sa propre convention.&lt;/strong&gt; &lt;code&gt;X-MCP-Tools&lt;/code&gt; chez GitHub, variables d’environnement chez mcp-atlassian, rien chez d’autres. Aucune règle commune n’est possible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cela ne fonctionne qu’avec les éditeurs qui l’ont prévu.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub le précise d’ailleurs à propos de &lt;code&gt;X-MCP-Lockdown&lt;/code&gt; : c’est un filtre « best-effort », pas une frontière de sécurité.&lt;/p&gt;

&lt;h2&gt;
  
  
  2️⃣ Solution 2 : une gateway MCP entre les agents et les serveurs
&lt;/h2&gt;

&lt;p&gt;L’idée est simple : les agents ne parlent plus directement aux serveurs MCP. Ils passent par une &lt;strong&gt;gateway&lt;/strong&gt; qui applique la politique de l’entreprise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxv9amz9q2fdtjdf3puy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxv9amz9q2fdtjdf3puy.webp" alt="Schéma : les agents de code passent par agentgateway, qui filtre les tools de Context7 et d’Atlassian" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tous les agents passent par la même gateway, qui applique la liste blanche des tools et journalise chaque appel.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nous avons testé cette approche avec &lt;a href="https://agentgateway.dev" rel="noopener noreferrer"&gt;agentgateway&lt;/a&gt;, un projet open source de l’Agentic AI Foundation (&lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;AAIF&lt;/a&gt;), en version 1.5.0. Toute la politique tient dans un fichier de configuration :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlassian&lt;/span&gt;
  &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;pathPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/atlassian/mcp&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mcpAuthorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Liste blanche : seuls ces tools existent pour l'agent&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp.tool.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;["jira_get_issue",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"jira_get_all_projects",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"confluence_search",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"confluence_get_page"]'&lt;/span&gt;
  &lt;span class="na"&gt;backends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlassian&lt;/span&gt;
        &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;//localhost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;8000/mcp&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les règles &lt;code&gt;mcpAuthorization&lt;/code&gt; sont des expressions CEL (Common Expression Language). Ici, un seul tableau suffit à décrire la liste blanche de la route Atlassian.&lt;/p&gt;

&lt;p&gt;⚡️ Voici ce que nous avons constaté pendant le test :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Les tools refusés disparaissent&lt;/strong&gt; de la liste envoyée à l’agent (&lt;code&gt;tools/list&lt;/code&gt;). Le modèle ne sait même pas qu’ils existent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un appel direct à un tool refusé est rejeté&lt;/strong&gt; , même forcé à la main avec &lt;code&gt;curl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Le token de l’utilisateur est relayé tel quel.&lt;/strong&gt; Atlassian voit toujours la vraie personne, avec ses propres droits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chaque appel est journalisé&lt;/strong&gt;  : quel serveur, quel tool, quand. Nous obtenons des statistiques d’usage sans effort supplémentaire.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voici un extrait réel des logs d’accès, simplifié :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"default/context7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"http.status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"mcp.method.name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"mcp_target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"context7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"mcp_tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"resolve-library-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2274ms"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enfin, &lt;strong&gt;côté poste, rien à installer.&lt;/strong&gt; L’agent déclare simplement l’URL de la gateway :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"atlassian"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-gateway.example.com/atlassian/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Basic &amp;lt;base64(email:token)&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Les limites de la gateway
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Il faut héberger et maintenir un composant de plus&lt;/strong&gt; , dans notre cas un service Cloud Run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Il faut tenir la liste blanche à jour&lt;/strong&gt; quand l’éditeur ajoute ou renomme des tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Il faut penser la sécurité réseau.&lt;/strong&gt; Par exemple, ne jamais transmettre le token Atlassian d’un utilisateur à un autre serveur :
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;authorization&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# sur la route Context7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Le filtrage se fait au niveau du tool&lt;/strong&gt; , pas de ses paramètres. Pour aller plus loin, par exemple réécrire un schéma ou limiter un projet Jira, agentgateway propose des &lt;em&gt;MCP guardrails&lt;/em&gt;. Il faut alors développer un service externe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💪 ProTip : gardez une route par serveur MCP plutôt qu’une route unique qui les agrège. Les noms de tools restent inchangés, et un token ne peut pas fuiter d’un serveur à l’autre.&lt;/p&gt;

&lt;p&gt;C’est la seule solution où &lt;strong&gt;la règle est appliquée côté serveur, au même endroit pour tous les agents&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3️⃣ Solution 3 : choisir les tools dans chaque agent de code
&lt;/h2&gt;

&lt;p&gt;Troisième voie : garder la connexion directe et restreindre les tools &lt;strong&gt;dans la configuration de l’agent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Côté Copilot CLI&lt;/strong&gt; , c’est simple : une liste blanche par serveur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"atlassian"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.example.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"jira_get_issue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"confluence_search"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;VS Code&lt;/strong&gt; propose en plus un sélecteur de tools dans l’interface du chat. Mais c’est à chaque développeur de choisir les tools qu’il charge dans sa fenêtre de contexte : l’entreprise ne peut pas imposer ce choix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Côté Claude Code&lt;/strong&gt; , c’est plus pénible. Il n’existe pas de liste &lt;code&gt;tools&lt;/code&gt; par serveur, il faut passer par les &lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;règles de permissions&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"mcp __atlassian__ jira_create_issue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"mcp __atlassian__ jira_update_issue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"mcp __atlassian__ jira_delete_issue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"mcp __atlassian__ confluence_create_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"mcp __atlassian__ confluence_delete_page"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un tool refusé est bien retiré du contexte du modèle, et une règle posée dans les managed settings ne peut pas être contournée par l’utilisateur. Mais deux points compliquent la maintenance :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;une règle &lt;code&gt;deny&lt;/code&gt; l’emporte toujours sur une règle &lt;code&gt;allow&lt;/code&gt;. Impossible d’écrire « tout refuser sauf ces quatre tools » avec &lt;code&gt;deny: mcp __atlassian__ *&lt;/code&gt; puis &lt;code&gt;allow: …&lt;/code&gt;. Il faut &lt;strong&gt;lister un par un&lt;/strong&gt; tout ce que nous refusons, et revoir la liste à chaque nouveau tool de l’éditeur ;&lt;/li&gt;
&lt;li&gt;les règles dépendent du &lt;strong&gt;nom local&lt;/strong&gt; donné au serveur (&lt;code&gt;mcp __atlassian__ …&lt;/code&gt;). Si l’utilisateur l’appelle &lt;code&gt;jira&lt;/code&gt;, la règle ne s’applique plus.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Surtout, &lt;strong&gt;chaque agent a sa propre syntaxe&lt;/strong&gt;. Une entreprise qui autorise Copilot, Claude Code et Cursor doit écrire et maintenir trois politiques différentes, puis vérifier qu’elles disent la même chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚖️ Comparatif des trois solutions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;1. Filtrage éditeur (client)&lt;/th&gt;
&lt;th&gt;2. Gateway MCP&lt;/th&gt;
&lt;th&gt;3. Réglage par agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Où vit la règle&lt;/td&gt;
&lt;td&gt;config client&lt;/td&gt;
&lt;td&gt;serveur (gateway)&lt;/td&gt;
&lt;td&gt;config de chaque agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contournable par l’utilisateur&lt;/td&gt;
&lt;td&gt;oui&lt;/td&gt;
&lt;td&gt;non&lt;/td&gt;
&lt;td&gt;oui, sauf managed settings Claude Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marche avec tous les éditeurs&lt;/td&gt;
&lt;td&gt;non&lt;/td&gt;
&lt;td&gt;oui&lt;/td&gt;
&lt;td&gt;oui&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marche avec tous les agents&lt;/td&gt;
&lt;td&gt;oui&lt;/td&gt;
&lt;td&gt;oui&lt;/td&gt;
&lt;td&gt;une syntaxe par agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Statistiques d’usage&lt;/td&gt;
&lt;td&gt;non&lt;/td&gt;
&lt;td&gt;oui&lt;/td&gt;
&lt;td&gt;non&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coût&lt;/td&gt;
&lt;td&gt;nul&lt;/td&gt;
&lt;td&gt;un service à héberger&lt;/td&gt;
&lt;td&gt;maintenance de N configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  🎯 Conclusion : il manque une brique dans la spec MCP
&lt;/h2&gt;

&lt;p&gt;Aujourd’hui, &lt;strong&gt;la seule manière fiable&lt;/strong&gt; de filtrer les tools des éditeurs est d’ajouter une gateway. Elle fonctionne bien, mais c’est une infrastructure de plus pour un besoin que toutes les entreprises partagent.&lt;/p&gt;

&lt;p&gt;La dernière version de la &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http" rel="noopener noreferrer"&gt;spécification MCP&lt;/a&gt; (2026-07-28) fait déjà un pas vers les intermédiaires :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chaque requête HTTP porte des en-têtes standard comme &lt;code&gt;Mcp-Method: tools/call&lt;/code&gt; et &lt;code&gt;Mcp-Name: jira_search&lt;/code&gt;, pour qu’une gateway puisse router sans lire le corps de la requête ;&lt;/li&gt;
&lt;li&gt;un serveur peut demander au client de recopier certains paramètres dans des en-têtes &lt;code&gt;Mcp-Param-*&lt;/code&gt;, grâce à l’extension &lt;code&gt;x-mcp-header&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mais rien de tout cela ne permet de dire au serveur «  &lt;strong&gt;n’expose que ces tools&lt;/strong&gt;  » ou «  &lt;strong&gt;lecture seule&lt;/strong&gt;  ». Chacun invente sa convention : &lt;code&gt;X-MCP-Tools&lt;/code&gt; et &lt;code&gt;X-MCP-Readonly&lt;/code&gt; chez GitHub, &lt;code&gt;READ_ONLY_MODE&lt;/code&gt; et &lt;code&gt;ENABLED_TOOLS&lt;/code&gt; chez mcp-atlassian.&lt;/p&gt;

&lt;p&gt;Ce qui manque, c’est une &lt;strong&gt;généralisation de ces réglages dans la spécification&lt;/strong&gt;. Par exemple, avec des en-têtes qui n’existent pas encore :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/mcp&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Mcp-Method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tools/list&lt;/span&gt;
&lt;span class="na"&gt;Mcp-Tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jira_get_issue,confluence_search&lt;/span&gt;
&lt;span class="na"&gt;Mcp-Read-Only&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trois règles simples suffiraient :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Des en-têtes standard&lt;/strong&gt; , compris par tous les serveurs, pour restreindre les tools et imposer la lecture seule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ils ne peuvent que restreindre&lt;/strong&gt; , jamais élargir ce que le serveur autorise déjà. GitHub applique déjà ce principe à son mode lockdown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les intermédiaires doivent les propager&lt;/strong&gt; , et peuvent les imposer. Une gateway ou un proxy d’entreprise pourrait alors ajouter &lt;code&gt;Mcp-Read-Only: true&lt;/code&gt; à toutes les requêtes, sans maintenir de liste de tools, et chaque serveur l’appliquerait lui-même.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avec cette brique, la gouvernance deviendrait simple :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;la &lt;strong&gt;registry&lt;/strong&gt; choisit les serveurs (A) ;&lt;/li&gt;
&lt;li&gt;les &lt;strong&gt;serveurs distants officiels&lt;/strong&gt; évitent toute installation sur les postes (B) ;&lt;/li&gt;
&lt;li&gt;un &lt;strong&gt;en-tête standard&lt;/strong&gt; , posé par l’entreprise et impossible à retirer côté poste, filtre les tools (C).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En attendant, la gateway reste la solution la plus robuste. C’est aussi un excellent point d’observation pour mesurer ce que les agents font vraiment de ces serveurs.&lt;/p&gt;

&lt;p&gt;👉 Et vous, comment filtrez-vous les tools MCP de vos agents de code ?&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Aller plus loin avec MCP
&lt;/h2&gt;

&lt;p&gt;Vous voulez comprendre MCP de l’intérieur ? En une journée, la &lt;strong&gt;&lt;a href="https://techtown.fr/formations/mcp/" rel="noopener noreferrer"&gt;formation Model Context Protocol (MCP) de TechTown&lt;/a&gt;&lt;/strong&gt; couvre :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;l’architecture client-serveur ;&lt;/li&gt;
&lt;li&gt;les tools, les resources et les prompts ;&lt;/li&gt;
&lt;li&gt;le cycle de vie d’une requête ;&lt;/li&gt;
&lt;li&gt;la création de serveurs MCP en Python et en Java ;&lt;/li&gt;
&lt;li&gt;l’intégration avec des agents IA et le déploiement dans le Cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La journée alterne théorie et ateliers pratiques.&lt;/p&gt;

</description>
      <category>devaugmenté</category>
      <category>agentsia</category>
    </item>
    <item>
      <title>My first talk at AWS Summit Paris: steer your code agents with Kiro</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Thu, 17 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/techtown-fr/my-first-talk-at-aws-summit-paris-steer-your-code-agents-with-kiro-hce</link>
      <guid>https://dev.to/techtown-fr/my-first-talk-at-aws-summit-paris-steer-your-code-agents-with-kiro-hce</guid>
      <description>&lt;p&gt;I presented Spec-Driven Development with Benjamin Bourgeois. The room was full, and the feedback made me very happy.&lt;/p&gt;

&lt;p&gt;I had the opportunity to give my first talk at &lt;a href="https://aws.amazon.com/events/summits/paris/" rel="noopener noreferrer"&gt;AWS Summit Paris&lt;/a&gt;, on 1 April 2026, at the Palais des Congrès. I presented with &lt;a href="https://bbourgeois.dev" rel="noopener noreferrer"&gt;Benjamin Bourgeois&lt;/a&gt;, Cloud and AI consultant at TechTown.&lt;/p&gt;

&lt;p&gt;Our session was &lt;strong&gt;DEV211&lt;/strong&gt; , in the Developer Community Zone, from 13:00 to 13:45. The title was &lt;em&gt;Kiro : Ne codez plus seul, pilotez vos agents de code&lt;/em&gt;. The &lt;a href="https://jlandure.dev/slides/2026-04-01-kiro-ne-codez-plus-seul-pilotez-vos-agents-de-code-at-aws-summit-paris/" rel="noopener noreferrer"&gt;slides&lt;/a&gt; are online if you want to replay the deck.&lt;/p&gt;

&lt;p&gt;I was very happy to host this talk. The room was more than full. The official feedback was excellent. In this article, I’ll share what we presented about &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, and what I take from this first Summit stage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foov6h0z9w4inrp6e7fnh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foov6h0z9w4inrp6e7fnh.webp" alt="Julien Landuré and Benjamin Bourgeois, speakers at AWS Summit Paris 2026" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Benjamin and I, before our first AWS Summit talk together.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A packed Developer Community Zone
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;235 people&lt;/strong&gt; came into a room built for &lt;strong&gt;100&lt;/strong&gt;. That is a &lt;strong&gt;235%&lt;/strong&gt; fill rate.&lt;/p&gt;

&lt;p&gt;Kiro was also easy to find around the event. In the AWS Village, this wall summed up the product in one sentence: an agentic AI development environment that brings structure to AI coding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feoxuj2xw5d9yemdei7iy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feoxuj2xw5d9yemdei7iy.webp" alt="AWS Village wall presenting Kiro as an agentic AI development environment" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kiro in the AWS Village at AWS Summit Paris 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you want the photos from the day, they are on my LinkedIn posts: the &lt;a href="https://www.linkedin.com/posts/jlandure_awssummit-aws-kiro-activity-7442583036245790740-rOK6" rel="noopener noreferrer"&gt;session announcement&lt;/a&gt; and the &lt;a href="https://www.linkedin.com/posts/jlandure_awssummit-aws-awscommunitybuilders-activity-7447531637208178688-_XMN" rel="noopener noreferrer"&gt;recap after the Summit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t code alone, steer your agents
&lt;/h2&gt;

&lt;p&gt;Kiro is the agentic IDE from AWS. It is more than a code editor. It helps you build applications with an AI-native workflow, while you keep control of quality.&lt;/p&gt;

&lt;p&gt;We started with a simple story of the last few years:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We &lt;strong&gt;ask&lt;/strong&gt; an AI for help.&lt;/li&gt;
&lt;li&gt;We use &lt;strong&gt;autocomplete&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;We now work with &lt;strong&gt;code agents&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Chat-only workflows have limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new conversation can lose the context.&lt;/li&gt;
&lt;li&gt;The context window is limited.&lt;/li&gt;
&lt;li&gt;The context is hard to share with the team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why we talked about the &lt;strong&gt;Augmented Developer&lt;/strong&gt;. You do not let the agent guess. You steer it. You write the intent first. Then the agent writes the code.&lt;/p&gt;

&lt;p&gt;The developer becomes the person who guides the agents, not the person who types every line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spec-Driven Development in Kiro
&lt;/h2&gt;

&lt;p&gt;The main idea of the talk was &lt;strong&gt;Spec-Driven Development (SDD)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In Kiro, a prompt can become a structured specification &lt;strong&gt;before&lt;/strong&gt; any code is generated. You can review the intent, the design, and the tasks. Then you let agents implement the work.&lt;/p&gt;

&lt;p&gt;A spec in Kiro usually has three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;requirements.md&lt;/code&gt; — what we want, with user stories and acceptance criteria&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;design.md&lt;/code&gt; — how we will build it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tasks.md&lt;/code&gt; — the steps to implement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You validate the specification first. You generate code second. That is the difference with a long prompt that jumps straight to files.&lt;/p&gt;

&lt;p&gt;One attendee summed it up well: they understood how Kiro turns development into an AI-native workflow, where prompts become structured specs before code generation. They also learned how to steer code agents, automate tests and documentation, and keep control while going faster.&lt;/p&gt;

&lt;p&gt;The result we wanted people to leave with: more automation, less guesswork, and better control of the final code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practices of the Augmented Developer
&lt;/h2&gt;

&lt;p&gt;We also shared practices that help you get useful code from agents, not just fast code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context Engineering&lt;/strong&gt; : give the agent the right context, not a bigger prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/strong&gt; : a project file that is added to the agent context automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steering&lt;/strong&gt; : markdown files that keep your team rules and conventions across chats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; : reusable packages with a prompt, examples, and scripts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt;: connect the agent to external tools and data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt; : run tests, linters, or docs when a file is saved or a task ends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We showed this live on stage. The goal was not to replace developers. The goal was to help developers stay the person who decides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ProTip:&lt;/strong&gt; before you ask Kiro to generate a feature, ask it to write the spec. Read &lt;code&gt;requirements.md&lt;/code&gt; and &lt;code&gt;design.md&lt;/code&gt;. If the intent is wrong, fix the spec. Do not fix 20 generated files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feedback that made me smile
&lt;/h2&gt;

&lt;p&gt;AWS later sent us the official session scores. They are based on &lt;strong&gt;19 survey responses&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content: &lt;strong&gt;4.74 / 5&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Speaker: &lt;strong&gt;4.79 / 5&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tech depth: &lt;strong&gt;4.68 / 5&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Learned something new: &lt;strong&gt;94.7%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One written comment was simple and kind: &lt;em&gt;“Très instructif.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AWS also turned the feedback into a song title, just for fun: &lt;em&gt;Come Together&lt;/em&gt;, by The Beatles. Two speakers, one story, and a room that came together. That made me smile.&lt;/p&gt;

&lt;p&gt;I ended the day at a community dinner with the AWS team. After a first Summit talk, it was a pleasure to meet people, talk about Kiro, and hear what developers want to try next.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take from this first Summit talk
&lt;/h2&gt;

&lt;p&gt;I am very proud of this first talk at AWS Summit. Sharing the stage with Benjamin felt natural. Seeing 235 people in a 100-seat room was a strong signal: developers want to understand how to work with agents, not only how to prompt them.&lt;/p&gt;

&lt;p&gt;If you missed the session, start here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://jlandure.dev/slides/2026-04-01-kiro-ne-codez-plus-seul-pilotez-vos-agents-de-code-at-aws-summit-paris/" rel="noopener noreferrer"&gt;slides&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;kiro.dev&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Sign in with an AWS Builder ID.&lt;/li&gt;
&lt;li&gt;Write a small spec before you generate code.&lt;/li&gt;
&lt;li&gt;Add an &lt;code&gt;AGENTS.md&lt;/code&gt; or a steering file, so the agent knows your project rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Have you tried Spec-Driven Development yet? I would love to hear how you steer your agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jlandure.dev/slides/2026-04-01-kiro-ne-codez-plus-seul-pilotez-vos-agents-de-code-at-aws-summit-paris/" rel="noopener noreferrer"&gt;Slides: Kiro at AWS Summit Paris&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/docs/specs/" rel="noopener noreferrer"&gt;Kiro specs documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/docs/steering/" rel="noopener noreferrer"&gt;Kiro steering and AGENTS.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jlandure.dev" rel="noopener noreferrer"&gt;Julien Landuré&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bbourgeois.dev" rel="noopener noreferrer"&gt;Benjamin Bourgeois&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/jlandure_awssummit-aws-kiro-activity-7442583036245790740-rOK6" rel="noopener noreferrer"&gt;LinkedIn: session announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/jlandure_awssummit-aws-awscommunitybuilders-activity-7447531637208178688-_XMN" rel="noopener noreferrer"&gt;LinkedIn: Summit recap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>événements</category>
      <category>devaugmenté</category>
    </item>
    <item>
      <title>Your Face on a World Cup Sticker: Our Nano Banana Story</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 28 Jul 2026 11:09:48 +0000</pubDate>
      <link>https://dev.to/gde/your-face-on-a-world-cup-sticker-our-nano-banana-story-43p6</link>
      <guid>https://dev.to/gde/your-face-on-a-world-cup-sticker-our-nano-banana-story-43p6</guid>
      <description>&lt;p&gt;There is something timeless about opening a pack of football stickers. You never know which player, celebration, or rare edition you will find next.&lt;/p&gt;

&lt;p&gt;For the 2026 World Cup, we wanted to recreate that feeling with a personal twist: what if the player on the sticker was you, your friends, or your family? That simple idea became &lt;a href="https://sticker.techtown.fr/" rel="noopener noreferrer"&gt;Sticker Studio&lt;/a&gt;, a small experiment powered by Google's Nano Banana image model.&lt;/p&gt;

&lt;p&gt;Take a photo, choose a national team, add your name, and the application transforms your face into a playful cartoon football sticker. In this article, we'll explore the idea, the prompt system behind every scenario, the reference images that fixed our 2026 jerseys, and why we upgraded from Nano Banana 1 to Nano Banana 2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrecuitwfii2npmnwyet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrecuitwfii2npmnwyet.png" alt="Friends and family become World Cup 2026 cartoon collectibles with Sticker Studio." width="800" height="972"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚽ 1. The idea: putting our own faces on the stickers
&lt;/h2&gt;

&lt;p&gt;The starting point was not a technical challenge. It was fun.&lt;/p&gt;

&lt;p&gt;We wanted the nostalgia of football sticker albums without simply reproducing a traditional player portrait. Each result had to feel personal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the face should remain recognizable.&lt;/li&gt;
&lt;li&gt;the rendering should look like a playful children's illustration, not a photorealistic edit.&lt;/li&gt;
&lt;li&gt;the person should wear their selected national team's kit.&lt;/li&gt;
&lt;li&gt;every sticker should tell a small story.&lt;/li&gt;
&lt;li&gt;rare borders and special effects should bring back the excitement of opening a pack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The experience is deliberately simple. You provide a photo and a name, choose your team, and let the application create the surprise.&lt;/p&gt;

&lt;p&gt;Behind that simple flow, the application runs an Astro frontend on Firebase Hosting and an API on Cloud Run. The browser sends the photo and the user's choices to the backend. The backend selects the scenario, builds the complete prompt, attaches the relevant references, and calls the Gemini image-generation API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv06iv7dlpbncobde433n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv06iv7dlpbncobde433n.png" alt="The public Studio keeps the experience simple: one photo, one team, one personalized sticker." width="609" height="866"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI is asked to preserve the real identity cues visible in the photo — face shape, skin tone, hairstyle, facial hair, eyebrows, expression, and accessories such as glasses. At the same time, it must turn that identity into a simplified, expressive cartoon.&lt;/p&gt;

&lt;p&gt;This balance matters. Too realistic, and the result loses its friendly illustrated character. Too generic, and the person no longer recognizes themselves. The fun happens in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✍️ 2. One prompt engine, dozens of scenarios
&lt;/h2&gt;

&lt;p&gt;A prompt like “turn this person into a football sticker” can produce an image. It does not produce a consistent collection.&lt;/p&gt;

&lt;p&gt;To make stickers generated for different people feel as if they belong to the same album, we built every prompt from the same six-part structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Subject and identity&lt;/strong&gt; — who the person is and which visible traits must be preserved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kit&lt;/strong&gt; — the selected national team and its official football jersey.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt; — the humorous pose or celebration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment&lt;/strong&gt; — the stadium, pitch, city, or imaginary setting around the player.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Style and card layout&lt;/strong&gt; — the shared visual rules, border, typography, and rarity effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output constraints&lt;/strong&gt; — a centered, vertical, full-bleed composition suitable for a sticker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the backend, those layers are assembled in TypeScript. Here is a shortened version of the production function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildStickerPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PromptOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PLAYER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;border&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styleBible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;styleBible&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_STYLE_BIBLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Using the attached real photo as the identity reference...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`The character wears the official kit of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;teamName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referenceInstruction&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referenceInstruction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
    &lt;span class="s2"&gt;`Scenario: the character is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Presented inside a trading-card layout with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;borderPhrase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`At the bottom, render the name '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' on one readable line.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;styleBible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Center the subject. Use a vertical, full-bleed composition.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function keeps the permanent art direction in one place, then injects only the values that make a sticker unique. It also makes optional instructions explicit: a jersey reference, trophy, ball, or rare effect is included only when the selected slot needs it.&lt;/p&gt;

&lt;p&gt;The scenarios provide variety. One person can perform a bicycle kick in a packed stadium. Another can ride a rocket-powered football through space, take a victory selfie, attempt a Panenka, meditate on the pitch, or lift the trophy under golden confetti.&lt;/p&gt;

&lt;p&gt;Here is a shortened example based on the real prompt structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using the attached real photo as the identity reference, transform this exact person into a humorous parodic cartoon caricature. Match their real face closely: face shape, skin tone, hairstyle, facial hair, eyebrows, and expression.

The character is a professional soccer player wearing the official kit of the France national football team.

Scenario: the character is performing a spectacular overhead bicycle
kick in mid-air, with the ball blurring off the foot and a packed
stadium behind.

Presented inside a rectangular trading-card layout with a vibrant blue parallel border. At the top, arched text reads 'WORLD CUP 2026'. At the bottom, the name 'JULIEN' is fully readable.

Art style: die-cut sticker design, bold clean white outer outline,
2D vector illustration, flat colors, simple cel shading, thick black
ink outlines, high contrast, and playful humorous cartoon caricature.

Center the subject. Use a vertical, full-bleed composition.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared visual instructions are our &lt;strong&gt;Style Bible&lt;/strong&gt;. They keep the thick outlines, flat colors, card frame, typography, and composition stable across all generations.&lt;/p&gt;

&lt;p&gt;We also randomize collectible details. Common stickers appear more frequently, while gold, silver, black, and holographic borders are less common. Some generations receive a World Cup trophy or official ball as a secondary prop. Around one sticker in twenty gets a golden &lt;code&gt;LEGENDAIRE&lt;/code&gt; badge.&lt;/p&gt;

&lt;p&gt;The result is more than a photo filter. The same input can become a genuinely different collectible each time.&lt;/p&gt;

&lt;p&gt;💪 &lt;strong&gt;ProTip:&lt;/strong&gt; separate stable visual rules from variable scenario instructions. It makes your prompt system easier to tune and prevents a change to one celebration from altering the visual identity of the whole collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  👕 3. When the AI remembered the wrong jerseys
&lt;/h2&gt;

&lt;p&gt;The first stickers looked promising, but one detail immediately broke the illusion: the kits were wrong. For example, this result clearly uses an older France jersey.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyajox2vwz1xzxvycwyb1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyajox2vwz1xzxvycwyb1.jpg" alt="Without a jersey reference, Nano Banana falls back to an older France kit." width="651" height="771"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nano Banana often generated older designs, including 2024-era jerseys. That is understandable — a text prompt can describe “the official 2026 France home kit,” but the model still needs reliable visual information about a new design.&lt;/p&gt;

&lt;p&gt;We solved this with multimodal reference images.&lt;/p&gt;

&lt;p&gt;For France, England, Spain, and Argentina, the backend now attaches a packshot of the official 2026 home jersey to the generation request. The prompt explicitly tells Nano Banana to reproduce its colors, pattern, collar, marks, and crest placement in the cartoon style.&lt;/p&gt;

&lt;p&gt;It also draws a strict boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This image is ONLY a clothing reference. Ignore any person, mannequin,
face, body, pose, or background shown in it, and use it solely for the
jersey design.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We applied the same technique to the official 2026 match ball. Whenever a scenario includes a football, the backend sends the ball image and asks the model to reproduce its base color, panel shapes, colored zones, and line patterns.&lt;/p&gt;

&lt;p&gt;The complete request can therefore contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user's portrait for identity;&lt;/li&gt;
&lt;li&gt;a 2026 jersey packshot for clothing;&lt;/li&gt;
&lt;li&gt;the official match-ball image for the ball design;&lt;/li&gt;
&lt;li&gt;the assembled text prompt for the scene and visual rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The actual Gemini request follows that order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inlineData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;portrait&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;jersey&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;inlineData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jersey&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
  &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;ball&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;inlineData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ball&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;imageConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;aspectRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3:4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ordering is intentional: the portrait remains the primary identity reference, followed by controlled visual assets and finally the instructions that explain how to use them. All of this happens on the backend. The browser sends the photo and business choices, but it never receives the Gemini API key or controls the raw production prompt.&lt;/p&gt;

&lt;p&gt;There was one important gotcha. A jersey reference containing a recognizable face or head could make the image request fail with &lt;code&gt;finishReason=IMAGE_OTHER&lt;/code&gt; and no generated image.&lt;/p&gt;

&lt;p&gt;⚡️ &lt;strong&gt;ProTip:&lt;/strong&gt; prepare product references as clean packshots. Crop jersey images to the torso, remove faces and heads, keep the background simple, and use a reasonably sized compressed image. Reference quality directly affects both reliability and adherence.&lt;/p&gt;

&lt;h2&gt;
  
  
  🍌 4. Moving from Nano Banana 1 to Nano Banana 2
&lt;/h2&gt;

&lt;p&gt;Our first production version used Nano Banana 1 through &lt;code&gt;gemini-2.5-flash-image&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It proved that the concept worked. It could transform a portrait, create the cartoon style, and follow imaginative football scenarios. But as our prompts gained more constraints, we needed stronger adherence: preserve the person's identity, copy a new jersey, use the correct ball, render the name, respect the card frame, and still create a dynamic scene.&lt;/p&gt;

&lt;p&gt;We upgraded the production model to Nano Banana 2, exposed through &lt;code&gt;gemini-3.1-flash-image&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The improvement was not about adding more adjectives to the prompt. Nano Banana 2 followed the combined identity, reference-image, text, and layout instructions more reliably. That made it a better fit for a product in which small details — the correct collar, visible glasses, or an uncropped name — determine whether the sticker feels right.&lt;/p&gt;

&lt;p&gt;We compared both versions with the same portraits, scenarios, and prompts before switching. Keeping the model name configurable also means we can repeat those A/B tests as the Gemini image family evolves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6qjzcltd9mu07e1acsg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6qjzcltd9mu07e1acsg.png" alt="A controlled A/B comparison made reference adherence and layout differences easier to evaluate." width="583" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The generation still uses a 1K output. For a fast, playful web experience, this gives us a practical balance between visual quality, latency, and cost.&lt;/p&gt;

&lt;p&gt;You can learn more about image generation in the &lt;a href="https://ai.google.dev/gemini-api/docs/image-generation" rel="noopener noreferrer"&gt;official Gemini API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 5. From a side project to real API limits
&lt;/h2&gt;

&lt;p&gt;The first audience was exactly who we built it for: friends and family.&lt;/p&gt;

&lt;p&gt;Then people started generating several versions, trying another team, looking for a rarer border, and sharing the funniest results. That is great product feedback — and a fast way to discover API quotas.&lt;/p&gt;

&lt;p&gt;At Gemini API Tier 1, our daily request limits became a real constraint. The API key is only the credential; the effective quota comes from the associated Gemini API project and usage tier. Once the studio became active, generation capacity mattered as much as prompt quality.&lt;/p&gt;

&lt;p&gt;A big thank you to &lt;strong&gt;Guillaume Vernade&lt;/strong&gt;, who helped us move to Tier 3 and give the experiment enough room for more people to play.&lt;/p&gt;

&lt;p&gt;This was a useful reminder: when an AI demo becomes shareable, usage can grow differently from a traditional application. One visit may trigger several expensive generation calls because exploration is part of the experience. Quotas, concurrency, retries, and graceful error messages belong in the product design from the beginning.&lt;/p&gt;

&lt;p&gt;Thanks to a spending cap, we can also control how much we spend on new generations. Each sticker costs approximately &lt;strong&gt;$0.20&lt;/strong&gt; to generate, so the cap protects the experiment from an unexpectedly high bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Conclusion: now create your World Cup sticker
&lt;/h2&gt;

&lt;p&gt;Sticker Studio started with one simple question: what if our friends and family were the players inside the pack?&lt;/p&gt;

&lt;p&gt;Building it taught us three practical lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured prompts turn isolated images into a coherent collection;&lt;/li&gt;
&lt;li&gt;current visual references are essential when recent jerseys and equipment must be accurate;&lt;/li&gt;
&lt;li&gt;a stronger model and enough API capacity matter once people start experimenting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the main result is much simpler: it is fun.&lt;/p&gt;

&lt;p&gt;Friends and family have already generated &lt;strong&gt;365 stickers&lt;/strong&gt;. With the World Cup final finished, now is the perfect time to create yours, try another team, compare rare editions, and share the result.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sticker.techtown.fr/" rel="noopener noreferrer"&gt;Create your own World Cup sticker with Sticker Studio&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which team, scenario, and rarity will you get?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;IMPORTANT NOTE: Sticker Studio is a playful, unofficial fan project and is not affiliated with Panini or FIFA.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nanobanana</category>
      <category>gemini</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>How to celebrate an achievement as an open source developer?</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 30 Jun 2020 14:40:05 +0000</pubDate>
      <link>https://dev.to/jlandure/how-to-celebrate-an-achievement-as-an-open-source-developer-19jo</link>
      <guid>https://dev.to/jlandure/how-to-celebrate-an-achievement-as-an-open-source-developer-19jo</guid>
      <description>&lt;p&gt;✏️ As an open source developer, I've maintained on GitHub a docker image since July 2017: an &lt;a href="https://github.com/Zenika/alpine-chrome" rel="noopener noreferrer"&gt;"Alpine Chrome"&lt;/a&gt; and now the image has been pulled more than 🎉 &lt;strong&gt;1 000 000 times&lt;/strong&gt; 🎉 on the Docker Hub.&lt;/p&gt;

&lt;p&gt;I wrote about it recently on Dev.to 👇&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/zenika" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1234%2F61529416-7337-4c59-ab27-aaa666d850fc.png" alt="Zenika" width="800" height="703"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192673%2Fa76bdc95-3cd3-4bd5-b177-01f02dabcdb3.jpg" alt="" width="450" height="450"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/zenika/crafting-the-perfect-container-to-play-with-a-headless-chrome-ilp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Crafting the perfect container to play with a Headless Chrome&lt;/h2&gt;
      &lt;h3&gt;Julien Landuré for Zenika ・ May 26 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#opensource&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#docker&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#testing&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  🎯 What can I do to celebrate this achievement?
&lt;/h1&gt;

&lt;p&gt;🤔 I do not know what kind of celebration I could do! It's a big achievement from my point of view.&lt;br&gt;
🚀 I've started with a tweet here&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1277969952415215617-374" src="https://platform.twitter.com/embed/Tweet.html?id=1277969952415215617"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1277969952415215617-374');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1277969952415215617&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;😊 &lt;em&gt;Disclaimer: I am not a designer and I've spent too much time designing the arrows to highlight the &lt;code&gt;docker pulls: 1M&lt;/code&gt; information. Please do not judge it... and I've successfully added a &lt;code&gt;:rocket:&lt;/code&gt; emoji on the screenshot.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  🎉 What else?
&lt;/h1&gt;

&lt;p&gt;💚 Do you have any kind of ideas or stories to share about celebrating your open source achievements?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a GIF?&lt;/li&gt;
&lt;li&gt;Invite my friends to celebrate it in a restaurant?&lt;/li&gt;
&lt;li&gt;&lt;em&gt;[your turn]&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>motivation</category>
      <category>docker</category>
    </item>
    <item>
      <title>⏱ 3 minutes to deploy your static Nuxt.js app on AppEngine</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 09 Jun 2020 12:53:17 +0000</pubDate>
      <link>https://dev.to/zenika/3-minutes-to-deploy-your-static-nuxt-js-app-on-appengine-2a2n</link>
      <guid>https://dev.to/zenika/3-minutes-to-deploy-your-static-nuxt-js-app-on-appengine-2a2n</guid>
      <description>&lt;p&gt;I’ve been deploying applications on &lt;a href="https://en.wikipedia.org/wiki/Serverless_computing" rel="noopener noreferrer"&gt;serverless solutions&lt;/a&gt; since 2010. Recently, I discovered &lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt.js&lt;/a&gt; and I wanted to create this post on how to quickly set up a static Nuxt.js app on &lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;AppEngine, the Google Cloud PaaS&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  📝 Getting started with Nuxt.js
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt.js&lt;/a&gt; is described as &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Intuitive Vue Framework&lt;br&gt;
Build your next Vue.js application with confidence using NuxtJS.&lt;br&gt;
An open source framework making web development simple and powerful. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's very powerful! You can use it to create various architectures: &lt;a href="https://nuxtjs.org/guide#server-rendered-universal-ssr-" rel="noopener noreferrer"&gt;SSR&lt;/a&gt;, &lt;a href="https://nuxtjs.org/guide#single-page-applications-spa-" rel="noopener noreferrer"&gt;SPA&lt;/a&gt; or &lt;a href="https://nuxtjs.org/guide#static-generated-pre-rendering-" rel="noopener noreferrer"&gt;Static&lt;/a&gt; renderings.&lt;br&gt;
For this demo, we will be focused on the &lt;strong&gt;static feature&lt;/strong&gt; using the &lt;code&gt;nuxt generate&lt;/code&gt; command.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚗ Create the project
&lt;/h2&gt;

&lt;p&gt;For the demo, we will use a fresh new project. To do this, execute the &lt;code&gt;npx create-nuxt-app&lt;/code&gt; command to create a &lt;a href="https://nuxtjs.org/guide/installation#using-code-create-nuxt-app-code-" rel="noopener noreferrer"&gt;simple Nuxt.js application&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
v12.16.3

&lt;span class="nv"&gt;$ &lt;/span&gt;npx create-nuxt-app

create-nuxt-app v2.15.0
✨  Generating Nuxt.js project &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
? Project name nuxt-static-deploy-appengine
? Project description My glorious Nuxt.js project
? Author name Julien Landuré
? Choose programming language TypeScript
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework None &lt;span class="o"&gt;(&lt;/span&gt;Recommended&lt;span class="o"&gt;)&lt;/span&gt;
? Choose the runtime &lt;span class="k"&gt;for &lt;/span&gt;TypeScript Default
? Choose Nuxt.js modules &lt;span class="o"&gt;(&lt;/span&gt;Press &amp;lt;space&amp;gt; to &lt;span class="k"&gt;select&lt;/span&gt;, &amp;lt;a&amp;gt; to toggle all, &amp;lt;i&amp;gt; to inv
ert selection&lt;span class="o"&gt;)&lt;/span&gt;
? Choose linting tools Prettier
? Choose &lt;span class="nb"&gt;test &lt;/span&gt;framework None
? Choose rendering mode Universal &lt;span class="o"&gt;(&lt;/span&gt;SSR&lt;span class="o"&gt;)&lt;/span&gt;
? Choose development tools &lt;span class="o"&gt;(&lt;/span&gt;Press &amp;lt;space&amp;gt; to &lt;span class="k"&gt;select&lt;/span&gt;, &amp;lt;a&amp;gt; to toggle all, &amp;lt;i&amp;gt; to invert selection&lt;span class="o"&gt;)&lt;/span&gt;
...
🎉  Successfully created project nuxt-static-deploy-appengine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✅ Test locally
&lt;/h2&gt;

&lt;p&gt;As described in the command line, run the &lt;code&gt;npm run start&lt;/code&gt; &lt;a href="https://nuxtjs.org/guide/commands#development-environment" rel="noopener noreferrer"&gt;script to get your app running&lt;/a&gt; on &lt;code&gt;http://localhost:3000/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's edit the &lt;code&gt;index.vue&lt;/code&gt; and write this html:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"subtitle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello Nuxt on AppEngine 🎉&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it works! Very easy to see our modification instantly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7a5a4pg96lydb6y02cky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7a5a4pg96lydb6y02cky.png" alt="Alt Text" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗 Build it
&lt;/h2&gt;

&lt;p&gt;To &lt;a href="https://nuxtjs.org/guide/commands#static-generated-deployment-pre-rendered-" rel="noopener noreferrer"&gt;build the app statically&lt;/a&gt;, run the &lt;code&gt;npm run generate&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run generate

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nuxt-static-deploy-appengine@1.0.0 generate .../nuxt-static-deploy-appengine
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nuxt generate

ℹ Production build                                                    10:52:36
✔ Builder initialized                                                 10:52:37
✔ Nuxt files generated                                                10:52:37
ℹ Starting &lt;span class="nb"&gt;type &lt;/span&gt;checking service...                   nuxt:typescript 10:52:39

✔ Client
  Compiled successfully &lt;span class="k"&gt;in &lt;/span&gt;6.95s

✔ Server
  Compiled successfully &lt;span class="k"&gt;in &lt;/span&gt;1.13s


Hash: c5e482cb9772cddb1178
Version: webpack 4.43.0
Time: 6951ms
Built at: 05/27/2020 10:52:46 AM
                         Asset       Size  Chunks                         Chunk Names
../server/client.manifest.json    6.6 KiB          &lt;span class="o"&gt;[&lt;/span&gt;emitted]              
       98f4a70ff71f0382866c.js   2.31 KiB       3  &lt;span class="o"&gt;[&lt;/span&gt;emitted] &lt;span class="o"&gt;[&lt;/span&gt;immutable]  runtime
       9fc3f5e914943c5b3a9b.js   2.97 KiB       2  &lt;span class="o"&gt;[&lt;/span&gt;emitted] &lt;span class="o"&gt;[&lt;/span&gt;immutable]  pages/index
                      LICENSES  389 bytes          &lt;span class="o"&gt;[&lt;/span&gt;emitted]              
       a51444c83f14a6afaeb5.js    153 KiB       1  &lt;span class="o"&gt;[&lt;/span&gt;emitted] &lt;span class="o"&gt;[&lt;/span&gt;immutable]  commons.app
       d267ba93006b46dadaa8.js   49.2 KiB       0  &lt;span class="o"&gt;[&lt;/span&gt;emitted] &lt;span class="o"&gt;[&lt;/span&gt;immutable]  app
 + 2 hidden assets
Entrypoint app &lt;span class="o"&gt;=&lt;/span&gt; 98f4a70ff71f0382866c.js a51444c83f14a6afaeb5.js d267ba93006b46dadaa8.js

Hash: fbd0c6af7579246a49e7
Version: webpack 4.43.0
Time: 1127ms
Built at: 05/27/2020 10:52:47 AM
                  Asset       Size  Chunks                         Chunk Names
818a9680ee5eeafdfb4b.js   3.05 KiB       1  &lt;span class="o"&gt;[&lt;/span&gt;emitted] &lt;span class="o"&gt;[&lt;/span&gt;immutable]  pages/index
              server.js   26.7 KiB       0  &lt;span class="o"&gt;[&lt;/span&gt;emitted]              app
   server.manifest.json  145 bytes          &lt;span class="o"&gt;[&lt;/span&gt;emitted]              
Entrypoint app &lt;span class="o"&gt;=&lt;/span&gt; server.js
ℹ Generating pages                                                    
✔ Generated /                                                        
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✨ Congratulations, we built our first Nuxt.js application! &lt;/p&gt;

&lt;p&gt;&lt;em&gt;ProTips: We can check the files in the &lt;code&gt;dist&lt;/code&gt; folder or run it with &lt;code&gt;npx http-server dist&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  ☁️ Deploy it on AppEngine
&lt;/h1&gt;

&lt;p&gt;Then, to share the project with the rest of the world, we can deploy it on AppEngine. &lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Appengine&lt;/a&gt; comes with a lot of features and quotas. &lt;/p&gt;

&lt;p&gt;It is free for a basic usage thanks to its quotas BUT we have to enable billing to enjoy it. As a new customer, we get &lt;a href="https://cloud.google.com/free" rel="noopener noreferrer"&gt;$300 to spend on Google Cloud Platform products&lt;/a&gt; during your free trial.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛒 Create a project
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="//console.cloud.google.com"&gt;&lt;code&gt;console.cloud.google.com&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://cloud.google.com/appengine/docs/standard/python3/building-app/creating-gcp-project" rel="noopener noreferrer"&gt;create a new Google Cloud Platform project&lt;/a&gt;.&lt;br&gt;
Please save the [PROJECT_ID] of the project: you have to replace the &lt;code&gt;nuxt-static-deploy&lt;/code&gt; with your [PROJECT_ID] for the rest of the post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ProTips: we can retrieve the [PROJECT_ID] in the url of the web console like &lt;code&gt;https://console.cloud.google.com/appengine/start?project=nuxt-static-deploy&lt;/code&gt; where nuxt-static-deploy is my [PROJECT_ID].&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ProTips: To create a project to host your app, we can also use the &lt;code&gt;gcloud projects create&lt;/code&gt; command. we need to enter a [PROJECT_ID], &lt;code&gt;nuxt-static-deploy&lt;/code&gt; for example.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a project &lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud projects create nuxt-static-deploy 
Create &lt;span class="k"&gt;in &lt;/span&gt;progress &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;https://cloudresourcemanager.googleapis.com/v1/projects/nuxt-static-deploy].
Waiting &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;operations/cp.4929250122387435991] to finish...done.              
Enabling service &lt;span class="o"&gt;[&lt;/span&gt;cloudapis.googleapis.com] on project &lt;span class="o"&gt;[&lt;/span&gt;nuxt-static-deploy]...
Operation &lt;span class="s2"&gt;"operations/acf.803672e3-74b0-43e1-8a6b-3d37a4a0975b"&lt;/span&gt; finished successfully.

&lt;span class="c"&gt;# List the billing accounts&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud beta billing accounts list
ACCOUNT_ID            NAME                                          OPEN
00ADEE-870384-XXXXXX  my_account                                    True

&lt;span class="c"&gt;# Link the billing account to my project&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud beta billing projects &lt;span class="nb"&gt;link &lt;/span&gt;nuxt-static-deploy &lt;span class="nt"&gt;--billing-account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;00ADEE-870384-XXXXXX
billingAccountName: billingAccounts/00ADEE-870384-XXXXXX
billingEnabled: &lt;span class="nb"&gt;true
&lt;/span&gt;name: projects/nuxt-static-deploy/billingInfo
projectId: nuxt-static-deploy

&lt;span class="c"&gt;# Enable Cloud Build API&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud &lt;span class="nt"&gt;--project&lt;/span&gt; nuxt-static-deploy services &lt;span class="nb"&gt;enable &lt;/span&gt;cloudbuild.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✏️ Write the descriptor &lt;code&gt;app.yaml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;AppEngine supports various programming languages: python, go, java, php, nodejs etc.&lt;br&gt;
To deploy static files, the best practice is to use the &lt;code&gt;python37&lt;/code&gt; runtime.&lt;/p&gt;

&lt;p&gt;Let's create a file in &lt;code&gt;.appengine/app.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python37&lt;/span&gt;

&lt;span class="na"&gt;handlers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
  &lt;span class="na"&gt;static_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;
  &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;
  &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/(.*)&lt;/span&gt;
  &lt;span class="na"&gt;static_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;\1&lt;/span&gt;
  &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(.*)&lt;/span&gt;
  &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information on the descriptor file, &lt;a href="https://cloud.google.com/appengine/docs/standard/python3/config/appref" rel="noopener noreferrer"&gt;please read this doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Don't forget to copy the file in our &lt;code&gt;dist&lt;/code&gt; folder using this command &lt;code&gt;cp .appengine/app.yaml dist/&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;ProTips: We can add it to the &lt;code&gt;build&lt;/code&gt; script in your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Deploy your app
&lt;/h2&gt;

&lt;p&gt;If this is the first you are using the Google Cloud Platform, you have to install the &lt;code&gt;gcloud&lt;/code&gt; command line utility following &lt;a href="https://cloud.google.com/sdk/docs/quickstarts" rel="noopener noreferrer"&gt;the official page&lt;/a&gt; or &lt;a href="https://formulae.brew.sh/cask/google-cloud-sdk" rel="noopener noreferrer"&gt;using Homebrew&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Deploy our app using the &lt;code&gt;gcloud app deploy&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud app deploy &lt;span class="nt"&gt;--project&lt;/span&gt; nuxt-static-deploy dist 

You are creating an app &lt;span class="k"&gt;for &lt;/span&gt;project nuxt-static-deploy.
WARNING: Creating an App Engine application &lt;span class="k"&gt;for &lt;/span&gt;a project is irreversible and the region
cannot be changed. More information about regions is at
&amp;lt;https://cloud.google.com/appengine/docs/locations&amp;gt;.

Please choose the region where you want your App Engine application 
located:

 &lt;span class="o"&gt;[&lt;/span&gt;1] asia-east2    &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;2] asia-northeast1 &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;3] asia-northeast2 &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;4] asia-northeast3 &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;5] asia-south1   &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;6] australia-southeast1 &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;7] europe-west   &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;8] europe-west2  &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;9] europe-west3  &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;10] europe-west6  &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;11] northamerica-northeast1 &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;12] southamerica-east1 &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;13] us-central    &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;14] us-east1      &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;15] us-east4      &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;16] us-west2      &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;17] us-west3      &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;18] us-west4      &lt;span class="o"&gt;(&lt;/span&gt;supports standard and flexible&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;19] cancel
Please enter your numeric choice:  7

Creating App Engine application &lt;span class="k"&gt;in &lt;/span&gt;project nuxt-static-deploy and region &lt;span class="o"&gt;[&lt;/span&gt;
europe-west]....done.                                                          
Services to deploy:

descriptor:      &lt;span class="o"&gt;[&lt;/span&gt;.../nuxt-static-deploy-appengine/dist/app.yaml]
&lt;span class="nb"&gt;source&lt;/span&gt;:          &lt;span class="o"&gt;[&lt;/span&gt;.../nuxt-static-deploy-appengine/dist]
target project:  &lt;span class="o"&gt;[&lt;/span&gt;nuxt-static-deploy]
target service:  &lt;span class="o"&gt;[&lt;/span&gt;default]
target version:  &lt;span class="o"&gt;[&lt;/span&gt;20200602t094056]
target url:      &lt;span class="o"&gt;[&lt;/span&gt;https://20200602t094056-dot-nuxt-static-deploy.ew.r.appspot.com]


     &lt;span class="o"&gt;(&lt;/span&gt;add &lt;span class="nt"&gt;--promote&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;you also want to make this service available from
     &lt;span class="o"&gt;[&lt;/span&gt;https://nuxt-static-deploy.ew.r.appspot.com]&lt;span class="o"&gt;)&lt;/span&gt;

Do you want to &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;Y/n&lt;span class="o"&gt;)&lt;/span&gt;?   

Beginning deployment of service &lt;span class="o"&gt;[&lt;/span&gt;default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 12 files to Google Cloud Storage               ═╣
╚════════════════════════════════════════════════════════════╝
File upload &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Updating service &lt;span class="o"&gt;[&lt;/span&gt;default]...done.                                             
Deployed service &lt;span class="o"&gt;[&lt;/span&gt;default] to &lt;span class="o"&gt;[&lt;/span&gt;https://20200602t094056-dot-nuxt-static-deploy.ew.r.appspot.com]

You can stream logs from the &lt;span class="nb"&gt;command &lt;/span&gt;line by running:
  &lt;span class="nv"&gt;$ &lt;/span&gt;gcloud app logs &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; default

To view your application &lt;span class="k"&gt;in &lt;/span&gt;the web browser run:
  &lt;span class="nv"&gt;$ &lt;/span&gt;gcloud app browse

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  🎉 Enjoy
&lt;/h1&gt;

&lt;p&gt;Go &lt;a href="https://nuxt-static-deploy.appspot.com/" rel="noopener noreferrer"&gt;here at https://nuxt-static-deploy.appspot.com/&lt;/a&gt; to see our first Nuxt.js app deployed with AppEngine.&lt;/p&gt;

&lt;h1&gt;
  
  
  🔍 Conclusion
&lt;/h1&gt;

&lt;p&gt;Nuxt.js is a cool technology to create Vue.js app and is trending with the release of &lt;code&gt;@nuxt/content&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1263795755954909184-839" src="https://platform.twitter.com/embed/Tweet.html?id=1263795755954909184"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1263795755954909184-839');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1263795755954909184&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;AppEngine comes with a lot of features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔁 Multiple versions available to do A/B testing or Traffic Splitting (each version has a different timestamp on the &lt;a href="https://console.cloud.google.com/appengine/versions" rel="noopener noreferrer"&gt;Versions Page on Appengine&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;🔐 &lt;a href="https://cloud.google.com/appengine/docs/standard/python3/mapping-custom-domains" rel="noopener noreferrer"&gt;Custom Domain Mapping&lt;/a&gt; &amp;amp; auto-renew, free, &lt;a href="https://cloud.google.com/appengine/docs/standard/python3/securing-custom-domains-with-ssl" rel="noopener noreferrer"&gt;managed SSL certificates&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💸 Free quota (1 GB bandwidth)&lt;/li&gt;
&lt;li&gt;⭐️ No instance used with Python37 runtime and our configuration because we have only static content &lt;/li&gt;
&lt;li&gt;🌐 Static content available through Google CDN and &lt;a href="https://peering.google.com/#/infrastructure" rel="noopener noreferrer"&gt;its Edge Network&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⚡️ Managed network: Surprise 🎉 our project uses the http3 procotol &lt;code&gt;h3-Q050&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm0egf2fxzh3xsxcvp46g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm0egf2fxzh3xsxcvp46g.png" alt="Alt Text" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to contact me on &lt;a href="https://twitter.com/jlandure" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; if you have questions!&lt;/p&gt;

&lt;p&gt;NB: You can find another guide on Nuxt.js with AppEngine &lt;a href="https://nuxtjs.org/faq/appengine-deployment" rel="noopener noreferrer"&gt;here in the official documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>googlecloud</category>
      <category>serverless</category>
      <category>vue</category>
    </item>
    <item>
      <title>Crafting the perfect container to play with a Headless Chrome</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 26 May 2020 06:19:17 +0000</pubDate>
      <link>https://dev.to/zenika/crafting-the-perfect-container-to-play-with-a-headless-chrome-ilp</link>
      <guid>https://dev.to/zenika/crafting-the-perfect-container-to-play-with-a-headless-chrome-ilp</guid>
      <description>&lt;p&gt;Since July 30th 2017, I have been contributing to a side project: the idea from the beginning was the ability to use Chrome to make some tests in headless mode and avoid a long queue of CI builds using a &lt;a href="https://github.com/Zenika/alpine-chrome" rel="noopener noreferrer"&gt;tiny Docker image&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  🤔 Why use a Headless Chrome
&lt;/h1&gt;

&lt;p&gt;In the world of webdev, the ability to run quickly end-to-end tests are important. Popular technologies like Puppeteer enable developers to make fun things like testing, automating forms, crawling, generating screenshots, capturing timeline... And there is a secret: some of these features are directly available on Chrome! 🙌&lt;/p&gt;

&lt;p&gt;For more information on why headless mode is a cool feature, please read a good example on what you can achieve here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/reverentgeek" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3194%2Feaf08445-1922-4191-bb07-8b5a3cd4657f.png" alt="reverentgeek"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/reverentgeek/convert-text-html-to-pdf-with-node-js-and-puppeteer-4c53" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Create PDFs with Node.js and Puppeteer&lt;/h2&gt;
      &lt;h3&gt;David Neal ・ Feb 6 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#node&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  💡 Crafting the perfect container
&lt;/h1&gt;

&lt;p&gt;With 28 contributors, I have been working on this project to create the perfect image to run Chromium in Headless mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 Tiniest Headless Chrome (&lt;a href="https://github.com/Zenika/alpine-chrome#image-disk-size" rel="noopener noreferrer"&gt;393 MB&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;🐳 Easy to use, ephemeral and reproducible Headless Chrome with Docker&lt;/li&gt;
&lt;li&gt;📝 Doc-friendly with examples for printing the DOM, generating an image with a mobile ratio or generating a PDF.&lt;/li&gt;
&lt;li&gt;👷‍♂️ Autobuild with the &lt;a href="https://hub.docker.com/repository/docker/zenika/alpine-chrome" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; to sync the project and ship the images with confidence&lt;/li&gt;
&lt;li&gt;📌 Up-to-date with Chromium 81 (81.0.4044.138) and tags available to test different versions of Chromium&lt;/li&gt;
&lt;li&gt;🔐 Secure with the best way to use Chrome and Docker - &lt;a href="https://github.com/Zenika/alpine-chrome#3-ways-to-securely-use-chrome-headless-with-this-image" rel="noopener noreferrer"&gt;See "3 ways to securely use Chrome Headless" section&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 Ready for internationalisation use: support for asian characters - &lt;a href="https://github.com/Zenika/alpine-chrome/blob/master/with-puppeteer/src/screenshot-asia.js" rel="noopener noreferrer"&gt;See "screenshot-asia.js" file&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💄 Ready for design use: support for WebGL - &lt;a href="https://github.com/Zenika/alpine-chrome#how-to-use-with-webgl" rel="noopener noreferrer"&gt;See "How to use with WebGL" section&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📄 Open Source with an Apache2 licence&lt;/li&gt;
&lt;li&gt;👥 Community-built with 28 contributors - &lt;a href="https://github.com/Zenika/alpine-chrome#-contributors" rel="noopener noreferrer"&gt;See "✨ Contributors" section&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💚 Dev-friendly with examples using NodeJS, Puppeteer, docker-compose and also a test with a X11 display - &lt;a href="https://github.com/Zenika/alpine-chrome#run-examples" rel="noopener noreferrer"&gt;See "Run examples" section&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  💚 Open Source &amp;amp; Fun
&lt;/h1&gt;

&lt;p&gt;Some open source &amp;amp; fun projects have been set up and used for this open source side project. I want to share it with you: it adds value and confidence to maintain a GitHub repository. 👌 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updates to follow the &lt;a href="https://opensource.guide/" rel="noopener noreferrer"&gt;recommended community standards&lt;/a&gt; from the GitHub community profile&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitmoji.carloscuesta.me/" rel="noopener noreferrer"&gt;Gitmoji&lt;/a&gt; for commit messages &lt;em&gt;where I have contributed in the past&lt;/em&gt; 😊&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://allcontributors.org/" rel="noopener noreferrer"&gt;All Contributors&lt;/a&gt; to thanks everyone who contributes to the project&lt;/li&gt;
&lt;li&gt;Inspiration from &lt;a href="https://github.com/kefranabg/readme-md-generator" rel="noopener noreferrer"&gt;readme-md-generator&lt;/a&gt; made by my friend &lt;a href="https://dev.to/kefranabg"&gt;Franck Abgrall&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Inspiration from this cheatsheet on &lt;a href="https://dev.to/kefranabg/cheat-sheet-how-to-promote-your-open-source-project-4flh"&gt;"How to promote your open-source project?"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://renovate.whitesourcesoftware.com/" rel="noopener noreferrer"&gt;Renovate&lt;/a&gt; for dependency updates&lt;/li&gt;
&lt;li&gt;Many reads on dev.to to know what web developers are doing with Chrome and Puppeteer&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  🚀 Reaching a point
&lt;/h1&gt;

&lt;p&gt;Here is the most interesting insight for a tool: to be used and useful! In almost two weeks, this image will be pulled on the &lt;a href="https://hub.docker.com/repository/docker/zenika/alpine-chrome" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt;... &lt;/p&gt;

&lt;p&gt;🎉 &lt;strong&gt;a million times&lt;/strong&gt;! 🎉 &lt;/p&gt;

&lt;p&gt;&lt;em&gt;there is currently ~922k downloads and it grows ~6k by day&lt;/em&gt;&lt;br&gt;
On &lt;a href="https://github.com/Zenika/alpine-chrome" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, the repository gets ~3000 views, ~1000 unique visitors every two weeks and ~464 stars.&lt;/p&gt;

&lt;p&gt;After 3 years maintaining it, I cannot thank everyone using this container. This is the first time a side project has become so popular.&lt;/p&gt;

&lt;h1&gt;
  
  
  🗓 The Future
&lt;/h1&gt;

&lt;p&gt;✨ Here is some features in the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;with-playwright&lt;/code&gt; version with &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; to reuse the installed headless Chromium (&lt;a href="https://github.com/Zenika/alpine-chrome/pull/85" rel="noopener noreferrer"&gt;PR in draft&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;with-deno&lt;/code&gt; version with &lt;a href="https://deno.land/" rel="noopener noreferrer"&gt;Deno&lt;/a&gt; to create a useful and fun things to do with deno&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;with-lighthouse&lt;/code&gt; version with &lt;a href="https://developers.google.com/web/tools/lighthouse/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; to be able to monitor your website performance in CI builds&lt;/li&gt;
&lt;li&gt;Create a dedicated homepage and documentation website: the &lt;a href="https://github.com/Zenika/alpine-chrome/blob/master/README.md" rel="noopener noreferrer"&gt;README.md&lt;/a&gt; begins to be complicated to navigate and update&lt;/li&gt;
&lt;li&gt;Create a gallery with the name of the project or product using this container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📣 Please test this container or play with it!&lt;br&gt;
💻 If you are interested to help me maintain this image, we are welcome! &lt;em&gt;There is some "Good first issue" labelled issue on the repository.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: these terms in this post are synonyms: "Chrome" vs "Chromium" and "image" vs "container".&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>docker</category>
      <category>testing</category>
      <category>javascript</category>
    </item>
    <item>
      <title>State of Cloud Communities in France</title>
      <dc:creator>Julien Landuré</dc:creator>
      <pubDate>Tue, 14 Apr 2020 06:52:54 +0000</pubDate>
      <link>https://dev.to/zenika/state-of-cloud-communities-in-france-7ko</link>
      <guid>https://dev.to/zenika/state-of-cloud-communities-in-france-7ko</guid>
      <description>&lt;p&gt;&lt;a href="https://www.linkedin.com/in/karentamrazyan/" rel="noopener noreferrer"&gt;Karen Tamrazyan&lt;/a&gt; wrote a good article that analyzes local communities of 3 major public cloud providers AWS, Azure and Google Cloud in Germany, Austria and Switzerland (German-speaking countries) here:&lt;br&gt;
&lt;a href="https://www.tamrazyan.com/state-of-cloud-communities-in-german-speaking-countries/" rel="noopener noreferrer"&gt;https://www.tamrazyan.com/state-of-cloud-communities-in-german-speaking-countries/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;His article made me think about France. He helped me to create a similar sheet to superficially compare regional communities behind the three major public cloud providers: Amazon Web Services (aka AWS), Microsoft Azure and Google Cloud.&lt;/p&gt;
&lt;h1&gt;
  
  
  Comparison
&lt;/h1&gt;

&lt;p&gt;The top right quadrant from the latest Gartner cloud market analysis clearly indicates three global cloud leaders: Amazon Web Services, Microsoft Azure, Google Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F0%2AHLk4BgFc7-QM6iWx" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F0%2AHLk4BgFc7-QM6iWx" alt="quadrant" width="1536" height="1536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How are we going to compare the communities with each other? As Karen did in his article, we will compare them by the amount of registered members or the group size in other words. It is obvious that the group size is just one metric of many, like e.g. the activity rate (how often do events take place), the average number of participants, the date of the last event and so on… The members count should serve as a simplified way of measurement of a group success or importance.&lt;/p&gt;

&lt;p&gt;The internet is big. What kind of groups are we going to count? Indeed, there are cloud communities as Facebook groups, on different messenger networks like e.g. Telegram, independently hosted forums and so on. In this work we will count only the communities, which are hosted on the Meetup.com platform. Meetup is at the moment a de facto most popular community platform in those 3 countries.&lt;/p&gt;

&lt;p&gt;We will only consider active groups. It means that a group should have at least 1 registered past or upcoming event. In the locations where multiple communities dedicated to the same cloud technology are present, only the biggest one will be considered because of the very high probability of members overlap between those groups.&lt;/p&gt;

&lt;p&gt;The article gives only a snapshot of the situation as of April 12, 2020. The author doesn’t intend to update it as the situation changes. It’s a photo, not a video.&lt;/p&gt;
&lt;h1&gt;
  
  
  Cloud communities in France
&lt;/h1&gt;

&lt;p&gt;France has a population of over 67 million people. It is one of the most important technological hubs of Europe (competing with Germany and England). Plenty of universities, industrial and service companies belong to the landscape of most of the cities in this country. Even small places can often have lively user groups here like in Brest, Tours or Cannes.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h1&gt;
  
  
  Analysis of Cloud Impact
&lt;/h1&gt;

&lt;p&gt;As we can clearly see from the table, AWS has far away the biggest communities in France : it demonstrates a tendency to go on this Cloud Provider. This popularity is clearly brought from the location of &lt;a href="https://aws.amazon.com/blogs/aws/now-open-aws-eu-paris-region/" rel="noopener noreferrer"&gt;one datacenter in Paris, France at the end of 2017&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Azure have broad community networks that cover the top 5 most populated cities. Azure is never the biggest community in the city.&lt;/p&gt;

&lt;p&gt;Google Cloud groups (aka “GDG Cloud” chapters) have the weakest cloud community network in France among the 3 contenders. However, one should not forget the much wider network of general Google Developer Groups (abbreviated “GDG”), which also on occasions run Google Cloud themed meetups.&lt;/p&gt;

&lt;p&gt;We can observe that Paris, Lyon and Nantes are the top 3 cities where the aws, azure and google cloud communities can enjoy events and meet together. &lt;/p&gt;

&lt;p&gt;Lyon is quite particular : it’s the only city where Google Cloud has more members than the others Cloud communities.&lt;/p&gt;

&lt;p&gt;This article highlights that cloud communities don’t match the &lt;a href="https://medium.com/frenchtech/bienvenue-aux-communaut%C3%A9s-et-capitales-french-tech-labellis%C3%A9es-le-3-avril-2019-f417539d3bd0" rel="noopener noreferrer"&gt;top “La French Tech” cities&lt;/a&gt;. However, many cloud providers provide Cloud credits for Startups like this &lt;a href="https://cloud.google.com/developers/startups" rel="noopener noreferrer"&gt;GCP initiative&lt;/a&gt;: &lt;a href="https://goo.gl/zY4pH2" rel="noopener noreferrer"&gt;check this page to redeem it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F0%2A7kDNA0EXz_-qRuUo" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F0%2A7kDNA0EXz_-qRuUo" alt="frenchtech" width="1400" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are some large cities in the country like Montpellier, Bordeaux or Lille and a couple more, where none of Azure or Google Cloud have community footprints. These are opportunities for the future to grow.&lt;/p&gt;

&lt;h1&gt;
  
  
  What about GDGs?
&lt;/h1&gt;

&lt;p&gt;GDGs, Google Developer Groups, are too general to be studied in this article but GDGs bring together tons of passionate developers in France : 4134 members for GDG Paris, 2360 for GDG Nantes, 2213 for GDG Lille and 2133 for GDG Toulouse. &lt;/p&gt;

&lt;p&gt;These numbers are boosted by the organization of a DevFest each year, a day or 2-days conference dedicated to share about the technology and its impact. And GDGs run some Cloud events sometimes, please check out the &lt;a href="https://developers.google.com/community/gdg/groups" rel="noopener noreferrer"&gt;listing of GDGs here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0i8dd17r1a35ox395i6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0i8dd17r1a35ox395i6.png" alt="gdg-france" width="447" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Disclaimer
&lt;/h1&gt;

&lt;p&gt;Author of this article runs voluntarily the &lt;a href="https://meetup.com/fr-FR/GDG-Nantes" rel="noopener noreferrer"&gt;Google Cloud developer group in Nantes (France)&lt;/a&gt; and also the &lt;a href="https://www.meetup.com/fr-FR/GDG-Cloud-Nantes" rel="noopener noreferrer"&gt;GDG Cloud Nantes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;He’s also a &lt;a href="https://developers.google.com/community/experts/directory/profile/profile-julien_landur_C3_A9" rel="noopener noreferrer"&gt;GDE Cloud&lt;/a&gt;, a community reward from Google Cloud. His opinions were intended to be honest and unbiased. Apart from this research, the following official sources were used: &lt;a href="https://aws.amazon.com/developer/community/usergroups/europe/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;, Azure &amp;amp; &lt;a href="https://cloud.google.com/community/meetups" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://twitter.com/KarenTamrazyan" rel="noopener noreferrer"&gt;Karen Tamrazyan&lt;/a&gt; &amp;amp; &lt;a href="https://twitter.com/charlyx" rel="noopener noreferrer"&gt;Charles-Henri Guérin&lt;/a&gt; for the review of the article.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>community</category>
      <category>meetup</category>
    </item>
  </channel>
</rss>
