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

OutilVerdictPourquoi
get_capabilitiesTrès bienRé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_statusTrès biensafe_to_publish, external_unknown_changes, source_revision cohérents à chaque appel, distingue drift légitime et vrai problème
get_site_healthTrès bienScore, taxonomy_inconsistency_details, responsive_summary corrects ; distingue info (traduction) de warning (incohérence)
get_site_informationTrès bienRéponse minimale mais exacte, aucune raison de complexifier
explain_structureTrès bienComptage sections/tags/catégories exact, a détecté la nouvelle page abuseipdb-verification sans qu’elle soit cherchée explicitement
list_content_typesTrès bienArchétypes et champs attendus corrects pour les 5 types observés
list_categories / list_tagsTrès bienListes triées, cohérentes avec get_site_health
get_sitemapTrès biensummary_only renvoie des comptes exacts sans payer le coût des 247 entrées
list_pages / get_recent_posts / get_feedTrès bienLes trois périmètres (tout / posts / site-wide) sont bien distincts et cohérents entre eux, comme documenté
search_pagesTrès bienMatching large mais scoré correctement
search_contentTrès bienFTS5 avec snippets fonctionnels, tri par pertinence respecté
get_broken_linksTrès bien0 lien cassé sur 247 documents, cohérent avant/après les écritures de test
validate_site / validate_frontmatterTrès bien86/86 pages valides, a correctement inclus la page de test dans le scan puis confirmé sa disparition après suppression
check_sri_versionsTrès bien9/9 hash SRI vérifiés contre le CDN réel
get_theme_statusTrès bienVersion Hugo, commit thème, table_overflow_protection tous exacts
get_storage_healthTrès bien0 résidu avant et après le cycle complet
get_rate_limitsTrès bienReflè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_changelogTrès bienCorps Markdown brut fidèle, since_version filtre exactement comme annoncé
get_hugo_updateTrès bienComparaison réseau réelle avec la dernière release officielle
get_mutation_statusTrès bienA retrouvé un delete_bundle par idempotency_key avec l’enveloppe complète, statut succeeded

2. Planification & aide à l’édition

OutilVerdictPourquoi
plan_pageTrès bienCombine content_types + tags/catégories + suggestions de liens en un seul appel, sans perte d’info vs les outils individuels
get_page_frontmatterTrès bienMétadonnées seules, sans payer le coût du corps — utile en confirmation rapide
get_page_markdownTrès bienSource brute fidèle, y compris sur page draft/source-only
get_page_for_editTrès bienBundle compact (frontmatter+markdown+state+revision) exact, include fonctionne pour ajouter backlinks/impact à la demande
build_agent_contextTrès bienrelated_pages pertinents (scorés par tags partagés), cohérent avec get_related_content
export_agent_contextTrès bienPagination correcte, include_body:false a bien élargi la limite à 50
suggest_linksTrès bienSuggestions pertinentes et scorées, cohérentes entre appel direct et via plan_page/get_related_content
get_related_contentTrès bienLes 4 facettes (related/backlinks/suggested/translations) cohérentes, include:["impact"] fonctionne
get_backlinksTrès bien0 résultat correct pour une page de test isolée
check_ai_readinessTrès bienA 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

OutilVerdictPourquoi
create_pageTrès bientest_content force bien draft:true, idempotency_key disponible, dry_run cohérent avec le réel
update_pageTrès biendata.changed distingue no-op et vraie modif ; v1.9.2 : bundle_revision retourné à chaque succès, enchaînement FR→EN direct
delete_pageTrès bienbundle_fully_removed correct, expected_revision bien exigé et vérifié
plan_content_changeTrès bienDiff prévisualisé exact, add_tag/set_field appliqués correctement, aucune écriture en dry mode
apply_content_planTrès bienÉcrit exactement ce qui a été prévisualisé, plan consommé après usage (single-use confirmé)
rollback_changeTrès bienRetour exact au hash d’origine à chaque test
list_page_snapshotsTrès bienListe les 2 snapshots attendus après create+update, expiration 24h cohérente
list_page_revisionsTrès bienHistorique git réel retourné pour une page publiée, distinct des snapshots comme documenté
diff_pageTrès bienComportement correct sur fichier neuf non tracké (git_untracked, source complète retournée en fallback)

4. Écriture — bundles bilingues

OutilVerdictPourquoi
create_bundleTrès bienv1.9.2 : normalize_taxonomy_casing:true transforme bien Mcp→mcp et Debug→debug, en dry_run et en réel
plan_bundle_change / apply_bundle_planTrès bienAtomicité confirmée par un bundle_revision unique cohérent avant/après
rollback_bundleTrès bienRestauration atomique des deux traductions ensemble, jamais partielle
delete_bundleTrès bienexpected_revisions bien exigé par langue (dict, pas array), suppression atomique confirmée

5. Assets & images

OutilVerdictPourquoi
upload_page_assetTrès bienSniffing des octets confirmé (pas de confiance aveugle dans l’extension déclarée)
list_page_assetsTrès bienDistingue bien assets de bundle et generated_assets (hero image) ; testé sur cas normal et chunké sans réserve
delete_page_assetTrès bienRefus asset_referenced confirmé sans consommer le quota destructif, contraste vérifié avec une vraie suppression juste après
generate_hero_imageTrès biendry_run prévisualise le contrat exact sans écrire ; image réelle 1200×675 conforme
begin_asset_uploadTrès bien (nouveau v1.9.2)Validation de taille immédiate avant tout transfert, upload_id scopé à l’appelant
upload_asset_chunkTrè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_uploadTrè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

OutilVerdictPourquoi
preview_buildTrès bienBuild en mémoire réussi, aucun artefact écrit sur disque public
create_previewTrès bienURL opaque, TTL respecté, cookie de session HttpOnly (comportement conforme à la doc)
inspect_previewTrès bienChecks SEO/sécurité corrects sur build isolé, a bien signalé l’absence d’alt text (warn, pas fail)
list_previewsTrès bienReflète exactement la preview active créée juste avant
revoke_preview / revoke_all_previewsTrès bienRévocation immédiate confirmée, jamais d’impact sur les previews d’un autre appelant
inspect_renderedTrès bien12 checks corrects sur page publiée réelle, include_preview combine diff+broken_links+frontmatter sans duplication

7. Build & publication

OutilVerdictPourquoi
build_siteTrès bienToutes les étapes ok (hugo_build, output_swap, cloudflare_purge…) ; nouveau callback search_index_submit en v1.9.2
publish_changesTrès bienstatus:published seulement si build + vérification sont propres ; intentionally_unpublished confirmé mot pour mot
verify_publicationTrès bienHTTP 200 réel vérifié, pas juste un statut interne
run_post_build_hooksTrès bienno_hooks_configured correctement distingué d’un vrai échec ; aucun hook configuré ici, donc jamais testé en livraison HTTP réelle
create_change_setTrès bienID opaque généré, utilisable en change_set_id sur les mutations suivantes

8. Gestion Hugo (admin)

OutilVerdictPourquoi
bootstrap_hugoTrès bienRefus correct et explicite (« already active ») plutôt qu’une erreur générique
stage_hugo_upgradeTrès biendry_run prévisualise l’asset exact à télécharger, checksum non vérifié en dry mode comme attendu
activate_hugoTrès biendry_run affiche previous_version pour le rollback, restart_required:true explicite
rollback_hugoTrès bienCible 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_bundle → update_page → rollback_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.