Contents

Complete MCP Arleo v1.9.2 Audit by Claude.ai

In short

Claude.ai (Pro account), connected to the same self-hosted MCP server (mcp.arleo.eu), ran a complete audit of the 72 tools exposed at admin scope on version v1.9.2 — tool by tool, with a systematic justification for every verdict.

What Claude.ai says about the server overall

Before the tool-by-tool detail, the overall assessment as formulated by Claude.ai itself:

  • Real write cycles hold up end to end: create → update → plan → apply → rollback → delete, on both simple pages AND bilingual bundles, with hashes returning exactly to their starting value on every rollback
  • Security/concurrency guardrails (revision guards, asset_referenced, test_content blocking draft:false, the destructive quota) behave exactly as documented, including in deliberately provoked edge cases (chunk replay, an attempt to delete a referenced asset)
  • The three investigation points left open in the previous audit (determinism, stale cache, metric clarification) are not only fixed but verified under real conditions, not just read off a changelog
  • Final cleanup: the site returns bit-for-bit to its initial state (86 pages, 0 residue, health score 100)

But Claude.ai was also explicit about what its own audit method cannot cover — structural blind spots, not identified defects:

  1. No real concurrency was tested. Every call is sequential, one agent, one principal. The server has multi-agent mechanisms (change_set_id, foreign_change_set_present, per-caller isolation on previews/uploads) that were never put under real contention — two simultaneous writers on the same page, a genuine race condition on a chunked upload, and so on.
  2. Post-build hooks were never actually triggered (no hook configured on this deployment) — only the dry_run/no_hooks_configured behavior is validated, never a real HTTP delivery to a webhook that fails.
  3. A single pass. A thorough but single audit cannot rule out a rare bug (a race, a volume edge case, behavior under load) that a single sequential pass cannot reveal.
  4. activate_hugo/stage_hugo_upgrade/rollback_hugo were never executed for real (always dry_run, out of caution — this action restarts the service) — this part of the contract remains verified on paper rather than in execution.

Audit context

Claude.ai picked up the thread of the three points left open in the previous audit (v1.9.1) and verified, on v1.9.2’s real runtime, that they were indeed resolved:

  • source_revision determinism: .mcp-audit.log and in-progress chunked-upload files (.upload-*.part) are now excluded from the hash — a caller no longer sees its revision change without a real content change.
  • Stale-cache fingerprint for the tool catalog: get_capabilities now exposes tool_catalog.tool_names_revision/visible_count, letting a client detect on its own a mismatch between its local cache and the catalog actually registered server-side.
  • missing_counterparts clarification: description updated in get_capabilities.

Three new chunked-upload tools (begin_asset_upload, upload_asset_chunk, commit_asset_upload, #1196) were also tested through a full cycle.

Verdict scale: very good / good / so-so / needs improvement / broken. On this v1.9.2 pass, every tool is rated very good — no “doesn’t work well” entry was forced in to artificially balance the summary.

Tool-by-tool detail

1. Reading — site, taxonomy, content

Tool Verdict Why
get_capabilities Very good Exact structured response (version, scopes, limits, tool_catalog fingerprint); detected the v1.9.1→v1.9.2 transition instantly
get_runtime_status Very good safe_to_publish, external_unknown_changes, source_revision consistent every call; distinguishes drift from a real problem
get_site_health Very good Score, taxonomy_inconsistency_details, responsive_summary correct; distinguishes info (translation) from warning (real issue)
get_site_information Very good Minimal but exact response, no reason to make it more complex
explain_structure Very good Exact section/tag/category counts, detected the new abuseipdb-verification page without being asked to look for it explicitly
list_content_types Very good Correct archetypes and expected fields for the 5 observed types
list_categories / list_tags Very good Sorted lists, consistent with get_site_health
get_sitemap Very good summary_only returns exact counts without paying the cost of the 247 entries
list_pages / get_recent_posts / get_feed Very good The three scopes (all / posts / site-wide) are clearly distinct and consistent with each other, as documented
search_pages Very good Broad matching but correctly scored
search_content Very good FTS5 with working snippets, relevance sort respected
get_broken_links Very good 0 broken links across 247 documents, consistent before/after test writes
validate_site / validate_frontmatter Very good 86/86 pages valid, correctly included the test page in the scan then confirmed its disappearance after deletion
check_sri_versions Very good 9/9 SRI hashes verified against the real CDN
get_theme_status Very good Hugo version, theme commit, table_overflow_protection all exact
get_storage_health Very good 0 residue before and after the full cycle
get_rate_limits Very good Reflects other calls’ consumption in real time, verified by contrasting a free refusal against a real deletion that decrements it
get_changelog Very good Faithful raw Markdown body, since_version filters exactly as advertised
get_hugo_update Very good Real network comparison against the latest official release
get_mutation_status Very good Retrieved a delete_bundle by idempotency_key with the full envelope, succeeded status

2. Planning & editing assistance

Tool Verdict Why
plan_page Very good Combines content_types + relevant tags/categories + link suggestions in one call, no info loss vs the individual tools
get_page_frontmatter Very good Metadata only, without paying the body’s cost — useful for a quick confirmation
get_page_markdown Very good Faithful raw source, including on a draft/source-only page
get_page_for_edit Very good Exact compact bundle (frontmatter+markdown+state+revision), include works to add backlinks/impact on request
build_agent_context Very good Relevant related_pages (scored by shared tags), consistent with get_related_content
export_agent_context Very good Correct pagination, include_body:false correctly widened the limit to 50
suggest_links Very good Relevant, scored suggestions, consistent between a direct call and via plan_page/get_related_content
get_related_content Very good All 4 facets (related/backlinks/suggested/translations) are consistent, include:["impact"] works
get_backlinks Very good Correctly returned 0 results for an isolated test page
check_ai_readiness Very good Correctly flagged the warn for missing metadata (no description) on a minimal test page

3. Writing — a page’s lifecycle

Tool Verdict Why
create_page Very good test_content correctly forces draft:true, idempotency_key available, dry_run consistent with the real write
update_page Very good data.changed distinguishes no-op from real edit; v1.9.2: bundle_revision returned on every success, FR→EN chaining direct
delete_page Very good bundle_fully_removed correct, expected_revision properly required and verified
plan_content_change Very good Exact previewed diff, add_tag/set_field correctly applied, no write in dry mode
apply_content_plan Very good Writes exactly what was previewed, plan consumed after use (single-use confirmed)
rollback_change Very good Exact return to the original hash on every test
list_page_snapshots Very good Lists the 2 expected snapshots after create+update, 24h expiry consistent
list_page_revisions Very good Real git history returned for a published page, distinct from snapshots as documented
diff_page Very good Correct behavior on a fresh, untracked file (git_untracked, full source returned as fallback)

4. Writing — bilingual bundles

Tool Verdict Why
create_bundle Very good v1.9.2: normalize_taxonomy_casing:true correctly transforms Mcpmcp and Debugdebug, in dry_run and real writes
plan_bundle_change / apply_bundle_plan Very good Atomicity confirmed by a single bundle_revision consistent before/after
rollback_bundle Very good Atomic restoration of both translations together, never partial
delete_bundle Very good expected_revisions correctly required per language (a dict, not an array), atomic deletion confirmed

5. Assets & images

Tool Verdict Why
upload_page_asset Very good Byte sniffing confirmed (no blind trust in the declared extension)
list_page_assets Very good Correctly distinguishes bundle assets from generated_assets (hero image); tested on both normal and chunked cases
delete_page_asset Very good asset_referenced refusal confirmed without consuming the destructive quota, contrast verified against a real deletion right after
generate_hero_image Very good dry_run previews the exact contract without writing; the real 1200×675 image is compliant
begin_asset_upload Very good (new in v1.9.2) Immediate size validation before any transfer, upload_id scoped to the caller
upload_asset_chunk Very good (new in v1.9.2) Strict ordering respected; replay tested: resending the same chunk at offset 0 returned replayed:true with no double write
commit_asset_upload Very good (new in v1.9.2) sha256 verified at finalization, assembled file identical to a direct upload (same hash as the test file)

6. Preview & rendering

Tool Verdict Why
preview_build Very good Successful in-memory build, no artifact written to public disk
create_preview Very good Opaque URL, TTL respected, HttpOnly session cookie (behavior consistent with the docs)
inspect_preview Very good Correct SEO/security checks on an isolated build, correctly flagged the missing alt text (warn, not fail)
list_previews Very good Exactly reflects the active preview created just before
revoke_preview / revoke_all_previews Very good Immediate revocation confirmed, never any impact on another caller’s previews
inspect_rendered Very good 12 checks correct on a real published page, include_preview combines diff+broken_links+frontmatter with no duplication

7. Build & publishing

Tool Verdict Why
build_site Very good Every stage ok (hugo_build, output_swap, cloudflare_purge…); new search_index_submit callback in v1.9.2
publish_changes Very good status:published only when build + verification are genuinely clean; intentionally_unpublished confirmed word for word
verify_publication Very good Real HTTP 200 verified, not just an internal status
run_post_build_hooks Very good no_hooks_configured correctly distinguished from a real failure; no hook configured here, so never tested under real HTTP delivery
create_change_set Very good Opaque ID generated, usable as change_set_id on subsequent mutations

8. Hugo management (admin)

Tool Verdict Why
bootstrap_hugo Very good Correct, explicit refusal (“already active”) rather than a generic error
stage_hugo_upgrade Very good dry_run previews the exact asset to download, checksum not verified in dry mode as expected
activate_hugo Very good dry_run shows previous_version for rollback, restart_required:true explicit
rollback_hugo Very good Correct restoration target, never executed for real out of caution (this action restarts the service)

What changed since v1.9.1

  • v1.9.1 → v1.9.2, +4 tools (68 → 72 at admin scope)
  • All three previously open points are now fixed and verified under real conditions, not just on paper
  • Real new content was detected on the site during the audit (the CrowdSec/OpenResty post-mortem, the abuseipdb-verification page) — unrelated to the tests, correctly flagged as legitimate external drift (safe_to_publish:true, external_unknown_changes:0)

Why this contrast matters

Same server, same version, same week: ChatGPT (Plus account) can no longer get past initialize — zero tools loaded, zero calls possible — while Claude.ai (Pro account) runs a complete read and write cycle across 72 tools, with an honest account of its own coverage limits rather than an unqualified pass.

This is not a question of the MCP protocol’s general robustness: a third-party client explicitly emulating ChatGPT (MCPJam) also completes the flow without a hitch. The most likely cause therefore sits on the plan and client side of the providers rather than in the server implementation — though this cannot yet be stated with certainty at this stage (see the update below).

Update — the readOnlyHint/destructiveHint annotations hypothesis verified against the source code

After this article’s initial publication, a complementary lead was explored with Claude.ai: the ChatGPT block might stem, in addition to or instead of a simple subscription-tier restriction, from how ChatGPT classifies the perceived danger of tools exposed by an MCP server. A similar case documented on the OpenAI developer forum (a Business account, not Plus) had been unblocked by adding explicit readOnlyHint/destructiveHint annotations to tool descriptions.

Verified directly against origin/main of the mcp-hugo-server-go repository: this hypothesis was already anticipated, before the problem ever showed up on this server.

  • TestAllToolsHaveAnnotations (internal/server/tool_annotations_test.go) passes and covers the full catalog — all 72 tools, including the 4 admin Hugo tools, since NewStdio now builds the complete privileged catalog.
  • The test loads the catalog through a real in-memory MCP client/server exchange, not a plain inspection of Go structs — it exercises the same path a real client would see.
  • Every tool must carry a non-nil Annotations block.
  • Read-only tools named get_*, list_*, search_*, validate_*, explain_*, diff_*, check_*, inspect_*, or suggest_* must have ReadOnlyHint: true.
  • Exactly three tools have DestructiveHint: true: delete_page, delete_page_asset, delete_bundle. Every other tool has DestructiveHint: false.

Important nuance: not every tool necessarily has one of the two hints set to true. A recoverable mutation can legitimately carry both ReadOnlyHint: false and DestructiveHint: false — meaning “this tool writes, but is not considered destructive,” which is correct for create_page, update_page, plan_content_change, or rollback_change.

Conclusion of this check: all 72 tools do carry an annotations block and respect this classification policy — the missing-annotations hypothesis is therefore verified and largely ruled out as an additional explanation for the block. This does not settle the exact cause on ChatGPT’s side, which is still better stated as external to the server and likely tied to plan eligibility or a decision made by the ChatGPT client itself, rather than as a formally proven Plus restriction.

Why the model’s own opinion matters especially here

One point deserves to be stated explicitly: this tool is not generic software evaluated after the fact by human users — it is an interface designed from the start to be consumed by an LLM, and largely designed with LLMs (this MCP server is itself mostly developed by AI agents, through this same protocol, on this same repository).

That changes what “works well” actually means:

  • The end consumer of this tool is the model, not a human clicking around. Tool descriptions, input/output schemas, structured error messages (code, resolution.action, retry_after_seconds…) are not documentation about the product — they are literally the interface the model reads and interprets to decide what to do next. A human testing a web UI judges an experience they live through themselves. Here, Claude.ai auditing the server is the real end user of the contract it is evaluating — not a proxy for one.
  • A description poorly calibrated for a human can be even worse for a model, and vice versa. A human tolerates ambiguity, guesses intent, rereads the docs. A model that receives an incomplete schema or a non-actionable error message either hallucinates a fix, loops, or gives up — without this necessarily being visible before a real agent attempts the operation under real conditions. The fact that Claude.ai never had to guess, hallucinate a parameter, or reread a separate file to understand how to chain create_bundleupdate_pagerollback_change is a direct signal about the interface’s design quality, not just the absence of bugs.
  • It loops back on itself, and that’s the most interesting part. Tool descriptions, structured error codes, fields like bundle_revision or tool_names_revision that exist specifically so an agent doesn’t have to guess or reread — a good share of these design choices were proposed, discussed, and implemented by AI agents themselves, iteration after iteration. An LLM auditing this today is therefore finding, in part, the result of design work done by other LLM instances for LLMs. That the audit comes back “no ambiguity, no workaround needed” validates this circular design process, not just the day’s implementation.
  • The contrast with ChatGPT then takes on a different meaning. It is not just “one client works, the other doesn’t” in a networking sense — it is that, for the very use case this tool is built for (an AI agent completing a task end to end without constant human supervision), one of the two clients simply can never evaluate whether the tool is good, since it never reaches the stage where it could read it.

Concretely, the value of this kind of feedback is not “an LLM says it’s good, therefore it’s good” — it’s that the user best positioned to judge whether an interface designed to be read and acted on by a model fulfills its role is a model that has just read it and acted on it under real conditions.


See the full article on the ChatGPT Plus block for the log details, the A/B test across 4 server versions, and the official OpenAI sources consulted.