docs(roadmap): phase 5 — MCP as a way to populate a bundle

Committed by the operator; the design is open and the scope is not. Recorded
in the roadmap with a scoping plan rather than in the v0.2 alignment doc, which
is about OKF v0.2 conformance and is the wrong home.

The plan settles nothing on purpose. It names the fork everything turns on —
whether we are the MCP server (an agent calls our doors, Door B's territory,
ours to design) or an MCP client (a manifest source type, which is normative in
commons' ingest-spec and therefore starts as a request to them, not as code
here). The two share a protocol and nothing else, including their owner.

Constraints written down because each has already refused something: the
one-runtime-dependency rule (an MCP SDK would be the second — stdlib JSON-RPC
or an optional extra), no model calls in the run path (the model may CALL us,
never the reverse), the guard persist gate (MCP is transport, not a bypass),
the network opt-in, and the required ingested_at.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-27 20:12:58 +02:00
commit ff9f2f82ca
2 changed files with 91 additions and 0 deletions

View file

@ -61,6 +61,14 @@ are the library baseline. First consumer: `portfolio-optimiser-claude`.
(docx/pdf/eml/html → md). Covers okr, linkedin-studio, ms-ai-architect,
and the marketplace catalog.
5. **Phase 5 — MCP as a way to populate a bundle.** Committed by the operator
2026-07-27; the design is open, the scope is not. Plan:
`docs/plan/mcp-bundle-population.md`. The fork that must be settled before
any code — whether we are the MCP **server** (an agent calls our doors as
tools) or an MCP **client** (a manifest source type that pulls from someone
else's server) — decides both the owner and the dependency question, and the
two are not variants of one feature.
The two halves share the OKF contract and fixture suite, **not code**.
### Upstream version policy (standing, non-negotiable)

View file

@ -0,0 +1,83 @@
# 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.