chore: add a backfill that gives legacy toolset aliases explicit endpoints

DA
1d ago
+2248-41

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_only network 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 a skip with its reason; one held by another server's endpoint or alias is a conflict and never taken.
  • Org slug prefix: the backfill passes slugPrefixExemptLegacyAlias, so an unprefixed slug the alias already serves keeps its URL. API creates still pass slugPrefixRequired. mcpEndpoints.update and UpdateMcpEndpointAddressInTransaction now 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_held or 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), mcpmetadata export and assistant connections (LoadAssistantMcpServers, hosted rows) now pick endpoints in endpointaddr.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:create audit rows by system:legacy-alias-endpoint-backfill via audit.WithoutOutbox(); no plugin attachment or publication.
  • Runner: dry run by default (same transaction, rolled back); -apply needs -environment/-confirm-environment, -confirm-target, -confirm-apply=legacy-alias-endpoints and a new 0600 -journal. A failed commit is reported as commit_outcome_unknown; the exit is non-zero while any server failed. Reruns report already_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_only network 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_alias event for each alias move, and reports a failed commit as commit_outcome_unknown instead of failing the rerun.

Advertised URLs

  • Toolset views, exported MCP metadata, and assistant connections now pick the hosted server's primary endpoint (endpointaddr.PrimaryEndpoint order: live custom domain first), matching the URL plugins already use instead of preferring the platform URL.