01
Pourquoi Claude Code, pas un chat dans un onglet
Claude Code est un agent en ligne de commande : il lit les
fichiers du projet, modifie ceux que tu autorises, exécute
des commandes shell, et observe le résultat avant de
continuer. Trois différences avec un chat dans un onglet :
-
Le contexte du projet est natif. Pas besoin
de copier-coller des fichiers, l'agent les ouvre lui-même.
Il sait ce qui a changé entre deux commits, ce qui est dans
.gitignore, quels tests passent.
-
Les boucles de feedback sont courtes. Il
modifie un fichier, lance les tests, voit l'erreur, corrige.
Ce qui prendrait quinze allers-retours dans un chat se fait
en deux minutes.
-
Le cadre est explicite. Le fichier
CLAUDE.md du projet décrit les conventions, le
.claude/settings.json liste ce qui est autorisé,
demandé ou refusé. L'agent reste dans ces bornes par défaut.
Pour le détail des concepts (skills, MCP, hooks, sous-agents),
voir le guide
Claude Code pour débutants.
Cette page-ci montre comment je les utilise tous, ensemble,
sur ce site précis.
02
Architecture du .claude/ de ce repo
Six couches, chacune avec un rôle clair. Plus c'est haut,
plus c'est abstrait ; plus c'est bas, plus c'est atomique.
CLAUDE.md ← les règles du projet (lues à chaque session)
├── .claude/
│ ├── settings.json ← permissions allow/ask/deny + hooks
│ ├── commands/ ← raccourcis : /serve, /verifier, /journal-nouveau
│ ├── skills/ ← workflows : journal-entree, audit-page, ...
│ ├── agents/ ← rôles spécialisés : relecteur-tonalite, ...
│ ├── hooks/ ← garde-fous : validate-journal.sh
│ └── mcp/ ← outils atomiques exposés par un serveur Python
│ └── ia-simplement-tools/
│ ├── server.py ← 7 outils domaine (FastMCP)
│ ├── pyproject.toml
│ └── tests/test_outils.py
└── .mcp.json ← Claude Code lance le MCP au démarrage
Comment ça s'enchaîne quand je tape /journal-nouveau :
- La slash command
/journal-nouveau charge la skill journal-entree.
- La skill délègue l'interview au sous-agent
redacteur-journal.
- Le sous-agent pose 5 questions à Pierre, produit un brouillon.
- La skill appelle l'outil MCP
chercher_superlatifs pour valider le titre et le résumé.
- La skill appelle l'outil MCP
creer_entree_journal qui écrit le fichier.
- Pierre lance
git commit, le hook validate-journal.sh vérifie que l'index est cohérent. Bloque sinon.
03
Le CLAUDE.md de ce repo, commenté
Le fichier CLAUDE.md à la racine est lu
automatiquement à chaque session. Il décrit les conventions
et les invariants du projet. Voici le mien (extraits), suivi
d'annotations.
# CLAUDE.md, brief Claude Code pour ce projet
## Ce que ce repo est
Site statique vanilla HTML/CSS/JS. Aucun build, aucune dépendance npm
en production. Servi tel quel par Vercel via vercel.json.
Cadrage en deux phrases. Claude sait qu'il ne doit pas
chercher de package.json ni proposer
npm install.
## Avant tout commit
1. Valider que journal/entries/index.json est du JSON :
python3 -m json.tool < journal/entries/index.json > /dev/null
2. Pour chaque slug dans entries, vérifier que
journal/entries/<slug>.md existe.
Les invariants critiques. Le hook
validate-journal.sh automatise la vérification,
mais avoir la règle écrite ici sert d'abord à informer
l'agent, ensuite à documenter pour Pierre.
## Conventions de commit (en français)
- Impératif présent, minuscule en tête, ≤72 caractères.
- Pas de prefix conventional commits (feat:/fix:).
- Exemples : "ajoute entrée journal du 18 mai", "corrige lien sitemap".
Sans ces lignes, Claude propose par défaut du
feat: add journal entry à l'anglaise. Trois
lignes de cadrage suffisent à le ramener au style maison.
## Ton et rédaction
Bannir : "expert", "révolutionnaire", "unique", "incroyable",
"magique", "secret", "ultime", "parfait", "extraordinaire",
"best practices", "game changer". Pas d'emoji décoratif.
Préférer toujours [À COMPLÉTER PAR PIERRE] à une demi-vérité.
La règle de sobriété, en clair. Le sous-agent
relecteur-tonalite et l'outil MCP
chercher_superlatifs appliquent cette liste
mécaniquement.
04
Mon MCP custom : ia-simplement-tools
MCP, pour Model Context Protocol, c'est le protocole
qui permet de connecter à Claude des outils maison écrits dans
le langage de son choix. Pour ce site, j'ai écrit un serveur
Python qui expose sept outils domaine.
Liste des outils (chacun est un endpoint MCP que Claude peut
appeler pendant une session) :
lister_entrees_journal() — renvoie le journal trié par date.
creer_entree_journal(date, titre, resume, contenu_markdown) — écrit un .md et met à jour index.json.
valider_index_json() — vérifie validité JSON et correspondance slugs ↔ .md.
auditer_page_html(chemin) — audit a11y, SEO, sobriété sur une page.
lister_pages_documentation() — renvoie les pages doc avec titre/description.
verifier_sitemap() — croise sitemap.xml avec les .html présents.
chercher_superlatifs(chemin) — traque les mots bannis.
Le code du serveur, en extrait, avec FastMCP :
from fastmcp import FastMCP
from bs4 import BeautifulSoup
mcp = FastMCP("ia-simplement-tools")
@mcp.tool()
def valider_index_json() -> dict:
"""Vérifie validité JSON + correspondance 1-pour-1 slugs/fichiers."""
data = json.loads(JOURNAL_INDEX.read_text(encoding="utf-8"))
entries = data.get("entries", [])
slugs_index = {e.get("slug", "") for e in entries}
fichiers_md = {p.stem for p in JOURNAL_DIR.glob("*.md")
if p.name != "_MODELE.md"}
erreurs = []
for slug in slugs_index - fichiers_md:
erreurs.append(f"slug sans .md : {slug}")
for nom in fichiers_md - slugs_index:
erreurs.append(f".md sans entrée dans index.json : {nom}")
return {"ok": not erreurs, "erreurs": erreurs}
if __name__ == "__main__":
mcp.run()
Le serveur est déclaré dans .mcp.json à la racine
du projet :
{
"mcpServers": {
"ia-simplement-tools": {
"command": ".claude/mcp/ia-simplement-tools/.venv/bin/python",
"args": [".claude/mcp/ia-simplement-tools/server.py"]
}
}
}
Le serveur tourne dans un venv Python local
(zéro dépendance globale). Au démarrage de Claude Code, le
serveur est lancé automatiquement et les sept outils sont
disponibles dans la session.
Côté sécurité, deux garde-fous : validation stricte des
chemins (refus des chemins absolus et de tout ce qui sort de
la racine du site), et écriture restreinte au seul dossier
journal/entries/. Le reste est en lecture seule.
Le serveur est testé par
pytest
sur un mini-site jouet en tmp_path, douze tests
qui passent en moins d'une seconde.
Pourquoi un MCP, et pas juste un script Python en CLI
C'est la question la plus probable. Trois raisons :
-
Composabilité native avec Claude Code. L'agent
appelle l'outil sans passer par un sous-shell ni un script
bash intermédiaire. Une skill peut chaîner trois outils
(
valider_index_json → verifier_sitemap
→ auditer_page_html) sans gymnastique de
parsing de sortie.
-
Description machine-lisible des outils. Le
MCP expose à Claude la signature de chaque fonction, le type
de chaque argument, la docstring. L'agent sait quoi appeler
et comment, sans que je doive le décrire dans un prompt.
-
Réutilisabilité hors de ce projet. Si je
monte un deuxième site avec la même logique éditoriale, je
copie le dossier
.claude/mcp/, j'ajuste deux
constantes (SITE_ROOT, SUPERLATIFS_BANNIS),
c'est fini. Là où un script bash maison demande de
re-réfléchir à chaque fois.
Ce qu'un MCP n'apporte pas ici : il n'accélère pas
l'exécution (le coût dominant est le LLM lui-même), et il
n'apporte rien si on n'utilise pas Claude Code. C'est un
choix qui se justifie par le contexte agent, pas dans
l'absolu.
Pourquoi FastMCP et pas le SDK MCP officiel
FastMCP est un wrapper du SDK officiel qui supprime le
boilerplate. Au lieu d'enregistrer manuellement chaque outil
avec son JSON Schema, on écrit @mcp.tool() au-dessus
d'une fonction Python typée et FastMCP génère le schéma à
partir des annotations. Pour sept outils simples, c'est ~60 %
de code en moins qu'avec le SDK pur.
05
Mes six skills (workflows)
Une skill, c'est un fichier Markdown dans
.claude/skills/<nom>/SKILL.md qui décrit
un workflow réutilisable. Chaque skill orchestre les outils
MCP comme briques de bas niveau.
journal-entree — créer une entrée de journal en interview guidée
Délègue l'interview à redacteur-journal, valide titre et résumé avec chercher_superlatifs, écrit le fichier avec creer_entree_journal, propose un message de commit en français. Ne commit jamais elle-même.
audit-page — auditer une page HTML
Appelle auditer_page_html et chercher_superlatifs, met en forme un rapport markdown trié bloquant / à corriger / à savoir, propose les corrections une à une. Pour les superlatifs, délègue à relecteur-tonalite.
nouvelle-page-doc — créer une nouvelle page de documentation
Copie le gabarit d'une page doc existante, demande titre / description / slug / eyebrow numéroté, génère head complet avec og tags et canonical, ajoute la carte dans documentation/index.html et l'URL dans sitemap.xml, valide avec verifier_sitemap.
verifier-avant-push — checklist complète avant git push
Lit git diff --name-only HEAD, orchestre valider_index_json, verifier_sitemap, auditer_page_html et chercher_superlatifs sur les fichiers modifiés. Produit un rapport prioritisé. Ne lance jamais le push.
publier-demo — ajouter une démo Claude testée sur une page
Délègue à createur-demo qui interview Pierre sur un cas réel (prompt verbatim, modèle, résultat). Refuse les démos non testées. Insère un <figure class="demo-claude"> dans la page et ajoute le CSS associé si absent.
revue-tonalite — relire une page pour la sobriété
Appelle chercher_superlatifs sur la page, délègue les reformulations à relecteur-tonalite qui propose 2-3 alternatives neutres par occurrence avec recommandation, attend validation avant d'éditer.
06
Mes quatre sous-agents (rôles spécialisés)
Un sous-agent, c'est un agent spécialisé que la session
principale peut invoquer pour déléguer un travail focalisé.
Le sous-agent a son propre prompt système, ses propres outils,
et son propre contexte. Mes quatre :
relecteur-tonalite — propose des reformulations neutres
Pour chaque mot banni trouvé, lit le paragraphe complet, propose 2-3 reformulations, indique la recommandée, attend validation. Tools : Read, Edit.
auditeur-qualite — audit complet avec inspection runtime
Croise l'audit statique (MCP) avec une inspection au runtime via curl sur le serveur local. Classe les findings par sévérité, propose un ordre de résolution. Tools : Bash, Read.
redacteur-journal — interview structurée pour une entrée
Pose 5 questions (ce que tu as fait, ce qui a marché, ce qui a raté, ta décision, la suite), produit un brouillon Markdown au format maison. Refuse de remplir à la place de Pierre : [À COMPLÉTER PAR PIERRE]. Tools : Read, Write.
createur-demo — produit une démo Claude à partir d'un cas réel
Interview sur un cas concret (prompt verbatim, modèle, résultat), génère le brouillon HTML d'un <figure class="demo-claude"> avec snippet SDK Python idiomatique. Refuse les démos non testées. Tools : Read, Edit, Bash.
07
Mon hook validate-journal.sh
Le hook est un shell qui se déclenche avant que Claude n'exécute
un outil Bash. Il filtre lui-même : il ne fait
quelque chose que si la commande contient git commit.
Le script complet :
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
COMMAND="$(printf '%s' "$INPUT" | python3 -c \
'import json, sys
try:
d = json.load(sys.stdin)
print(d.get("tool_input", {}).get("command", ""))
except Exception:
print("")' 2>/dev/null || echo "")"
if [[ ! "$COMMAND" =~ git[[:space:]]+commit ]]; then
exit 0
fi
INDEX_JSON="journal/entries/index.json"
if ! python3 -m json.tool "$INDEX_JSON" > /dev/null 2>&1; then
echo "[validate-journal] $INDEX_JSON n'est pas du JSON valide." >&2
exit 2
fi
MISSING="$(python3 <<'PY'
import json, os
with open("journal/entries/index.json", encoding="utf-8") as f:
data = json.load(f)
missing = [e["slug"] for e in data.get("entries", [])
if not os.path.isfile(f'journal/entries/{e["slug"]}.md')]
if missing:
print("\n".join(missing))
PY
)"
if [[ -n "$MISSING" ]]; then
echo "[validate-journal] Slugs sans .md correspondant :" >&2
echo "$MISSING" >&2
exit 2
fi
exit 0
Pourquoi un hook plutôt qu'un test CI : pour un site
personnel, je veux que la garantie soit locale, immédiate,
et indépendante d'une infrastructure externe. Le hook tourne
sur ma machine, en moins d'une seconde, avant chaque commit.
08
Mes trois slash commands
Une slash command, c'est un raccourci dans la session Claude
Code, défini par un fichier .md dans
.claude/commands/. Trois pour ce site :
-
/serve — lance python3 -m http.server 8000
en arrière-plan et donne l'URL.
-
/verifier — exécute la skill verifier-avant-push
(checklist complète avant git push).
-
/journal-nouveau — lance la skill journal-entree
(interview + écriture du fichier).
09
Mon settings.json
Permissions explicites, séparées en trois catégories (extrait) :
{
"permissions": {
"allow": [
"Bash(python3:*)", "Bash(uv:*)",
"Bash(git status)", "Bash(git diff:*)", "Bash(git add:*)",
"Bash(git commit:*)", "Bash(grep:*)", "Bash(find:*)",
"WebFetch(domain:docs.anthropic.com)",
"WebFetch(domain:modelcontextprotocol.io)"
],
"ask": [
"Bash(git push:*)", "Bash(git checkout:*)",
"Bash(git reset:*)", "Bash(git rebase:*)"
],
"deny": [
"Bash(rm -rf /)", "Bash(rm -rf ~)", "Bash(npm:*)",
"Bash(curl * | sh)", "Bash(sudo:*)"
]
}
}
Choix conscient : git push reste dans
ask plutôt que allow. Même si j'ai
confiance dans le stack, je veux confirmer chaque
déploiement à la main. Trente secondes plus tard,
vercel.com a remis la production à jour.
10
Workflow type : publier une entrée de journal
La séquence exacte depuis la commande jusqu'au déploiement :
- Je tape
/journal-nouveau.
- La skill appelle
lister_entrees_journal, m'informe que la dernière entrée date de 6 jours.
- Le sous-agent
redacteur-journal prend la main, pose 5 questions. Je réponds.
- Il me propose un brouillon. Je relis, j'ajuste un paragraphe.
- La skill me demande titre et résumé. Je propose un titre, elle vérifie via
chercher_superlatifs. Pas de mot banni, OK.
- La skill appelle
creer_entree_journal. Le serveur MCP écrit journal/entries/2026-05-19-titre.md et insère le bloc en tête de index.json.
- La skill suggère un message de commit. Je tape
/verifier d'abord.
- La skill
verifier-avant-push tourne, sort « Prêt à pousser ».
- Je tape
git commit -m "ajoute entrée journal du 19 mai". Le hook validate-journal.sh intercepte, vérifie, laisse passer.
- Je tape
git push. Claude Code me demande confirmation (à cause de la règle ask). Je valide. Trente secondes plus tard, Vercel a redéployé.
Temps total, pour une entrée déjà claire dans la tête : 8 à
10 minutes, dont 5 d'écriture pure. Pour une entrée que je
dois muscler, plutôt 20 minutes.
11
Limites assumées
Ce stack ne fait pas tout. Les manques que j'assume :
-
Pas de CI automatisée. Le hook tourne
localement uniquement. Si quelqu'un poussait depuis une
autre machine sans le hook, l'index pourrait casser.
-
Pas de tests HTML automatisés. Tidy ou
html-validator ne sont pas branchés.
auditer_page_html
traite l'a11y et le SEO mais pas la conformité W3C stricte.
-
Pas de MCP tiers branchés. J'aurais pu
connecter le MCP Vercel pour lire les déploiements, ou un
MCP browser pour des audits Lighthouse automatiques. Ce
n'est pas encore fait. À ajouter une fois le MCP maison
rodé.
-
Le sous-agent
auditeur-qualite a besoin
du serveur local lancé. Pas de fallback si le port
8000 est occupé.
Ces manques ne m'empêchent pas de bosser. Quand ils
deviendront gênants, je les comblerai. C'est l'inverse du
stack « tout-CI tout-test tout-MCP » qu'on monte le premier
jour et qu'on n'utilise jamais.
Pour aller plus loin