ADR-0019: MCP servers as review context ​
Status: accepted (2026-10-15); the feature stays inert until the maintainer signs the threat analysis mcp-context.md and flips MCP_CONTEXT_SIGNED_OFF. Builds on the LLM gateway and endpoint guard (ADR-0010), measured code context (ADR-0011), key rotation (ADR-0008) and tenancy (ADR-0002).
Context ​
Teams keep knowledge that explains a change outside the repository: design documents in a wiki, tickets, runbooks. Many of these systems expose a Model Context Protocol (MCP) server. Letting the review finder query them can explain intent, but it adds a new egress path for pull request content and a new channel for prompt injection into the review.
Decision ​
Transport and SDK ​
- Remote servers over Streamable HTTP only. There is no stdio transport and no command field anywhere; ESLint bans every
@modelcontextprotocol/**/*stdio*import. @modelcontextprotocol/client2.3.1 (Apache-2.0, pinned) withversionNegotiation: { mode: 'auto' }: the 2026-07-28 protocol (server/discover, stateless) and the 2025initializehandshake as fallback. The client advertises no capabilities and registers no request handlers.- The client lives in
@quavon/llm(src/mcp/), next to the endpoint guard,untrusted,redactandLlmTool. The transport gets an injected fetch: the guarded fetch in every edition (the self-hostedproviderFetchwould allow private hosts), wrapped bycappedFetch(configured origin only, credential header, 10 s timeout, streamed byte cap). - Self-hosted instances may list hostnames in
QUAVON_MCP_PRIVATE_HOSTSthat may resolve to private addresses (link-local, multicast and cloud metadata addresses stay blocked). The enterprise edition ignores the list.
Configuration and data ​
- Tenant admins register servers (
mcp_servers: name, URL, encrypted credentialnone | bearer | x-* header, hint, tool snapshots, acknowledgement, last discovery and error). Credentials use the contextmcp_servers:<id>and are covered bykeys rotate. - Tool allowlists are built from a live discovery inside the request; the client never supplies schemas or hashes. Each snapshot stores the admin-reviewed description and the SHA-256 over name, description, canonical input schema and annotations. Write-capable tools (
destructiveHint: trueorreadOnlyHintnottrue) needconfirmWrite. - Only flat object schemas with scalar properties are supported; they are converted to strict zod objects with 500-character strings. We build the zod schema directly instead of
z.fromJSONSchemaso that every accepted keyword is explicit and string caps are always applied. mcp_tool_callsrecords status, size, duration and an argument hash per call, never arguments or output; both tables have forced RLS and the call table follows the tenant retention.- Repository maintainers choose up to five servers and a call budget in the repository policy (
features.mcp).
Review pipeline ​
- Only the finder receives the tools (
externalToolsinReviewPipelineInput); the verifier and the summary do not, so findings stay provable from the repository. - The finder prompt gets
MCP_TOOLS_SECTIONonly when tools are offered; reviews without MCP keep a byte-identical prompt andREVIEW_PROMPT_VERSION.MCP_PROMPT_VERSION(mcp-v1) is recorded in the run stats. ContextSourceKindstays unchanged, because addingmcpwould change the finder's output schema for every review. MCP use is visible through the run stats (mcp: { prompt, calls, bytes, blocked }) and the run detail.- A server that cannot be reached is skipped; the review never fails because of MCP.
Gates ​
MCP_CONTEXT_SIGNED_OFF(shared-types) andQUAVON_MCP_CONTEXT(defaultdisabled, hard off in the Helm air-gap profile) gate the API (422not_signed_off/instance_disabled), the repository policy and the worker.- MCP context never runs on public repositories.
Consequences ​
- Static credentials only; OAuth clients for MCP servers can be added later (the Jira OAuth 3LO flow is the precedent).
- Search terms derived from private pull requests reach third-party servers by design; admins acknowledge this per server, and secrets and e-mail addresses are blocked.
- Debug prompt logs, when a tenant enables them, contain MCP output for their TTL (documented as MC12).
- The red-team corpus has an
mcp_outputvector, and the harness checks outbound MCP arguments for the canary and secrets (mcp_egress).