2026-09-23·by Sijie Wang#standmeet#architecture#design

ui-mcp-parity

UI–MCP parity — whatever the UI can do, the handle can do

Parent: service-handle

Status:

built. The mechanism is internal/infra/facadeparity — each domain declares its ops as data (fp.Op: id, description, input schema, kind, reach) and every face is a projection of that one registry (convergence-inbound-and-outbound). Enforced by paritymanifest plus a ratchet. The owner-MCP↔admin gap it exposed was real and larger than estimated — 56, not the ~15 guessed — and was paid down 56→0. Re-checked 2026-09-07 at 36789537d: paritymanifest.KnownMCPGaps() returns the empty list (gaps.go:17) and the ratchet (outward_test.go) turns red on any new admin route without an MCP twin; the MCP face is grown from the dispatcher (routes/mcphandle/from_dispatcher.go:45), and intentional asymmetries are fp.Only(reason, facade)keypairs.create/list/delete are Only(FacadeAdmin) exactly as predicted below (manifest_table.go:41-49). Before that (until 2026-07) the roadmap tracked "MCP vs HTTP admin parity" under the externalization migration with no enforcement mechanism — the admin HTTP surface and the service handle's tool surface were two hand-maintained lists, and drift between them was silent. The three options below are the analysis that led to the build; ② shipped as fp.Op (not the Command{} sketch) and ① shipped as the ratchet + Only(...) exemptions.

The invariant wanted

Every owner operation reachable through the admin UI is also reachable through the service handle (StandMeet-as-MCP-server) — so the owner's AI session is never second-class to the browser. Exceptions must be explicit, never accidental.

Three mechanisms, increasing strength

① The parity gate (cheap — build first). A CI test walks the chi admin route table × OwnerMCPBindings() and checks both against an explicit mapping table with an exemption list; any unmapped, unexempted admin endpoint fails the build. Same species as check-connector-boundary (mechanical-guardrails). The exemption list is itself review material — drift either gets fixed or gets confessed in writing. Legitimate exemptions exist: keypair creation (circular — you need a keypair to call the handle), login/claim (bootstrap), upload-shaped UX.

② Declare once, project twice (structural — the real fix). Drift exists because there are two hand-written surfaces. Register each owner operation once — Command{name, input schema, handler} — and derive both the HTTP route and the MCP tool from the same registry. Parity stops being a checked property and becomes unviolatable by construction. This is the platform's own manifest philosophy applied to itself (OwnerMCPBindings is already a walked declaration; the HTTP side just hasn't joined it), and single-source-of-truth discipline (judgment-audit dimension 3: derived things aren't hand-written).

③ UI drinks from the handle (radical — not recommended now). Make the admin UI itself a client of the service handle: parity by definition. Philosophically consistent with entry-agnostic-agent ("the UI is just another consumer"), but the costs are real — internal calls pay wire-protocol tax, session-cookie ↔ Sigv1 bridging, and upload/SSE shapes fit MCP awkwardly. Mechanism ② gets the same guarantee in-process without the tax.

Recommendation

② as the rule for everything new, ① as the gate over the legacy and the exemptions. ② kills the drift class; ① catches regressions and keeps the exemption list honest. Revisit ③ only if a real second UI (IM gateway) makes "UI as consumer" load-bearing anyway.

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →