chore: add a backfill that gives legacy toolset aliases explicit endpoints
AIM-821
Summary
Adds go run ./cmd/tools/migrations legacy-alias-endpoints (run from server/), a one-off backfill that gives each hosted MCP server an explicit endpoint for every address its legacy toolset alias still serves and no live endpoint holds.
- Addresses: the alias's own address, plus the platform address the loose lookup serves for a custom-domain alias (no live platform alias with the slug, no other live-project custom-domain alias with it, no custom-domain lockdown), including aliases on deleted domains. Servers the fallback refuses are skipped: deleted toolset, project or server, a non-canonical server,
private_onlynetwork mode, organization drift or missing metadata. Disabled servers still get endpoints. - Rules: every address goes through the hosted create path's checks (
checkHostedNewAddress: slug length, verified and activated org domain, private ingress) and slug availability, under the same locks and order, with the organization's custom domain also locked so the lockdown read is consistent. A rejected address is askipwith its reason; one held by another server's endpoint or alias is aconflictand never taken. - Org slug prefix: the backfill passes
slugPrefixExemptLegacyAlias, so an unprefixed slug the alias already serves keeps its URL. API creates still passslugPrefixRequired.mcpEndpoints.updateandUpdateMcpEndpointAddressInTransactionnow check the prefix against the locked endpoint row and only when the address changes, so a grandfathered endpoint can be re-saved. - Alias moves: the alias follows the endpoints only when its own address ended
create/already_heldor its domain was deleted, so it never leaves a pending, unverified, foreign or ingress-rejected domain. Every move is printed and journaled. - Advertised URL: the toolset view (
ListCanonicalHostingByToolsetIDs),mcpmetadataexport and assistant connections (LoadAssistantMcpServers, hosted rows) now pick endpoints inendpointaddr.PrimaryEndpoint's order (live custom domain first), as plugin generation already does. Without this the backfill's new platform endpoints would switch those surfaces from a customer's custom domain to the platform URL. - Writes:
mcp-endpoint:createaudit rows bysystem:legacy-alias-endpoint-backfillviaaudit.WithoutOutbox(); no plugin attachment or publication. - Runner: dry run by default (same transaction, rolled back);
-applyneeds-environment/-confirm-environment,-confirm-target,-confirm-apply=legacy-alias-endpointsand a new 0600-journal. A failed commit is reported ascommit_outcome_unknown; the exit is non-zero while any server failed. Reruns reportalready_held. - Runbook:
server/cmd/tools/migrations/LEGACY_ALIAS_ENDPOINTS_BACKFILL.md(dev first, verification, journal-driven rollback).
Motivation
AIM-670 (#8248) deletes the alias and its /mcp/{slug} fallback. Applying this first keeps every stored client URL working and the slugs reserved. Once this has run in prod, #8248 must also delete server/internal/mcpendpoints/alias_backfill.go, the slugPrefixExemptLegacyAlias branch and the three backfill queries in legacyalias/queries.sql.
🤖 Generated with Claude Code
Summary by cubic
Adds legacy-alias-endpoints, a one-off backfill that creates an explicit MCP endpoint for every address a hosted server's legacy toolset alias still serves and no live endpoint holds, so stored client URLs keep working once the alias is removed (prerequisite for AIM-670). The run defaults to a dry run and needs explicit confirmations plus a journal to apply.
Backfill
- Addresses the alias's own slug plus the platform address a loose custom-domain alias serves, skipping deleted or non-canonical servers,
private_onlynetwork mode, org drift, and missing metadata. - Every address goes through the regular hosted create checks and slug availability under the same locks; a rejected address is a skip and one owned elsewhere a conflict.
- The unprefixed alias slug is grandfathered, while API creates still require the org slug prefix; endpoint updates check the prefix only when the address changes, so grandfathered endpoints can be re-saved.
- The alias follows the new endpoints only when its own address can no longer serve, so it never leaves a pending, unverified, foreign, or ingress-rejected domain.
- Writes audit rows without outbox fan-out, including a distinct
toolset:move_legacy_aliasevent for each alias move, and reports a failed commit ascommit_outcome_unknowninstead of failing the rerun.
Advertised URLs
- Toolset views, exported MCP metadata, and assistant connections now pick the hosted server's primary endpoint (
endpointaddr.PrimaryEndpointorder: live custom domain first), matching the URL plugins already use instead of preferring the platform URL.