Files
pad/cmd
xarmian 7b000405c1 feat(mcp): resolve MCP addressing from config; mcp_enabled setting with an env lock (TASK-3302)
PLAN-2310 unit 1 (DR-2, DR-3, the data behind DR-7). No route changes:
nothing is gated or exposed by this unit, and U2 wires the predicate
into the routes.

Addressing (internal/config/mcp.go, ResolveMCPEndpoints):
- The origin is cfg.URL (PAD_URL, toml url, --url), else PUBLIC_URL,
  never the Host:Port fallback and never a request's Host.
- The MCP URL, which is also the OAuth audience, is PAD_MCP_PUBLIC_URL,
  else <origin>/mcp. The auth-server URL is PAD_AUTH_SERVER_URL, else the
  origin.
- A value must be an absolute http(s) URL without user info, query or
  fragment; an origin also has no path. An unusable value never fails
  startup (PAD_URL also serves the CLI). It is logged, leaves its field
  empty, and keeps MCP unavailable. An unusable override does not fall
  back to the origin.
- HTTPS() is the auth-server URL's scheme: https means OAuth plus PATs,
  http means PATs only.

The setting (internal/server/handlers_admin_mcp.go):
- platform_settings.mcp_enabled, default off, read per request with no
  cache (the webmcp_enabled precedent). A read error counts as off.
- PAD_MCP_ENABLED (true/false/1/0/yes/no/on/off) forces it and locks the
  toggle. An unparseable value forces nothing and is logged. Cloud is
  always on.
- mcpAvailable() is DR-1's predicate: cloud, or the setting on and the
  addressing usable.
- GET/PUT /api/v1/admin/mcp, admin-only. GET reports enabled, source
  (setting|environment|cloud), locked and the readiness: origin, MCP URL,
  auth-server URL, https, auth methods, problems, state on|off|blocked
  with the reason, and the resume counts. PUT answers 409
  set_by_environment under the env lock (the decision-provider shape),
  403 managed_by_operator on cloud, and 400 without {enabled}. It is
  audit-logged as settings_changed.
- The resume counts (internal/store/mcp_resume.go) count live OAuth
  chains the way the Connected Apps page does (request_id with an active
  access or refresh row), and PATs the way /mcp accepts them (has a
  user_id; expiry parsed as a time, as ValidateToken does).

Tests:
- config resolution table (11 cases); PAD_MCP_ENABLED parsing and Load.
- Store counts: a rotated chain counted once, a revoked chain excluded, a
  refresh-only chain counted; the legacy and expired PATs excluded.
- Router tests: default off (the upgrade case); https on; http PAT-only;
  blocked without or with an unusable origin; the env lock with a stored
  value underneath; cloud; admin-only and body validation; a setting read
  error is off, with a control leg.
- Mutants, each BUILD OK and killed by the test aimed at it: env override
  ignored; availability ignoring addressing; PUT skipping the env lock;
  origin path allowed; PUT allowed on cloud; read error counted as on
  (error branch only); invalid override falling back; expired PATs
  counted; refresh-only chains missed.

Docs for the variables and the endpoint land with PLAN-2310 U7.

Claude-Session: https://claude.ai/code/session_016PXRzXhokr61CLbbu3Txj9
2026-09-29 15:45:30 +00:00
..