MCP Verify — Product architecture, dedicated /search route implementation
Target release: 1.0.540.
Source: Track 2's deferred registry/search split (doc §12, Requirement R2, Phase 4), resumed after the user supplied the full 56-section source doc and answered four scoping questions for the remaining phases:
- Phases 5 (server-profile tier regrouping), 9 (analytics rename), 10
- Registry/search split: **additive
/searchpage** — keep/exactly - Methodology split: **propose the 3-way boundary from the current page's
- Deploy cadence: one phase at a time, each with its own commit/test/
(compare-telemetry aggregation): skip this round — no detail beyond a one-line name was available before the full doc arrived, and once it did, Phase 5's request (Decision/Risks/Compatibility/Evidence/ Governance/History/Technical groups, suppress empty sections) is already substantially satisfied by the existing tier restructure (TASK-09, 5 tiers/43 subsections, shipped 2026-08-04) — deferred for a dedicated audit against the doc's exact 7-group model rather than redone blind.
as today, add /search as a new dedicated page, cross-link both.
content**, for a separate implementation pass.
deploy/verify cycle.
This document covers the registry/search phase only.
What the doc actually requires (§12, Phase 4)
- Requirement R1: homepage search stays prominent.
- Requirement R2: the full registry should live primarily under
- Requirement R3: homepage keeps a small sample (5-10 results) plus a
/search — avoid it dominating the homepage's lower half.
"Browse all MCP servers" link.
The user's explicit scope choice ("keep / exactly as today") is a smaller, lower-risk step than R2's literal "primarily" language — Phase 4 in full would also shrink the homepage's own filter panel. That's deliberately left for a possible follow-up once /search has been live and cross-linked for a while; this phase ships the destination page and wires it into navigation without touching /'s own content or result count, matching doc §45's "implement incrementally" instruction and this engagement's established staged-rollout pattern.
Implementation
- **
render_index_page(main.py) gained apage_mode: str = "index" - **New
hero_marketing_htmlbranch.** Inpage_mode="search", this is - **New
cross_link_html**: on/, a line inside the existing search - Route registration: the existing
index()handler inmain.py - Cache-key route-scoping (real bug avoided, not just theoretical):
- Primary navigation:
MAIN_NAV_ITEMS's("Search", "/")entry - Sitemap:
/searchadded to_build_static_sitemap_entries
parameter.** No route previously called it with anything but the default, so this is additive. A page_path = "/search" if page_mode == "search" else "/" local drives every route-relative reference in the function: the SEO title/description/canonical URL/structured-data SearchAction target, the filter form's action, the "Clear" link, the two result-scope chip links (?all_servers=1, ?show_metadata_only=1), and the closing analytics script's route_pattern.
a compact eyebrow + <h1>Search MCP servers</h1> + one-line description + site nav + footer links — no hero CTA row, no lifecycle section, no coverage-stats panel (those stay homepage-only, matching the doc's own intent that /search is a focused utility page, not a second marketing surface). In page_mode="index" (default), this is byte-for-byte the previous homepage hero content, unchanged.
fieldset — "Prefer the dedicated search experience? Open full search →" — pointing at /search. On /search, the mirror — "Looking for the homepage? Back to Verify home." This is the "cross-linked from both" half of the user's scope decision; nothing else on / changed.
(the single function backing /, ~500 lines of filter parsing, default-listing logic, commerce-filter handling, and two-tier caching) is now also registered at /search via a second @app.get decorator on the same function — zero logic duplication. A page_mode = "search" if request.url.path == "/search" else "index" local at the top of the handler threads through to both render_index_page call sites (the cache-hit path's build_index_page_for and the cache-miss-fallback path's build_fast_index_page).
the handler's two cache-key f-strings (site-{VERSION}:filtered:.../site-{VERSION}:default:...) did not vary by path before this change — / and /search requests with identical filter state would have shared one cache entry and served whichever page rendered first to both URLs. Both keys now include a {page_mode} segment. The startup prewarm write for the bare-/ index page (_prewarm_default_route_caches, main.py:16581+) used the pre-page-mode key format and would have silently become an orphaned, never-matched cache entry under the new scheme — the exact bug class a much earlier round (TASK-08b) fixed for build-SHA scoping. Caught before commit and fixed in the same pass by adding the matching :index: segment to the prewarm's key.
(the 5-item, test-enforced primary nav from the R13 remediation round) now points at ("Search", "/search"). This is the single highest- leverage cross-link — every page rendering site_nav_html now sends its "Search" nav item to the dedicated page — without touching /'s own content, and without exceeding the existing 5-item cap (test_r13_primary_navigation_is_capped_at_five_items still passes unmodified, since it only asserts on labels).
(main.py:2894), the single source for /sitemap.xml.
Deliberately unchanged
/'s hero copy, CTA row (still "Search MCP servers" anchoring to the- No redirect was added from any old
/?filter=...URL to/search—
inline #search-mcp-servers form, unchanged), lifecycle section, coverage-stats panel, default result count/limit, and filter-panel size — none of it moved. This is what "keep / exactly as today" was scoped to mean.
both remain independently valid, fully-featured registry views. This matches the "additive" (not "full IA move") option chosen, and avoids a real SEO/bookmark risk the more aggressive option would have carried.
Tests
verify/tests/test_api.py::test_arch_track2_search_route_is_dedicated_registry_page_home_stays_unchanged- Manually verified via a throwaway
build_test_client()script (not PYTHONPATH=verify/src pytest verify/tests -q— 492 passed (up frompython3 scripts/export_openapi.py --check— HTML page routes are not
(new): asserts /search renders 200 with the compact header, the filter form's action="/search", no hero/lifecycle/coverage-stats content, and the reciprocal "Back to Verify home" link; asserts / renders unchanged (hero copy, anchor CTA, action="/", lifecycle section all still present) plus the new "Open full search" cross-link; asserts the primary nav's "Search" item resolves to /search on both pages; asserts /sitemap.xml lists /search.
committed) before writing the assertions above, to confirm the actual rendered behavior matched the design rather than writing tests against assumptions.
491), 1 new.
part of the OpenAPI schema at all (only JSON API routes are), so no content diff is expected from this change beyond the version bump itself; regenerated and synced separately.
Remaining Track 2 phases — still not started
Methodology 3-way split (next phase, doc §25-28/Phase 7 — this session's proposal is pending in a follow-up commit), server-profile tier audit against the doc's exact 7-group model (Phase 5, likely already satisfied, needs confirmation not a rewrite), analytics classification work (Phase 9), compare-telemetry parent/child metric cleanup (Phase 10), and the literal "shrink /'s own registry dominance" half of Phase 4 that this round deliberately deferred.