List the app's HTTP routes and their scope mappings
Enumerate the app’s own HTTP routes with each route’s current scope mapping —
the mapper’s route picker and its “unassigned routes” bucket (the mapped: null
entries).
routes is the app’s live route table (the route-adapter extractor hands the
operation request.app.routes so it stays request-free). One entry per
:class:starlette.routing.Route, sorted by path. Mount entries (the sub-MCP
mount, the MCP mount) are EXCLUDED — a mount is not a bindable url and the sub-MCP
surface has its own door. The exclusion is an explicit isinstance filter: any
route-table entry that is neither a Route nor a Mount raises loudly (a new
Starlette routing type must be classified here, never silently dropped). methods
is the route’s method set sorted with HEAD removed (Starlette auto-adds it to
every GET route — noise for the mapper). mapped is the url’s value from
get_all_route_mappings looked up by the EXACT path string — a scope id, the
public marker for a public pin, or null when the path has no mapping. Exact-key
lookup only: this door does not attempt dynamic-pattern matching.
Each entry is JOINED with its :class:RouteMetadata (from
route_registry.load_all_routes(), a separate collection) to also carry the
route’s feature tags + summary + authorization action — the data the
Studio Roles page groups the per-tag tri-state by and marks the fenced/secret
(admin-only) routes it must never offer a grant for. The join is keyed on the route
TEMPLATE + its method set with HEAD stripped on BOTH sides (Starlette auto-adds
HEAD to a GET), so the two collections compare like-for-like; a registered route
whose method set fails to join is a loud STOP (a normalization drift), never a
silently dropped row.

