Sentinel Signal

MCP Verify Round 5 implementation notes

Source: docs/mcp-verify-round-5-implementation.md

Document Content

MCP Verify Round 5 implementation notes

Date: 2026-08-04

This documents Round 5's two scoped changes, done as separate passes: TASK-16 (the /manage split) first, then TASK-09 (tier restructure), in the order planned in advance specifically so TASK-09 would have fewer sections to regroup.

TASK-16 — Move publisher/maintainer sections off the public page onto /manage

The public server-detail page (render_server_detail_page) carried 8 sections that are only relevant to a server's publisher or maintainer, not to an evaluator deciding whether to use the server: Own this MCP? (owner funnel), Publisher readiness checklist, Claims & monitoring, Alert routing, Maintainer analytics, Maintainer response quality, Maintainer annotations, and Maintainer rebuttals & expected behavior. /servers/{namespace}/{name}/manage (aliased at /manage/{namespace}/{name}) already existed as the maintainer control plane (claim, revalidate, update metadata, add annotations/rebuttals, Continuous Verify plan) — this was pure relocation of render call sites onto that already-built, already-noindex page, not new functionality.

**Placement on /manage** (render_management_page, main.py):

  • Own this MCP? (owner funnel) and Publisher readiness checklist — near the top, right after the header panel, ahead of "Where points are being lost".
  • Claims & monitoring — after the "Claim server" form.
  • Maintainer analytics and Maintainer response quality — grouped together, ahead of "Update metadata".
  • Maintainer annotations and Maintainer rebuttals — the read-only list renders inside the same section as the existing add-form (one <h2>, list above the form) rather than as a duplicate heading, so a maintainer sees current entries before adding a new one.
  • Alert routing — immediately before "Continuous Verify plan".

Public page: the 8 sections and their now-unused HTML variables were removed from render_server_detail_page. In their place, the Tier-1 stat grid gained one new "Ownership" stat (claim status + link to /manage) using manage_url, which was already computed but unused on that page — a cheap way to avoid an information gap where the owner-funnel/publisher-readiness content used to sit. The stray id="badge" anchor that lived on the old "Claims & monitoring" <section> was dropped along with the section; no inbound deep links to it were found (not exhaustively verified — same caveat as the original design note).

CSS fix required for the move: /manage's inline <style> block was hand-rolled and never defined .stat, .score-value, .score-hint, .compare-table-wrap, or .tag-chip — classes the relocated render functions depend on. Rather than hand-copy those rules, render_management_page now uses the shared render_verify_page_styles() helper (already used by most other Verify pages, e.g. /badges) with an extra_css block preserving /manage's original form-specific rules (full-width inputs, .status-card variants, .help-block, etc.). This was a correctness fix, not a redesign — without it the moved sections would have rendered unstyled on /manage. It also picks up the shared stylesheet's existing @media (max-width: 760px) rules for free, which /manage previously lacked.

Scope cut from the original design note: the note additionally proposed a prominent, 3-variant "Badge embed" section near the top of /manage, reusing /badges' rendering logic. Not done — /badges turned out to be hardcoded to one fixed example server (io.sentinelsignal/scoring), not parameterized per-server, so reusing it would have meant extracting a new shared fragment. /manage already has a working single-badge preview + copy-to-clipboard (inside "Continuous Verify plan") that several existing tests pin by exact string/element ID. Redoing it wasn't part of TASK-16's actual ask (relocating publisher/maintainer sections) and would have added risk for no requested benefit, so it was left alone.

JSON API parity: left as-is. GET /v1/servers/{namespace}/{name}/manage still does not include alert_routing, maintainer_analytics, maintainer_response_quality, or publisher_readiness — no known external consumer of that endpoint was identified, and the HTML page sources this data directly from the same server/detail object rather than through that JSON contract, so there's no drift risk from leaving it unextended.

Tests: updated verify/tests/test_api.py assertions that had pinned the 8 sections' text to the public page — each now has a negative assertion confirming the string is gone from /servers/{ns}/{name} and a positive assertion confirming it's present on /servers/{ns}/{name}/manage (or /manage/{ns}/{name}). Added coverage for the new "Ownership" stat and its link to manage_url.

TASK-09 — Collapse the flat section list into 5 tiers behind the existing tab bar

