feat(mcp): serve OKF bundles over MCP in two shapes, plus the generic skill
The eval was written RED at `5f1772e` with no server in the tree. This is the
capability it was written against.
`okf mcp --bundle <dir>` serves exactly one bundle, whose tools take no bundle
argument. `okf mcp --root <dir>` (repeatable) serves every bundle under the
roots and knows NONE of them by name. Four tools -- `okf_list`,
`okf_describe`, `okf_ask`, `okf_fetch` -- each carrying its reason in the
description a client actually reads.
Gate today: 1 (7/7) - 2 (83/181) - 3 (4/4) - 4 (9/9) - 5 (3/3) - 6 (6/6),
`GATE RED: rows 2`, exit 1.
THE PROTOCOL IS STDLIB, AND THAT IS THE PACKAGING INVARIANT KEPT RATHER THAN
A TASTE. An MCP SDK would be this package's second runtime dependency on the
DEFAULT install path, for four JSON-RPC methods and a newline framing, and
`test_the_only_runtime_dependency_is_the_security_boundary` pins that list
literally. Chosen hand-written because the surface needed is `initialize`,
`notifications/initialized`, `tools/list` and `tools/call`; `uv.lock` is
untouched.
NOTHING IS CACHED ACROSS CALLS, and row 3 is why. Every call re-walks the
roots and recomputes `bundle_ref`, so a bundle added, removed or rebuilt while
the process runs is seen by the next call with no restart, no configuration
edit and no code change -- 9 of 9 discovery checks over three bundles written
while the server was serving. The cost is paid per call and is published
rather than hidden: 0.75 s for the identity of a 2 756-concept bundle, 5.6 s
for one ask, 4 min 13 s for row 2's full run over four bundles.
CONTAINMENT IS TWO INDEPENDENT CHECKS: the bundle's own index must name the
concept, AND `connectors.safe_resolve` must place it inside the bundle. A
mutant removing either one alone still refuses -- with a DIFFERENT code, which
row 6 asserts by name -- and one removing both is killed. Row 6 declares a
code set per case because its first run had the 10 MB concept refused as
`concept_unknown`: the fixture had not named the file in the index, so the
size ceiling never ran and the row was green for a reason unrelated to the
attack.
`okf card <bundle>` and `okf skill --generic` are the one-to-many skill
candidate. The card is DERIVED on every run and never written into the bundle:
storing it would move the bytes of all six `examples/*/expected-bundle` trees
(23 files compared byte-for-byte) and of the pinned reference bundle, to keep
something recomputable in under a second, and a stored card is one more
artefact that can disagree with what is beside it. Measured here rather than
taken from the order: two per-bundle skills are identical on 280 of 312 and
310 lines; the 62 that differ are identity, concept count, the
conditional-field table, the whole-bundle cost and the breaking point. The
generic skill carries none of them, and `render_generic()` takes no argument,
so there is no bundle it could have read.
Row 2 decomposes into three numbers and the middle one is the finding: 99 of
181 (bundle, anchor) pairs are present in the bundles at all, 83 of those 99
were reached, and 0 of 83 were met by `okf_fetch` on the anchor as a concept
id. The set's anchors and this library's concept ids are different
vocabularies, so every pair met was met through the ranker -- 83 is a FLOOR on
the ceiling, never the ceiling.
13 mutants in a scratch copy, never in the working tree: 12 killed, 1 survived
with its mechanism printed, 0 errors, control green first. Suite 2323 passed,
2 skipped. The architecture choice between the two shapes is the OPERATOR's;
these rows are its input. Report: docs/2026-09-20-mcp-to-varianter.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5f1772e832
commit
df5a1183c9
10 changed files with 1823 additions and 59 deletions
51
CLAUDE.md
51
CLAUDE.md
|
|
@ -1406,6 +1406,57 @@ and fixtures, never code.
|
|||
held by a test. README publishes this bar behind
|
||||
`<!-- quality-boundary-threshold: ... -->`; SS 7 of the threshold document
|
||||
carries the seven bundles and the honesty limits.
|
||||
- **Serve a bundle over MCP: `okf mcp --bundle <dir>` (one bundle) or
|
||||
`okf mcp --root <dir>` (every bundle under the roots, none known by name).**
|
||||
Four tools -- `okf_list`, `okf_describe`, `okf_ask`, `okf_fetch` -- each
|
||||
carrying its REASON in the description a client reads, and every answer
|
||||
carrying the bundle id and concept id a claim must be attributed to. The
|
||||
JSON-RPC is written narrowly with stdlib only: an MCP SDK would be this
|
||||
package's SECOND runtime dependency on the default install path, and
|
||||
`test_the_only_runtime_dependency_is_the_security_boundary` pins that list
|
||||
literally; `uv.lock` is untouched. **NOTHING IS CACHED ACROSS CALLS** -- every
|
||||
call re-walks the roots and recomputes `bundle_ref`, so a bundle added,
|
||||
removed or rebuilt while the process runs is seen by the next call with no
|
||||
restart, no config edit and no code change (measured, 9 of 9 discovery checks
|
||||
over three bundles written while serving). The cost is paid per call and is
|
||||
published: **0.75 s** for the identity of a 2 756-concept bundle, **5.6 s**
|
||||
for one ask. Containment is TWO independent checks -- the bundle's own index
|
||||
must name the concept AND `connectors.safe_resolve` must place it inside the
|
||||
bundle -- and a mutant removing either one alone still refuses, with a
|
||||
different code. A concept above `MAX_CONCEPT_BYTES` is refused whole rather
|
||||
than truncated, and a directory that cannot be read as a bundle is REPORTED
|
||||
in `okf_list`'s `unreadable` rather than skipped: an absence with no
|
||||
denominator is not a boundary. The eval is
|
||||
`tools/okf_mcp_gate.py`, written RED at `5f1772e` before any server existed;
|
||||
it speaks real stdio to a SUBPROCESS and never imports the server. Today:
|
||||
**1 (7/7) - 2 (83/181) - 3 (4/4) - 4 (9/9) - 5 (3/3) - 6 (6/6)**,
|
||||
`GATE RED: rows 2`. **Row 2 decomposes into three numbers and the middle one
|
||||
is the finding**: 99 of 181 (bundle, anchor) pairs are present in the bundles
|
||||
at all, 83 of those 99 were reached, and **0 of 83 were met by `okf_fetch` on
|
||||
the anchor as a concept id** -- the set's anchors and this library's concept
|
||||
ids are different vocabularies, so every pair met was met through the ranker
|
||||
and 83 is a FLOOR on the ceiling, never the ceiling. **The architecture choice
|
||||
between the two shapes is the OPERATOR's**; the rows are its input. Report:
|
||||
`docs/2026-09-20-mcp-to-varianter.md`.
|
||||
- **`okf card <bundle>` and `okf skill --generic` are the one-to-many skill
|
||||
candidate.** The card is one bundle's identity, concept count,
|
||||
conditional-field counts and whole-bundle cost as JSON, **DERIVED on every run
|
||||
and never written into the bundle** -- storing it would move the bytes of all
|
||||
six `examples/*/expected-bundle` trees (23 files compared byte-for-byte) and
|
||||
of the pinned reference bundle, to keep something recomputable in under a
|
||||
second, and a stored card is one more artefact that can disagree with the
|
||||
bytes beside it. `okf skill --generic` writes ONE installable consumption
|
||||
skill for ANY bundle: it carries no bundle's identity and no bundle's numbers,
|
||||
and the property that makes that checkable rather than asserted is that
|
||||
`skill.render_generic()` **takes no argument** -- there is no bundle it could
|
||||
have read. Measured 2026-09-20: two per-bundle skills are identical on **280
|
||||
of 312** and **310** lines, and the 62 that differ are exactly identity,
|
||||
concept count, the conditional-field table, the whole-bundle cost and the
|
||||
breaking point -- the five things that go stale on a rebuild. The update
|
||||
drill, four artefact classes: MCP one-to-one **0 artefacts / 0 steps**, MCP
|
||||
one-to-many **0 / 0**, today's per-bundle skill **1 / 1 per consuming
|
||||
project** (it refuses out loud through `bundle_mismatch`, so its cost is not
|
||||
silence), generic skill **0 / 0**.
|
||||
- Consume a bundle: `okf consume <bundle> --question "<q>"
|
||||
[--k N] [--limit N] [--out PATH] [--ref IDENTITY]` — the **pre-pass**
|
||||
`docs/consumption-contract.md` § 1 defines, and the only reading direction
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue