FROZEN — immutable history. Superseding/current decisions live in
docs/decisions/. Do not edit.
ADR 0011: MCP Posture
Date: 2026-06-24 Status: Accepted (decided with Brian Anderson, 2026-06-24).
Context
Flow Agents integrates with agent runtimes through two mechanisms: hooks (PostToolUse
capture, Stop gate, SessionStart — deterministic, automatic, can block) and a CLI
(workflow-sidecar — operations the agent invokes: init-plan, record-evidence,
advance-state, current, render-trust-panel). The question arose whether Flow Agents
should also expose an MCP surface — and specifically how to surface a workflow’s
trust report inline in the conversation by leveraging Surface’s existing MCP-UI trust
panel (buildTrustPanelUiResource, ui://surface/trust-panel/…).
Two facts shaped the decision:
- MCP tools are agent-invoked — the agent chooses to call them; they cannot be forced and cannot block.
- Surface already exposes an MCP server (
surface mcp) whose tools re-derive a trust report from a trust input and return the MCP-UI panel.
Decision
1. Enforcement stays hooks. MCP never carries the gate.
The gate (stop-goal-fit blocking) and capture (PostToolUse) are deterministic runtime
interception — they fire automatically and exit non-zero to block. MCP tools are
agent-invoked and cannot intercept or block. MCP therefore cannot carry Flow Agents’
differentiator (deterministic teeth); enforcement remains hooks. Any MCP surface is
additive to — never a replacement for — hooks.
2. Trust-surfacing in-conversation = consume Surface’s MCP; do not build our own.
Flow Agents produces .flow-agents/<slug>/trust.bundle; Surface’s MCP projects it
to the MCP-UI trust panel. Flow Agents writes zero MCP code (consume-never-fork; Surface
owns projection). Ingestion uses Surface’s per-call path argument (the skill passes the
active task’s bundle, resolved from current.json), not a static --input set at server
launch — a single static input cannot follow a session’s many per-task bundles or its moving
“current.” A directory / current-aware Surface ingestion is the cleaner long-term shape,
but that is Surface’s design to evaluate (kontourai/surface#95) — not something Flow
Agents hacks around.
3. Never auto-inject MCP config into files we do not own.
Registering an MCP server edits runtime config (Claude Code .mcp.json, Codex equivalent)
that belongs to the user, not Flow Agents. Hooks are core to Flow Agents’ function
(justified to write); MCP-surfacing is optional sugar (must not be force-injected).
Therefore:
- Default — zero writes: the installer documents/prints the exact
surface mcpregistration snippet; the user adds it if they want it. - Convenience — explicit, reversible opt-in: a
flow-agents trust:mcp enable|disablecommand writes a fenced managed block (# BEGIN flow-agents (managed) … # END) — easy to enable, trivial to remove. Never on plain install. Tracked in flow-agents#137.
4. A Flow Agents invocation MCP is a separate, deferred decision.
Flow Agents could expose its workflow operations as MCP tools — valuable for reach (MCP hosts without shell access, e.g. claude.ai web) and first-class, discoverable operations. But an MCP-only host gets invocation without enforcement (no hooks) — a capped conformance tier (invoke-without-enforce ranks below hook-enforce in the L0/L1/L2 model). This is a larger architecture decision, not adopted here; recorded as a future possibility gated on a real need for non-shell reach.
Consequences
- Trust-in-conversation is opt-in and boundary-pure — Surface owns the MCP + the UI; Flow Agents produces the bundle and (optionally, with consent) points Surface at it.
- The local HTML projection (
render-trust-panel, #135) stays the no-config default; MCP-surfacing is the opt-in upgrade. - Flow Agents’ config posture is explicit: core (hooks) writes; optional (MCP) is documented / opt-in / reversible.
- The
surface mcp --inputingestion is referred to Surface for evaluation (kontourai/surface#95).
Alternatives Considered
- Auto-inject Surface’s MCP on install. Rejected: edits config Flow Agents does not own, for optional sugar; surprising and hard to cleanly remove.
- Build an MCP-UI trust panel in Flow Agents. Rejected: forks Surface’s
buildTrustPanelUiResourceand requires standing up an MCP server (consume-never-fork violation). - Replace hooks with MCP tools. Rejected: MCP is agent-invoked and cannot block — it cannot carry deterministic enforcement.
- Static
--inputfor trust ingestion. Rejected for Flow Agents’ use: cannot follow per-task bundles or a moving current; use per-callpath. The exposure itself is referred to Surface (kontourai/surface#95).
References
- ADR 0010: Workflow Trust State as a Hachure Trust Bundle — the bundle this surfaces; #135 (
render-trust-panellocal projection). @kontourai/surfacesrc/commands/mcp.ts(surface mcp --input, per-callpath,buildTrustPanelUiResource).- kontourai/surface#95 — evaluate
surface mcp --inputingestion exposure. - flow-agents#137 — opt-in
trust:mcpwiring command.