docs(mcp): Q2 gains upstream's tool surface as measured prior art

sources/base.py plus tools/source_tools.py is the same problem phase 5 option
(A) contemplates, already solved in ADK-specific form: a data system exposed as
model-callable tools. Recorded under Q2 because that question calls the tool
surface the real design work, and this is a worked example of it.

Three signals, each with the reason it is a signal: enumerate and read are
abstract while sample_rows defaults to None, so sampling is advisory; the
surface is per-concept, which is evidence that Door B's per-run collision gate
and index generation are the side that has to give; and two things not to copy
— an untyped dict[str, Any] return that only a model can interpret, and a find()
that linear-scans list_concepts() and so assumes total enumeration is cheap,
against this library's rule that nothing enumerates unless a profile says
derived.

Read at the pinned commit, not vendored.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-27 20:16:47 +02:00
commit ddc8f76446

View file

@ -66,6 +66,30 @@ something:
`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.
**Prior art, measured 2026-07-27 at the pinned upstream commit.** Upstream has
already built this surface in ADK-specific form, and it is worth reading before
we draw our own. `sources/base.py` (38 lines, stdlib only) defines a `Source`
ABC; `tools/source_tools.py` wraps its methods as model-callable tools, which
`agent.py` registers. Three things in it are design signals rather than
incidental:
- **`list_concepts` and `read_concept` are abstract; `sample_rows` is optional
with a `None` default; `find` ships a default implementation.** So the
surface's obligations are *enumerate* and *read*, and sampling is advisory.
A tool surface that made sampling mandatory would be claiming more than the
problem needs.
- **It is per-CONCEPT** (`read_concept(ref)`), which is the granularity this
question is about. That an independent implementation landed on per-concept
is evidence that the per-run shape — Door B's collision gate and index
generation — is the side that has to give, not the tool surface.
- **What not to copy.** `read_concept` returns `dict[str, Any]`, untyped
because a model interprets it; we render bytes and cannot. And `find` is a
linear scan over `list_concepts()`, which assumes total enumeration is cheap
— colliding directly with this library's rule that nothing enumerates unless
a profile says derived.
Read, not vendored. The point is the shape of the question they answered.
- **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.