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_contentbloquantdraft: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 :
- 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. - 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_configuredest validé, jamais une vraie livraison HTTP vers un webhook qui échoue. - 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.
activate_hugo/stage_hugo_upgrade/rollback_hugojamais exécutés en réel (toujoursdry_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.loget 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_capabilitiesexpose maintenanttool_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 dansget_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 Mcp→mcp et Debug→debug, 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, puisqueNewStdioconstruit 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
Annotationsnon nul. - Les outils de lecture nommés
get_*,list_*,search_*,validate_*,explain_*,diff_*,check_*,inspect_*ousuggest_*doivent avoirReadOnlyHint: true. - Exactement trois outils ont
DestructiveHint: true:delete_page,delete_page_asset,delete_bundle. Tous les autres ontDestructiveHint: 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_changeest 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_revisionoutool_names_revisionqui 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.