ADR 0072: A provider's MCP wiring is a typed status, accounted by a forcing function
Status: accepted; applies ADR 0051 (every canonical set tied to its satellites) to the MCP seam of ADR 0031, and builds on ADR 0045 (MCP is core infrastructure)
Context
§The provider registry modelled MCP wiring as mcp?: McpIntegration — optional, with a // TODO(provider:codex) comment where Codex's and Gemini's integrations would go. That is a silent, unenforced gap. Nothing distinguishes "this agent has no committable MCP mechanism" from "we haven't wired it yet"; nothing fails when a new agent is added with no MCP declaration at all; and the only record of the intended target file was prose in a comment. As discern goes from three agents to six — four of which do have a committable MCP target that a later plan will wire — an unenforced optional field is exactly the drift ADR 0051 forbids: a member of a canonical set with no mechanical tie back to it.
Decision
§Provider.mcp is a required, typed McpStatus, and a forcing-function test asserts every provider accounts for its MCP wiring.
type McpStatus =
| { kind: "wired"; integration: McpIntegration }
| { kind: "pending"; targetFile: string } // committable, not yet authored
| { kind: "none" }; // no committable MCP mechanism
- The field is required, so a new agent in
AGENT_NAMEScannot compile without declaring its MCP status — the compile-time half of the forcing function (the strongest tie, ADR 0051). pendingcarries the named committable target file discern will write the server into once the integration is authored (codex → .codex/config.toml,gemini → .gemini/settings.json) — the intent that was a comment is now typed data the diagnostic and the test read.- A parity test (
tests/agent_parity_test.ts) is the runtime half: it loops the registry and asserts each provider iswired,pendingwith a real target file, ornone. It passes in this phase (Claude wired; Codex and Gemini pending) and tightens automatically: a later plan flipping apendingtowiredkeeps it green with no edit — the set of pending shrinks, the invariant holds. wiredMcp(provider)is the one place "is this MCP wired?" is decided; the wirer registers onlywiredproviders.doctorreports apendingMCP explicitly, with its target file, so the gap is visible rather than mistaken for a bug.
The explicit nos:
- No vendor's
register()is authored here. Codex/Gemini MCP is Plan B. This is the typed status + the forcing function only; thependingmarker names where the server will go, nothing more. noneis kept as a distinct state. An agent that genuinely has no committable project-scoped MCP target is not the same as one whose wiring is merely pending; conflating them would make "accounted for" meaningless.
Consequences
§- A new agent must account for its MCP, mechanically. The required union field is a compile error if omitted; the parity test fails if a
pendingnames no target. The silent TODO can't recur. - The diagnostic tells the truth.
doctordistinguishes wired from committable-but-pending (with the file), so an operator sees why an agent's tools aren't there yet and what discern will wire. - The guard tightens itself as the vendors land. B/C/D flip
pending → wiredand the test stays green automatically — the ADR 0051 payoff for this seam. - A small migration of the field's readers.
wireProviderMcpanddoctorreadmcp.kind/wiredMcpinstead of a presentmcp?; the registry entries gained a one-line status. No behaviour change for the wirer (pending is skipped, exactly as a present-but-absentmcp?was).
Alternatives considered
§- Keep
mcp?optional and add a separatemcpPendinglist. Rejected: a second hand-maintained list is the drift ADR 0051 eliminates; one typed field per provider is the single source. - A boolean
mcpWiredplus amcpTargetstring. Rejected: two fields that can contradict each other (wired and a target? neither?) where a discriminated union makes the illegal states impossible to express. - Leave it a comment and rely on review. Rejected outright — that is the status quo the gap came from; ADR 0051 exists because "a policy without a forcing function is a hope."