Contenu

Audit complet MCP Arleo v1.9.2 par Claude.ai

En bref

Claude.ai (compte Pro), connecté au même serveur MCP auto-hébergé (mcp.arleo.eu), a mené un audit complet des 72 outils exposés en scope admin sur la version v1.9.2 — outil par outil, avec justification systématique pour chaque verdict.

Ce que Claude.ai dit du serveur en général

Avant le détail outil par outil, le constat global tel que formulé par Claude.ai lui-même :

  • Les cycles d’écriture réels tiennent de bout en bout : create → update → plan → apply → rollback → delete, sur pages simples ET bundles bilingues, avec les hashes qui reviennent exactement à leur valeur de départ à chaque rollback
  • Les garde-fous de sécurité/concurrence (revision guards, asset_referenced, test_content bloquant draft:false, quota destructif) se comportent exactement comme documenté, y compris dans des cas limites délibérément provoqués (rejeu de chunk, tentative de suppression d’asset référencé)
  • Les trois points d’investigation ouverts lors de l’audit précédent (déterminisme, cache stale, clarification métrique) sont non seulement corrigés mais vérifiés en conditions réelles, pas juste lus dans un changelog
  • Nettoyage final : le site revient bit à bit à son état initial (86 pages, 0 résidu, health score 100)

Mais Claude.ai a aussi été explicite sur ce que sa propre méthode d’audit ne peut pas couvrir — angles morts structurels, pas des défauts identifiés :

  1. Aucune vraie concurrence testée. Tous les appels sont séquentiels, un seul agent, un seul principal. Le serveur a des mécanismes multi-agent (change_set_id, foreign_change_set_present, isolation par appelant sur les previews/uploads) jamais mis sous contrainte réelle — deux writers simultanés sur la même page, une vraie race condition sur un upload chunké, etc.
  2. Les hooks post-build ne sont jamais réellement déclenchés (aucun hook configuré sur ce déploiement) — seul le comportement dry_run/no_hooks_configured est validé, jamais une vraie livraison HTTP vers un webhook qui échoue.
  3. Un seul passage. Un audit exhaustif mais unique ne peut pas exclure un bug rare (race, edge case sur volume, comportement sous charge) qu’un seul passage séquentiel ne peut pas révéler.
  4. activate_hugo/stage_hugo_upgrade/rollback_hugo jamais exécutés en réel (toujours dry_run, par prudence — action qui redémarre le service) — cette portion du contrat reste vérifiée sur le papier plutôt qu’en exécution.

Contexte de l’audit

Claude.ai a suivi le fil des trois points laissés ouverts lors du précédent audit (v1.9.1) et vérifié, sur le runtime réel de v1.9.2, qu’ils étaient bien résolus :

  • Déterminisme de source_revision : .mcp-audit.log et les fichiers de chunked-upload en cours (.upload-*.part) sont désormais exclus du hash — un caller ne voit plus sa révision changer sans changement de contenu réel.
  • Fingerprint anti-cache-stale du catalogue d’outils : get_capabilities expose maintenant tool_catalog.tool_names_revision/visible_count, permettant à un client de détecter lui-même un décalage entre son cache local et le catalogue réellement enregistré côté serveur.
  • Clarification de missing_counterparts : description mise à jour dans get_capabilities.

Trois nouveaux outils de chunked upload (begin_asset_upload, upload_asset_chunk, commit_asset_upload, #1196) ont aussi été testés en cycle complet.

Convention de verdict : très bien / bien / so-so / need improvement / broken. Sur cette passe v1.9.2, tous les outils sont notés très bien — aucune entrée “ne fonctionne pas bien” n’a été forcée pour équilibrer artificiellement la synthèse.

Détail outil par outil

1. Lecture — site, taxonomie, contenu

Outil Verdict Pourquoi
get_capabilities Très bien Réponse structurée exacte (version, scopes, limites, tool_catalog fingerprint) ; a détecté le passage v1.9.1→v1.9.2 sans ambiguïté
get_runtime_status Très bien safe_to_publish, external_unknown_changes, source_revision cohérents à chaque appel, distingue drift légitime et vrai problème
get_site_health Très bien Score, taxonomy_inconsistency_details, responsive_summary corrects ; distingue info (traduction) de warning (incohérence)
get_site_information Très bien Réponse minimale mais exacte, aucune raison de complexifier
explain_structure Très bien Comptage sections/tags/catégories exact, a détecté la nouvelle page abuseipdb-verification sans qu’elle soit cherchée explicitement
list_content_types Très bien Archétypes et champs attendus corrects pour les 5 types observés
list_categories / list_tags Très bien Listes triées, cohérentes avec get_site_health
get_sitemap Très bien summary_only renvoie des comptes exacts sans payer le coût des 247 entrées
list_pages / get_recent_posts / get_feed Très bien Les trois périmètres (tout / posts / site-wide) sont bien distincts et cohérents entre eux, comme documenté
search_pages Très bien Matching large mais scoré correctement
search_content Très bien FTS5 avec snippets fonctionnels, tri par pertinence respecté
get_broken_links Très bien 0 lien cassé sur 247 documents, cohérent avant/après les écritures de test
validate_site / validate_frontmatter Très bien 86/86 pages valides, a correctement inclus la page de test dans le scan puis confirmé sa disparition après suppression
check_sri_versions Très bien 9/9 hash SRI vérifiés contre le CDN réel
get_theme_status Très bien Version Hugo, commit thème, table_overflow_protection tous exacts
get_storage_health Très bien 0 résidu avant et après le cycle complet
get_rate_limits Très bien Reflète en temps réel la consommation des autres appels, vérifié en contrastant un refus gratuit vs une vraie suppression qui décrémente
get_changelog Très bien Corps Markdown brut fidèle, since_version filtre exactement comme annoncé
get_hugo_update Très bien Comparaison réseau réelle avec la dernière release officielle
get_mutation_status Très bien A retrouvé un delete_bundle par idempotency_key avec l’enveloppe complète, statut succeeded

2. Planification & aide à l’édition

Outil Verdict Pourquoi
plan_page Très bien Combine content_types + tags/catégories + suggestions de liens en un seul appel, sans perte d’info vs les outils individuels
get_page_frontmatter Très bien Métadonnées seules, sans payer le coût du corps — utile en confirmation rapide
get_page_markdown Très bien Source brute fidèle, y compris sur page draft/source-only
get_page_for_edit Très bien Bundle compact (frontmatter+markdown+state+revision) exact, include fonctionne pour ajouter backlinks/impact à la demande
build_agent_context Très bien related_pages pertinents (scorés par tags partagés), cohérent avec get_related_content
export_agent_context Très bien Pagination correcte, include_body:false a bien élargi la limite à 50
suggest_links Très bien Suggestions pertinentes et scorées, cohérentes entre appel direct et via plan_page/get_related_content
get_related_content Très bien Les 4 facettes (related/backlinks/suggested/translations) cohérentes, include:["impact"] fonctionne
get_backlinks Très bien 0 résultat correct pour une page de test isolée
check_ai_readiness Très bien A correctement signalé le warn sur métadonnées manquantes (pas de description) sur une page de test minimaliste

3. Écriture — cycle de vie d’une page

Outil Verdict Pourquoi
create_page Très bien test_content force bien draft:true, idempotency_key disponible, dry_run cohérent avec le réel
update_page Très bien data.changed distingue no-op et vraie modif ; v1.9.2 : bundle_revision retourné à chaque succès, enchaînement FR→EN direct
delete_page Très bien bundle_fully_removed correct, expected_revision bien exigé et vérifié
plan_content_change Très bien Diff prévisualisé exact, add_tag/set_field appliqués correctement, aucune écriture en dry mode
apply_content_plan Très bien Écrit exactement ce qui a été prévisualisé, plan consommé après usage (single-use confirmé)
rollback_change Très bien Retour exact au hash d’origine à chaque test
list_page_snapshots Très bien Liste les 2 snapshots attendus après create+update, expiration 24h cohérente
list_page_revisions Très bien Historique git réel retourné pour une page publiée, distinct des snapshots comme documenté
diff_page Très bien Comportement correct sur fichier neuf non tracké (git_untracked, source complète retournée en fallback)

4. Écriture — bundles bilingues

Outil Verdict Pourquoi
create_bundle Très bien v1.9.2 : normalize_taxonomy_casing:true transforme bien Mcpmcp et Debugdebug, en dry_run et en réel
plan_bundle_change / apply_bundle_plan Très bien Atomicité confirmée par un bundle_revision unique cohérent avant/après
rollback_bundle Très bien Restauration atomique des deux traductions ensemble, jamais partielle
delete_bundle Très bien expected_revisions bien exigé par langue (dict, pas array), suppression atomique confirmée

5. Assets & images

Outil Verdict Pourquoi
upload_page_asset Très bien Sniffing des octets confirmé (pas de confiance aveugle dans l’extension déclarée)
list_page_assets Très bien Distingue bien assets de bundle et generated_assets (hero image) ; testé sur cas normal et chunké sans réserve
delete_page_asset Très bien Refus asset_referenced confirmé sans consommer le quota destructif, contraste vérifié avec une vraie suppression juste après
generate_hero_image Très bien dry_run prévisualise le contrat exact sans écrire ; image réelle 1200×675 conforme
begin_asset_upload Très bien (nouveau v1.9.2) Validation de taille immédiate avant tout transfert, upload_id scopé à l’appelant
upload_asset_chunk Très bien (nouveau v1.9.2) Ordre strict respecté ; rejeu testé : renvoyer le même chunk à offset 0 a retourné replayed:true sans double écriture
commit_asset_upload Très bien (nouveau v1.9.2) sha256 vérifié à la finalisation, fichier assemblé identique à un upload direct (même hash que le fichier de test)

6. Preview & rendu

Outil Verdict Pourquoi
preview_build Très bien Build en mémoire réussi, aucun artefact écrit sur disque public
create_preview Très bien URL opaque, TTL respecté, cookie de session HttpOnly (comportement conforme à la doc)
inspect_preview Très bien Checks SEO/sécurité corrects sur build isolé, a bien signalé l’absence d’alt text (warn, pas fail)
list_previews Très bien Reflète exactement la preview active créée juste avant
revoke_preview / revoke_all_previews Très bien Révocation immédiate confirmée, jamais d’impact sur les previews d’un autre appelant
inspect_rendered Très bien 12 checks corrects sur page publiée réelle, include_preview combine diff+broken_links+frontmatter sans duplication

7. Build & publication

Outil Verdict Pourquoi
build_site Très bien Toutes les étapes ok (hugo_build, output_swap, cloudflare_purge…) ; nouveau callback search_index_submit en v1.9.2
publish_changes Très bien status:published seulement si build + vérification sont propres ; intentionally_unpublished confirmé mot pour mot
verify_publication Très bien HTTP 200 réel vérifié, pas juste un statut interne
run_post_build_hooks Très bien no_hooks_configured correctement distingué d’un vrai échec ; aucun hook configuré ici, donc jamais testé en livraison HTTP réelle
create_change_set Très bien ID opaque généré, utilisable en change_set_id sur les mutations suivantes

8. Gestion Hugo (admin)

Outil Verdict Pourquoi
bootstrap_hugo Très bien Refus correct et explicite (« already active ») plutôt qu’une erreur générique
stage_hugo_upgrade Très bien dry_run prévisualise l’asset exact à télécharger, checksum non vérifié en dry mode comme attendu
activate_hugo Très bien dry_run affiche previous_version pour le rollback, restart_required:true explicite
rollback_hugo Très bien Cible de restauration correcte, jamais exécuté en réel par prudence (action qui redémarre le service)

Ce qui a changé depuis v1.9.1

  • Version v1.9.1 → v1.9.2, +4 outils (68 → 72 en scope admin)
  • Les trois points ouverts précédemment sont tous corrigés et vérifiés en conditions réelles, pas seulement sur le papier
  • Nouveau contenu réel détecté sur le site pendant l’audit (post-mortem CrowdSec/OpenResty, page abuseipdb-verification) — sans rapport avec les tests, signalé correctement comme drift externe légitime (safe_to_publish:true, external_unknown_changes:0)

Pourquoi ce contraste compte

Le même serveur, la même version, la même semaine : ChatGPT (compte Plus) n’arrive plus à dépasser initialize — zéro outil chargé, zéro appel possible — pendant que Claude.ai (compte Pro) exécute un cycle complet de lecture et d’écriture sur 72 outils, avec un retour honnête sur ses propres limites de couverture plutôt qu’un satisfecit sans nuance.

Ce n’est pas une question de robustesse du protocole MCP en général : un client tiers émulant explicitement ChatGPT (MCPJam) complète lui aussi le flow sans accroc. La cause la plus probable se situe donc du côté des plans et des clients des fournisseurs plutôt que dans l’implémentation du serveur — sans qu’on puisse encore l’affirmer avec certitude à ce stade (voir la mise à jour ci-dessous).

Mise à jour — l’hypothèse des annotations readOnlyHint/destructiveHint vérifiée sur le code source

Après la publication initiale de cet article, une piste complémentaire a été explorée avec Claude.ai : le blocage ChatGPT pourrait tenir, en plus ou à la place d’une simple restriction de palier d’abonnement, à la façon dont ChatGPT classe la dangerosité perçue des outils exposés par un serveur MCP. Un cas similaire documenté sur le forum développeur OpenAI (compte Business, pas Plus) avait été débloqué en ajoutant des annotations readOnlyHint/destructiveHint explicites aux descriptions d’outils.

Vérification faite directement sur origin/main du dépôt mcp-hugo-server-go : cette hypothèse était déjà anticipée, avant même que le problème ne se manifeste sur ce serveur.

  • TestAllToolsHaveAnnotations (internal/server/tool_annotations_test.go) passe et couvre le catalogue complet — les 72 outils, y compris les 4 outils Hugo admin, puisque NewStdio construit désormais le catalogue privilégié complet.
  • Le test charge le catalogue via un vrai échange MCP client/serveur en mémoire, pas une simple inspection de structs Go — il exerce le même chemin qu’un vrai client verrait.
  • Chaque outil doit avoir un bloc Annotations non nul.
  • Les outils de lecture nommés get_*, list_*, search_*, validate_*, explain_*, diff_*, check_*, inspect_* ou suggest_* doivent avoir ReadOnlyHint: true.
  • Exactement trois outils ont DestructiveHint: true : delete_page, delete_page_asset, delete_bundle. Tous les autres ont DestructiveHint: false.

Nuance importante : tous les outils n’ont pas nécessairement l’un des deux hints à true. Une mutation récupérable peut légitimement porter ReadOnlyHint: false et DestructiveHint: false à la fois — ça signifie « cet outil écrit, mais n’est pas considéré comme destructif », ce qui est cohérent pour create_page, update_page, plan_content_change ou rollback_change.

Conclusion de cette vérification : les 72 outils ont bien un bloc d’annotations et respectent cette politique de classification — l’hypothèse des annotations manquantes est donc vérifiée et largement exclue comme explication supplémentaire du blocage. Ça ne tranche pas pour autant la cause exacte côté ChatGPT, qui reste préférable à formuler comme externe au serveur et probablement liée à l’éligibilité du plan ou à une décision du client ChatGPT lui-même, plutôt que comme une restriction Plus formellement prouvée.

Pourquoi l’avis du modèle lui-même compte particulièrement ici

Un point mérite d’être souligné explicitement : cet outil n’est pas un logiciel générique évalué après coup par des utilisateurs humains — c’est une interface conçue dès le départ pour être consommée par un LLM, et en bonne partie conçue avec des LLM (ce serveur MCP est lui-même développé en grande majorité par des agents IA, via ce même protocole, sur ce même dépôt).

Ça change la nature de ce que “fonctionne bien” veut dire :

  • Le consommateur final de cet outil, c’est le modèle, pas un humain qui clique. Les descriptions d’outils, les schémas d’entrée/sortie, les messages d’erreur structurés (code, resolution.action, retry_after_seconds…) ne sont pas de la documentation à propos du produit — ce sont littéralement l’interface que le modèle lit et interprète pour décider quoi faire ensuite. Un humain qui teste une UI web juge une expérience qu’il vit lui-même. Ici, Claude.ai qui audite le serveur est l’utilisateur final réel du contrat qu’il évalue — pas un proxy.
  • Une description mal calibrée pour un humain peut être encore pire pour un modèle, et inversement. Un humain tolère l’ambiguïté, devine l’intention, relit la doc. Un modèle qui reçoit un schéma incomplet ou un message d’erreur non actionnable soit hallucine une correction, soit boucle, soit abandonne — sans qu’on le voie forcément avant qu’un vrai agent tente l’opération en conditions réelles. Le fait que Claude.ai n’ait jamais eu besoin de deviner, halluciner un paramètre ou relire un fichier annexe pour comprendre comment enchaîner create_bundleupdate_pagerollback_change est un signal directement sur la qualité de conception de l’interface, pas seulement sur l’absence de bugs.
  • Ça boucle sur lui-même, et c’est le point le plus intéressant. Les descriptions d’outils, les codes d’erreur structurés, les champs comme bundle_revision ou tool_names_revision qui existent spécifiquement pour qu’un agent évite de deviner ou de relire — une bonne partie de ces choix de conception ont été proposés, discutés et implémentés par des agents IA eux-mêmes, au fil des itérations. Un LLM qui audite aujourd’hui trouve donc, pour partie, le résultat d’un travail de conception fait par d’autres instances de LLM pour des LLM. Que l’audit revienne “aucune ambiguïté, aucun contournement nécessaire” valide ce processus de conception circulaire — pas juste l’implémentation du jour.
  • Le contraste avec ChatGPT prend alors un sens différent. Ce n’est pas seulement “un client marche, l’autre pas” au sens réseau — c’est que, pour l’usage même auquel cet outil est destiné (un agent IA qui accomplit une tâche de bout en bout sans supervision humaine constante), un des deux clients ne peut tout simplement jamais évaluer si l’outil est bon, puisqu’il n’atteint jamais le stade où il pourrait le lire.

Concrètement, la valeur de ce type de retour n’est pas “un LLM dit que c’est bien, donc c’est bien” — c’est que l’utilisateur le mieux placé pour juger si une interface conçue pour être lue et actionnée par un modèle remplit son rôle, c’est un modèle qui vient de la lire et de l’actionner en conditions réelles.


Voir l’article complet sur le blocage ChatGPT Plus pour le détail des logs, du test A/B sur 4 versions serveur, et des sources officielles OpenAI consultées.