Skip to content

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/client 2.3.1 (Apache-2.0, pinned) with versionNegotiation: { mode: 'auto' }: the 2026-07-28 protocol (server/discover, stateless) and the 2025 initialize handshake 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, redact and LlmTool. The transport gets an injected fetch: the guarded fetch in every edition (the self-hosted providerFetch would allow private hosts), wrapped by cappedFetch (configured origin only, credential header, 10 s timeout, streamed byte cap).
  • Self-hosted instances may list hostnames in QUAVON_MCP_PRIVATE_HOSTS that 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 credential none | bearer | x-* header, hint, tool snapshots, acknowledgement, last discovery and error). Credentials use the context mcp_servers:<id> and are covered by keys 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: true or readOnlyHint not true) need confirmWrite.
  • 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.fromJSONSchema so that every accepted keyword is explicit and string caps are always applied.
  • mcp_tool_calls records 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 &ZeroWidthSpace;

  • Only the finder receives the tools (externalTools in ReviewPipelineInput); the verifier and the summary do not, so findings stay provable from the repository.
  • The finder prompt gets MCP_TOOLS_SECTION only when tools are offered; reviews without MCP keep a byte-identical prompt and REVIEW_PROMPT_VERSION. MCP_PROMPT_VERSION (mcp-v1) is recorded in the run stats.
  • ContextSourceKind stays unchanged, because adding mcp would 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 &ZeroWidthSpace;

  • MCP_CONTEXT_SIGNED_OFF (shared-types) and QUAVON_MCP_CONTEXT (default disabled, hard off in the Helm air-gap profile) gate the API (422 not_signed_off / instance_disabled), the repository policy and the worker.
  • MCP context never runs on public repositories.

Consequences &ZeroWidthSpace;

  • 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_output vector, and the harness checks outbound MCP arguments for the canary and secrets (mcp_egress).

The open core is released under the Apache License 2.0.