# Phase 5 — MCP as a way to populate a bundle **Status: scoped, not designed.** Committed by the operator 2026-07-27. This document records the commitment, the fork it turns on, and the constraints any design has to satisfy. It deliberately does not choose an implementation: the fork below decides who owns the work, and choosing it wrongly is expensive in a way writing it later is not. ## The fork — settle this first "Support MCP to populate a bundle" names two different pieces of work. They share a protocol and nothing else. **(A) We are the MCP server.** An agent connects to us and calls tools — `add_concept`, `list_bundle`, `generate_index` — and the bundle fills up through our doors. The natural home is **Door B**, which already answers "here is content, make it OKF": MCP would be a protocol surface over an existing door rather than a new ingestion path. **(B) We are an MCP client.** A manifest names an MCP server as a source, the same way it names `file`/`sql`/`http` today, and Door A pulls concepts from it. **Default reading, for confirmation rather than assumption:** the phrase "to populate a bundle" points at (A). Recorded as a reading, not a decision — the operator has not been asked which one, and the two have different owners. ## Why the fork decides the owner (B) adds a **source type**, and the source type set is normative in commons' `ingest-spec.md`. This repo implements that spec and does not edit it. So (B) cannot start here: it starts as a request to commons, and it lands here only after they have specified it. (A) adds no source type and no spec surface — the concepts arrive through Door B exactly as dropped files do — so (A) is ours to design. That asymmetry is the whole reason this document exists before any code. ## Constraints any design inherits None of these are negotiable by this feature, and each one has already refused something: 1. **Exactly one runtime dependency, ever** (`llm-ingestion-guard`), enforced by a packaging test. An MCP SDK would be the second. Either the protocol is hand-rolled over stdlib JSON-RPC on stdio, or it lives behind an optional extra the way binary parsers do in `[extract]` — never in the core install. 2. **No model calls anywhere in the run path.** MCP is model-facing, and that is fine in exactly one direction: under (A) the model is the *caller* and we stay deterministic tools. A design where this library calls a model to decide what to write is out of scope, and would be a different product. 3. **The persist gate still applies.** Content arriving over MCP is untrusted by construction. It goes through Door B's injected guard gate like every other dropped file; MCP is a transport, never a reason to bypass a gate. Security stays the guard's, as always. 4. **Network access is an explicit per-run opt-in.** A server listening on a socket, or a client dialing one, is network. The existing fail-fast refusal is the baseline, not something to reinvent. 5. **Determinism.** `ingested_at` is a required argument with no wall-clock default. A protocol handler does not get to invent one because a call arrived "now". ## Open questions - **Q1 — (A) or (B), or both in order?** Blocks everything else. - **Q2 — If (A): what is the tool surface?** Door B's public surface is `process_inbox` over a directory. An MCP tool taking one concept at a time is a different granularity, and the collision gate and index generation are currently per-run rather than per-concept. This is the real design work. - **Q3 — Transport.** stdio is the vendorable, zero-dependency answer and matches how the marketplace consumes things. HTTP would be a second network surface with its own gate question. - **Q4 — Which half?** Phase 4 puts a Node/ESM half next to the Python one. MCP servers in the plugin world are overwhelmingly Node. If (A) lands in Python only, the second-brain consumers cannot use it; if it lands in both, the "share contract and fixtures, not code" rule applies to the tool surface too. - **Q5 — Does an MCP-populated bundle differ observably** from one Door B wrote from files? It must not, or the transport has leaked into the artifact. ## Non-goals - Reimplementing any security check the guard owns. - Calling a model from this library, under any transport. - Editing commons' source-type set locally to make (B) possible sooner.