You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Priority: P0 for execution reliability. This is the next architecture epic after the Executable Workforce Beta (#119, closed) because MCP ambiguity is now the main reason approved tasks can still stall at handoff.
Canonical architecture (handoff-ready, no reasoning required to implement): docs/adr/0009-mcp-admission-contract.md. Every child slice below references it.
Context
Forge distinguishes three kinds of MCP involvement, but the code does not distinguish them in one place:
Planning-only MCP context — the Architect says an MCP would help; Forge records it only as run-scoped prompt instructions. It grants nothing.
Bounded read-only context grants — Forge assembles a project-scoped, inspectable read-only context packet (file names, selected excerpts) after an explicit operator grant.
Live MCP tool handles — a worker calling an MCP at runtime. This is deferred; it is not implemented in this epic and must read as a product boundary, not a broken install.
Current failure mode (observed): the Architect produced a valid plan and work packages; MCP access was shown as planning-only; but package handoff still blocked on MCP grant semantics. filesystem.project.write commonly appears in plans even though Forge writes generated files through its sandbox execution JSON path, not live MCP tools. Required filesystem read/list/search requirements, prompt-only overlays, project-level filesystem approval, and no-capability MCP entries are handled by different code paths with subtly different rules.
Root cause: the same beta capability policy is re-derived by four overlapping code paths that do not agree (plus a fifth client-side copy). Full evidence with file:line anchors is in ADR 0009 §Context:
The most important consequence: web/app/api/tasks/[id]/approve/route.ts never runs the handoff broker — it gates only on the filesystem path — so a package the broker will block at handoff for a non-filesystem reason passes approval and stalls later. This epic makes planning, approval, UI state, filesystem grants, and handoff follow one admission contract so this cannot recur with the next workforce slice.
Desired Outcome
Forge should let normal approved work packages advance without confusing MCP blockers, while keeping live MCP tool access out of scope until a deliberate later security-reviewed slice.
Architecture principles
One contract, many surfaces. The same MCP admission decision drives preview badges, approval blocking, handoff blocking, and recovery messaging.
Planning is not permission. Architect proposals and prompt overlays are context, not tool grants.
Bounded context is explicit. File/repository context is packaged, visible, and attached to run evidence.
Writes stay on the Forge execution path. Generated files are written through sandbox output and Forge-applied host-repository writes, not live MCP filesystem write tools.
Blocks must be actionable. Every blocked MCP state names the exact missing grant, unhealthy MCP, unsupported capability, or deferred feature.
No silent privilege widening. Broad aliases cover project read/list/search only. Write, destructive, admin, secrets, and merge operations remain blocked.
The admission model — every surface consumes one package-level evaluation (admitWorkPackageMcp → McpWorkPackageAdmission) built from one per-requirement producer (admitMcpRequirement → McpAdmissionDecision); validateMcpExecutionDesign, deriveMcpGrantDecisions, evaluateWorkPackageMcpBroker, and requiresFilesystemGrantApproval become shape-preserving adapters over it. Full types + producer + adapters + the total precedence-ordered decision table are in ADR 0009; the per-requirement decision is:
Cross-surface parity is asserted on the canonical (mode, admissionStatus) pair. The grant-preview adapter keeps its legacy status field (allowed → proposed) and adds a canonical admissionStatus: 'allowed' | 'warning' | 'blocked' so shape preservation and literal status parity both hold.
Capability classes (sourced from MCP_CATALOG.runtime.capabilities, one classifier):
Planning-only — prompt overlays, MCP-aware subtasks, filesystem.project.write. Warn, do not block, when no live MCP handles are issued.
Bounded read-only — filesystem.project.read|list|search. May require explicit project-level filesystem approval; produce visible effective grant metadata.
Deferred live MCP — live tool handles; github write/branch/pr/merge/settings/secret; filesystem write/delete/admin (an enumerated set of recognized live-tool families). Blocked/deferred until a later security-reviewed runtime-grant epic. A first-class named mode, not a generic "beta scope" error. A required deferred capability blocks with revise_plan; an optional one warns with defer_live_mcp_feature.
Unknown / typo — a capability naming no known MCP, or a known MCP with an unrecognized/typo capability, is unknown and blocks with revise_plan — never silently treated as safe or as deferred.
Operator UX contract — the task detail page answers four questions without logs: (1) is this 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)?
Scope boundary: live MCP tool handles remain unissued; filesystem.project.write is planning-only or normalized into the sandbox write path; required filesystem read/list/search stays explicit, recoverable, and auditable; approved tasks cannot enter a handoff block that was already visible at approval.
Tasks
The epic is delivered as six sweeping child slices plus the existing Architect-assignment slice. All are linked as sub-issues of this epic and detailed with file-level architecture in their own bodies and in ADR 0009.
Child slices (dependency-ordered):
[FEATURE] S1 — Unified MCP admission contract and capability taxonomy #176 — S1: Unified MCP admission contract and capability taxonomy. New web/lib/mcps/admission.ts + capability-normalization.ts; catalog-driven classifyCapability; the McpAdmissionDecision contract, admitMcpRequirement, and shape-preserving adapters. Blocks all others. (This pass also authored ADR 0009.)
Plan approval runs the same admission decision as handoff over a captured MCP health snapshot, so a block already visible in the approval-time snapshot is surfaced at approval rather than missed until handoff. (Health/config can still change between approval and handoff; the guarantee is "no missed approval-time block", not "never blocks later".) ([FEATURE] S2 — Consolidate MCP admission onto the shared core and enforce it at approval #177)
Every blocked MCP state names the exact missing grant / unhealthy MCP / unsupported capability / deferred feature, with the correct recovery action; a required deferred capability still offers revise_plan (actionable) while an optional one is approvable; deferred-live reads as a product boundary, not a broken install; planning-only context is visually separate from hard blockers. ([FEATURE] S5 — Unified operator UI copy and recovery-action contract for MCP admission #180)
Issue Type
Epic.
Priority: P0 for execution reliability. This is the next architecture epic after the Executable Workforce Beta (#119, closed) because MCP ambiguity is now the main reason approved tasks can still stall at handoff.
Canonical architecture (handoff-ready, no reasoning required to implement):
docs/adr/0009-mcp-admission-contract.md. Every child slice below references it.Context
Forge distinguishes three kinds of MCP involvement, but the code does not distinguish them in one place:
Current failure mode (observed): the Architect produced a valid plan and work packages; MCP access was shown as planning-only; but package handoff still blocked on MCP grant semantics.
filesystem.project.writecommonly appears in plans even though Forge writes generated files through its sandbox execution JSON path, not live MCP tools. Required filesystem read/list/search requirements, prompt-only overlays, project-level filesystem approval, and no-capability MCP entries are handled by different code paths with subtly different rules.Root cause: the same beta capability policy is re-derived by four overlapping code paths that do not agree (plus a fifth client-side copy). Full evidence with file:line anchors is in ADR 0009 §Context:
validateMcpExecutionDesign—web/worker/mcp-execution-design.ts:326-454(approval validation)deriveMcpGrantDecisions/decisionStatus—web/worker/mcp-execution-design.ts:753-867(preview)evaluateWorkPackageMcpBroker—web/worker/mcp-execution-design.ts:599-739(handoff)requiresFilesystemGrantApproval/summarizeFilesystemCapabilities—web/lib/mcps/filesystem-grants.ts:90-339web/app/dashboard/tasks/[id]/page.tsx:348-444The most important consequence:
web/app/api/tasks/[id]/approve/route.tsnever runs the handoff broker — it gates only on the filesystem path — so a package the broker will block at handoff for a non-filesystem reason passes approval and stalls later. This epic makes planning, approval, UI state, filesystem grants, and handoff follow one admission contract so this cannot recur with the next workforce slice.Desired Outcome
Forge should let normal approved work packages advance without confusing MCP blockers, while keeping live MCP tool access out of scope until a deliberate later security-reviewed slice.
Architecture principles
The admission model — every surface consumes one package-level evaluation (
admitWorkPackageMcp→McpWorkPackageAdmission) built from one per-requirement producer (admitMcpRequirement→McpAdmissionDecision);validateMcpExecutionDesign,deriveMcpGrantDecisions,evaluateWorkPackageMcpBroker, andrequiresFilesystemGrantApprovalbecome shape-preserving adapters over it. Full types + producer + adapters + the total precedence-ordered decision table are in ADR 0009; the per-requirement decision is:Cross-surface parity is asserted on the canonical
(mode, admissionStatus)pair. The grant-preview adapter keeps its legacystatusfield (allowed → proposed) and adds a canonicaladmissionStatus: 'allowed' | 'warning' | 'blocked'so shape preservation and literal status parity both hold.Capability classes (sourced from
MCP_CATALOG.runtime.capabilities, one classifier):filesystem.project.write. Warn, do not block, when no live MCP handles are issued.filesystem.project.read|list|search. May require explicit project-level filesystem approval; produce visible effective grant metadata.revise_plan; an optional one warns withdefer_live_mcp_feature.unknownand blocks withrevise_plan— never silently treated as safe or as deferred.Operator UX contract — the task detail page answers four questions without logs: (1) is this 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)?
Scope boundary: live MCP tool handles remain unissued;
filesystem.project.writeis planning-only or normalized into the sandbox write path; required filesystem read/list/search stays explicit, recoverable, and auditable; approved tasks cannot enter a handoff block that was already visible at approval.Tasks
The epic is delivered as six sweeping child slices plus the existing Architect-assignment slice. All are linked as sub-issues of this epic and detailed with file-level architecture in their own bodies and in ADR 0009.
Child slices (dependency-ordered):
web/lib/mcps/admission.ts+capability-normalization.ts; catalog-drivenclassifyCapability; theMcpAdmissionDecisioncontract,admitMcpRequirement, and shape-preserving adapters. Blocks all others. (This pass also authored ADR 0009.)admission-copy.tsmappingmode+recoveryAction+statusto badges/CTAs across the task, projects, and MCPs pages. Blocked by [FEATURE] S1 — Unified MCP admission contract and capability taxonomy #176, [FEATURE] S2 — Consolidate MCP admission onto the shared core and enforce it at approval #177, [FEATURE] S4 — Specialist prompt and bounded context packet assembly with run evidence #179 (renders S4's run-evidence schema).github.*/filesystem.project.*.Dependency graph
Related issues (stay independent; dependencies/dependents, not subsumed):
Acceptance Criteria
docs/adr/0009-mcp-admission-contract.md) used by preview, approval, UI, and handoff. ([FEATURE] S1 — Unified MCP admission contract and capability taxonomy #176)(mode, admissionStatus)for a fixed package and fixed health snapshot across all capability classes, the deny-wins-beats-optional case, a known-MCP typo, a required github read without materialized prompt context, all four requirement fields, and all three fallback actions. ([FEATURE] S2 — Consolidate MCP admission onto the shared core and enforce it at approval #177, [FEATURE] S6 — End-to-end MCP admission regression (tiny task-tracker flow) and preview==handoff invariant #181)filesystem.project.writeis treated as planning-only (warn) or normalized into the sandbox write path — never a live MCP grant — consistently across all paths. ([FEATURE] S1 — Unified MCP admission contract and capability taxonomy #176, [FEATURE] S2 — Consolidate MCP admission onto the shared core and enforce it at approval #177)revise_plan(actionable) while an optional one is approvable; deferred-live reads as a product boundary, not a broken install; planning-only context is visually separate from hard blockers. ([FEATURE] S5 — Unified operator UI copy and recovery-action contract for MCP admission #180)npm run lint,npx tsc --noEmit,npm test, andnpm run buildpass for each slice.