With TASK-16 landed first, render_server_detail_page was down to 44 flat <h2> sections (not the ~41 estimated in planning — the earlier audit undercounted; 44 is the real, directly-counted number) between the header panel and the end of the function. A 5-item tab bar (#tool-risk, #compatibility, #evidence, #fix-it, #policy) already existed but each anchor only landed on the first section of its conceptual tier, leaving the rest flat and indistinguishable.

What changed: added two small helpers, render_tier_subsection() and render_tier_section(). Each of the 44 sections becomes one <details class="details-panel tier-subsection"> (reusing the .details-panel disclosure pattern already used elsewhere on this page, e.g. the raw-evidence toggle) with the original heading text verbatim as <summary> — no renaming, no content dropped. The 5 tiers each wrap their subsections in one <section class="panel tier-panel"> carrying the pre-existing tab-bar id, so the tab bar itself needed zero changes. Risk tier's subsections are open by default (it's the tier most relevant to an evaluator's decision); the other 4 tiers start collapsed. All 44 sections stayed in the same server-provided data — this was purely a wrapping/grouping change, no render function's internal logic changed.

Tier assignments (5 tiers, 44 subsections total): Risk (5: Security posture, Tool capability & risk inventory, Write-action governance, Action-controls diff, Critical alerts) · Compatibility (7: Client compatibility verdicts, Client compatibility gate details, Verdict traces, Publishability policy profiles, Compatibility fixtures, Recommended for, Agent Commerce & Payment Readiness) · Evidence (22: Current trust snapshot, Evidence confidence, Latest validation evidence, Raw evidence view, Known versions, Validation history, Validation timeline, Recent validation runs, Public server reputation, Incident & change feed, Capabilities, Benchmark tasks, Utility coverage, Transport compliance drilldown, Request association, Connector replay, Tool snapshot diff & changelog, Validation diff, Registry & provenance divergence, Active alerts, Aliases & registry graph, Alias consolidation) · Fix it (5: Why this score?, Algorithmic score breakdown, Actionable remediation, Point loss breakdown, Compatibility profiles) · Governance (5: MCP TrustOps, MCP Runtime hosting, Authenticated validation sessions, Install snippets, Agent access & tool surface).

Two anchors the original design note missed: id="raw-report" (Raw evidence view) and id="history" (Validation history) existed on the flat page and weren't in the original audit's grep (which only found the 5 tab-bar ids plus the now-removed id="badge"). No inbound links to either were found, but since preserving them costs nothing, both ids moved onto their <details> element rather than being dropped.

Agent Commerce is self-contained: render_agent_commerce_readiness() emits its own embedded <h2> in every branch (it's called directly, not through the per-section loop). Rather than modify that function to suppress its internal heading, it's wrapped as-is inside a <details><summary>Agent Commerce & Payment Readiness</summary>...</details> — the summary and the inner h2 text are slightly redundant when expanded, which is a minor, accepted cosmetic tradeoff, not a defect.

Also folded in while touching this region: a second, redundant "Production decision / Current score / Next action" stat grid sat directly below the Tier-1 DECISION SUMMARY block, sourced from the same executive_verdict dict already rendered there. "Production decision" duplicated the DECISION SUMMARY's own <h2>{decision}</h2>; "Current score" duplicated its Score stat. Both were dropped. "Next action" and its "why" explanation were the only genuinely unique content in that grid (not shown anywhere else) — merged into a single stat card rather than lost.

Not done: the optional hash-auto-expand JS (deep-linking straight into a specific collapsed subsection, not just its tier) described as a nice-to-have in the original design note. Every subsection is a native <details>/<summary> element, so it's already fully reachable with JavaScript disabled — click to expand, same as the rest of the site's disclosure pattern. Landing on a tier and clicking through remains the only way to reach a specific subsection via a fresh link; this can be added as a fast-follow without touching the HTML structure again.

Tests: no existing test needed updating — the pre-implementation audit of test_api.py found that all existing assertions were plain substring checks ("heading text" in page.text), which keep passing regardless of whether the heading is inside an <h2> or a <summary>. One thing the audit did have to get right: several headings (Registry & provenance divergence, Tool capability & risk inventory, Agent access & tool surface, Incident & change feed) are pinned by existing tests with a literal, non-entity-encoded & — render_tier_subsection() deliberately does not HTML-escape its title parameter (every caller passes a hardcoded string, never user input) to preserve that. Added one new test, test_server_detail_page_regroups_sections_into_five_tiers, asserting: ≤8 rendered <h2> tags (5 tier headers + the pre-existing DECISION SUMMARY h2 + Agent Commerce's embedded h2), all 5 tab-bar ids resolve to exactly one tier section each, both extra inner anchors still exist, all 44 subsections render as <details class="details-panel tier-subsection">, Risk opens by default and Governance/Evidence don't, a sampling of ampersand-heading text survives verbatim, the pre-existing history/timeline/recent-runs ordering assertion still holds, and the duplicate decision-grid fields are gone while "Next action" survives.

Verification

PYTHONPATH=verify/src pytest verify/tests   # 243 passed
python3 -m py_compile verify/src/mcp_verify/main.py

Manual verification was structural (tag-balance counts — <section>, <details>/<summary>, <div>, <h2>/<h3>, <table>, <ul> all matched open/close counts; CSS-class and anchor presence; HTML fetched through the test client) rather than an actual browser render — no browser tooling was available in this environment. Worth a real-browser spot check at 375/768/1440 and a JS-disabled click-through pass on the next opportunity to touch this page.

Not done this round

  • The hash-auto-expand JS fast-follow for TASK-09 (see above).
  • The badge-kit prominence enhancement described in the original TASK-16 design note.
  • Everything else carried in Round 4's backlog (TASK-06, TASK-11, TASK-12, TASK-18/20/23/24/25, Sign in relabel) — untouched this round.