Skip to content

[FEATURE] S5 — Unified operator UI copy and recovery-action contract for MCP admission #180

Description

@Joncallim

Issue Type

Feature — child slice S5 of EPIC #172. Depends on #176 (S1), #177 (S2). Canonical architecture: docs/adr/0009-mcp-admission-contract.md.

Problem Statement

The task detail page must let an operator answer four questions without reading logs: (1) is this MCP request just planning context? (2) does this package need bounded project filesystem context (show the exact grant)? (3) is an MCP unhealthy/missing (link setup/retry)? (4) is this a deferred-live-MCP feature (say deferred, not broken install)? Today it cannot:

  • Grant-preview copy is a 3-way ternary on decision.status only (web/app/dashboard/tasks/[id]/page.tsx:3171-3175); a deferred GitHub-write capability renders the identical destructive "resolve this MCP issue" copy as a genuinely unhealthy MCP.
  • statusBadgeClass (:1203-1245) has no neutral bucket for deferred/planning — anything new falls to red/amber.
  • Planning-only warnings (filesystem.project.write) sit in the same amber list as real degradation (:3146-3155).
  • Blocked reasons are raw engine strings with no recoveryAction → CTA mapping; RetryHandoffControls (:1568-1632) always offers "Re-run stalled handoff" even for non-retryable blocks.
  • The projects page (web/app/dashboard/projects/[id]/page.tsx:1035-1052) offers remediation CTAs only for filesystem configuration_required and github auth_required, not unhealthy/missing. The MCPs catalog page (web/app/dashboard/mcps/page.tsx) shows no health and no beta boundary.
  • There is no shared UI copy contract, so copy drifts across surfaces — the "preview and handoff must not disagree" failure on the presentation layer.

Desired Outcome

One admission-decision → badge/CTA/copy mapping drives every MCP surface. The four operator questions are answerable on the task page without logs; deferred-live reads as a product boundary, not a broken install; planning-only context is visually separate from hard blockers; and unhealthy/missing MCPs link to setup/retry.

User Story

As a Forge operator, I want the task page to tell me plainly whether a block is planning context, a filesystem grant I can approve, an MCP I need to fix, or a deferred feature — with the right button next to it — so I never have to read logs to know what to do.

Requirements

  • Create web/lib/mcps/admission-copy.ts: a pure map from mode+recoveryAction+status{ statusKey, badgeText, headline, body, cta? } per ADR 0009 §S5 (planning_only → neutral "Planning context", no CTA; bounded_context_required → amber "Needs project context", scroll CTA; bounded_context_approved → green "Context approved"; blocked+install_or_fix_mcp → red, deep-link /dashboard/projects/{projectId}#project-mcps-heading; blocked+revise_plan → red, open Request-changes flow; deferred_live_mcp → neutral slate "Deferred — beta boundary", no CTA). Keep all human strings here.
  • Extend web/lib/mcps/execution-design-metadata.ts decision type/normalizer (:50-75,110-142) to carry mode, recoveryAction, normalizedCapabilities, evidenceRefs; back-derive mode for old artifacts.
  • tasks/[id]/page.tsx: replace the status-only ternary (:3171-3175) with admissionPresentation(decision); split planning-only warnings into a neutral callout (:3146-3155); stop rendering deferred/unsafe capabilities in the destructive blocked alert (:3135-3144); add deferred/planning neutral buckets to statusBadgeClass (:1203-1245); gate RetryHandoffControls on retryability (pass broker.retryable); surface the bounded-context packet inline in FilesystemGrantControls/ApprovedGrantSnapshot (:1844-1978); route McpGrantCards/BrokerRetrySummary (:1685-1752) through the shared copy helper. Replace the client filesystem reimplementations (:348-444) with the shared helpers or a server-computed grant-state payload.
  • projects/[id]/page.tsx:1035-1052: add remediation CTAs for unhealthy/missing/disabled from catalog.remediation, plus a one-line "Bounded read-only context, no live tool handles" boundary note (from catalog.runtime.mode/liveTools). Ensure the #project-mcps-heading anchor is the deep-link target.
  • mcps/page.tsx: add a runtime boundary badge per entry (bounded_context_packet → "Bounded context", external_service → "External service") and a static "No live tool handles (beta)" line.
  • The worker (S2) must persist mode+recoveryAction on BOTH the grantDecisions preview decisions AND the per-package block metadata (blockedReason) so RetryHandoffControls routes correctly.

Acceptance Criteria

  • A single admission-copy.ts maps mode+recoveryAction+status to badge/headline/body/CTA and is consumed by the task page, projects page, and MCPs page.
  • A deferred-live capability renders as a neutral "Deferred — beta boundary" (not destructive red) with no retry button; an unhealthy/missing MCP renders a setup/retry CTA; a bounded-context requirement renders an "approve project context" CTA; planning-only context is visually separated from hard blockers.
  • RetryHandoffControls only offers retry for retryable (install_or_fix_mcp) blocks.
  • The bounded-context packet (root, files, counts) is visible inline at approval.
  • The client no longer re-implements filesystem admission logic divergently.
  • npm run lint, npx tsc --noEmit, npm test, npm run build pass; UI verified in the preview.

Dependencies

Implementation Scope

Large — new copy contract plus changes across three dashboard surfaces and the metadata reader.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions