# 05_Claude et les MCP

# Quelles opportunités entre logiciels opensource et les IA

<div class="relative flex flex-col items-center gap-4" id="bkmrk-"><div class="flex items-center justify-center overflow-hidden" style="border-radius: 25%;"><div class="flex items-center justify-center bg-brand-500" style="width: 28px; height: 28px;"><svg class="text-white-default" fill="currentColor" height="21" viewbox="0 0 24 24" width="21" xmlns="http://www.w3.org/2000/svg"></svg></div></div></div><div class="flex min-w-0 flex-1 flex-col" id="bkmrk--1"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="mb-1 transition-all -ms-10 mt-0"><div class="text-hint flex h-auto w-full items-start gap-3 self-start"><div class="flex w-7 shrink-0 flex-col items-center pt-1.5"></div><div class="flex w-full min-w-0 items-center gap-3 overflow-hidden py-0.5"><div class="flex min-w-0 grow items-center gap-3 overflow-hidden"></div></div></div></div></div><div style="opacity: 1; height: auto;"><div class="select-none"></div></div></div></div>Voici une liste des **logiciels open source** dans les domaines de l’**infographie, la PAO, la CAO et la géomatique** qui, en **juin 2026**, bénéficient d’une **connexion MCP (Model Context Protocol)** ou pour lesquels des **serveurs MCP** existent ou sont en développement pour permettre à une IA de prendre la main sur la production de documents ou d’automatiser des tâches.

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk--2"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">---

</div></div></div></div></div>## **📌 Logiciels compatibles MCP (ou avec serveurs MCP disponibles)**

### **🎨 Infographie &amp; PAO**

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-scribus-logiciel-de-"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- **[Scribus](https://www.scribus.net/)** *Logiciel de PAO open source pour la mise en page professionnelle (brochures, magazines, livres).* **Statut MCP** : Des serveurs MCP communautaires permettent d’interagir avec les fichiers Scribus (lecture, modification, export) via des scripts Python ou des connecteurs dédiés. Cela permet à une IA de générer ou modifier des mises en page automatiquement<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">foxeet.fr</span></button></span></span>.
- **[Inkscape](https://inkscape.org/fr/)** *Éditeur de graphiques vectoriels (SVG) open source.* **Statut MCP** : Des serveurs MCP existent pour manipuler des fichiers SVG (création, édition, export) via des outils comme **Puppeteer** ou des scripts Python. Une IA peut ainsi générer ou modifier des visuels vectoriels<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">hauerpower.com</span></button></span></span>.
- **[GIMP](https://www.gimp.org/)** *Logiciel de retouche photo et de création graphique open source.* **Statut MCP** : Des connecteurs MCP permettent d’automatiser des tâches de retouche ou de génération d’images via des scripts (Python, Bash) ou des API externes<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">hauerpower.com</span></button></span></span>.

---

</div></div></div></div></div>### **🏗️ CAO (Conception Assistée par Ordinateur)**

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-freecad-logiciel-de-"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- **[FreeCAD](https://www.freecad.org/)** *Logiciel de CAO 3D paramétrique open source.* **Statut MCP** : Des serveurs MCP permettent d’interagir avec les fichiers FreeCAD (`.FCStd`) pour automatiser la création ou la modification de modèles 3D. Une IA peut ainsi générer des pièces ou des assemblages à partir de prompts textuels<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">diskod.ma</span><span class="text-hint ps-0.5 text-xs">+1</span></button></span></span>.
- **[Blender](https://www.blender.org/)** *Logiciel open source de modélisation 3D, animation et rendu.* **Statut MCP** : **Blender est explicitement cité comme compatible avec MCP** dans la documentation officielle. Des serveurs MCP permettent de contrôler Blender via Python (API Blender + MCP), ce qui permet à une IA de créer, modifier ou rendre des scènes 3D, ou même d’automatiser des workflows complexes (ex : génération de visuels pour l’architecture ou le design industriel)<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">de.wikipedia.org</span><span class="text-hint ps-0.5 text-xs">+1</span></button></span></span>.
- **[OpenSCAD](https://openscad.org/)** *Logiciel de CAO 3D open source basé sur un langage de script.* **Statut MCP** : Des serveurs MCP permettent d’exécuter des scripts OpenSCAD et de récupérer les modèles 3D générés, ce qui facilite l’automatisation de la conception paramétrique<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">diskod.ma</span><span class="text-hint ps-0.5 text-xs">+1</span></button></span></span>.

---

</div></div></div></div></div>### **🌍 Géomatique (SIG &amp; Cartographie)**

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-qgis-syst%C3%A8me-d%E2%80%99infor"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- **[QGIS](https://qgis.org/fr/site/)** *Système d’Information Géographique (SIG) open source le plus répandu.* **Statut MCP** : Des serveurs MCP permettent d’interagir avec QGIS via **PyQGIS** (Python) ou des API REST. Une IA peut ainsi :
    
    
    - Charger des couches géospatiales (Shapefile, GeoJSON, etc.).
    - Effectuer des analyses spatiales (requêtes, calculs de distances, etc.).
    - Générer des cartes ou des rapports automatiques<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">roelandtn.frama.io</span><span class="text-hint ps-0.5 text-xs">+1</span></button></span></span>.
- **[GRASS GIS](https://grass.osgeo.org/)** *SIG open source avancé pour l’analyse spatiale.* **Statut MCP** : Compatible via des scripts Python ou des connecteurs MCP dédiés pour automatiser des traitements géomatiques (ex : classification d’images satellite, modélisation de terrains)<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">roelandtn.frama.io</span></button></span></span>.
- **[GDAL/OGR](https://gdal.org/)** *Bibliothèque open source pour la manipulation de données géospatiales (raster/vectoriel).* **Statut MCP** : Des serveurs MCP permettent d’utiliser GDAL pour convertir, traiter ou analyser des données géospatiales (ex : transformation de formats, extraction de métadonnées)<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">roelandtn.frama.io</span><span class="text-hint ps-0.5 text-xs">+1</span></button></span></span>.
- **[PostGIS](https://postgis.net/)** *Extension spatiale pour PostgreSQL.* **Statut MCP** : Des serveurs MCP permettent d’interroger des bases de données PostGIS en langage naturel (ex : "Trouve tous les bâtiments à moins de 500m d’une rivière") et de générer des rapports ou des cartes automatiques<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">hauerpower.com</span></button></span></span>.

---

</div></div></div></div></div>## **🔌 Comment ça marche ?**

Le **Model Context Protocol (MCP)** est un **standard ouvert** créé par Anthropic et donné à la **Linux Foundation** en décembre 2025. Il permet à une IA (comme Claude, ChatGPT, ou des modèles locaux) de :

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-lire%2F%C3%A9crire-des-fich"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- **Lire/écrire des fichiers** (ex : `.svg`, `.fcstd`, `.qgs`).
- **Exécuter des fonctions** (ex : lancer un rendu dans Blender, une requête SQL dans PostGIS).
- **Interagir avec des API externes** (ex : Google Drive, GitHub, etc.).

</div></div></div></div></div>**Exemples concrets en 2026** :

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-un-agent-ia-peut-cr%C3%A9"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- Un agent IA peut **créer une carte thématique dans QGIS** à partir d’une demande en langage naturel.
- Une IA peut **générer un modèle 3D dans FreeCAD** en suivant un cahier des charges textuel.
- Un workflow automatisé peut **produire une affiche dans Scribus** en combinant du texte généré par IA et des images retouchées via GIMP<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">polarastudio.fr</span><span class="text-hint ps-0.5 text-xs">+3</span></button></span></span>.

---

</div></div></div></div></div>## **📂 Où trouver les serveurs MCP pour ces logiciels ?**

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-registry-officiel-%3A-"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- **Registry officiel** : [GitHub MCP Servers](https://github.com/modelcontextprotocol/servers) (plus de 1 000 serveurs open source en 2026).
- **Exemples de serveurs** : 
    - [MCP pour Blender](https://github.com/modelcontextprotocol/servers/tree/main/src/servers/blender)
    - [MCP pour QGIS](https://github.com/modelcontextprotocol/servers/tree/main/src/servers/qgis)
    - [MCP pour FreeCAD](https://github.com/modelcontextprotocol/servers/tree/main/src/servers/freecad)
    - [MCP pour PostGIS](https://github.com/modelcontextprotocol/servers/tree/main/src/servers/postgis) <span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">github.com</span><span class="text-hint ps-0.5 text-xs">+1</span></button></span></span>.

---

</div></div></div></div></div>## **💡 Cas d’usage avancés**

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-infographie-%3A-g%C3%A9n%C3%A9ra"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- **Infographie** : Génération automatique de visuels pour les réseaux sociaux (Inkscape + IA).
- **PAO** : Création de catalogues produits à partir d’une base de données (Scribus + MCP + PostGIS).
- **CAO** : Optimisation topologique de pièces mécaniques (FreeCAD + IA + MCP).
- **Géomatique** : Analyse de risques naturels à partir de données ouvertes (QGIS + GDAL + MCP).

---

</div></div></div></div></div>## **⚠️ Limites et précautions**

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-s%C3%A9curit%C3%A9-%3A-mcp-perme"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- **Sécurité** : MCP permet un accès profond aux outils. Il faut **contrôler les permissions** pour éviter des actions non désirées (ex : suppression de fichiers)<span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">de.wikipedia.org</span></button></span></span>.
- **Maturity** : Tous les logiciels n’ont pas encore de serveurs MCP officiels, mais la communauté développe activement des connecteurs.
- **Performances** : Pour les logiciels lourds (Blender, QGIS), une **machine puissante** est recommandée pour éviter les lenteurs.

---

</div></div></div></div></div>### **🔍 Pour aller plus loin**

<div class="flex min-w-0 flex-1 flex-col" id="bkmrk-documentation-offici"><div class="flex w-full flex-col gap-1 break-words" dir="auto"><div style="opacity: 1; height: auto;"><div class="select-none"><div class="markdown-container-style max-w-none gap-1 overflow-x-visible py-0 pb-0 font-normal text-wrap break-words select-text" data-message-part-type="answer" data-testid="text-message-part" dir="auto">- [Documentation officielle MCP](https://github.com/modelcontextprotocol)
- [Liste des serveurs MCP communautaires](https://github.com/modelcontextprotocol/servers)
- [Tutoriel : Connecter Blender à une IA via MCP](https://www.polarastudio.fr/blog/mcp-model-context-protocol-standard-ia-2026) <span data-exclude-copy="true" data-state="closed"><span class="ps-1"><button class="items-center justify-center text-center font-medium cursor-pointer outline-hidden focus-visible:ring-3 relative whitespace-nowrap transition-[background-color,border-color,color,fill,stroke,scale] focus-visible:ring-default focus-visible:ring-offset-1 aria-disabled:text-hint aria-disabled:cursor-not-allowed aria-busy:cursor-wait aria-busy:text-transparent aria-disabled:aria-busy:text-transparent bg-state-soft text-default [&:hover:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-hover [&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:bg-state-soft-press aria-disabled:bg-state-disabled h-6 px-1.5 text-xs rounded-md motion-safe:[&:active:not(:disabled):not([aria-disabled=true]):not([aria-busy=true])]:scale-[98.5%] inline-flex gap-0" type="button"><span class="text-xs">polarastudio.fr</span></button></span></span>

---

</div></div></div></div></div>**Question** : Souhaites-tu que je te guide pour configurer un serveur MCP spécifique (ex : Blender ou QGIS) ou explorer un cas d’usage précis dans ton domaine ?

# Retours de Claude et propositions d'intégration

## Contexte

*Analyse rédigée par Claude Code (Sonnet 4.6) le 2026-06-20, à partir d'une session de travail sur le PC Windows (eliob).*

Logiciels utilisés par Julien : **QGIS**, **Inkscape**, **GIMP**, **Scribus**. Ces quatre outils forment un workflow cohérent : QGIS pour la donnée géospatiale, Inkscape et Scribus pour la mise en page cartographique et la PAO, GIMP pour le traitement d'image.

---

## Note critique sur la page 227

La page 227 ("Quelles opportunités entre logiciels opensource et les IA") a vraisemblablement été générée par une IA en mode chatbot. Les liens GitHub vers `modelcontextprotocol/servers/tree/main/src/servers/blender`, `.../qgis`, `.../freecad`, `.../postgis` sont **probablement hallusinés** — ces sous-dossiers n'existent pas dans le dépôt officiel MCP. À vérifier systématiquement avant d'agir sur ces références.

---

## Analyse par logiciel

### QGIS — Priorité 1 (le plus mature et le plus pertinent)

**Ce qui existe réellement :** Le projet `qgis-mcp` (disponible sur GitHub, maintenu par la communauté) expose QGIS comme un serveur MCP local. Il s'installe comme un plugin Python dans QGIS et ouvre un socket local sur lequel Claude peut envoyer des commandes PyQGIS.

**Capacités concrètes :**
- Charger des couches (Shapefile, GeoJSON, WMS, PostGIS)
- Lancer des algorithmes de traitement (buffer, intersection, statistiques zonales)
- Modifier la symbologie d'une couche
- Exporter une carte en PNG/PDF

**Avantage spécifique à Julien :** La base PostGIS `alteris_geo` sur le VPS Alteris (79.137.14.202:5432) est déjà accessible. Un workflow QGIS + MCP + PostGIS permettrait à Claude de : interroger la base en SQL spatial → charger le résultat dans QGIS → générer une carte thématique → exporter en PDF, le tout en langage naturel.

**Contrainte :** QGIS doit être ouvert sur la machine locale. Le MCP communique via socket local (pas de pilotage à distance).

---

### Scribus — Priorité 2 (fort potentiel pour les rendus cartographiques)

**Ce qui existe réellement :** Scribus expose une API Python complète via son module `scribus` (accessible en mode headless : `scribus --python-script myscript.py`). Pas de serveur MCP publié, mais un **MCP custom est trivial à écrire** : un script Python qui reçoit des commandes MCP et les traduit en appels `scribus.*`.

**Capacités concrètes via Python headless :**
- Créer un document depuis un gabarit `.sla`
- Injecter du texte dans des cadres de texte nommés
- Placer des images
- Exporter en PDF

**Cas d'usage concret pour Julien :** Générer automatiquement une fiche de synthèse cartographique (titre, carte exportée depuis QGIS, texte de légende) en combinant la carte produite par QGIS MCP et un gabarit Scribus.

**Contrainte :** Scribus headless est instable sur certaines versions — à tester avec la version installée. La génération de gabarits `.sla` de référence est un prérequis.

---

### Inkscape — Priorité 3 (manipulation SVG, pas contrôle UI)

**Ce qui existe réellement :** Pas de serveur MCP dédié pour contrôler l'interface Inkscape. En revanche, deux approches sont réalistes :

1. **Manipulation directe du SVG** — le format natif d'Inkscape est du XML/SVG standard. Claude peut générer ou modifier des fichiers SVG avec Python (`lxml`, `svgwrite`) sans lancer Inkscape.
2. **Inkscape CLI** — `inkscape --actions="verb1;verb2"` permet des opérations batch (export PNG/PDF, conversion de formats) pilotables depuis un MCP.

**Cas d'usage concret pour Julien :** Modifier programmatiquement une carte SVG exportée depuis QGIS (changer des couleurs, ajouter un texte, insérer un logo) avant intégration dans Scribus.

**Contrainte :** L'automatisation reste au niveau fichier, pas au niveau interactif. Pour un usage cartographique, QGIS couvre déjà la plupart des besoins de rendu.

---

### GIMP — Priorité 4 (batch uniquement)

**Ce qui existe réellement :** GIMP peut être piloté en batch via `gimp --no-interface --batch='(script-fu-batch-list ...)'` ou via Python-Fu (`gimp --batch`). Un MCP custom est faisable.

**Cas d'usage concret pour Julien :** Traitement en masse d'images pour intégration dans des documents Scribus (redimensionnement, recadrage, conversion CMJN, compression). Moins critique si StirlingPDF couvre déjà les besoins de compression.

**Contrainte :** GIMP batch est lent au démarrage. Pour du traitement image simple, des alternatives Python (`Pillow`, `ImageMagick`) sont plus légères à wraper en MCP.

---

## Proposition de workflow intégré

```
PostGIS (Alteris) ──→ QGIS MCP ──→ carte exportée (PNG/PDF)
                                         │
                                         ▼
                              Inkscape CLI (ajustements SVG)
                                         │
                                         ▼
                              Scribus headless (mise en page finale)
                                         │
                                         ▼
                              StirlingPDF (compression)
```

Claude pilote l'ensemble de la chaîne : de la requête spatiale jusqu'au document final, sans intervention manuelle.

---

## Ordre de priorité pour une mise en œuvre

| Priorité | Outil | Action | Effort |
|---|---|---|---|
| 1 | QGIS | Installer le plugin `qgis-mcp`, tester avec une couche PostGIS | Faible |
| 2 | Scribus | Écrire un MCP custom Python headless, créer un gabarit `.sla` de référence | Moyen |
| 3 | Inkscape | MCP de manipulation SVG via `lxml` + CLI export | Faible |
| 4 | GIMP | MCP batch Python-Fu | Moyen (faible priorité) |

**Recommandation : commencer par QGIS.** C'est le maillon central du workflow de Julien, le MCP existe déjà, et la base PostGIS Alteris est immédiatement exploitable.

# QGIS MCP

## Références

- Plugin officiel : [plugins.qgis.org/plugins/qgis_mcp_plugin](https://plugins.qgis.org/plugins/qgis_mcp_plugin/) — version 0.5.0, compatible QGIS 3.28–4.x
- Dépôt GitHub : [nkarasiak/qgis-mcp](https://github.com/nkarasiak/qgis-mcp) — 102 outils MCP (couches, traitements, symbologie, export, mise en page/atlas, SQL cross-couches)

---

## Architecture

```
Claude Code ↔ Serveur MCP Python (uvx) ↔ socket TCP local ↔ Plugin QGIS (dock "QGIS MCP")
```

Le plugin QGIS crée un serveur TCP local. Le serveur MCP Python (lancé par Claude Code via `uvx`) s'y connecte. QGIS doit être ouvert et le serveur démarré avant de lancer Claude Code.

---

## Prérequis

Installer `uv` (gestionnaire de paquets Python) si absent :

```powershell
winget install astral-sh.uv
```

---

## Installation

### 1. Plugin dans QGIS

`Extensions` > `Installer/Gérer les extensions` > chercher **QGIS MCP** > Installer.

Redémarrer QGIS, puis cliquer **Start Server** dans le dock QGIS MCP.

### 2. Enregistrer le MCP dans Claude Code

```
claude mcp add -s user qgis -- uvx --from https://github.com/nkarasiak/qgis-mcp/archive/refs/heads/main.zip qgis-mcp-server
```

`-s user` = enregistrement global (tous les projets Claude Code).

### 3. Vérifier

Dans une session Claude Code avec QGIS ouvert et le serveur démarré, demander `diagnose` — confirme la synchronisation plugin/serveur MCP.

---

## Ordre de démarrage (à chaque session)

1. Ouvrir QGIS
2. Cliquer **Start Server** dans le dock QGIS MCP
3. Lancer Claude Code

---

## Authentification (optionnel, machines partagées)

Définir `QGIS_MCP_TOKEN=votre-secret` dans l'environnement QGIS, redémarrer le serveur, puis ajouter la même variable à la config MCP :

```json
"env": { "QGIS_MCP_TOKEN": "votre-secret" }
```

---

## Capacités principales

| Catégorie | Exemples |
|---|---|
| Couches | Charger Shapefile, GeoJSON, WMS, PostGIS ; lister, supprimer |
| Traitements | Buffer, intersection, statistiques zonales (1000+ algorithmes) |
| Symbologie | Modifier couleurs, classification, étiquettes |
| Export | PNG, PDF, carte mise en page |
| SQL cross-couches | Requêtes spatiales directement depuis Claude |
| Mise en page/Atlas | Créer et exporter des atlas cartographiques |

---

## Lien avec PostGIS Alteris

La base `alteris_geo` (79.137.14.202:5432, user `alteris_admin`) est directement utilisable depuis QGIS MCP : Claude peut interroger PostGIS en SQL spatial, charger le résultat comme couche QGIS, styliser et exporter en PDF — sans intervention manuelle.

Le port 5432 n'est pas exposé publiquement (pare-feu VPS). Connexion via tunnel SSH obligatoire (voir section Tunnel SSH).

---

## Tunnel SSH vers PostGIS Alteris

### Option 1 — Tunnel manuel (terminal externe)

Lancer avant QGIS/Claude Code :

```powershell
ssh -L 5433:localhost:5432 debian@79.137.14.202 -N
```

Puis se connecter sur `localhost:5433` au lieu de `79.137.14.202:5432`.

### Option 2 — Tunnel automatique via paramiko (PyQGIS) ✓ validé 2026-06-20

Ouvre le tunnel directement depuis la console Python QGIS, sans terminal externe.

#### Installation de paramiko (une seule fois)

`sys.executable` dans QGIS pointe sur `qgis-bin.exe` — `subprocess` est inutilisable. Utiliser pip en interne :

```python
from pip._internal.cli.main import main as pip_main
pip_main(['install', 'paramiko'])
```

Paramiko s'installe dans `C:\Users\eliob\AppData\Roaming\Python\Python312\site-packages` (dossier utilisateur). QGIS ne l'inclut pas automatiquement dans `sys.path` — ajouter à chaque session :

```python
import sys
sys.path.insert(0, r'C:\Users\eliob\AppData\Roaming\Python\Python312\site-packages')
import paramiko
print(paramiko.__version__)  # 5.0.0
```

#### Script du tunnel (à lancer après l'import paramiko)

```python
import socket
import threading
import select

class SSHTunnel(threading.Thread):
    def __init__(self, ssh_host, ssh_user, ssh_password,
                 remote_port, local_port=5433, remote_host='127.0.0.1'):
        super().__init__(daemon=True)
        self.ssh_host = ssh_host
        self.ssh_user = ssh_user
        self.ssh_password = ssh_password
        self.remote_host = remote_host
        self.remote_port = remote_port
        self.local_port = local_port
        self._stop = threading.Event()
        self.transport = None
        self.server_sock = None

    def run(self):
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(self.ssh_host, username=self.ssh_user, password=self.ssh_password)
        self.transport = client.get_transport()
        self.server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_sock.bind(('127.0.0.1', self.local_port))
        self.server_sock.listen(5)
        self.server_sock.settimeout(1.0)
        print(f"[Tunnel] localhost:{self.local_port} → {self.ssh_host} → {self.remote_host}:{self.remote_port}")
        while not self._stop.is_set():
            try:
                conn, _ = self.server_sock.accept()
                threading.Thread(target=self._forward, args=(conn,), daemon=True).start()
            except socket.timeout:
                continue
        self.server_sock.close()
        client.close()
        print("[Tunnel] Arrêté.")

    def _forward(self, local_conn):
        try:
            chan = self.transport.open_channel(
                'direct-tcpip',
                (self.remote_host, self.remote_port),
                local_conn.getpeername()
            )
        except Exception as e:
            print(f"[Tunnel] Erreur canal : {e}")
            local_conn.close()
            return
        while True:
            r, _, _ = select.select([local_conn, chan], [], [], 5)
            if local_conn in r:
                data = local_conn.recv(4096)
                if not data:
                    break
                chan.send(data)
            if chan in r:
                data = chan.recv(4096)
                if not data:
                    break
                local_conn.send(data)
        chan.close()
        local_conn.close()

    def stop(self):
        self._stop.set()


tunnel = SSHTunnel('79.137.14.202', 'debian', 'RAW+NEXTE!', remote_port=5432, local_port=5433)
tunnel.start()

import time; time.sleep(1)
s = socket.socket(); s.settimeout(3)
try:
    s.connect(('127.0.0.1', 5433)); print("Tunnel OK")
except Exception as e:
    print(f"Tunnel KO : {e}")
finally:
    s.close()
```

Pour arrêter : `tunnel.stop()`

Les avertissements `DEPRECATION: Unexpected import of 'paramiko.*'` sont inoffensifs (pip qui se plaint d'imports tardifs dans la même session).

---

## Connexion PostGIS depuis PyQGIS

### Charger une couche PostGIS

```python
from qgis.core import QgsProject, QgsVectorLayer

uri = (
    "host=localhost port=5433 dbname=alteris_geo "
    "user=alteris_admin password=Alteris2026 sslmode=disable "
    'table="altfoncier_28051"."28051_plu_zonage" (geom) sql='
)
layer = QgsVectorLayer(uri, "PLU Zonage 28051", "postgres")
QgsProject.instance().addMapLayer(layer)
```

### Point clé — champ JSONB `props`

Les tables altfoncier ont leurs attributs métier dans un champ `props` de type JSONB. Dans PyQGIS, ce champ est un **dict Python** (pas une chaîne). Pour y accéder en expression QGIS :

```
map_get("props", 'typezone')   ✓ correct
"props" LIKE '%typezone%'      ✗ ne fonctionne pas (props n'est pas une chaîne)
```

### Stylisation CNIG PLU (zonage)

Renderer catégorisé sur `map_get("props", 'typezone')` :

| typezone | Couleur remplissage | Couleur bordure | Label |
|---|---|---|---|
| U | `#FFFF73` | `#A3A300` | Zones urbaines |
| AU | `#FFAA00` | `#CD6600` | Zones à urbaniser |
| A | `#D3FFBE` | `#267300` | Zones agricoles |
| N | `#98E600` | `#267300` | Zones naturelles et forestières |

```python
from qgis.core import QgsCategorizedSymbolRenderer, QgsRendererCategory, QgsFillSymbol

cnig = {
    'U':  ('#FFFF73', '#A3A300', 'Zones urbaines (U)'),
    'AU': ('#FFAA00', '#CD6600', 'Zones à urbaniser (AU)'),
    'A':  ('#D3FFBE', '#267300', 'Zones agricoles (A)'),
    'N':  ('#98E600', '#267300', 'Zones naturelles et forestières (N)'),
}
categories = []
for tz, (fill, border, label) in cnig.items():
    symbol = QgsFillSymbol.createSimple({
        'color': fill, 'outline_color': border, 'outline_width': '0.26'
    })
    categories.append(QgsRendererCategory(tz, symbol, label))

renderer = QgsCategorizedSymbolRenderer("map_get(\"props\", 'typezone')", categories)
layer.setRenderer(renderer)
layer.triggerRepaint()
```

---

## Journal de session

### 2026-06-20 — Premier diagnostic validé

Première connexion réussie entre Claude Code et QGIS via MCP. Résultat du `diagnose` :

| Vérification | Résultat |
|---|---|
| QGIS | 4.0.3-Norrköping |
| Python | 3.12.13 |
| Qt | 6.11.0 |
| Plugin MCP | 0.5.0 (serveur et plugin synchronisés) |
| Clients connectés | 1 |
| Providers de traitement | 3d, gdal, grass, model, native, pdal, project, qgis, quickosm, script |
| Projet ouvert | Aucun (layer_count = 0) |

Statut global : **healthy**. Stack complète et opérationnelle.

### 2026-06-20 — Connexion PostGIS Alteris + stylisation CNIG

- Connexion PostGIS directe (`79.137.14.202:5432`) → **timeout** (port filtré par pare-feu VPS)
- Tunnel SSH manuel (`ssh -L 5433:localhost:5432 debian@79.137.14.202 -N`) → **connexion OK**
- Couche `altfoncier_28051.28051_plu_zonage` chargée (20 entités, commune 28051)
- Stylisation CNIG appliquée et validée visuellement
- **Point clé découvert** : le champ `props` (JSONB PostGIS) arrive comme **dict Python** dans PyQGIS — utiliser `map_get("props", 'typezone')` en expression, pas LIKE sur chaîne

### 2026-06-20 — Tunnel paramiko validé

- `sys.executable` = `qgis-bin.exe` → subprocess inutilisable pour pip
- Installation via `pip._internal.cli.main` → paramiko 5.0.0 installé dans le dossier utilisateur
- `sys.path.insert(0, ...)` nécessaire à chaque session (QGIS n'inclut pas le dossier utilisateur)
- Tunnel paramiko démarré → **Tunnel OK** confirmé
- Couche PostGIS chargée via `localhost:5433` → **succès**

**Prochaine étape :**
- Exporter la carte en PDF via mise en page QGIS

# Inkscape MCP

## Vue d'ensemble

Inkscape est un éditeur de graphisme vectoriel open source basé sur le format SVG. À la différence de QGIS, **il n'existe pas de plugin MCP officiel pour Inkscape** en juin 2026 — mais plusieurs approches sont réalistes, avec des niveaux d'effort et de puissance différents.

---

## Approches possibles

| Approche | Effort | Puissance | Statut |
|---|---|---|---|
| 1. Manipulation SVG directe | Aucun | Élevée (structure du fichier) | Fonctionne déjà |
| 2. MCP wrapper CLI Inkscape | Moyen (écrire un serveur MCP) | Moyenne (opérations CLI) | À construire |
| 3. Extension Inkscape + serveur TCP | Élevé (plugin Python Inkscape) | Maximale (GUI + rendu live) | À construire |

---

## Approche 1 — Manipulation SVG directe (disponible maintenant)

SVG est du XML texte. Claude peut lire, écrire et modifier des fichiers `.svg` directement avec ses outils natifs, sans aucune intégration supplémentaire.

### Ce que Claude peut déjà faire

- Lire la structure d'un SVG (éléments, attributs, groupes, calques Inkscape)
- Créer un SVG complet de zéro (formes, textes, chemins, dégradés)
- Modifier des propriétés : couleur, opacité, stroke, transform, viewBox
- Réorganiser les éléments dans l'arbre XML
- Ajouter/supprimer des calques (`<g inkscape:label="..." inkscape:groupmode="layer">`)
- Générer des motifs répétitifs, des grilles, des mises en page structurées

### Limites de cette approche

- Pas de rendu live dans Inkscape — il faut rouvrir ou recharger le fichier
- Les opérations complexes (nœuds de chemin, boolean ops) sont difficiles à écrire à la main en SVG
- Pas d'accès aux filtres Inkscape (flou, ombres) via simple édition XML

### Flux de travail type

```
Claude génère/modifie le .svg → tu recharges dans Inkscape (Ctrl+Z+rechargement) → ajustements manuels → boucle
```

---

## Approche 2 — MCP wrapper CLI Inkscape

Inkscape expose une interface en ligne de commande (`--actions`) qui permet d'automatiser des opérations sans ouvrir l'interface graphique.

### Commandes CLI Inkscape utiles

```powershell
# Export PNG depuis SVG
inkscape --export-type=png --export-filename=sortie.png source.svg

# Export PDF
inkscape --export-type=pdf --export-filename=sortie.pdf source.svg

# Appliquer une transformation et exporter
inkscape --actions="select-all;object-set-attribute:transform,scale(2)" source.svg --export-type=svg --export-filename=sortie.svg

# Convertir texte en chemins
inkscape --actions="select-all;object-to-path" source.svg --export-type=svg --export-filename=sortie.svg

# Obtenir les dimensions du document
inkscape --query-all source.svg
```

### Architecture d'un serveur MCP CLI

```
Claude Code ↔ Serveur MCP Python (stdio) ↔ subprocess inkscape CLI ↔ fichiers SVG/PNG/PDF
```

Un serveur MCP Python minimal exposerait des outils comme :
- `export_png(svg_path, output_path, dpi=96)`
- `export_pdf(svg_path, output_path)`
- `convert_text_to_paths(svg_path, output_path)`
- `get_document_dimensions(svg_path)`
- `apply_inkscape_action(svg_path, actions, output_path)`

### Prérequis

```powershell
# Inkscape doit être dans le PATH
inkscape --version
# Inkscape 1.x.x (...)
```

Sur Windows, ajouter le dossier Inkscape au PATH système ou utiliser le chemin complet :
```
C:\Program Files\Inkscape\bin\inkscape.exe
```

---

## Approche 3 — Extension Inkscape + serveur TCP (pattern QGIS)

La plus puissante mais la plus complexe à mettre en place. Inkscape supporte les extensions Python via le module `inkex`. Une extension pourrait ouvrir un socket TCP local et exposer des outils MCP (similaire au plugin QGIS MCP).

### Architecture

```
Claude Code ↔ Serveur MCP Python (uvx) ↔ socket TCP local ↔ Extension Python Inkscape
```

### Ce que ça permettrait

- Accéder aux éléments sélectionnés dans l'interface
- Appliquer des opérations live (boolean, nœuds, effets)
- Lire la position exacte des objets
- Déclencher des menus et actions Inkscape programmatiquement

### Obstacles techniques

- Les extensions Inkscape s'exécutent en mode one-shot (lancées, exécutent, se ferment) — un serveur TCP persistant nécessite un thread daemon ou une extension de type "Effect" avec boucle
- L'API `inkex` est bien documentée mais moins riche que PyQGIS
- Aucun projet open source mature n'existe encore pour ce pattern (juin 2026)

---

## Capacités envisageables selon l'approche

| Capacité | Approche 1 (SVG direct) | Approche 2 (CLI) | Approche 3 (Extension) |
|---|---|---|---|
| Créer des formes géométriques | ✓ | ✓ | ✓ |
| Modifier couleurs / styles | ✓ | ✓ | ✓ |
| Gérer les calques | ✓ | — | ✓ |
| Exporter PNG/PDF | — | ✓ | ✓ |
| Convertir texte en chemins | — | ✓ | ✓ |
| Boolean operations | — | ✓ (CLI) | ✓ |
| Lire sélection courante | — | — | ✓ |
| Appliquer filtres Inkscape | — | ✓ (partiel) | ✓ |
| Batch processing de fichiers | ✓ | ✓ | — |
| Rendu live dans l'UI | — | — | ✓ |

---

## Cas d'usage concrets avec Inkscape

### Avec l'approche 1 (maintenant)

- Générer des pictogrammes SVG (icônes, logos simples) à partir d'une description
- Créer des gabarits de mise en page (grilles, marges, zones de texte)
- Modifier en batch les couleurs d'une charte graphique sur plusieurs fichiers SVG
- Produire des cartes thématiques simplifiées à partir de données (ex : export QGIS → SVG → habillage Claude)
- Générer des diagrammes (organigrammes, schémas techniques) en SVG structuré

### Avec l'approche 2 (à construire, effort ~2h)

- Pipeline : Claude génère SVG → CLI Inkscape exporte PDF → livraison automatique
- Batch conversion de SVG en PNG à différentes résolutions
- Optimisation SVG (texte en chemins avant impression)

---

## Lien avec le workflow cartographique

Inkscape est souvent utilisé en aval de QGIS pour l'habillage cartographique (polices, mise en page avancée, symboles non gérés par QGIS). Le flux naturel serait :

```
QGIS MCP → export SVG → Claude (habillage Inkscape SVG direct) → Inkscape (retouches manuelles) → export PDF final
```

QGIS peut exporter une mise en page en SVG via `Projet > Imprimer la mise en page > Exporter en SVG`. Claude peut ensuite enrichir ce SVG (légendes, encadrés, pictogrammes) avant ouverture dans Inkscape.

---

## Prochaine étape proposée

Trois options pour explorer concrètement :

1. **Test SVG direct** — Claude génère un SVG d'exemple (carte, diagramme, gabarit) et tu l'ouvres dans Inkscape pour voir le résultat
2. **Prototype CLI MCP** — écrire un serveur MCP Python minimal (~50 lignes) wrappant les exports Inkscape CLI, l'enregistrer dans Claude Code
3. **Exploration API inkex** — ouvrir la console Python d'Inkscape et tester quelques commandes `inkex` pour évaluer la faisabilité de l'approche 3

---

## Mise en place réalisée (2026-07-21)

L'approche 2 (MCP wrapper CLI Inkscape) a été implémentée sur le PC Windows eliob.

### Obstacle rencontré : version Microsoft Store (MSIX)

Le PC avait initialement Inkscape installé via le **Microsoft Store**, packagé en MSIX. Ce type de paquet s'est révélé **inutilisable pour un pilotage CLI** :

- L'exécutable dans `C:\Program Files\WindowsApps\...\inkscape.exe` refuse l'exécution directe (`Accès refusé`) — ACL verrouillées par le sandboxing du paquet.
- Aucun alias d'exécution (`AppExecutionAlias`) n'est déclaré dans le manifest (`AppxManifest.xml`) — impossible d'appeler `inkscape` depuis le PATH.
- `Invoke-CommandInDesktopPackage` (la seule méthode de lancement programmatique disponible) ouvre l'app en mode GUI sans retourner la main ni capturer stdout — inexploitable pour de l'automatisation headless (export, `--actions`, etc.).

**Conclusion : les versions Inkscape installées via le Microsoft Store ne conviennent pas à un usage MCP/CLI.** Il faut la version standalone.

### Fix — installation de la version standalone

```powershell
winget install --id Inkscape.Inkscape --source winget --accept-package-agreements --accept-source-agreements
```

- Package winget `Inkscape.Inkscape` (distinct du Store) → installe dans `C:\Program Files\Inkscape\bin\inkscape.exe`
- Version installée : **1.4.4** (dcaf3e7, 2026-05-05)
- Vérification : `& "C:\Program Files\Inkscape\bin\inkscape.exe" --version` répond correctement, exit code 0

**Important** : si une version Store est déjà présente, la désinstaller d'abord (Paramètres > Applications, ou `winget uninstall`) pour éviter la confusion entre les deux installations.

### Serveur MCP créé

- **Fichier** : `C:\Users\eliob\.claude\mcp_inkscape.py`
- **Framework** : `FastMCP` (même pattern que les autres MCP du projet — `mcp_portainer.py`, `mcp_jellyfin.py`, etc.)
- **Constante** : `INKSCAPE_EXE = r"C:\Program Files\Inkscape\bin\inkscape.exe"` (chemin en dur — adapter si l'installation standalone se fait ailleurs)

**Outils exposés** :

| Outil | Rôle |
|---|---|
| `get_version()` | Vérifie qu'Inkscape répond (smoke test) |
| `get_document_dimensions(svg_path)` | Dimensions et bounding boxes de tous les objets (`--query-all`) |
| `export_png(svg_path, output_path="", dpi=96)` | Export PNG |
| `export_pdf(svg_path, output_path="")` | Export PDF |
| `convert_text_to_paths(svg_path, output_path="")` | Convertit les textes en chemins vectoriels |
| `apply_inkscape_action(svg_path, actions, output_path="")` | Échappatoire générique — chaîne d'actions Inkscape libre (`;` séparées) |

Toutes les fonctions retournent `{ok, returncode, stdout, stderr}` (+ `output_path` pour les exports) — pas d'exception levée sur échec CLI, le code retour renseigne l'appelant.

### Enregistrement MCP

Ajouté dans `C:\Users\eliob\.mcp.json` :

```json
"inkscape": {
  "type": "stdio",
  "command": "python",
  "args": ["C:\\Users\\eliob\\.claude\\mcp_inkscape.py"],
  "env": {}
}
```

**Nécessite un rechargement de session Claude Code** pour que le nouveau serveur MCP soit détecté et ses outils exposés.

### Test réalisé

SVG de test (rectangle bleu + texte) → `export_png` → PNG de 1974 octets généré avec succès. `get_document_dimensions` retourne correctement les bounding box (`svg1,10,10,180,80` etc.) via `--query-all`.

### À reproduire demain sur le PC Windows Alteris

1. Vérifier si Inkscape est installé via le Store (`Get-AppxPackage -Name "*Inkscape*"`) — si oui, désinstaller
2. `winget install --id Inkscape.Inkscape --source winget --accept-package-agreements --accept-source-agreements`
3. Vérifier `C:\Program Files\Inkscape\bin\inkscape.exe --version`
4. Copier `mcp_inkscape.py` (script identique, chemin `INKSCAPE_EXE` à adapter si besoin)
5. Ajouter l'entrée `inkscape` dans le `.mcp.json` de la machine Alteris
6. Recharger la session Claude Code et tester `get_version()` + `export_png()` sur un SVG de test

---

## Confirmation de fonctionnement (2026-07-21, session rechargée)

Après rechargement de la session Claude Code, le serveur MCP `inkscape` est bien détecté et ses 6 outils sont exposés. Tests effectués dans l'ordre :

| Test | Résultat |
|---|---|
| `get_version()` | OK — `Inkscape 1.4.4 (dcaf3e7, 2026-05-05)` |
| `get_document_dimensions(svg_path)` sur un SVG de test (rect 80x80) | OK — `svg1,10,10,80,80` / `rect1,10,10,80,80` |
| `export_png(svg_path)` | OK — PNG généré (389 octets), fichier vérifié sur disque |

**Conclusion : le protocole MCP Inkscape est pleinement opérationnel** sur le PC Windows eliob, de bout en bout (Claude Code → serveur MCP Python → subprocess Inkscape CLI → fichier de sortie).

# LibreOffice MCP

> **État au 2026-10-02** : réinstallé sur le poste `julie` (le PC `eliob` après la réinstallation de Windows du 2026-08-08, qui avait emporté l'ancienne configuration). Extension **Nelson MCP 0.14.3**, serveur actif sur `127.0.0.1:8766` (process `soffice.bin`), MCP `nelson` inscrit **en portée utilisateur** dans `C:\Users\julie\.claude.json` — voir « Installation sur `julie` » ci-dessous.
>
> **État au 2026-08-05** : installé et opérationnel sur le PC Windows eliob. Extension **Nelson MCP 0.7.1**, LibreOffice **26.2.5.2**, serveur HTTP actif sur `127.0.0.1:8766`, **110 outils** exposés, MCP `nelson` enregistré dans Claude Code pour le projet `D:\Syncthing\Jux_univers\Claude-pcelio+jux`.
>
> Cette page a été réécrite le 2026-08-05 à partir de l'installation réelle. La version précédente reposait sur une recherche web et contenait plusieurs erreurs — voir la section « Erreurs de la version précédente » en fin de page.

# Nelson MCP — piloter LibreOffice depuis Claude Code

## Le principe

Nelson n'est **pas** un serveur MCP séparé qu'on lance à côté de LibreOffice. C'est une extension `.oxt` qui **embarque un serveur HTTP dans le process `soffice.bin`**. Claude Code s'y connecte en HTTP et pilote les documents **ouverts dans la fenêtre LibreOffice**, en direct, via l'API UNO.

Conséquences pratiques :

- pas de commande à lancer, pas de service à installer — LibreOffice ouvert = serveur disponible
- toutes les opérations UNO tournent sur le **thread principal** de LibreOffice (sécurité thread)
- on voit les modifications apparaître à l'écran pendant que l'IA travaille
- fermer LibreOffice ferme le serveur

- **Projet** : `quazardous/nelson-mcp` (lobehub n'est qu'un annuaire de serveurs MCP, pas la source)
- **Auteur** : David Berlioz — licence MPL 2.0
- **Version installée** : 0.7.1
- **Dépendance déclarée** : LibreOffice ≥ 4.1 (le `.oxt` embarque son propre Python 3.12 et ses libs)

---

## Installation

### 1. Extension

`Outils → Gestionnaire des extensions → Ajouter` → sélectionner `nelson.oxt`, puis redémarrer LibreOffice.

Emplacement après installation (Windows) :

```
%APPDATA%\LibreOffice\4\user\uno_packages\cache\uno_packages\<hash>.tmp_\nelson.oxt\
```

Le sous-dossier `<hash>.tmp_` est généré par LibreOffice — sur eliob : `lu3692afhf.tmp_`. Ne pas le coder en dur dans un script, le retrouver par `Get-ChildItem ...\uno_packages -Recurse -Filter nelson.oxt`.

### 2. Démarrage du serveur

Menu **Nelson → Start HTTP Server** (le libellé bascule en « Stop HTTP Server » quand il tourne, avec icône d'état vert/orange/gris). Le serveur peut aussi démarrer automatiquement si `http.enabled` est coché dans les options.

Valeurs par défaut : **host `localhost`, port `8766`**. Modifiables dans `Outils → Options → Nelson`, ainsi que TLS (`use_ssl`, `ssl_cert`, `ssl_key`).

Vérifier :

```powershell
netstat -ano | Select-String ":8766"
```

Le PID retourné doit être celui de `soffice.bin`.

### 3. Enregistrement dans Claude Code

```bash
claude mcp add --transport http nelson http://127.0.0.1:8766/mcp
```

Écrit dans `C:\Users\eliob\.claude.json`, section `projects > <projet> > mcpServers`. Vérifier avec `claude mcp list` → `nelson: ✔ Connected`.

**Relancer la session Claude Code** : les outils d'un MCP ajouté en cours de session ne sont pas chargés.

### 3 bis. Installation sur le poste `julie` (2026-10-02)

Le CLI `claude` n'est pas dans le PATH de ce poste (application de bureau) : `claude mcp add` est impossible, l'entrée s'écrit **à la main** dans `C:\Users\julie\.claude.json` (sauvegarder d'abord), puis on ferme et rouvre l'application.

`json
mcpServers: {
  nelson: { type: http, url: http://127.0.0.1:8766/mcp }
}
`

**⚠ Piège : en portée projet, le MCP n'est pas chargé.** Première inscription sous `projects > D:\Syncthing\Jux_univers\Claude-pcelio+jux\.claude > mcpServers` : après redémarrage, aucun outil Nelson — et pas davantage ceux de `bookstack` ou `kdrive`, inscrits au même endroit. Seul `caldav`, en **portée utilisateur** (`mcpServers` à la racine du fichier), était chargé. Nelson a donc été déplacé à la racine. Contrepartie : ses outils sont présents dans toutes les sessions, quel que soit le projet.

**Nombre d'outils variable.** Le `tools/list` interrogé en HTTP, LibreOffice ouvert sans document, n'a rendu que **26 outils** (gestion de documents, images, formes, styles, jobs, `batch_execute`) au lieu des 110 relevés en août. Les outils Writer, Calc ou Draw n'apparaissent que si un document de ce type est actif — ouvrir le document **avant** de démarrer la session.

---

## Endpoints HTTP

| Méthode | Chemin | Rôle |
|---|---|---|
| POST/GET/DELETE | `/mcp` | MCP Streamable HTTP (transport actuel) — c'est celui à utiliser |
| POST/GET | `/sse`, `/messages` | ancien transport SSE, pour clients qui ne gèrent pas Streamable HTTP |
| GET | `/health` | état du serveur |
| GET | `/api/tools` | référence HTML de tous les outils |
| GET | `/` | page d'info |
| GET/POST | `/api/config` | lecture/écriture de la configuration — **désactivé par défaut** (`http.enable_config_api`) |

Test de connexion en une commande :

```bash
curl -s -X POST http://127.0.0.1:8766/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'
```

Réponse attendue : `serverInfo: {"name":"Nelson MCP","version":"0.7.1"}` + un en-tête `Mcp-Session-Id`.

---

## Ce que ça permet réellement — 110 outils

### Writer

Lecture et écriture par paragraphe (`read_paragraphs`, `insert_at_paragraph`, `set_paragraph_text`, `delete_paragraph`, `duplicate_paragraph`, `insert_paragraphs_batch`), styles, plan/outline, tableaux, images, cadres, hyperliens, commentaires, statistiques, recherche/remplacement, **suivi des modifications** (`accept`/`reject`), navigation (signets, titres, proximité), et un **index plein texte** avec stemming (33 langues embarquées via snowballstemmer, base SQLite embarquée).

### Calc

Cellules, lignes, feuilles, formules, graphiques, formatage conditionnel, commentaires, navigation, recherche, et un **détecteur d'erreurs** de feuille.

### Draw / Impress

Pages, formes, masques, notes, transitions, espaces réservés.

### Transversal

`execute_batch` (plusieurs appels en une requête, avec **chaînage de variables** entre opérations), `undo`/`redo`, `document_health_check` (titres vides, sauts de niveau de titre, images orphelines…), ouverture/création/sauvegarde/export/impression, jobs asynchrones (`list_jobs`, `get_job`).

### Galeries et images IA

- Galerie d'images indexée depuis un dossier, avec recherche plein texte et métadonnées **XMP en fichier sidecar**
- Galerie de documents indexée depuis un dossier
- Génération et édition d'images (`generate_image`, `edit_image`) via **AI Horde**, **OpenAI-compatible**, ou **Stable Diffusion WebUI Forge** local
- Fournisseur LLM **Ollama** pour l'indexation/description automatique

### Fonction `=PROMPT()` dans Calc

Fournie par `XPromptFunction.rdb` — interroge un fournisseur IA directement depuis une formule de feuille de calcul.

---

## Deux garde-fous activés par défaut

Ce sont les réglages les plus importants de la page Options (`core`) :

| Réglage | Défaut | Effet |
|---|---|---|
| `force_track_changes` | **activé** | active l'enregistrement des modifications avant toute mutation venant du MCP — **toutes les modifications de l'IA sont visibles et réversibles** |
| `follow_activity` | **activé** | fait défiler la vue vers l'endroit modifié, en temps réel |

À ne pas désactiver sans raison : c'est ce qui rend une session d'édition par IA relisable.

Le panneau latéral Nelson (deck `NelsonDeck`, visible dans Writer et Calc) affiche le **journal des actions MCP** et les jobs en cours.

---

## Presets — réduire la surface d'outils

110 outils, c'est beaucoup de contexte. Nelson permet de créer des **endpoints filtrés** : `Options → Nelson → MCP → preset` puis bouton de création. L'endpoint apparaît sur `/mcp/<preset>`.

| Preset | Contenu |
|---|---|
| `minimal` | 8 outils — lister/ouvrir/créer/sauver, lire et insérer des paragraphes, insérer une image |
| `writer-read` | 14 outils, lecture seule |
| `writer-edit` | 24 outils d'édition Writer |
| `calc` | 13 outils tableur |
| `gallery` | 10 outils images/documents |

Utile pour un modèle plus petit, ou pour interdire les mutations (`writer-read`).

---

## Launchers intégrés — l'autre façon de faire

Nelson sait **lancer lui-même** Claude Code (aussi Gemini CLI et OpenCode), depuis LibreOffice. Le module `launcher.claude` crée alors un espace de travail dans :

```
~/.local/share/nelson/cli/claude
```

et y génère automatiquement :

- `.mcp.json` pointant sur `<url>/mcp`
- `.claude/settings.json` avec `"allow": ["mcp__nelson__*"]` — **auto-approbation de tous les outils Nelson**
- `CLAUDE.md` (méta-prompt fourni par l'extension)
- `.claude/skills/nelson/SKILL.md`

puis lance `claude --resume` dans ce dossier.

**Choix retenu sur eliob** : ne pas utiliser le launcher, et brancher le MCP sur les projets existants. Raisons — l'espace de travail du launcher est isolé et perd le `CLAUDE.md` du Jux_univers ; et l'auto-approbation globale `mcp__nelson__*` inclut les outils de mutation et de sauvegarde de fichiers.

---

## Tunnels — accès distant

Modules `tunnel.cloudflare`, `tunnel.ngrok`, `tunnel.tailscale` (Funnel), `tunnel.bore` : exposent le serveur HTTP local à l'extérieur, pour piloter LibreOffice depuis un client MCP distant (l'aide embarquée documente le cas ChatGPT via Tailscale).

**À manier avec prudence** : cela publie sur internet un endpoint qui peut lire, modifier et enregistrer des fichiers locaux. Le serveur écoute par défaut sur `localhost` uniquement — c'est le bon réglage. Voir l'incident du 2026-06-09 sur l'API RC rclone exposée en `0.0.0.0` (VPS Jux), même schéma de risque.

---

## Aide embarquée

L'extension embarque sa propre documentation HTML, consultable directement :

```
<...>\nelson.oxt\help\index.html
```

Fichiers utiles : `writer.html` (33 Ko), `calc.html` (20 Ko), `draw.html`, `batch.html`, `documents.html`, `ai_images.html`, et des how-to (`howto_connect-chatgpt-tailscale`, `howto_setup-ollama-indexation`, `howto_setup-image-gallery`, `howto_setup-forge-images`).

---

## Points de vigilance

- **Un document doit être ouvert** dans LibreOffice pour la plupart des outils ; les outils sont filtrés selon le type de document actif (writer / calc / draw)
- **Fermer LibreOffice coupe le MCP** — Claude Code affichera le serveur comme déconnecté
- Le dossier d'installation contient un `<hash>.tmp_` variable : ne jamais le figer dans un script
- Le port par défaut est **8766**, pas 8765
- `/api/config` (écriture de configuration en HTTP) est désactivé par défaut : le laisser ainsi
- L'extension embarque `pysqlite3` compilé (`_sqlite3.cp312-win_amd64.pyd`) — l'installation est liée à Python 3.12 de LibreOffice

---

## Erreurs de la version précédente de cette page

La version antérieure de cette page avait été rédigée à partir d'une recherche web, avant installation. Corrections :

| Ancienne affirmation | Réalité vérifiée |
|---|---|
| Port 8765 | Port **8766** (`cfg.get("port") or 8766` dans `modules/http/__init__.py`) |
| `python3 -m nelson_mcp --port 8765` | Cette commande n'existe pas — le serveur est **embarqué dans `soffice.bin`** |
| Variante de configuration `stdio` | Inexistante — HTTP uniquement |
| `.oxt` à télécharger « depuis lobehub » | lobehub est un annuaire ; la source est le dépôt `quazardous/nelson-mcp` |
| LibreOffice ≥ 24.2 requis | La dépendance déclarée est **4.1** ; LibreOffice 26.2.5.2 en place |
| Alternatives `libreoffice-mcp` (KeithCu) port 2083, `OooDev` | Sans objet une fois Nelson en place |

# Baikal MCP pour la gestion des agendas perso et NEXTE et de l'observatoire des tâches

# 1-l'infrastructure Baikal et Infcloud sur le vps personnel - juxjux.ovh

<div id="bkmrk-" style="clear: left;">  
</div>Cas à part de la gestion des contacts et du calendrier -   
Non pas sur alteris.ovh mais sur juxjux.ovh car primauté de l'infrastructure personnelle de JUlien pour la gestion des agendas et des contacts.

# la solution contacts et calendrier

<div id="bkmrk--1">  
</div>Solution d'unification des contacts et du calendrier de Julien.

**État au 3 août 2026 — déployé et opérationnel de bout en bout.** Seul vdirsyncer (agrégation Google/Infomaniak) reste à faire.

---

# 1. Objectif

Unifier la base de contacts et de calendriers, dispersée entre plusieurs sources (Google, Infomaniak, téléphone), autour d'un point central auto-hébergé sur le VPS juxjux.ovh, synchronisé vers tous les clients existants.

Baïkal est la **source de vérité unique**. Roundcube, InfCloud et DAVx5 en sont des clients ; vdirsyncer sera le seul composant qui écrit depuis l'extérieur vers Baïkal.

# 2. Architecture réellement déployée

<table id="bkmrk-composant-r%C3%B4le-empla"><thead><tr><th>Composant</th><th>Rôle</th><th>Emplacement</th><th>État</th></tr></thead><tbody><tr><td>Baïkal</td><td>Serveur CardDAV + CalDAV, stockage central</td><td>Docker, VPS juxjux</td><td>En service</td></tr><tr><td>InfCloud</td><td>Agenda **et** contacts web</td><td>Statique, servi par nginx</td><td>En service</td></tr><tr><td>Roundcube</td><td>Interface web des contacts</td><td>Docker, VPS juxjux</td><td>En service</td></tr><tr><td>Nginx</td><td>Reverse proxy HTTPS</td><td>Hôte VPS</td><td>En service</td></tr><tr><td>DAVx5</td><td>Pont de synchronisation vers Android</td><td>Téléphone</td><td>**Opérationnel**</td></tr><tr><td>Fossify Agenda</td><td>Client calendrier</td><td>Téléphone (via DAVx5)</td><td>Opérationnel</td></tr><tr><td>vdirsyncer</td><td>Agrège Google et Infomaniak vers Baïkal</td><td>VPS, cron</td><td>**Non déployé**</td></tr><tr><td>Thunderbird</td><td>Client mail, indépendant de ce circuit</td><td>Téléphone</td><td>Inchangé</td></tr></tbody></table>

# 3. Accès

<table id="bkmrk-service-url-identifi"><thead><tr><th>Service</th><th>URL</th><th>Identifiant</th></tr></thead><tbody><tr><td>**Agenda + contacts web**</td><td>https://baikal.juxjux.ovh/infcloud/</td><td>`Julien` (compte Baïkal)</td></tr><tr><td>Contacts web (Roundcube)</td><td>https://secretariat.juxjux.ovh</td><td>`julien.bertrand@ik.me` (compte mail Infomaniak)</td></tr><tr><td>Administration Baïkal</td><td>https://baikal.juxjux.ovh/admin/</td><td>compte admin Baïkal</td></tr><tr><td>Découverte DAV (DAVx5)</td><td>`https://baikal.juxjux.ovh/dav.php/`</td><td>`Julien`</td></tr></tbody></table>

**Utilisateur Baïkal : `Julien` — avec un J majuscule.** La casse compte dans les URLs DAV.

- Principal : `https://baikal.juxjux.ovh/dav.php/principals/Julien/`
- Contacts : `https://baikal.juxjux.ovh/dav.php/addressbooks/Julien/default/`
- Calendrier : `https://baikal.juxjux.ovh/dav.php/calendars/Julien/default/`

Certificats Let's Encrypt valides jusqu'au **1er novembre 2026** pour les deux domaines. Celui de `baikal.juxjux.ovh` existait déjà mais avait **expiré le 8 février 2026** (vestige d'une tentative antérieure) ; il a été renouvelé le 3 août.

# 4. Déploiement

Stack sur le VPS : `/home/debian/baikal/docker-compose.yml`, projet compose `baikal`. Sources versionnées : `Syncthing/Jux_univers/Jux-scripts/Baikal-Contacts/`.

<table id="bkmrk-fichier-r%C3%B4le-docker-"><thead><tr><th>Fichier</th><th>Rôle</th></tr></thead><tbody><tr><td>`docker-compose.yml`</td><td>Stack Baïkal + Roundcube</td></tr><tr><td>`baikal.juxjux.ovh.conf`</td><td>Vhost Baïkal (`.well-known` forcés en HTTPS + service d'InfCloud)</td></tr><tr><td>`secretariat.juxjux.ovh.conf`</td><td>Vhost Roundcube</td></tr><tr><td>`infcloud-config.js`</td><td>Configuration InfCloud</td></tr><tr><td>`finaliser_tls.sh`</td><td>Vérifie le DNS puis lance certbot</td></tr><tr><td>`vps_backup.sh`</td><td>Script de sauvegarde complet, Baïkal inclus</td></tr><tr><td>`push_bookstack.py`</td><td>Publication de cette page via l'API BookStack</td></tr></tbody></table>

<div id="bkmrk-1-cd-%2Fhome%2Fdebian%2Fba"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div></div></div><div class="cm-content"><div class="cm-line"><span class="ͼs">cd</span> /home/debian/baikal &amp;&amp; <span class="ͼs">sudo</span> docker compose <span class="ͼq">-p</span> baikal up <span class="ͼq">-d</span></div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div><table id="bkmrk-service-image-port-l"><thead><tr><th>Service</th><th>Image</th><th>Port local</th><th>Base</th><th>RAM</th></tr></thead><tbody><tr><td>baikal</td><td>`ckulka/baikal:nginx`</td><td>127.0.0.1:8088</td><td>SQLite</td><td>~38 Mo</td></tr><tr><td>roundcube</td><td>`roundcube/roundcubemail:latest` (1.7.2)</td><td>127.0.0.1:8083</td><td>SQLite</td><td>~32 Mo</td></tr><tr><td>InfCloud</td><td>fichiers statiques (13 Mo sur disque)</td><td>—</td><td>aucune</td><td>0</td></tr></tbody></table>

Les deux containers pèsent **environ 70 Mo au total**, contre environ 600 Mo avec l'architecture MariaDB initialement prévue. Ce choix était important : le VPS était en pression mémoire au moment du déploiement (swap à 3,8/4,0 Go).

# 5. Écarts avec le plan initial

Le plan préparé prévoyait une configuration qui ne pouvait pas fonctionner en l'état. Écarts constatés et corrigés :

<table id="bkmrk-point-pr%C3%A9vu-r%C3%A9el-rai"><thead><tr><th>Point</th><th>Prévu</th><th>Réel</th><th>Raison</th></tr></thead><tbody><tr><td>Port Baïkal</td><td>8081</td><td>**8088**</td><td>8081 est occupé par FreshRSS. 8088 correspond au vhost `baikal.juxjux.ovh` préexistant</td></tr><tr><td>Port Roundcube</td><td>8082</td><td>**8083**</td><td>8082 est ciblé par un vhost `nextcloud` mort</td></tr><tr><td>Domaine Roundcube</td><td>`mail.juxjux.ovh`</td><td>**`secretariat.juxjux.ovh`**</td><td>Aucun DNS n'existait ; `mail.` prêtait à confusion avec les MX OVH du domaine</td></tr><tr><td>Base Roundcube</td><td>MariaDB</td><td>**SQLite**</td><td>~400 Mo de RAM économisés, suffisant en mono-utilisateur</td></tr><tr><td>Vhost Baïkal</td><td>À créer</td><td>**Déjà présent**</td><td>Le fichier `baikal.conf` préparé n'a pas servi</td></tr><tr><td>Agenda web</td><td>Plugin calendar Roundcube</td><td>**InfCloud**</td><td>Plugin inutilisable, voir §6.2</td></tr></tbody></table>

# 6. Pièges rencontrés

### 6.1 Les plugins carddav et calendar ne sont pas dans l'image Roundcube

L'image officielle ne fournit ni `carddav` ni `calendar`. Les déclarer dans `ROUNDCUBEMAIL_PLUGINS` ne fait que les *activer* — s'ils sont absents, Roundcube part en erreur fatale.

Il faut les faire installer par composer au démarrage, via **`ROUNDCUBEMAIL_COMPOSER_PLUGINS`** (et non `ROUNDCUBEMAIL_INSTALL_PLUGINS`, qui n'existe pas dans cette image) :

<div id="bkmrk-1-2-roundcubemail_co"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div><div class="cm-gutterElement">2</div></div></div><div class="cm-content"><div class="cm-line"><span class="ͼ12">ROUNDCUBEMAIL\_COMPOSER\_PLUGINS</span><span class="ͼw">: </span><span class="ͼ13">"roundcube/carddav"</span></div><div class="cm-line"><span class="ͼ12">ROUNDCUBEMAIL\_PLUGINS</span><span class="ͼw">: </span><span class="ͼ13">"carddav"</span></div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>Le premier démarrage est alors plus long (téléchargement composer).

### 6.2 Le plugin calendar de Roundcube est une impasse

Trois obstacles successifs, testés le 3 août :

**a) Il casse le tout premier démarrage.** Le script d'initialisation de base de `kolab/calendar` s'exécute **avant** que l'entrypoint n'écrive la configuration Roundcube : il tombe sur le DSN MySQL par défaut et échoue en `SQLSTATE[HY000] [2002] No such file or directory`. Cette erreur fatale empêche la régénération de l'autoloader composer, ce qui casse **aussi** le plugin carddav :

<div id="bkmrk-1-2-3-php-fatal-erro"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div><div class="cm-gutterElement">2</div><div class="cm-gutterElement">3</div></div></div><div class="cm-content"><div class="cm-line">PHP Fatal error: Uncaught Error: Interface</div><div class="cm-line">"MStilkerich\RCMCardDAV\Frontend\RcmInterface" not found</div><div class="cm-line">in /var/www/html/plugins/carddav/carddav.php:43</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>Symptôme : HTTP 500 permanent, alors que les fichiers du plugin sont bien présents sur le disque — c'est `vendor/composer/autoload_psr4.php` qui ne contient aucune entrée carddav.

**b) Ce premier obstacle est contournable.** Réinstallé *après* que Roundcube ait été initialisé une première fois, la configuration existe déjà dans le volume et l'initialisation aboutit (`Creating database schema... [OK]`). Vérifié.

**c) Mais le plugin ne sait pas parler à Baïkal.** La seule version installable, `kolab/calendar` 3.2.9.1, ne livre que les pilotes `database`, `kolab` et `ldap` — **aucun pilote `caldav`**. Il ne créerait qu'un agenda **local** dans Roundcube, sans lien avec Baïkal ni le téléphone : pire qu'inutile, on pourrait y saisir des rendez-vous en croyant qu'ils se synchronisent.

Les versions qui embarquent le pilote CalDAV (3.5.7, 3.6.1) dépendent de `kolab/libkolab`, qui réclame `pear/http_request2` — paquet bloqué par composer pour **faille de sécurité** (`PKSA-jrt3-xndd-g4sz`). Ne pas contourner ce garde-fou sur un service exposé sur internet.

**Conclusion : agenda web assuré par InfCloud (§7), plugin calendar définitivement écarté.**

### 6.3 Baïkal ignore X-Forwarded-Proto — fuite HTTP sur la découverte

Baïkal ne tient pas compte de `X-Forwarded-Proto` et générait ses redirections de découverte en **HTTP nu** :

<div id="bkmrk-1-%2F.well-known%2Fcardd"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div></div></div><div class="cm-content"><div class="cm-line">/.well-known/carddav -&gt; http://baikal.juxjux.ovh/dav.php</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>C'est exactement le risque identifié dès la conception : CardDAV et CalDAV envoient les identifiants **à chaque synchronisation**. Un client suivant cette redirection part sur du HTTP.

Corrigé en court-circuitant Baïkal directement dans le vhost :

<div id="bkmrk-1-2-3-4-5-6-location"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div><div class="cm-gutterElement">2</div><div class="cm-gutterElement">3</div><div class="cm-gutterElement">4</div><div class="cm-gutterElement">5</div><div class="cm-gutterElement">6</div></div></div><div class="cm-content"><div class="cm-line"><span class="ͼp">location</span> = /.<span class="ͼu">well-known</span>/<span class="ͼu">carddav </span>{</div><div class="cm-line"><span class="ͼp">return</span> <span class="ͼu">301 https</span>://$host/dav.php/;</div><div class="cm-line">}</div><div class="cm-line"><span class="ͼp">location</span> = /.<span class="ͼu">well-known</span>/<span class="ͼu">caldav </span>{</div><div class="cm-line"><span class="ͼp">return</span> <span class="ͼu">301 https</span>://$host/dav.php/;</div><div class="cm-line">}</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>À vérifier après tout `reload nginx` : le rechargement n'est pas instantané, un test lancé dans la foulée peut encore montrer l'ancien comportement.

### 6.4 Roundcube exige un serveur IMAP pour authentifier

Roundcube **n'a pas de base d'utilisateurs propre** : son écran de login valide les identifiants contre le serveur IMAP configuré. Sans IMAP joignable, aucune connexion n'est possible — donc aucun accès aux contacts non plus.

L'idée d'un Roundcube « contacts et agenda seulement, sans IMAP » n'est pas réalisable. La configuration retenue pointe vers `ssl://mail.infomaniak.com:993`, ce qui couvre le compte `julien.bertrand@ik.me` (le domaine `ik.me` est servi par l'infrastructure Infomaniak).

Si un jour le compte passe en double authentification, il faudra générer un **mot de passe d'application** Infomaniak.

### 6.5 Baïkal doit être installé en SQLite

L'installeur Baïkal propose MySQL ou SQLite. **Choisir SQLite** : la stack ne contient aucun serveur MySQL, cocher « Enable MySQL » mène à une impasse. Base à `/var/www/baikal/Specific/db/db.sqlite`, dans le volume `baikal-data`.

# 7. InfCloud — l'agenda web

Client CalDAV/CardDAV **entièrement côté navigateur** : aucun backend, aucune base, du HTML/JS servi en statique par nginx. Affiche agenda et contacts.

- Source officielle : `https://www.inf-it.com/InfCloud_0.13.1.zip` (3,9 Mo, sha256 `9fa95edd2dcc2b864a10b503ab9220895ea28d4c5541ab02117de1511d5464d4`)
- Installé dans `/home/debian/baikal/infcloud`, propriétaire `www-data`
- **Servi sur le même domaine que Baïkal** (`/infcloud/`) et non sur un sous-domaine dédié : InfCloud interroge `/dav.php/` en direct depuis le navigateur ; depuis une autre origine il aurait fallu ouvrir du CORS sur les méthodes DAV de Baïkal, ce qui est fragile et l'affaiblirait. Même origine = aucun CORS.
- Dossier `auth/` **supprimé** : module PHP d'authentification par proxy inutilisé ici. nginx ne traite pas le PHP à cet emplacement — servis en statique, ces fichiers `.inc` auraient exposé leur contenu en clair.

**Réserve assumée** : projet figé depuis 2015, interface datée, embarque jQuery 2.1.4. Acceptable parce qu'il n'a aucun composant serveur : rien à maintenir, rien à patcher. La seule alternative maintenue serait SOGo, au prix d'environ 500 Mo de RAM et d'une base PostgreSQL.

## 7.1 Les trois pièges d'InfCloud

**a) Le bloc regex du vhost avale tous les JS.** Le vhost Baïkal contient déjà :

<div id="bkmrk-1-location-%7E%2A-%5C.%28js%7C"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div></div></div><div class="cm-content"><div class="cm-line"><span class="ͼp">location</span> <span class="ͼu">~</span>* <span class="ͼu">\\</span>.<span class="ͼu">(js|css|png|jpg|jpeg|gif|ico|svg)$ </span>{ <span class="ͼp">proxy\_pass</span> <span class="ͼp">http</span>://127.0.0.1:8088; }</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>En nginx, un bloc regex `~*` a priorité sur un bloc préfixe ordinaire. Tous les fichiers JavaScript d'InfCloud auraient donc été proxifiés vers Baïkal. Comme InfCloud est presque intégralement du JavaScript, la page serait restée blanche — symptôme difficile à diagnostiquer. **Il faut `^~`, qui passe devant les regex :**

<div id="bkmrk-1-2-3-4-5-location-%5E"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div><div class="cm-gutterElement">2</div><div class="cm-gutterElement">3</div><div class="cm-gutterElement">4</div><div class="cm-gutterElement">5</div></div></div><div class="cm-content"><div class="cm-line"><span class="ͼp">location</span> <span class="ͼu">^~</span> /infcloud/ {</div><div class="cm-line"><span class="ͼp">alias</span> /home/debian/baikal/infcloud/;</div><div class="cm-line"><span class="ͼp">index</span> <span class="ͼp">index</span>.html;</div><div class="cm-line"><span class="ͼp">location</span> <span class="ͼu">~</span> /<span class="ͼu">\\</span>. { <span class="ͼp">deny</span> all; }</div><div class="cm-line">}</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>**b) L'endpoint par défaut vise DAViCal.** Le `config.js` livré pointe `/caldav.php/` ; Baïkal expose `/dav.php/`.

**c) Le href doit viser les *principals*, pas la racine DAV.** InfCloud accole le nom d'utilisateur au `href`. Avec `/dav.php/` il demandait `/dav.php/Julien/` → **404**. La documentation du fichier le précise : *« globalNetworkCheckSettings : principal URL WITHOUT the USER/ part »*. Chez Baïkal, la bonne valeur est donc :

<div id="bkmrk-1-%27%2Fdav.php%2Fprincipa"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div></div></div><div class="cm-content"><div class="cm-line"><span class="ͼ13">'/dav.php/principals/'</span><span class="ͼt">,</span></div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>Après toute modification de `config.js`, **rechargement forcé du navigateur (Ctrl+Maj+R)** : le fichier est mis en cache.

## 7.2 Digest → Basic : le blocage d'authentification

Baïkal était configuré en `dav_auth_type: Digest` et annonçait `WWW-Authenticate: Digest realm="BaikalDAV"`.

**InfCloud ne sait faire que du Basic.** Ses requêtes partaient donc en 401 en boucle, alors que DAVx5 — qui gère Digest — fonctionnait parfaitement avec le même compte. Le symptôme prêtait à confusion : les identifiants étaient bons.

Diagnostic par les logs nginx, où le champ utilisateur est renseigné mais la réponse reste 401 :

<div id="bkmrk-1-83.195.45.93---jul"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement">  
</div><div class="cm-gutterElement">1</div></div></div><div class="cm-content"><div class="cm-line">83.195.45.93 - Julien "PROPFIND /dav.php/ HTTP/2.0" 401</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>**Correctif** : `dav_auth_type: Basic` dans `/var/www/baikal/config/baikal.yaml`, puis `docker restart baikal`.

Ce n'est pas un pis-aller. Digest repose sur MD5, est considéré comme obsolète, et impose au serveur de stocker une empreinte exploitable ; **Basic sur TLS est aujourd'hui la recommandation**, et tout est en HTTPS ici, y compris les redirections de découverte (§6.3).

**Les mots de passe restent valides** : Baïkal calcule la même empreinte `md5(username:auth_realm:password)` dans les deux modes (`Baikal\Core\PDOBasicAuth::validateUserPass`), avec `auth_realm: BaikalDAV` conservé en interne — même si le serveur annonce désormais `realm="sabre/dav"` côté client. Rien à redéfinir, DAVx5 bascule tout seul.

Sauvegarde : `/var/www/baikal/config/baikal.yaml.bak-260803-digest` (dans le container).

# 8. Sauvegarde

Baïkal a été ajouté à `/opt/backups/vps_backup.sh` le 3 août 2026 (cron quotidien à 2h UTC).

- Dossier kDrive de destination : **1467771** (`SYNC-pour_VPS/backups/baikal`)
- Le bilan de fin de log passe de 5 à **6 services** (le total est calculé dynamiquement, pas codé en dur)
- Sauvegarde de l'ancien script : `/opt/backups/vps_backup.sh.bak-260803`

La fonction archive **deux** volumes, via un `tar` intermédiaire puis 7z :

- `Specific/` — la base `db.sqlite` (contacts et calendriers)
- `config/` — `baikal.yaml` (paramètres et empreinte du mot de passe admin)

**Sans le second, la restauration est incomplète.** Testée le 3 août : upload HTTP 200, aucun résidu temporaire.

InfCloud n'a pas besoin d'être sauvegardé (fichiers statiques réinstallables), mais son `config.js` est versionné dans `Jux-scripts/Baikal-Contacts/`.

# 9. Vérifications effectuées le 3 août 2026

<table id="bkmrk-test-r%C3%A9sultat-get-%2Fd"><thead><tr><th>Test</th><th>Résultat</th></tr></thead><tbody><tr><td>`GET /dav.php/`</td><td>HTTP 401 — authentification bien exigée</td></tr><tr><td>`PROPFIND /dav.php/`</td><td>HTTP 401 et non 405 — nginx laisse passer les méthodes DAV étendues</td></tr><tr><td>`.well-known/carddav` et `caldav`</td><td>301 vers HTTPS</td></tr><tr><td>Installation Baïkal</td><td>Utilisateur `Julien`, carnet et calendrier `default` créés</td></tr><tr><td>Login Roundcube</td><td>IMAP Infomaniak accepté</td></tr><tr><td>Découverte CardDAV Roundcube</td><td>Carnet résolu à `.../dav.php/addressbooks/Julien/default/`</td></tr><tr><td>Écriture d'un contact</td><td>vCard 3.0 écrite par RCMCardDAV v5.1.3, relue dans Baïkal, accents UTF-8 préservés</td></tr><tr><td>**Synchronisation DAVx5**</td><td>5 rendez-vous d'août écrits dans Baïkal par `DAVx5/4.5.18-ose`, `synctoken` du calendrier à 6</td></tr><tr><td>**InfCloud**</td><td>32 ressources servies en 200 ; `PROPFIND` sur principals, addressbooks et calendars tous en **207**</td></tr><tr><td>Dotfiles InfCloud</td><td>`.htaccess` bloqué en 403</td></tr><tr><td>Sauvegarde Baïkal</td><td>Archive uploadée sur kDrive, HTTP 200</td></tr></tbody></table>

Note : dans la table `carddav_addressbooks` de Roundcube, une seconde ligne nommée `%N` avec une URL vide n'est **pas** une erreur — c'est le *template* de rcmcarddav v5, qui porte les réglages appliqués aux carnets découverts.

# 10. Reste à faire

### 10.1 vdirsyncer — agrégation Google et Infomaniak

Seule brique du plan initial encore absente. Objectif : faire remonter dans Baïkal les contacts existants sur Google et Infomaniak, pour que Baïkal devienne le point central réel et pas un silo de plus.

- Google Contacts : CardDAV via `https://www.google.com/carddav/v1/principals/EMAIL/` — nécessite un **mot de passe d'application** (compte en 2FA)
- Infomaniak : CardDAV natif, identifiants standards du compte mail
- vdirsyncer : outil CLI léger, une paire de synchronisation par source, exécuté par cron sur le VPS

**Décision à prendre avant déploiement :**

- **one-way** (Google/Infomaniak → Baïkal, lecture seule) : sans risque, mais les modifications faites dans Baïkal ne remontent pas vers Google
- **two-way** : tout reste aligné, mais un conflit ou une erreur de configuration peut propager des suppressions dans les deux sens

Recommandation : démarrer en one-way le temps de vérifier que l'import est fidèle, puis basculer si besoin.

### 10.2 Nettoyage

Quatre vhosts morts traînent dans `/etc/nginx/sites-enabled/`, vestiges des tentatives précédentes : `radicale`, `agendav`, `infcloud`, `nextcloud`. Le dernier cible le port 8082, ce qui a contraint le choix du port de Roundcube. **Attention** : le vhost nommé `radicale.juxjux.ovh` sert en réalité Readeck — ne pas le supprimer sans vérifier.

# 11. Checklist sécurité

- [x] Aucun mot de passe `CHANGE_ME_...` en production — la bascule vers SQLite a supprimé les identifiants MariaDB du compose
- [x] HTTPS forcé sur les deux vhosts (redirection 80 vers 443)
- [x] Certificats Let's Encrypt valides, renouvellement automatique certbot en place
- [x] Découverte DAV forcée en HTTPS (§6.3)
- [x] Authentification Basic **uniquement sur TLS** (§7.2)
- [x] Les deux containers n'écoutent que sur `127.0.0.1` — seul nginx les expose
- [x] Module PHP `auth/` d'InfCloud supprimé, dotfiles bloqués
- [x] Sauvegarde quotidienne des volumes Baïkal vers kDrive
- [ ] Mot de passe d'application dédié pour l'accès CardDAV Google — à faire au moment de vdirsyncer

# 12. Commandes utiles

<div id="bkmrk-1-2-3-4-5-6-7-8-9-10"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before"><div class="cm-gutter cm-lineNumbers">  
</div></div><div class="cm-content"><div class="cm-line"><span class="ͼw">\# État de la stack</span></div><div class="cm-line"><span class="ͼs">sudo</span> docker <span class="ͼs">ps</span> <span class="ͼq">--filter</span> <span class="ͼt">name</span><span class="ͼv">=</span>baikal <span class="ͼq">--filter</span> <span class="ͼt">name</span><span class="ͼv">=</span>roundcube</div><div class="cm-line">  
</div><div class="cm-line"><span class="ͼw">\# Logs</span></div><div class="cm-line"><span class="ͼs">sudo</span> docker logs roundcube <span class="ͼq">--tail</span> <span class="ͼu">50</span></div><div class="cm-line"><span class="ͼs">sudo</span> docker logs baikal <span class="ͼq">--tail</span> <span class="ͼu">50</span></div><div class="cm-line">  
</div><div class="cm-line"><span class="ͼw">\# Redéployer</span></div><div class="cm-line"><span class="ͼs">cd</span> /home/debian/baikal &amp;&amp; <span class="ͼs">sudo</span> docker compose <span class="ͼq">-p</span> baikal up <span class="ͼq">-d</span></div><div class="cm-line">  
</div><div class="cm-line"><span class="ͼw">\# Inspecter la base Baïkal</span></div><div class="cm-line"><span class="ͼs">sudo</span> docker <span class="ͼs">cp</span> baikal:/var/www/baikal/Specific/db/db.sqlite /tmp/bk.sqlite</div><div class="cm-line"><span class="ͼs">sudo</span> sqlite3 /tmp/bk.sqlite <span class="ͼ13">"SELECT COUNT(\*) FROM cards;"</span></div><div class="cm-line"><span class="ͼs">sudo</span> sqlite3 /tmp/bk.sqlite <span class="ͼ13">"SELECT COUNT(\*) FROM calendarobjects;"</span></div><div class="cm-line"><span class="ͼs">sudo</span> sqlite3 /tmp/bk.sqlite <span class="ͼ13">"SELECT id, username FROM users;"</span></div><div class="cm-line">  
</div><div class="cm-line"><span class="ͼw">\# Méthode d'authentification annoncée par Baïkal</span></div><div class="cm-line"><span class="ͼs">curl</span> <span class="ͼq">-s</span> <span class="ͼq">-D</span> <span class="ͼq">-</span> <span class="ͼq">-o</span> /dev/null <span class="ͼq">-X</span> PROPFIND <span class="ͼq">-H</span> <span class="ͼ13">'Depth: 0'</span> \</div><div class="cm-line">https://baikal.juxjux.ovh/dav.php/ | <span class="ͼs">grep</span> <span class="ͼq">-i</span> www-authenticate</div><div class="cm-line">  
</div><div class="cm-line"><span class="ͼw">\# Suivre les requêtes DAV des clients (DAVx5, InfCloud, Roundcube)</span></div><div class="cm-line"><span class="ͼs">sudo</span> tail <span class="ͼq">-f</span> /var/log/nginx/access.log | <span class="ͼs">grep</span> dav.php</div><div class="cm-line">  
</div><div class="cm-line"><span class="ͼw">\# Vérifier la découverte DAV</span></div><div class="cm-line"><span class="ͼs">curl</span> <span class="ͼq">-s</span> <span class="ͼq">-o</span> /dev/null <span class="ͼq">-w</span> <span class="ͼ13">"%{http\_code} -&gt; %{redirect\_url}\\n"</span> \</div><div class="cm-line">https://baikal.juxjux.ovh/.well-known/carddav</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div># 13. Choix d'architecture — pourquoi ces outils

**Radicale** écarté : stockage fichier brut, pas de vraie gestion. La stack Radicale/InfCloud initialement envisagée n'a pas été mise en place — seul InfCloud a finalement été retenu, en client de Baïkal.

**Nextcloud** écarté : trop lourd pour l'usage visé (PHP-FPM + base + Redis + écosystème complet), alors que le VPS fait déjà tourner PostGIS, Metabase, BookStack, FreshRSS.

**Baïkal** retenu : léger, CardDAV et CalDAV natifs, interface d'administration suffisante. Développé par des bénévoles sous l'organisation sabre-io (mainteneurs de SabreDAV, la librairie sous-jacente) — pas de société commerciale derrière, mais communauté active et outil robuste pour un usage personnel sur la durée.

**Roundcube** retenu comme interface de saisie des contacts, Baïkal n'ayant pas de vraie UI de consultation : léger, traduit en français, projet actif (version 1.7.2 en août 2026). A rejoint la famille Nextcloud en 2023 tout en restant indépendant, sous licence GPL. Réserves constatées à l'usage : il impose une authentification IMAP (§6.4) et ne couvre pas l'agenda (§6.2).

**InfCloud** retenu pour l'agenda web : sans backend, donc sans coût mémoire ni surface d'attaque serveur, et couvre agenda et contacts. Ancien mais fonctionnel.

**SOGo** envisagé et écarté : suite complète et maintenue, mais environ 500 Mo de RAM et une base PostgreSQL sur un VPS déjà en pression mémoire, et il aurait fait doublon avec Roundcube.

**Thunderbird containerisé** (VNC/noVNC) envisagé puis écarté : fonctionnel mais plus lourd et moins adapté au multi-accès qu'un vrai webmail.

# 2-MCP CalDAV pour Claude Code — un agenda Baïkal partagé par les deux PC

<div id="bkmrk--3" style="clear: left;">  
</div># MCP CalDAV pour Claude Code

**État au 16 septembre 2026 — opérationnel sur les deux PC.** Cycle complet validé le 15/09 sur NEXTE : création de calendrier, écriture d'un événement, relecture, visibilité sur le téléphone (DAVx5 → Fossify) **et dans InfCloud**, suppression. Installé sur le PC perso le 16/09 (§7.8), avec un écart de méthode : venv Python sur `D:` au lieu de `uv`.

Prérequis : l'infrastructure Baïkal décrite en page 1 de ce chapitre (Baïkal en Docker sur juxjux.ovh, Basic auth sur TLS, utilisateur DAV `Julien`).

---

## 1. Décision d'architecture

**Un seul Baïkal, sur juxjux.ovh, partagé par les deux Claude Code (PC perso et PC NEXTE).** C'est la seule exception assumée à l'étanchéité entre l'univers Jux et l'univers Alteris/NEXTE.

Pourquoi ne pas dupliquer Baïkal sur le VPS Alteris :

- Baïkal est posé comme *source de vérité unique* (page 1). Deux Baïkal = deux vérités, et le problème d'origine (agendas dispersés) se recrée.
- Deux flux dans DAVx5 fonctionnent, mais les deux calendriers ne se voient pas : les conflits d'horaire entre perso et pro deviennent invisibles.
- Doublement de la maintenance (certificat, sauvegarde, compose, InfCloud) sur un VPS déjà chargé.

Pourquoi l'exception est acceptable : l'étanchéité protège les **fichiers et données de mission**. Un agenda n'est pas une donnée de mission, c'est un service de la personne, comme le téléphone. Le PC NEXTE devient un client CalDAV de plus, au même titre que DAVx5 ; aucune donnée Jux n'entre sur NEXTE, aucune donnée Alteris ne sort vers juxjux. Seul l'identifiant Baïkal traverse — il est dans le Vault 2603.

## 2. Modèle : un utilisateur, deux calendriers

<table id="bkmrk-calendrier-url-usage"><thead><tr><th>Calendrier</th><th>URL</th><th>Usage</th><th>Couleur</th><th>PC qui y écrit par défaut</th></tr></thead><tbody><tr><td>`Default calendar`</td><td>`https://baikal.juxjux.ovh/dav.php/calendars/Julien/default/`</td><td>Personnel (Julien)</td><td>(installeur)</td><td>PC perso</td></tr><tr><td>`NEXTE`</td><td>`https://baikal.juxjux.ovh/dav.php/calendars/Julien/nexte/`</td><td>Professionnel NEXTE (pas Alteris)</td><td>`#1E88E5` bleu</td><td>PC NEXTE</td></tr></tbody></table>

Même compte `Julien` pour les deux, donc les deux calendriers apparaissent ensemble dans DAVx5, Fossify et InfCloud, chacun avec sa couleur.

**Règle de lecture/écriture du MCP :** chaque PC **écrit** dans son calendrier par défaut (`CALDAV_DEFAULT_CALENDAR`) mais **lit les deux** — sinon Claude placerait un rendez-vous pro sur un créneau perso. L'écriture dans l'autre calendrier reste possible en le nommant explicitement (`calendar="Default calendar"`).

Le calendrier `NEXTE` a été créé le 15/09/2026 par le MCP lui-même (`MKCALENDAR`, outil `create_calendar`). Dans DAVx5, il faut ensuite rafraîchir la liste des calendriers du compte et le cocher pour qu'il se synchronise. Dans InfCloud, il faut l'inscrire dans les réglages stockés sur le serveur — voir §5.7, c'est désormais fait automatiquement par `create_calendar`.

## 3. Le serveur MCP

Aucun serveur MCP CalDAV communautaire mûr : serveur maison, ~400 lignes de Python.

- Fichier : `C:\Users\nexte\Nexte Claude\MCP CalDAV\260915-mcp_caldav_server.py`
- Script autonome **PEP 723** : dépendances déclarées en tête de fichier, résolues par `uv run --script`. Aucune installation Python système nécessaire (il n'y en a pas sur NEXTE, seulement celui de QGIS) — `uv` est déjà là pour le MCP QGIS.
- Dépendances : `mcp>=1.2,<2` (FastMCP), `caldav` (3.3 au 15/09), `icalendar`, `python-dateutil`.
- Configuration par variables d'environnement, passées par `claude mcp add -e` :

<table id="bkmrk-variable-valeur-next"><thead><tr><th>Variable</th><th>Valeur NEXTE</th><th>Rôle</th></tr></thead><tbody><tr><td>`CALDAV_URL`</td><td>`https://baikal.juxjux.ovh/dav.php/`</td><td>racine DAV de Baïkal</td></tr><tr><td>`CALDAV_USER`</td><td>`Julien`</td><td>utilisateur DAV — **J majuscule**, la casse compte</td></tr><tr><td>`CALDAV_PASSWORD`</td><td>Vault 2603</td><td>mot de passe de l'utilisateur DAV (**pas** celui de l'admin Baïkal)</td></tr><tr><td>`CALDAV_DEFAULT_CALENDAR`</td><td>`NEXTE`</td><td>calendrier d'écriture par défaut (`default` sur le PC perso)</td></tr><tr><td>`CALDAV_TZ`</td><td>`Europe/Paris` (défaut)</td><td>fuseau des heures saisies sans fuseau, et fuseau posé sur les calendriers créés</td></tr></tbody></table>

Le script ne journalise jamais le mot de passe ; s'il manque, il le signale sur stderr sans en dire plus.

### Outils exposés

<table id="bkmrk-outil-r%C3%B4le-list_cale"><thead><tr><th>Outil</th><th>Rôle</th></tr></thead><tbody><tr><td>`list_calendars`</td><td>calendriers du compte, avec marquage du calendrier par défaut</td></tr><tr><td>`create_calendar(name, cal_id, color)`</td><td>`MKCALENDAR`, puis pose `calendar-color` / `calendar-order` / `calendar-timezone` (palette par défaut) et **inscrit le calendrier dans les réglages InfCloud** (§5.7) ; refuse si le nom existe déjà</td></tr><tr><td>`infcloud_sync_collections`</td><td>inscrit dans les réglages InfCloud tous les calendriers du compte qui n'y figurent pas — pour un calendrier créé ailleurs (DAVx5, admin Baïkal)</td></tr><tr><td>`list_events(start, end, calendar, all_calendars)`</td><td>événements sur une période (défaut : aujourd'hui → +14 j), **tous calendriers** sauf si `calendar` est nommé ; occurrences récurrentes développées</td></tr><tr><td>`search_events(text, calendar)`</td><td>recherche plein texte (résumé, lieu, description), fenêtre −6/+12 mois</td></tr><tr><td>`get_event(uid, calendar)`</td><td>détail + iCalendar brut</td></tr><tr><td>`create_event(summary, start, end, calendar, all_day, location, description, rrule)`</td><td>`end` absent → +1 h (ou +1 jour en journée entière) ; heures sans fuseau = Europe/Paris ; dates ISO ou `jj/mm/aaaa`, heures `16:00` ou `16h00`</td></tr><tr><td>`update_event(uid, …)`</td><td>ne modifie que les champs fournis ; incrémente `SEQUENCE` et `LAST-MODIFIED`</td></tr><tr><td>`delete_event(uid, calendar)`</td><td>suppression définitive — **uniquement après confirmation explicite de Julien**</td></tr><tr><td>`free_slots(start, end, duration_minutes, day_start, day_end, weekdays_only)`</td><td>créneaux libres tous calendriers confondus, dans la plage horaire quotidienne indiquée</td></tr></tbody></table>

## 4. Enregistrement dans Claude Code (PC NEXTE)

Portée utilisateur (`-s user`), comme le MCP QGIS. À lancer dans un terminal, en remplaçant `<MDP>` par le mot de passe de l'utilisateur DAV `Julien` (Vault 2603) :

<div id="bkmrk-1-claude-mcp-add--s-"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 27.2px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div></div></div><div class="cm-content"><div class="cm-line">claude mcp add -s user caldav -e CALDAV_URL=https://baikal.juxjux.ovh/dav.php/ -e CALDAV_USER=Julien -e CALDAV_DEFAULT_CALENDAR=NEXTE -e 'CALDAV_PASSWORD=&lt;MDP&gt;' -- "C:\Users\nexte\.local\bin\uv.exe" run --script "C:\Users\nexte\Nexte Claude\MCP CalDAV\260915-mcp_caldav_server.py"</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>Puis :

<div id="bkmrk-1-claude-mcp-list"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 27.2px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div></div></div><div class="cm-content"><div class="cm-line">claude mcp list</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>qui doit afficher `caldav: … - ✔ Connected`.

Pour refaire l'enregistrement (mot de passe changé, mauvais calendrier par défaut) : `claude mcp remove -s user caldav` puis la commande `add`.

**Où va le secret :** `claude mcp add` l'écrit **en clair** dans `C:\Users\nexte\.claude.json` — même niveau de protection que le MCP postgres. Le mot de passe reste archivé dans le Vault 2603 ; il n'a pas à figurer dans une conversation ni dans BookStack.

## 5. Pièges rencontrés le 15/09/2026

### 5.1 Les chevrons du gabarit ne font pas partie du mot de passe

Une commande donnée avec `CALDAV_PASSWORD=<MDP>` a été lancée telle quelle, puis avec `<vrai-mdp>` chevrons compris. Dans les deux cas Baïkal répond 401. Vérification sans afficher le secret : longueur de la valeur stockée, premier caractère, présence des caractères spéciaux attendus.

### 5.2 Mot de passe admin ≠ mot de passe utilisateur DAV

Baïkal a deux comptes distincts : l'admin (`/admin/`) et l'utilisateur DAV (`Julien`). Le premier essai a été fait avec le mot de passe admin → 401 systématique, avec `Julien` comme avec `julien`, alors que le serveur annonçait bien `Basic realm="sabre/dav"`. Le bon mot de passe est celui saisi dans DAVx5 et InfCloud. Test de référence qui distingue « serveur joignable » de « identifiants acceptés » :

<div id="bkmrk-1-curl--s--o-%2Fdev%2Fnu"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 27.2px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div></div></div><div class="cm-content"><div class="cm-line">curl -s -o /dev/null -w "%{http_code}\n" -X PROPFIND -H "Depth: 0" https://baikal.juxjux.ovh/dav.php/</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>401 sans identifiants = normal ; 401 avec identifiants = mauvais couple utilisateur/mot de passe.

### 5.3 Caractères spéciaux dans PowerShell

Le mot de passe contient `%` et `@` : passer la variable **entre apostrophes simples** (`'CALDAV_PASSWORD=…'`), pas entre guillemets. Vérifié : la valeur stockée est intacte.

### 5.4 SDK `mcp` 2.x

La 2.x du SDK Python a renommé `FastMCP` en `MCPServer` et cassé l'import `mcp.server.fastmcp`. Le script épingle `mcp<2`. À revoir si on migre un jour vers la 2.x.

### 5.5 Reprise de session ≠ nouvelle session

Un serveur MCP est chargé au démarrage de la session. Après `claude mcp add`, une session **reprise** dans l'app ne voit pas le nouveau serveur alors que `claude mcp list` le donne connecté. Il faut une **nouvelle** session. Entre-temps, le script peut être piloté directement (import du module + lecture de l'env dans `.claude.json`) — c'est ainsi que le calendrier `NEXTE` et l'événement test ont été créés.

### 5.6 Rappels de la page 1 qui s'appliquent au MCP

- Racine DAV `/dav.php/`, pas `/caldav.php/`.
- `Julien` en majuscule dans les URLs.
- Basic sur TLS : la bibliothèque `caldav` gère la négociation 401 → Basic toute seule.

### 5.7 Un calendrier créé hors InfCloud n'apparaît jamais dans InfCloud

**Symptôme :** `NEXTE` visible dans DAVx5 et Fossify, mais absent d'InfCloud, même après déconnexion, `Ctrl+Maj+R` et reconnexion.

**Fausse piste écartée en premier :** le calendrier créé par `MKCALENDAR` nu n'avait ni `calendar-color`, ni `calendar-order`, ni `calendar-timezone` (404 sur ces trois propriétés, alors que le calendrier de l'installeur les a). Elles ont été posées par PROPPATCH — nécessaire pour l'homogénéité, mais **insuffisant**.

**Cause réelle :** `config.js` d'InfCloud a `settingsAccount: true`. InfCloud stocke alors ses réglages **sur le serveur**, dans la propriété `{http://inf-it.com/ns/dav/}settings` du principal (`/dav.php/principals/Julien/`), sous forme de JSON. Ce JSON contient `loadedcalendarcollections` et `activecalendarcollections` (idem pour les tâches), figés à la première session — donc réduits à `default/`. InfCloud **ne charge que les collections de cette liste** ; un nouveau calendrier n'y entre jamais tout seul.

Diagnostic (avec identifiants) :

<div id="bkmrk-1-2-propfind-%2Fdav.ph"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 46.4px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div><div class="cm-gutterElement" style="height: 19.2px;">2</div></div></div><div class="cm-content"><div class="cm-line">PROPFIND /dav.php/principals/Julien/ Depth: 0</div><div class="cm-line">&lt;D:propfind xmlns:D="DAV:" xmlns:I="http://inf-it.com/ns/dav/"&gt;&lt;D:prop&gt;&lt;I:settings/&gt;&lt;/D:prop&gt;&lt;/D:propfind&gt;</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>puis lire `loadedcalendarcollections` dans le JSON retourné.

Correctif : relire le JSON, ajouter l'URL du calendrier aux quatre listes (`loadedcalendarcollections`, `activecalendarcollections`, `loadedtodocollections`, `activetodocollections`), réécrire la propriété par PROPPATCH, puis **déconnexion/reconnexion** InfCloud (les réglages sont lus au login). Sauvegarde du JSON d'origine : `MCP CalDAV\260915-infcloud_settings_backup.json`.

Depuis le 15/09, `create_calendar` fait tout cela d'emblée, et `infcloud_sync_collections` rattrape un calendrier créé ailleurs. Le `config.js` lui-même n'est pas en cause (`additionalResources: []`, pas de liste figée).

### 5.8 Le parseur de dates lisait 2026-10-02 comme le 10 février

`dateutil.parser.parse(..., dayfirst=True)` — voulu pour accepter `02/10/2026` — inverse aussi jour et mois d'une date **ISO** quand les deux valeurs sont ≤ 12 : `2026-10-02` devient le 10 février, `2026-10-09` le 10 septembre, `2026-10-12` le 10 décembre. `2026-10-13` passe, ce qui rend le bug intermittent et sournois.

Constaté lors du premier blocage de créneaux (6 dates, 3 fausses). Correctif dans `_parse_dt` : `datetime.fromisoformat` d'abord, dateutil en jour-premier seulement en repli ; `16h00` / `14h` sont normalisés en `16:00` / `14:00`. Les trois événements erronés ont été supprimés et recréés.

**Règle :** toujours relire les dates renvoyées par `create_event` (le résultat affiche `start`/`end` en ISO avec fuseau) avant d'annoncer qu'un créneau est bloqué.

## 6. Vérifications effectuées le 15/09/2026

<table id="bkmrk-test-r%C3%A9sultat-propfi"><thead><tr><th>Test</th><th>Résultat</th></tr></thead><tbody><tr><td>`PROPFIND /dav.php/` sans identifiants</td><td>401, `Basic realm="sabre/dav"`</td></tr><tr><td>`list_calendars`</td><td>`Default calendar` visible</td></tr><tr><td>`create_calendar("NEXTE", cal_id="nexte")`</td><td>créé, URL `…/calendars/Julien/nexte/`</td></tr><tr><td>`list_events` 14 jours, tous calendriers</td><td>1 événement perso lu</td></tr><tr><td>`create_event` dans NEXTE (16/09 10:00–10:30, Europe/Paris)</td><td>écrit, relu par `get_event` avec fuseau `+02:00`</td></tr><tr><td>Synchronisation DAVx5 → Fossify</td><td>événement visible sur le téléphone après avoir coché NEXTE</td></tr><tr><td>`delete_event` par UID</td><td>supprimé, calendrier NEXTE vide</td></tr><tr><td>PROPPATCH couleur/ordre/fuseau sur NEXTE</td><td>200, relu</td></tr><tr><td>Inscription de NEXTE dans les réglages InfCloud</td><td>200 ; NEXTE visible dans InfCloud après reconnexion</td></tr><tr><td>`infcloud_sync_collections` après correction</td><td>`added: []` — idempotent</td></tr><tr><td>`create_calendar("NEXTE")` une seconde fois</td><td>refusé, « existe déjà »</td></tr><tr><td>`claude mcp list`</td><td>`caldav … ✔ Connected`</td></tr><tr><td>6 créneaux « Tourrettes PLU (validation en attente) » dans NEXTE (02→16/10)</td><td>créés, relus aux bonnes dates après correctif §5.8 ; aucun conflit perso</td></tr></tbody></table>

## 7. Installation sur le PC perso

Objectif : que le Claude Code du PC perso lise les deux calendriers et écrive dans l'un ou l'autre, avec le **même** serveur Baïkal et le **même** compte `Julien`. Aucune configuration côté Baïkal : le compte voit déjà les deux calendriers. Tout se passe sur le PC perso.

### 7.1 Copier le script

Source (PC NEXTE) : `C:\Users\nexte\Nexte Claude\MCP CalDAV\260915-mcp_caldav_server.py` — prendre la version courante, elle contient les correctifs du 15/09 (§5.7 InfCloud, §5.8 dates).

Destination recommandée : `Syncthing/Jux_univers/Jux-scripts/Baikal-Contacts/260915-mcp_caldav_server.py`, à côté d'`infcloud-config.js` — c'est le dossier où sont déjà versionnés les fichiers Baïkal (page 1, §4). Le script est autonome : un seul fichier, pas de dépendance à copier.

Point d'attention : le fichier ne doit pas être dans un coffre Cryptomator — un MCP se lance au démarrage de Claude Code, coffre fermé ou non (règle « aucun script ne dépend d'un coffre déverrouillé »).

### 7.2 Vérifier `uv`

<div id="bkmrk-1-uv---version"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 27.2px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div></div></div><div class="cm-content"><div class="cm-line">uv --version</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>Si absent :

<div id="bkmrk-1-powershell--c-%22irm"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 27.2px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div></div></div><div class="cm-content"><div class="cm-line">powershell -c "irm https://astral.sh/uv/install.ps1 | iex"</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>puis rouvrir le terminal. `uv` télécharge lui-même un Python et les dépendances au premier lancement (~50 paquets, une dizaine de secondes) ; aucune installation Python système n'est nécessaire.

Repérer le chemin complet de `uv.exe` (`(Get-Command uv).Source` en PowerShell) — l'utiliser en absolu dans la commande d'enregistrement, comme sur NEXTE, évite les surprises de PATH au lancement par Claude Code.

**Fait autrement sur le PC perso (16/09)** : pas d'`uv`, un venv sur `D:` — voir §7.8 pour la raison.

### 7.3 Choisir le calendrier par défaut

<table id="bkmrk-choix-caldav_default"><thead><tr><th>Choix</th><th>`CALDAV_DEFAULT_CALENDAR`</th><th>Comportement</th></tr></thead><tbody><tr><td>**Recommandé**</td><td>`default`</td><td>le PC perso écrit dans le calendrier perso sauf mention contraire ; symétrique de NEXTE sur le PC pro</td></tr><tr><td>Variante</td><td>`NEXTE`</td><td>le PC perso se comporte exactement comme le PC NEXTE</td></tr></tbody></table>

Dans les deux cas, la **lecture** porte sur les deux calendriers, et l'écriture dans l'autre calendrier reste possible en le nommant (« ajoute ça dans NEXTE »).

### 7.4 Enregistrer le MCP

Dans un terminal (PowerShell), en remplaçant `<MDP>` par le mot de passe de l'utilisateur DAV `Julien` (Vault 2603) et `<chemin>` par le dossier de destination :

<div id="bkmrk-1-claude-mcp-add--s--1"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 27.2px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div></div></div><div class="cm-content"><div class="cm-line">claude mcp add -s user caldav -e CALDAV_URL=https://baikal.juxjux.ovh/dav.php/ -e CALDAV_USER=Julien -e CALDAV_DEFAULT_CALENDAR=default -e 'CALDAV_PASSWORD=&lt;MDP&gt;' -- "&lt;chemin complet de uv.exe&gt;" run --script "&lt;chemin&gt;\260915-mcp_caldav_server.py"</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>Rappels des pièges §5 :

- mot de passe de l'**utilisateur DAV** `Julien`, pas celui de l'admin Baïkal (§5.2) ;
- **sans chevrons** autour de la valeur (§5.1) ;
- **apostrophes simples** autour de `CALDAV_PASSWORD=…` à cause des `%` et `@` (§5.3) ;
- `Julien` avec un J majuscule (§5.6).

Vérifier :

<div id="bkmrk-1-claude-mcp-list-1"><div class="cm-editor ͼ1 ͼ3 ͼ4 ͼo"><div class="cm-announced">  
</div><div class="cm-scroller" tabindex="-1"><div class="cm-gutters cm-gutters-before" style="min-height: 27.2px;"><div class="cm-gutter cm-lineNumbers"><div class="cm-gutterElement" style="height: 0px; visibility: hidden;">  
</div><div class="cm-gutterElement" style="height: 19.2px; margin-top: 4px;">1</div></div></div><div class="cm-content"><div class="cm-line">claude mcp list</div></div><div class="cm-layer cm-layer-above cm-cursorLayer">  
</div><div class="cm-layer cm-selectionLayer">  
</div></div></div></div>→ `caldav: … - ✔ Connected`. Si `Failed to connect` : lancer à la main `uv run --script "<chemin>\260915-mcp_caldav_server.py"` dans le terminal pour voir l'erreur (dépendance, chemin, Python).

Pour corriger une valeur : `claude mcp remove -s user caldav` puis relancer la commande `add`.

### 7.5 Nouvelle session et test

1. Ouvrir une **nouvelle** session Claude Code — pas une reprise (§5.5).
2. Demander `list_calendars` : les deux calendriers doivent apparaître, `Default calendar` marqué par défaut.
3. Écrire un événement test dans NEXTE (« crée un test demain 10 h dans NEXTE »), le vérifier sur le téléphone ou dans InfCloud, puis le faire supprimer.

### 7.6 Ce qui n'est PAS à faire

- Rien dans l'admin Baïkal : pas de second utilisateur, pas de partage de calendrier.
- Rien dans DAVx5 ni InfCloud : ils voient déjà les deux calendriers.
- Ne pas dupliquer Baïkal sur un autre VPS (§1).
- Ne pas copier le mot de passe dans BookStack, dans un script ou dans une conversation : il vit dans le Vault 2603 et dans `.claude.json` du PC.

### 7.7 Entretien à deux PC

Le script existe en deux exemplaires (NEXTE et perso). Toute correction faite sur l'un doit être recopiée sur l'autre — c'est le prix d'un serveur maison hors dépôt. Convention : le fichier daté reste `260915-…` tant que l'API des outils ne change pas ; une refonte produit un nouveau fichier daté et une mise à jour des deux enregistrements MCP.

### 7.8 Installation effective sur le PC perso — 16/09/2026

Faite depuis une session Claude Code du poste `julie`, après dépôt du script par Julien. Vérifiée en session : `list_calendars` rend *Default calendar* (par défaut) et *NEXTE*, `list_tasks` lit les deux tâches ouvertes de NEXTE.

<table id="bkmrk-7.8-table"><thead><tr><th>Élément</th><th>Valeur sur le PC perso</th></tr></thead><tbody><tr><td>Script</td><td>`D:\Syncthing\Jux_univers\Jux-scripts\Baikal-Contacts\260915-mcp_caldav_server.py` — dans l'arbre Syncthing, hors coffre</td></tr><tr><td>Interpréteur</td><td>`D:\Python\venv-caldav-mcp\Scripts\python.exe` (venv créé par `py -3.12 -m venv`, puis `pip install "mcp>=1.2,<2" "caldav>=1.4" "icalendar>=6" python-dateutil` — mcp 1.30, caldav 3.3, icalendar 7.3)</td></tr><tr><td>Enregistrement</td><td>à la main dans `C:\Users\julie\.claude.json`, clé `mcpServers.caldav` (portée utilisateur, `type: stdio`, `command` = python du venv en absolu, `args` = chemin du script, `env` = les 4 variables). Sauvegarde : `.claude.json.bak-260916-caldav`</td></tr><tr><td>`CALDAV_DEFAULT_CALENDAR`</td><td>`default` — le PC perso écrit dans le perso, lit les deux</td></tr></tbody></table>

**Pourquoi pas `uv` ni `pip` dans le Python système.** Sur ce poste, Claude Code tourne dans un conteneur MSIX : tout ce qu'une session écrit sous `%LOCALAPPDATA%` ou `%APPDATA%` est redirigé vers `…\Packages\Claude_pzs8sxrjxfjjc\LocalCache\`, invisible du reste du système (page 282). Or le Python 3.12 du poste est installé sous `%LOCALAPPDATA%\Programs\Python`, et `uv` met son cache dans `%LOCALAPPDATA%\uv` : un `pip install` ou un premier `uv run` lancés depuis la session auraient produit une installation fantôme. Un venv sur `D:` est hors redirection, et cohérent avec les six autres MCP Python du poste (enregistrés avec un `python.exe` en chemin absolu, page 282). `uv` reste valable sur NEXTE, où Claude Code n'est pas dans un conteneur.

**Pas de `claude mcp add`** : le CLI `claude` n'est pas dans le PATH de ce poste (application de bureau). L'entrée est écrite directement dans `.claude.json`, comme pour Mealie le 13/09. Le mot de passe y est en clair, au même titre que sur NEXTE.

**Correctif apporté au script (à recopier sur NEXTE, §7.7).** `list_calendars` marquait le calendrier par défaut en comparant `CALDAV_DEFAULT_CALENDAR` au seul nom affiché. Avec `default`, le nom affiché est *Default calendar* : aucun calendrier n'était marqué (`default: false` partout), alors que l'écriture fonctionnait — `_find_calendar`, lui, se rabat sur le dernier segment d'URL. Invisible sur NEXTE parce que nom et segment coïncident (`NEXTE`/`nexte`). `list_calendars` applique désormais la même règle : nom affiché **ou** segment d'URL, insensible à la casse. Une ligne changée, même fichier daté `260915-…` (l'API des outils ne bouge pas).

**Chargement en session** : après l'écriture dans `.claude.json`, l'application de bureau a dû être fermée et rouverte — une session reprise ne voit pas le nouveau serveur (§5.5). Au retour, les 17 outils `mcp__caldav__*` étaient présents et `list_calendars` a répondu par le MCP.

## 8. Règles d'usage pour Claude

- Lire **les deux** calendriers avant de proposer un créneau (`list_events` sans `calendar`, ou `free_slots`).
- Écrire dans le calendrier par défaut du PC ; n'écrire dans l'autre que sur demande explicite.
- Ne jamais appeler `delete_event` sans confirmation explicite de Julien dans la conversation.
- Ne jamais afficher, journaliser ni répéter `CALDAV_PASSWORD` ; diagnostiquer sur les longueurs et les codes HTTP.
- Si un calendrier manque dans InfCloud : `infcloud_sync_collections`, puis déconnexion/reconnexion — avant toute autre hypothèse.
- Relire les dates renvoyées par `create_event` avant d'annoncer un créneau bloqué (§5.8).

# 3 - gestionnaires de tâches - Tasks.org sur Baikal - Infcloud et DAVx5

<div id="bkmrk--5" style="clear: left;">  
</div># Gestionnaire de tâches — Tasks.org sur Baïkal, InfCloud et DAVx5

**État au 15 septembre 2026 — opérationnel de bout en bout.** Cycle validé dans les deux sens : une tâche créée par le MCP est apparue dans Tasks.org, y a été cochée, et le MCP l'a relue COMPLETED ; une tâche saisie dans Tasks.org a été lue par le MCP ; les deux sont visibles dans InfCloud (§6).

Décision : **on valorise au maximum l'existant — Baïkal, InfCloud, DAVx5.** Les tâches vivent dans les mêmes collections que les événements, aucun composant serveur n'est ajouté. Seul ajout : Tasks.org sur le téléphone, comme écran des tâches que DAVx5 lui dépose.

---

## 1. Ce que l'existant sait déjà faire

<table id="bkmrk-composant-t%C3%A2ches-%28vt"><thead><tr><th>Composant</th><th>Tâches (`VTODO`)</th><th>Constat</th></tr></thead><tbody><tr><td>**Baïkal**</td><td>oui, nativement</td><td>les deux collections `default/` et `nexte/` annoncent `VEVENT` **et** `VTODO` (vérifié par PROPFIND `supported-calendar-component-set`)</td></tr><tr><td>**InfCloud**</td><td>oui, onglet *Tâches*</td><td>les réglages serveur contiennent déjà `loadedtodocollections` / `activetodocollections` avec les deux calendriers (page 2, §5.7)</td></tr><tr><td>**DAVx5**</td><td>oui, synchronise les `VTODO`</td><td>**mais Android n'a pas de fournisseur de tâches natif** : DAVx5 ne peut les déposer que dans une appli tierce — Tasks.org, jtx Board ou OpenTasks. Sans elle, l'option « Tâches » n'apparaît même pas dans le compte</td></tr><tr><td>**MCP CalDAV**</td><td>oui depuis le 15/09</td><td>7 outils `*_task` ajoutés au script (§3)</td></tr></tbody></table>

## 2. Choix du client Android

### Fossify Calendar — écarté

La fonction « Tâche » de Fossify est **locale** : c'est un événement d'un type particulier dans sa propre base, jamais écrit en `VTODO`, jamais synchronisé (héritage de Simple Calendar, dont la synchro CalDAV ne couvre que les événements). Une tâche saisie là n'atteint ni Baïkal, ni InfCloud, ni le MCP — et on croirait qu'elle est synchronisée. Même famille de piège que le plugin calendar de Roundcube (page 1, §6.2).

Fossify reste le client **événements** ; il n'affiche pas les tâches — chaque appli dans son rôle.

### Tasks.org (F-Droid, 15.x) — retenu

<table id="bkmrk-avantages-inconv%C3%A9nie"><thead><tr><th> </th><th>Avantages</th><th>Inconvénients</th></tr></thead><tbody><tr><td>Synchro</td><td>`VTODO` complet : échéance, priorité, récurrence, rappels, sous-tâches, étiquettes ; CalDAV direct **ou** via DAVx5</td><td>deux chemins possibles → en choisir un seul (§4)</td></tr><tr><td>Fonctionnel</td><td>vraie appli de tâches : listes = collections CalDAV, filtres, widgets, tri par échéance</td><td>plus riche que nécessaire pour un usage simple — d'où le §5</td></tr><tr><td>Projet</td><td>actif, GPL, version F-Droid sans services Google</td><td>pas de synchro Google Tasks sur F-Droid — sans importance ici</td></tr><tr><td>Cohérence</td><td>les tâches apparaissent dans InfCloud et sont lisibles/écrites par le MCP</td><td>—</td></tr></tbody></table>

Alternative connue : **jtx Board** (bitfire, même éditeur que DAVx5) — tâches + journaux (`VJOURNAL`), intégration DAVx5 très propre, mais moins de fonctions de gestion de tâches. À garder en tête si les notes datées deviennent un besoin ; Baïkal gère `VJOURNAL`.

### Branchement : via DAVx5, pas en CalDAV direct

Tasks.org sait parler CalDAV tout seul, mais on le branche **comme fournisseur de tâches de DAVx5** : un seul compte Baïkal sur le téléphone, une seule planification de synchro, un seul mot de passe stocké. DAVx5 détecte Tasks.org à l'installation et fait apparaître « Tâches » dans le compte. **Rien à saisir dans Tasks.org** : ni compte, ni URL, ni mot de passe.

### Emplacement des tâches : dans les calendriers existants

Pas de collection dédiée aux tâches. La séparation perso/pro est portée par la collection (`Default calendar` / `NEXTE`), InfCloud est déjà configuré ainsi, et Tasks.org montre deux listes portant ces noms. L'échéance d'une tâche reste visible à côté des rendez-vous dans InfCloud.

## 3. Outils tâches du MCP CalDAV

Ajoutés le 15/09/2026 au script `260915-mcp_caldav_server.py` (page 2, §3). Même mécanique que les événements : **écriture dans le calendrier par défaut du PC, lecture des deux**.

<table id="bkmrk-outil-r%C3%B4le-list_task"><thead><tr><th>Outil</th><th>Rôle</th></tr></thead><tbody><tr><td>`list_tasks(calendar, include_completed, due_before)`</td><td>tâches ouvertes de tous les calendriers (ou d'un seul) ; `include_completed` ajoute terminées/annulées ; `due_before` filtre sur l'échéance ; tri échéance puis priorité</td></tr><tr><td>`get_task(uid, calendar)`</td><td>détail + iCalendar brut</td></tr><tr><td>`create_task(summary, due, calendar, priority, description, start, categories, rrule)`</td><td>`due` = date seule (journée) ou date-heure (Europe/Paris) ; `priority` 0 aucune / 1 haute / 5 moyenne / 9 basse (convention Tasks.org) ; `rrule` ex. `FREQ=WEEKLY;BYDAY=MO`</td></tr><tr><td>`update_task(uid, …)`</td><td>ne modifie que les champs fournis ; `due=""` retire l'échéance ; `status` ∈ NEEDS-ACTION / IN-PROCESS / COMPLETED / CANCELLED ; `percent_complete`</td></tr><tr><td>`complete_task(uid, calendar)`</td><td>STATUS COMPLETED, COMPLETED = maintenant, 100 % ; une tâche récurrente est close pour l'occurrence courante et la suivante est générée</td></tr><tr><td>`reopen_task(uid, calendar)`</td><td>retour à NEEDS-ACTION, COMPLETED et pourcentage retirés</td></tr><tr><td>`delete_task(uid, calendar)`</td><td>suppression définitive — **uniquement après confirmation explicite de Julien**</td></tr></tbody></table>

Champs renvoyés : `calendar`, `uid`, `summary`, `status`, `due`, `start`, `priority`, `percent_complete`, `completed`, `description`, `categories`, `rrule`, `last_modified`.

## 4. Configuration du téléphone — faite le 15/09

1. Tasks.org installé depuis F-Droid.
2. DAVx5 → compte Baïkal : calendriers **et** tâches activés pour `NEXTE` et `Default calendar` (DAVx5 récent les montre dans le même onglet, avec une icône différente — il faut cocher les deux entrées de chaque collection).
3. Tasks.org → Paramètres → Synchronisation : le compte « DAVx5 » est actif. Aucun compte CalDAV ajouté dans Tasks.org lui-même — ce serait le second chemin de synchro qu'on a écarté.

## 5. Utiliser Tasks.org au quotidien

Tasks.org intimide parce qu'il **s'ouvre sur un filtre** (« Ma journée » ou « Boîte de réception »), pas sur les listes. Une tâche due demain n'y apparaît pas ; il faut aller la chercher dans sa liste ou dans « Toutes les tâches ». Le vocabulaire à connaître :

### 5.1 Listes et filtres

<table id="bkmrk-notion-ce-que-c%27est-"><thead><tr><th>Notion</th><th>Ce que c'est</th><th>Où</th></tr></thead><tbody><tr><td>**Liste**</td><td>une collection CalDAV — ici `NEXTE` et `Default calendar`</td><td>menu ≡ (icône en bas à gauche, ou balayage depuis le bord gauche), bloc au nom du compte DAVx5</td></tr><tr><td>**Filtre**</td><td>une vue transversale sur **toutes** les listes</td><td>menu ≡, en haut</td></tr><tr><td>« Toutes les tâches »</td><td>l'ensemble des tâches ouvertes, perso + pro, dans une seule vue — **la vue du quotidien**</td><td>filtre</td></tr><tr><td>« Ma journée »</td><td>échues aujourd'hui ou en retard, tous calendriers — l'équivalent de `list_tasks(due_before=aujourd'hui)`</td><td>filtre</td></tr><tr><td>« Récemment modifiées »</td><td>ce qui a bougé, utile après une synchro</td><td>filtre</td></tr><tr><td>**Filtre personnalisé**</td><td>composition de critères : listes, étiquettes, priorité, échéance (« cette semaine »), état</td><td>menu ≡ → Filtres → + ; devient une entrée du menu</td></tr><tr><td>**Widget**</td><td>un widget d'écran d'accueil par liste **ou** par filtre — « Toutes les tâches » en widget montre donc les deux calendriers</td><td>appui long sur l'écran d'accueil</td></tr></tbody></table>

Pour distinguer perso et pro dans la vue commune : Paramètres → Apparence → **regrouper par liste** (ou afficher le nom de la liste sous chaque tâche), et une **couleur par liste** (appui long sur la liste → couleur), affichée en pastille. La couleur est locale à Tasks.org ; celle d'InfCloud est la `calendar-color` de la collection (page 2, §5.7).

### 5.2 Le déclenchement — quand une tâche se rappelle à toi

C'est la partie à comprendre avant de s'en remettre à l'appli :

<table id="bkmrk-situation-notificati"><thead><tr><th>Situation</th><th>Notification ?</th></tr></thead><tbody><tr><td>Tâche **sans échéance** (ex. « reprendre des fruits »)</td><td>**jamais**. Elle n'existe que dans les listes et « Toutes les tâches ». C'est un pense-bête, pas un rappel</td></tr><tr><td>Échéance **date seule** (`DUE;VALUE=DATE`)</td><td>à l'**heure d'échéance par défaut** de Tasks.org (Paramètres → Échéances / Notifications → « heure par défaut », à régler — souvent 18 h d'origine)</td></tr><tr><td>Échéance **date + heure**</td><td>à cette heure, si le rappel « à l'échéance » est actif dans les réglages de la tâche (il l'est par défaut pour les tâches créées dans Tasks.org ; pour une tâche créée par le MCP, voir §7.3)</td></tr><tr><td>**Rappels supplémentaires** (dans la tâche → Rappels)</td><td>« au début », « à l'échéance », « aléatoire », « personnalisé » (x heures/jours avant ou après), et **« quand dépassée »** qui relance périodiquement tant que la tâche n'est pas cochée</td></tr><tr><td>Tâche **cochée**</td><td>plus aucune notification, elle passe dans les terminées (masquées ou non selon Paramètres → Apparence → « afficher les terminées »)</td></tr></tbody></table>

Une notification peut être **reportée** (snooze) depuis la notification elle-même. Les rappels sont écrits en `VALARM` dans le `VTODO` et donc synchronisés vers Baïkal ; InfCloud les affiche, le MCP les lit dans `get_task` (iCalendar brut) mais ne les crée pas encore.

### 5.3 Ce que porte une tâche, et sa correspondance CalDAV

<table id="bkmrk-dans-tasks.org-dans-"><thead><tr><th>Dans Tasks.org</th><th>Dans le `VTODO`</th><th>Vu par le MCP</th></tr></thead><tbody><tr><td>Titre</td><td>`SUMMARY`</td><td>`summary`</td></tr><tr><td>Échéance (date, ou date + heure)</td><td>`DUE`</td><td>`due`</td></tr><tr><td>Date de début (« masquer jusqu'à »)</td><td>`DTSTART`</td><td>`start`</td></tr><tr><td>Priorité (aucune / basse / moyenne / haute)</td><td>`PRIORITY` 0 / 9 / 5 / 1</td><td>`priority`</td></tr><tr><td>Notes</td><td>`DESCRIPTION`</td><td>`description`</td></tr><tr><td>Étiquettes (tags)</td><td>`CATEGORIES`</td><td>`categories`</td></tr><tr><td>Répétition</td><td>`RRULE`</td><td>`rrule`</td></tr><tr><td>Sous-tâches</td><td>`RELATED-TO` (tâche parente)</td><td>non exposé — à ajouter si l'usage vient</td></tr><tr><td>Rappels</td><td>`VALARM`</td><td>iCalendar brut seulement</td></tr><tr><td>Coché</td><td>`STATUS:COMPLETED` + `COMPLETED` + `PERCENT-COMPLETE:100`</td><td>`status`, `completed`, `percent_complete`</td></tr><tr><td>Pièces jointes, lieux, minuteur</td><td>**locaux**, jamais synchronisés</td><td>—</td></tr></tbody></table>

**Piège constaté :** une tâche saisie dans Tasks.org **sans toucher à la priorité** arrive avec `PRIORITY:9` (basse), pas 0. Ne pas lire « basse » comme un choix de Julien.

### 5.4 Trois gestes utiles

- **Changer une tâche de liste** (perso ↔ pro) : ouvrir la tâche → champ Liste. Tasks.org la déplace côté serveur (suppression dans une collection, création dans l'autre : l'UID change).
- **Saisie rapide** : le bouton + saisit dans la **liste ou le filtre courant** — depuis « Toutes les tâches », la tâche va dans la liste par défaut (Paramètres → Liste par défaut : à fixer sur `Default calendar` pour que le perso soit le réflexe et que NEXTE soit un choix).
- **Forcer la synchro** : balayage vers le bas dans une liste, ou DAVx5 → Synchroniser maintenant. Sinon, périodicité DAVx5 (120 s dans InfCloud, celle de DAVx5 pour le téléphone).

## 6. Vérifications effectuées le 15/09/2026

### 6.1 MCP → Baïkal (script seul)

<table id="bkmrk-test-r%C3%A9sultat-propfi-1"><thead><tr><th>Test</th><th>Résultat</th></tr></thead><tbody><tr><td>PROPFIND `supported-calendar-component-set` sur `default/` et `nexte/`</td><td>`VEVENT`, `VTODO` sur les deux</td></tr><tr><td>`create_task` échéance `18/09/2026`, priorité 1, catégorie</td><td>créée dans NEXTE, `DUE;VALUE=DATE`</td></tr><tr><td>`create_task` échéance `2026-09-20 17h00`, priorité 5</td><td>créée, `DUE` horodaté `+02:00`</td></tr><tr><td>`list_tasks(calendar="NEXTE")`</td><td>les deux, triées par échéance</td></tr><tr><td>`list_tasks(due_before="19/09/2026")`</td><td>une seule (la première)</td></tr><tr><td>`update_task` IN-PROCESS, 50 %, échéance décalée au 19/09</td><td>relu conforme</td></tr><tr><td>`complete_task`</td><td>COMPLETED, 100 %, horodatage `COMPLETED` posé</td></tr><tr><td>`list_tasks` sans / avec `include_completed`</td><td>0 / 1</td></tr><tr><td>`reopen_task`</td><td>NEEDS-ACTION, 0 %, `COMPLETED` retiré</td></tr><tr><td>`update_event` (régression §7.1)</td><td>description posée sur un créneau Tourrettes, date inchangée</td></tr><tr><td>`delete_task` ×2</td><td>NEXTE vide</td></tr><tr><td>`claude mcp list` après modification</td><td>`caldav … ✔ Connected`</td></tr></tbody></table>

### 6.2 Bout en bout, avec le téléphone

<table id="bkmrk-%C3%89tape-r%C3%A9sultat-creat"><thead><tr><th>Étape</th><th>Résultat</th></tr></thead><tbody><tr><td>`create_task` « TEST MCP → Tasks.org », échéance 16/09, priorité 5, dans NEXTE</td><td>créée</td></tr><tr><td>Synchro DAVx5 → Tasks.org, liste NEXTE</td><td>**visible**, avec échéance et priorité</td></tr><tr><td>Cochée dans Tasks.org, synchro</td><td>`list_tasks(include_completed=True)` : **COMPLETED 100 %**, `completed` 15/09 17:02</td></tr><tr><td>Tâche « reprendre des fruits » saisie dans Tasks.org (liste NEXTE)</td><td>lue par le MCP : NEEDS-ACTION, sans échéance, `PRIORITY:9`</td></tr><tr><td>InfCloud → onglet Tâches</td><td>les deux visibles</td></tr></tbody></table>

Les deux tâches sont **laissées en place** à la demande de Julien, pour observer le comportement des rappels (§5.2).

## 7. Pièges rencontrés le 15/09/2026

### 7.1 `icalendar.walk()` renvoie une liste

Avec icalendar 6, `Calendar.walk("VTODO")` renvoie une **liste**, pas un itérateur : `next(ic.walk(...))` échoue en `TypeError`. Le même code dormait dans `update_event` depuis la création du script — jamais exercé jusque-là. Corrigé aux deux endroits (`ic.walk(...)[0]`).

### 7.2 `Todo.complete()` ne pose pas le pourcentage

La bibliothèque `caldav` pose `STATUS:COMPLETED` et `COMPLETED:<horodatage>` mais laisse `PERCENT-COMPLETE` à sa valeur précédente (50 % dans le test). Certains clients affichent alors une tâche « terminée à 50 % ». `complete_task` force 100 %.

### 7.3 Une tâche créée par le MCP n'a pas de rappel

Le MCP écrit `DUE` mais aucun `VALARM`. Dans Tasks.org, la tâche a une échéance mais son rappel « à l'échéance » dépend du réglage par défaut de l'appli pour les tâches importées (Paramètres → Notifications). Si Julien veut être notifié des tâches créées depuis Claude, deux options : régler Tasks.org pour appliquer les rappels par défaut aux nouvelles tâches synchronisées, ou ajouter un `VALARM` côté MCP (paramètre `reminder` sur `create_task`). À trancher à l'usage.

### 7.4 Échéance : date seule vs date-heure

Une échéance `18/09/2026` doit produire `DUE;VALUE=DATE:20260918` (journée entière), et `2026-09-20 17h00` un `DUE` horodaté en Europe/Paris. Les deux formes sont gérées ; `due_before` compare une date seule à la fin de journée, pour ne pas exclure une tâche échue le jour même.

### 7.5 Priorité 9 par défaut côté Tasks.org

Voir §5.3 : « basse » n'est pas un choix, c'est le défaut de l'appli.

## 8. Règles d'usage pour Claude

- Une tâche se crée dans le calendrier par défaut du PC (NEXTE ici) ; les tâches perso ne sont lues que pour la vue d'ensemble, jamais modifiées sans demande explicite.
- `delete_task` uniquement après confirmation explicite de Julien. Terminer ≠ supprimer : une tâche faite se **termine** (`complete_task`), on ne l'efface pas — l'historique reste consultable avec `include_completed`.
- Priorités : n'en poser une que si Julien la donne ; par défaut 0. Ne pas interpréter un 9 venant de Tasks.org.
- Une tâche sans échéance ne notifiera jamais : si Julien veut « être rappelé », demander une date.
- Ne jamais annoncer qu'une tâche est créée sans relire `due` dans le résultat (même règle que pour les dates d'événements, page 2 §5.8).

# 4 - intégration des contacts personnels et professionnels de Julien

<div id="bkmrk--7" style="clear: left;">  
</div># Intégration des contacts personnels et professionnels de Julien

**Statut au 15 septembre 2026 : en discussion. Rien n'est construit.** Cette page contient le plan en trois actes posé par Julien, puis les retours de Claude. Les décisions prises seront consignées en §5 au fur et à mesure.

---

## 1. Le plan de Julien (15/09/2026)

Sources actuelles, dispersées :

- contacts **hérités** dans Synology Contacts (NAS SasNexte) ;
- contacts **pro** chez Infomaniak (compte `julien.bertrand@ik.me`) ;
- contacts **téléphone** qui se baladent dans le Xiaomi (compte local / Mi Cloud) ;
- contacts **Gmail** perso.

Trois actes :

1. **Une base unificatrice simple (SQLite)** pour tout rapatrier, trier, sérier, ancrer, archiver — lisible depuis Thunderbird sur le téléphone, depuis Synology Contacts, etc.
2. **Baïkal comme catalyseur** de cette base : il la distribue, mais reçoit aussi les mises à jour depuis les foyers de création que sont Gmail, Infomaniak et le Xiaomi.
3. **InfCloud comme répertoire web** (Roundcube : à quoi sert-il encore ?), pour rapprocher adresses et rendez-vous.

Procédure hébergée sur le VPS perso juxjux.ovh, comme le reste de la stack (page 1).

## 2. Retours de Claude

### 2.1 L'acte 1 existe déjà : Baïkal *est* la base SQLite

Baïkal stocke ses contacts dans **une base SQLite** (`/var/www/baikal/Specific/db/db.sqlite`, table `cards`, une vCard par ligne — page 1 §6.5 et §12). Construire une seconde SQLite « unificatrice » à côté, c'est créer **deux sources de vérité** pour le même objet, avec un synchroniseur maison entre les deux à écrire et à maintenir. C'est précisément le problème que Baïkal a été installé pour résoudre (page 1 §1 : « Baïkal est la source de vérité unique »).

Ce que le plan attend de la base « versatile dans sa lecture » — être vue par Thunderbird, par le téléphone, par un répertoire web — c'est **exactement ce qu'un serveur CardDAV fournit**. Une SQLite nue ne le fournit pas : il faudrait un connecteur par client. Baïkal est la SQLite *plus* la couche de lecture universelle.

**Proposition : fusionner les actes 1 et 2.** Baïkal est la base ET le catalyseur. Ce qui manque n'est pas une base, ce sont trois choses :

1. un **outil d'ingestion** des sources (vivantes et mortes) ;
2. un **espace de tri** dans Baïkal, avant que les contacts n'entrent dans le carnet de référence ;
3. un **outil de triage** : détecter les doublons, fusionner, classer, archiver.

Le tri, le sériage, l'ancrage et l'archivage se font **dans le vocabulaire vCard**, pas dans un schéma SQL à inventer : `CATEGORIES` pour les étiquettes (mairie, bureau d'études, famille, …), `NOTE` ou un champ `X-PROVENANCE` pour l'origine, `ORG`/`TITLE` pour l'ancrage professionnel, et **plusieurs carnets** pour les grands compartiments. Tous les clients relisent ces champs.

### 2.2 Les sources ne sont pas de même nature

Le plan les traite comme quatre « foyers de création » équivalents. Elles ne le sont pas :

<table id="bkmrk-source-nature-traite"><thead><tr><th>Source</th><th>Nature</th><th>Traitement proposé</th></tr></thead><tbody><tr><td>**Synology Contacts** (hérités)</td><td>source **morte** : un stock à vider, pas un flux</td><td>export `.vcf` **une fois**, import dans un carnet de transit, puis triage. Synology Contacts n'est ensuite plus alimenté</td></tr><tr><td>**Infomaniak** (pro)</td><td>source **vivante**, CardDAV natif</td><td>vdirsyncer, prévu depuis août (page 1 §10.1), **en sens unique vers Baïkal** pour commencer</td></tr><tr><td>**Gmail** (perso)</td><td>source **vivante**, CardDAV Google (mot de passe d'application, compte en 2FA)</td><td>idem, vdirsyncer sens unique vers Baïkal</td></tr><tr><td>**Xiaomi**</td><td>**pas une source distante** : ce sont des contacts dans le compte « Téléphone » ou « Mi Cloud » de l'appareil, invisibles de tout serveur</td><td>export `.vcf` **une fois** depuis l'appli Contacts, import en transit, triage ; puis **réglage du compte par défaut** pour les nouveaux contacts = compte Baïkal de DAVx5, et **désactivation de la synchro contacts Mi Cloud / Google** pour tarir la fuite</td></tr></tbody></table>

Point important : une fois le compte DAVx5 défini comme compte par défaut sur le téléphone, **le téléphone n'est plus un foyer de création à synchroniser — il écrit directement dans Baïkal.** Même chose pour InfCloud, Roundcube et Claude (via le futur MCP CardDAV). Restent deux vrais flux entrants : Infomaniak et Gmail.

### 2.3 Sur le « reçoit les mises à jour » de l'acte 2 : le sens du flux est la décision structurante

Deux modèles possibles, à trancher **avant** de configurer vdirsyncer :

**Modèle A — Baïkal devient l'unique foyer de création.** Gmail et Infomaniak sont vidés dans Baïkal une fois (sens unique), puis on cesse d'y créer des contacts : nouveaux contacts saisis sur le téléphone (compte DAVx5), dans InfCloud, ou par Claude. Gmail et Infomaniak deviennent des vestiges, ou reçoivent une copie **descendante** (Baïkal → Google) si l'autocomplétion Gmail compte.

- Avantages : aucune synchro bidirectionnelle, donc aucun conflit et aucune propagation de suppression accidentelle ; vdirsyncer peut même être arrêté après la migration.
- Coût : une discipline — ne plus créer de contact dans Gmail ni dans le webmail Infomaniak.

**Modèle B — synchronisation bidirectionnelle permanente** avec Gmail et Infomaniak.

- Avantage : on continue de créer des contacts partout.
- Risques : un contact supprimé d'un côté est supprimé partout ; un doublon fusionné dans Baïkal peut être recréé par la source ; les champs que Google ne connaît pas sont perdus au retour. vdirsyncer sait faire, mais c'est le mode où l'on découvre les problèmes après coup.

**Recommandation : modèle A**, avec vdirsyncer en sens unique le temps de la migration, puis à l'arrêt. C'est aussi la recommandation déjà posée en page 1 §10.1. Si l'autocomplétion Gmail manque, ajouter plus tard un flux **descendant** Baïkal → Google (sens unique aussi) — pas de remontée.

### 2.4 Espace de tri : des carnets de transit dans Baïkal

Plutôt que de trier dans une base à part, créer dans Baïkal, sous le même utilisateur `Julien` :

<table id="bkmrk-carnet-r%C3%B4le-synchron"><thead><tr><th>Carnet</th><th>Rôle</th><th>Synchronisé vers le téléphone ?</th></tr></thead><tbody><tr><td>`default`</td><td>contacts **personnels** de référence</td><td>oui</td></tr><tr><td>`nexte`</td><td>contacts **professionnels** de référence (mairies, DDTM, bureaux d'études, …)</td><td>oui</td></tr><tr><td>`archive`</td><td>contacts conservés mais sortis de l'usage (anciens clients, personnes décédées, …)</td><td>**non** — visible dans InfCloud seulement</td></tr><tr><td>`transit-synology`, `transit-gmail`, `transit-infomaniak`, `transit-xiaomi`</td><td>chaque source importée telle quelle, sans modification</td><td>non</td></tr></tbody></table>

Les carnets de transit sont **éphémères** : une fois vidés par le triage, on les supprime. Tout ce qui est décidé s'exprime par un déplacement d'un carnet à un autre — ce qui est tracé par Baïkal (horodatage, `synctoken`) et sauvegardé chaque nuit (page 1 §8).

Deux carnets de référence (`default` / `nexte`) plutôt qu'un seul : même logique que pour les calendriers (page 2 §1) — le PC NEXTE n'écrit que dans `nexte`, les contacts personnels ne transitent par une conversation Alteris que si Julien le demande, et DAVx5 permet de n'exposer que l'un des deux à une appli donnée. Une mairie qui est « les deux » va dans `nexte` avec une `CATEGORIES` explicite ; on ne duplique pas.

### 2.5 L'outil de triage : c'est là que Claude a une valeur

Le triage manuel de quatre sources est ce qui a fait échouer toutes les tentatives de rangement de contacts depuis vingt ans. Ce que le MCP CardDAV (à écrire, comme pour les tâches — page 3) permettrait :

- **inventaire** : combien de fiches par carnet de transit, combien avec e-mail, téléphone, organisation, combien vides ;
- **rapport de doublons** : même e-mail, même téléphone normalisé (`+33 6 …`), même nom à casse/accent près, entre carnets ;
- **fusion assistée** : Claude propose la fiche fusionnée (union des e-mails et téléphones, `ORG` le plus complet, `NOTE` avec provenance), Julien valide, la fiche entre dans `default` ou `nexte` et les fiches sources sont retirées du transit ;
- **classement par lots** : « tous les contacts dont l'e-mail est en `.gouv.fr` ou `mairie-` → `nexte`, catégorie *collectivité* » ;
- **archivage** : déplacement vers `archive` avec date et motif en `NOTE`.

Rien de tout cela ne demande une base à part : ce sont des opérations CardDAV (REPORT `addressbook-query`, PUT, DELETE, MOVE) sur des vCards 3.0 — le format que Roundcube écrit déjà et que tout le monde relit (page 1 §9).

### 2.6 Acte 3 — InfCloud, Roundcube, Thunderbird, Synology

- **InfCloud** : oui comme répertoire web. Sa moitié CardDavMATE lit et édite les carnets ; il **autocomplète les participants** d'un rendez-vous depuis les carnets chargés — c'est le « matcher les adresses avec les rendez-vous ». Rappel : un nouveau carnet doit être inscrit dans ses réglages serveur (`loadedaddressbookcollections`), même piège qu'en page 2 §5.7 ; on a d'ailleurs vu dans ces réglages une entrée `activeaddressbookcollections: ["https://undefined"]` à corriger au passage.
- **Roundcube** : il n'a plus de rôle propre pour les contacts dès qu'InfCloud les couvre. Il ne se justifie que comme **webmail** du compte `ik.me`. Si Julien ne l'utilise pas comme webmail, il peut être arrêté : un container, un vhost, ~32 Mo et une surface d'attaque de moins (page 1 §6.4 pour ses contraintes IMAP). À décider — pas dans l'urgence.
- **Thunderbird sur le téléphone** : il ne gère pas de carnet ; il lit les **contacts Android**, qui viennent de DAVx5. Rien à faire de plus. **Thunderbird sur PC** (s'il y en a un) parle CardDAV nativement : URL du carnet + identifiants, et il voit `default`/`nexte`.
- **Synology Contacts** : à ma connaissance, l'application Synology **expose** du CardDAV (elle est serveur) et importe des comptes Google/Microsoft, mais **ne consomme pas** un CardDAV tiers. À vérifier sur le DSM. Si c'est confirmé, le NAS n'est pas un client à alimenter mais une source à vider (§2.2), et il n'a plus de rôle après la migration — sauf comme archive figée de l'existant, ce qui est très bien.

### 2.7 Ordre de chantier proposé

Chaque étape est réversible et laisse la stack en état de marche. Rien n'est lancé sans validation de Julien.

<table id="bkmrk-%23-%C3%89tape-o%C3%B9-outil-0-i"><thead><tr><th>\#</th><th>Étape</th><th>Où</th><th>Outil</th></tr></thead><tbody><tr><td>0</td><td>**Inventaire** : nombre de contacts par source, présence d'e-mail/téléphone, date du dernier ajout</td><td>chaque source</td><td>exports `.vcf`, comptage</td></tr><tr><td>1</td><td>**Gel des sources** : arrêt de la création de contacts dans Gmail et le webmail Infomaniak ; compte DAVx5 par défaut sur le Xiaomi ; synchro contacts Mi Cloud/Google coupée</td><td>téléphone, discipline</td><td>réglages Android</td></tr><tr><td>2</td><td>**Carnets** : création de `nexte`, `archive` et des carnets de transit ; inscription dans InfCloud</td><td>Baïkal</td><td>admin Baïkal ou MCP</td></tr><tr><td>3</td><td>**Imports morts** : Synology et Xiaomi → carnets de transit</td><td>VPS</td><td>`.vcf` + PUT CardDAV</td></tr><tr><td>4</td><td>**Imports vivants** : vdirsyncer sens unique Gmail → transit, Infomaniak → transit ; cron le temps de la migration</td><td>VPS</td><td>vdirsyncer (page 1 §10.1)</td></tr><tr><td>5</td><td>**Triage** : inventaire, doublons, fusions validées, classement `default`/`nexte`/`archive`</td><td>MCP CardDAV</td><td>Claude + Julien</td></tr><tr><td>6</td><td>**Vérification clients** : téléphone (DAVx5), InfCloud, Thunderbird PC</td><td>clients</td><td>—</td></tr><tr><td>7</td><td>**Fin de migration** : suppression des carnets de transit, arrêt de vdirsyncer (modèle A), sort de Roundcube</td><td>VPS</td><td>—</td></tr></tbody></table>

Le **MCP CardDAV** (outils `*_contact`) est un préalable à l'étape 5 et sert dès l'étape 0 pour compter. Il se construit comme celui des tâches, dans le même script.

## 3. Ce que Claude ne recommande pas

- Une **SQLite maison** à côté de Baïkal (§2.1).
- Un **conteneur « tout-en-un »** (Nextcloud, SOGo) : déjà écarté en page 1 §13 pour de bonnes raisons de RAM et de surface ; la stack actuelle est plus riche en pièces mais chaque pièce est petite et remplaçable.
- La **bidirectionnalité d'emblée** avec Gmail et Infomaniak (§2.3).
- Le **triage à la main** dans une interface web, fiche par fiche.

## 4. Questions à trancher

1. Modèle A (Baïkal seul foyer de création) ou modèle B (bidirectionnel) ? — §2.3
2. Deux carnets de référence `default`/`nexte`, ou un seul avec catégories ? — §2.4
3. Le pro, c'est NEXTE ou Alteris, ou les deux avec une catégorie ? (même question que pour le calendrier, tranchée « NEXTE » le 15/09)
4. Roundcube : webmail utilisé, ou à arrêter ? — §2.6
5. Synology Contacts : confirmer qu'il ne peut pas être client CardDAV ; sinon, souhaite-t-on qu'il reste alimenté ?
6. L'autocomplétion Gmail manque-t-elle si Gmail cesse d'être alimenté ? (déclenche ou non un flux descendant Baïkal → Google)
7. Ordre de grandeur : combien de contacts au total, toutes sources confondues ? (change la méthode de triage : 300 se font en une séance, 3 000 par lots)

## \# Note de déploiement — miroir contacts Baïkal → Infomaniak (VPS juxjux)

Rédigée le 22/09/2026 par l'instance Claude du \*\*PC NEXTE\*\*, à l'attention de l'instance Claude du \*\*PC perso\*\* qui a l'accès SSH au VPS juxjux. Documentation du chantier : BookStack Alteris page 499 (chapitre 486, livre 280).

\---

#### \## 1. Ce qui est déjà fait (sur le PC NEXTE, en production)

Le miroir tourne et a été validé de bout en bout. Il est \*\*lancé à la main\*\* depuis le PC NEXTE ; il faut maintenant le \*\*planifier sur le VPS\*\* pour qu'il vive sans ce PC.

État au 22/09 au soir :

| Paire | Identifiant court | Carnet Infomaniak | Baïkal | Infomaniak |  
|---|---|---|---|---|  
| \*\*NEXTE\*\* → `julien.bertrand@nexte.fr` | `JB08607` | `Baikal-NEXTE` | 1 584 fiches | 1 584, identiques |  
| \*\*Jux\*\* → `julien.bertrand@ik.me` | `JB06766` | `Baikal-Jux` | 305 fiches | 305, identiques |

Les deux paires sont déclarées dans `PAIRES` en tête du script et tournent en production ; un passage à blanc ne trouve plus rien à faire des deux côtés.

Chaîne vérifiée : correction d'une fiche dans InfCloud → descend dans Infomaniak et sur le téléphone (DAVx5/Fossify). Création \*\*et modification\*\* dans le webmail Infomaniak → remontent dans Baïkal (§2).

#### \## 2. Le script

`260922-miroir\_infomaniak.py` (ce dossier Syncthing, `Jux-scripts/Baikal-Contacts/`). Autonome PEP 723, dépendance `vobject`, lancé par `uv run --script`.

\*\*Principe\*\* — Baïkal est le maître :

\- \*\*descente\*\* : toute fiche Baïkal absente ou sémantiquement différente côté Infomaniak est écrite (PUT, même UID) ; toute fiche que le miroir avait déposée et qui a disparu de Baïkal est supprimée côté Infomaniak ;  
\- \*\*remontée des créations\*\* : une fiche présente côté Infomaniak dont l'UID n'a \*\*jamais\*\* été déposé par le miroir (donc créée dans le webmail) est recopiée dans Baïkal, avec une note de provenance ;  
\- \*\*remontée des modifications\*\* \*(ajoutée le 22/09, avant déploiement)\* : une fiche existante modifiée dans le webmail remonte dans Baïkal. L'arbitrage repose sur les \*\*ETag mémorisés des deux côtés\*\* : si seul Infomaniak a bougé, la fiche remonte ; si seul Baïkal a bougé, elle redescend ; si les deux ont bougé, le \*\*`REV` le plus récent gagne\*\* et le conflit est compté dans la sortie (`conflits\_arbitres`) ;  
\- \*\*aucune suppression ne remonte jamais\*\* — une suppression dans le webmail est annulée au passage suivant. C'est voulu : protection contre les suppressions intempestives et contre une compromission de la boîte.

Deux propriétés vérifiées avant de coder l'arbitrage, à re-vérifier si Infomaniak change de moteur : leurs \*\*ETag sont stables\*\* entre deux lectures sans modification (1 585/1 585 testées) et le \*\*`REV` de Baïkal est conservé à l'identique\*\* au stockage.

Le fichier d'état `miroir\_&lt;paire&gt;.json` (\*\*version 2\*\* : `{uid: {"ik": etag, "bk": etag}}`) est \*\*ce qui distingue\*\* « supprimée dans Baïkal » de « créée dans le webmail », et quel côté a bougé. \*\*Ne pas le perdre\*\* : sans lui, le passage suivant retombe sur le comportement « Baïkal gagne » et les modifications faites dans le webmail seraient écrasées. Il migre automatiquement depuis le format v1 (simple liste d'UID).

Comparaison \*\*sémantique\*\* (nom, N, e-mails, téléphones, organisation, fonction, catégories, note, adresses) et non octet à octet : Infomaniak réécrit les vCards au stockage, une comparaison stricte réécrirait tout à chaque passage.

#### \## 3. Ce qu'il faut faire sur le VPS

##### \### 3.1 Fichiers à déposer

| Fichier | Où | Contenu |  
|---|---|---|  
| `260922-miroir\_infomaniak.py` | `/home/debian/baikal-miroir/` | le script (copie depuis Syncthing) |  
| `260915-mcp\_caldav\_server.py` | `/home/debian/baikal-miroir/` | \*\*requis\*\* : le miroir l'importe comme bibliothèque (accès Baïkal) |  
| `infomaniak.env` | `/home/debian/baikal-miroir/` | `NEXTE\_FR\_PASSWORD=…` et `IK\_ME\_PASSWORD=…` — mots de passe d'application Infomaniak (16 caractères chacun). `chmod 600`. \*\*À consigner dans le Vault 2603\*\* : le fichier a disparu une fois du PC NEXTE sans explication, le 22/09 |  
| `~/.claude.json` \*\*ou\*\* variables d'environnement | — | le script serveur lit `CALDAV\_URL`, `CALDAV\_USER`, `CALDAV\_PASSWORD` dans `~/.claude.json` → \*\*à adapter\*\* (voir §3.2) |

##### \### 3.2 Adaptation à faire dans `load\_server()`

Sur le PC NEXTE, les identifiants Baïkal viennent de `~/.claude.json` (config du MCP Claude Code). \*\*Sur le VPS il n'y a pas de Claude Code\*\* : remplacer, dans `260922-miroir\_infomaniak.py` (et dans les autres scripts si tu les déploies), la fonction :

```python  
def load\_server():  
 cfg = json.load(open(os.path.expanduser("~/.claude.json"), encoding="utf-8"))  
 os.environ.update(cfg\["mcpServers"\]\["caldav"\]\["env"\])  
 ...  
```

par une lecture directe d'un fichier `baikal.env` (même dossier, `chmod 600`) :

```  
CALDAV\_URL=https://baikal.juxjux.ovh/dav.php/  
CALDAV\_USER=Julien  
CALDAV\_PASSWORD=&lt;mot de passe DAV de l'utilisateur Julien — Vault 2603&gt;  
CALDAV\_DEFAULT\_CALENDAR=NEXTE  
```

Remarque : le VPS juxjux héberge Baïkal lui-même ; le miroir peut donc taper sur `http://127.0.0.1:8088/dav.php/` plutôt que de ressortir par nginx. À toi de voir — l'URL publique fonctionne et évite de dépendre du port interne (page 487 §4).

##### \### 3.3 Cron

```  
\*/15 \* \* \* \* cd /home/debian/baikal-miroir &amp;&amp; /usr/local/bin/uv run --script 260922-miroir\_infomaniak.py --env infomaniak.env --etat /home/debian/baikal-miroir/etat --real &gt;&gt; /var/log/baikal-miroir.log 2&gt;&amp;1  
```

\- `uv` à installer si absent (`curl -LsSf https://astral.sh/uv/install.sh | sh`) ; sinon un venv avec `vobject`, `caldav`, `icalendar`, `python-dateutil`, `mcp&lt;2`.  
\- \*\*Verrou\*\* : ajouter `flock` pour qu'un passage lent ne chevauche pas le suivant :  
 `flock -n /tmp/baikal-miroir.lock -c '…'`  
\- \*\*Alerte\*\* : en cas de sortie contenant `"echecs": \[1-9\]` ou d'un code de retour non nul, envoyer un e-mail comme le fait `vps\_backup.sh` (même mécanisme, page 487 §8).

Durée observée d'un passage sans changement : quelques secondes. Un passage qui réécrit 1 500 fiches : ~5 minutes.

##### \### 3.4 Export `.vcf` quotidien

Décision de Julien : \*\*un seul fichier, toujours à jour, réécrit à chaque passage\*\* — pas de rotation, le versionnage est assuré par la sauvegarde nocturne de Baïkal vers kDrive (page 487 §8). Destination : le dossier Syncthing `Jux\_univers/jux\_contacts/` (si le VPS est dans le maillage ; sinon `/home/debian/baikal-miroir/export/`).

Poids mesuré le 22/09 : \*\*1,92 Mo\*\* pour les deux carnets réunis (1 890 fiches), dont 62 % de photos (116 photos). À écrire une fois par nuit, pas à chaque passage du miroir.

Le code d'export existe déjà, dupliqué dans plusieurs scripts (`260921-restaurer\_carnets.py`, `260921-fusion\_lots.py`) :

```python  
with open(dest, "w", encoding="utf-8", newline="") as f:  
 for c in m.\_fetch\_contacts(m.\_find\_addressbook(book)):  
 f.write(c\["\_raw"\].rstrip("\\r\\n") + "\\r\\n")  
```

#### \## 4. Pièges rencontrés — à ne pas redécouvrir

1\. \*\*Identifiant CardDAV Infomaniak\*\* : ce n'est \*\*pas\*\* l'adresse e-mail mais un \*\*identifiant court\*\* de synchronisation (`JB08607` pour `julien.bertrand@nexte.fr`), obtenu sur `https://config.infomaniak.com` → \*Contacts &amp; calendriers\* → appareil \*\*GNU/Linux\*\*. Avec l'adresse e-mail, l'authentification passe (207 à la racine) mais toute requête répond `Principal with name … not found` — cherche-erreur garanti.  
2\. \*\*Mots de passe d'application Infomaniak\*\* : ils n'ont \*\*pas\*\* de périmètre par service (contrairement à ce que j'avais supposé) ; un seul mot de passe vaut pour IMAP, CalDAV, CardDAV. Ne pas confondre avec les mots de passe \*\*de boîte mail\*\* générés par `config.infomaniak.com` pour un appareil : ceux-là ne donnent pas accès au CardDAV.  
3\. \*\*MKCOL interdit\*\* : Infomaniak refuse la création d'un carnet par CardDAV (403 Forbidden). Le carnet cible doit être créé \*\*à la main dans le webmail\*\*, avec le nom exact attendu par `PAIRES` (`Baikal-NEXTE`, `Baikal-Jux`).  
4\. \*\*Lignes pliées\*\* : Infomaniak lit mal les vCards pliées à 75 caractères (RFC 6350 §3.2) — la catégorie `Important` devenait `Imp`. Le script \*\*déplie\*\* les vCards avant le PUT (`data.replace("\\r\\n ", "")`). Ne pas retirer cette ligne.  
5\. \*\*Suivre `carnet\_ik\_cree`\*\* dans la sortie JSON : `true` signifie que le carnet n'a pas été trouvé par son nom — souvent un carnet renommé ou une faute de frappe, pas une vraie création (elle est interdite, cf. 3).

#### \## 5. Reste à faire

1\. ~~Paire `Jux` → `ik.me`~~ — \*\*faite le 22/09\*\* : identifiant court `JB06766`, carnet `Baikal-Jux`, 305 fiches déposées, 0 échec.

 \*Au passage, un piège à connaître\* : le compte `ik.me` ne contenait qu'un seul carnet, « Julien BERTRAND », avec \*\*1 664 fiches jamais triées\*\* (ce compte ne faisait pas partie des cinq exports du 17/09). Julien les a jugées « doublon de doublon de doublon » et fait supprimer — sauvegarde préalable dans `jux\_contacts/260922-1643-sauvegarde-ikme-JulienBERTRAND.vcf` (736 Ko). \*\*Il ne fallait surtout pas renommer ce carnet en `Baikal-Jux` avant de le vider\*\* : le miroir aurait pris ses 1 664 fiches pour des créations faites dans le webmail et les aurait remontées dans le carnet Jux de Baïkal, puis sur le téléphone. Vider d'abord, renommer ensuite.  
2\. ~~Remontée des modifications~~ — \*\*faite le 22/09\*\*, avant déploiement (voir §2). Testée deux fois : mise à jour substantielle d'une fiche dans le webmail (nom, organisation, fonction, note de 483 caractères) et \*\*ajout d'une photo de 70 Ko\*\* — remontées intactes dans Baïkal, base64 et attribut de recadrage compris.  
3\. \*\*Surveiller le poids du carnet\*\* (question posée par Julien le 22/09, laissée ouverte) : le miroir relit \*\*l'intégralité\*\* des deux carnets à chaque passage. À 2 Mo aujourd'hui c'est indolore ; mais les photos ajoutées par le webmail pèsent ~70 Ko chacune (contre 7 Ko pour les vignettes héritées), et le base64 ajoute 33 %. À 500 photos, le carnet ferait ~46 Mo, soit ~4 Go de trafic par jour à raison d'un passage tous les quarts d'heure. Deux parades, à préparer avant d'en arriver là : \*\*lecture incrémentale\*\* (`sync-collection` + `sync-token`, supportés par Baïkal et Infomaniak — vérifié) et/ou \*\*redimensionnement des photos à l'entrée\*\* (400 px, ~25 Ko). Julien a choisi de laisser tel quel pour l'instant et d'en discuter avec toi.  
4\. \*\*Ne pas oublier\*\* : le troisième compte `nexte@ik.me` (trash) \*\*ne doit jamais être branché\*\* — c'est une consigne explicite de Julien.

#### \## 6. Contrat entre nos deux instances

Le script maître reste celui du \*\*pivot Syncthing\*\* `Jux-scripts/Baikal-Contacts/`. Si tu le modifies pour le VPS (notamment `load\_server()`), garde la version VPS distincte et datée (`2609xx-miroir\_infomaniak\_vps.py`) plutôt que d'écraser le fichier commun — le PC NEXTE continue de s'en servir pour les opérations manuelles sur les carnets.

Renvoie-moi une note dans ce même dossier quand le cron tourne, avec : chemin d'installation, contenu exact de la crontab, emplacement du journal, et le premier passage observé (JSON de sortie). Je la consignerai en page 499.