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
|
|
@ -54,6 +54,7 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -179,6 +180,10 @@ TEMPLATE_ENUMERATION = (
|
|||
|
||||
TEMPLATE_OUTPUT = "Write to `<OUT>`. It must carry: the bundle ref; the findings, each with a"
|
||||
|
||||
#: Every per-corpus hole the template carries. A generic skill that left one
|
||||
#: would be the unfilled template with better manners, so it is refused.
|
||||
_PLACEHOLDER = re.compile(r"<[A-Z][A-Z_]*>")
|
||||
|
||||
REPLACED_BLOCKS = (
|
||||
TEMPLATE_HEADER,
|
||||
TEMPLATE_PRE_PASS,
|
||||
|
|
@ -664,7 +669,12 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
|
|||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument("bundle", type=Path, help="the OKF bundle to instantiate a skill for")
|
||||
parser.add_argument(
|
||||
"bundle",
|
||||
type=Path,
|
||||
nargs="?",
|
||||
help="the OKF bundle to instantiate a skill for (unused with --generic)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out", type=Path, required=True, help="the skill directory to write (SKILL.md inside)"
|
||||
)
|
||||
|
|
@ -677,14 +687,30 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
|
|||
parser.add_argument(
|
||||
"--force", action="store_true", help="replace an existing SKILL.md at --out"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--generic",
|
||||
action="store_true",
|
||||
help=(
|
||||
"write the one-to-many skill instead: one installable document for ANY "
|
||||
"bundle, carrying no bundle's identity or numbers. `bundle` is then "
|
||||
"unused, and the reader is told to run `okf card <bundle>` at run time"
|
||||
),
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
try:
|
||||
written = generate(
|
||||
args.bundle, out=args.out, question=args.example_question, force=args.force
|
||||
if args.bundle is None and not args.generic:
|
||||
print("refused (bundle_missing): name a bundle, or pass --generic", file=sys.stderr)
|
||||
return 2
|
||||
written = (
|
||||
generate_generic(out=args.out, force=args.force)
|
||||
if args.generic
|
||||
else generate(
|
||||
args.bundle, out=args.out, question=args.example_question, force=args.force
|
||||
)
|
||||
)
|
||||
except okf_consume.ConsumeError as exc:
|
||||
print(f"refused ({exc.code}): {exc}")
|
||||
|
|
@ -701,3 +727,213 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
|
||||
# --- The one-to-many candidate ------------------------------------------------
|
||||
|
||||
#: The name the generic skill carries. Claude Code takes a project skill's
|
||||
#: command from its DIRECTORY name and uses `name` only as a display label, so
|
||||
#: this is the label and not the command.
|
||||
GENERIC_NAME = "okf-consume-any"
|
||||
|
||||
#: The command that hands a reader the per-bundle numbers this skill does not
|
||||
#: carry. It has to exist for the skill to be honest: a generic document that
|
||||
#: told a reader to "check the denominators somewhere" would be the unfilled
|
||||
#: template with better manners.
|
||||
CARD_COMMAND = "okf card"
|
||||
|
||||
GENERIC_BUNDLE = "<the bundle you were pointed at>"
|
||||
|
||||
|
||||
def render_generic() -> str:
|
||||
"""One installable skill for ANY bundle, carrying no bundle's numbers.
|
||||
|
||||
The measured fact this answers: two skills generated for two different
|
||||
bundles are identical on 280 of 312 and 310 lines (measured 2026-09-20 on
|
||||
this machine, over `examples/ingest-golden-segmented-okf-v0-2` and
|
||||
`tests/fixtures/consume-bundle`; the order's own 227 of 285 is a different
|
||||
pair of bundles and neither number contradicts the other). The 30-odd lines
|
||||
that differ are identity, concept count, the conditional-field table, the
|
||||
whole-bundle cost and the breaking point -- all of them recomputable from
|
||||
the bundle in under a second, and all of them what makes a generated skill
|
||||
go stale the moment its bundle is rebuilt.
|
||||
|
||||
So this text carries NONE of them, and says where to read each one instead.
|
||||
The property that makes that claim checkable is that this function takes no
|
||||
argument: there is no bundle it could have read, and two calls return the
|
||||
same bytes.
|
||||
"""
|
||||
text = template_path().read_text(encoding="utf-8")
|
||||
text = text.split("---\n", 2)[2]
|
||||
replacements: list[tuple[str, str]] = [
|
||||
(
|
||||
TEMPLATE_HEADER,
|
||||
"**This file is generic: it carries no bundle's identity and no bundle's\n"
|
||||
"numbers,** and it is therefore never stale. It serves whichever bundle you\n"
|
||||
"are pointed at. Before answering, read that bundle's own card:\n\n"
|
||||
"```sh\n"
|
||||
f"{CARD_COMMAND} {GENERIC_BUNDLE}\n"
|
||||
"```\n\n"
|
||||
"The card is DERIVED from the bundle on every run, never stored in it, so\n"
|
||||
"there is no second artefact that can disagree with the bytes. Its\n"
|
||||
"`bundle_id` and `ref` are the identity to carry into your output; its\n"
|
||||
"`concept_count`, `conditional_fields` and `whole_bundle_bytes` are the\n"
|
||||
"denominators the sections below ask for. The section headings are fixed:\n"
|
||||
"the contract checker reads them by name.",
|
||||
),
|
||||
(
|
||||
TEMPLATE_PRE_PASS,
|
||||
"```sh\n"
|
||||
f"{PRE_PASS_COMMAND} \\\n"
|
||||
f" {GENERIC_BUNDLE} \\\n"
|
||||
' --question "your question" \\\n'
|
||||
" --ref THE_REF \\\n"
|
||||
" --out /tmp/payload.json\n"
|
||||
"```\n\n"
|
||||
"`--ref` is an **assertion**, never an override: the identity is computed\n"
|
||||
"from the bytes either way, and a mismatch refuses. Read the pre-pass's\n"
|
||||
"own exit status, which carries three values: **0** a payload was written,\n"
|
||||
"**1** the run happened and refused, **2** the run did not happen at all.",
|
||||
),
|
||||
(
|
||||
TEMPLATE_CHECK,
|
||||
f"```sh\n{CHECKER_COMMAND} --skill <this file> --payload /tmp/payload.json\n```",
|
||||
),
|
||||
(
|
||||
TEMPLATE_CONTRACT_LINE,
|
||||
f"The contract this skill is held to is `{CONTRACT}`. Where this",
|
||||
),
|
||||
(
|
||||
TEMPLATE_EXTENSIONS,
|
||||
"**Extensions.** This skill declares none. A corpus needing one declares it\n"
|
||||
"in its own documentation; the five markings below are never extended here,\n"
|
||||
"because a marking invented for one bundle would travel to every other.",
|
||||
),
|
||||
(
|
||||
TEMPLATE_CONDITIONAL,
|
||||
"**Conditionally-written fields.** Read `conditional_fields` from the card:\n"
|
||||
"it gives, per field, how many of the bundle's concepts carry it. A field\n"
|
||||
"written on some concepts and not others means its ABSENCE on one concept\n"
|
||||
"is a measurement about that concept, never a fact about the world — so\n"
|
||||
"report the count beside any claim that rests on an absence. The fields\n"
|
||||
f"this profile can write are: {', '.join(f'`{field}`' for field in CONDITIONAL_FIELDS)}.",
|
||||
),
|
||||
(
|
||||
TEMPLATE_SCALING,
|
||||
"**Scaling.** Cost tracks the QUESTION, not the corpus: the payload is cut\n"
|
||||
f"to {okf_consume.DEFAULT_LIMIT} {okf_consume.BUDGET_UNIT} whatever the bundle's size. What\n"
|
||||
"does track the corpus is the bookkeeping — one `withheld` entry per\n"
|
||||
"considered-and-not-delivered concept — so the point at which this strategy\n"
|
||||
"stops fitting is a property of the bundle. Read `whole_bundle_bytes` from\n"
|
||||
"the card and compare it with the budget: a bundle costing less than the\n"
|
||||
"budget could have been handed over whole, and the pre-pass is then a\n"
|
||||
"convenience rather than a necessity.",
|
||||
),
|
||||
(
|
||||
TEMPLATE_DENOMINATORS,
|
||||
"The payload reports three counts — `considered`, `withheld`, `delivered` —\n"
|
||||
"and `considered == withheld + delivered`. Carry them into your output, and\n"
|
||||
"carry the card's `concept_count` beside them: `considered` is what the cut\n"
|
||||
"looked at, and the card says how much of the bundle that was.",
|
||||
),
|
||||
(
|
||||
TEMPLATE_ENUMERATION,
|
||||
f"- **No directory enumeration** unless the profile (`{PROFILE_NAME}`) says the\n"
|
||||
" index is derived. The payload's own `bundle.entries_match_directory` says\n"
|
||||
" whether it does, for the bundle in front of you.",
|
||||
),
|
||||
(
|
||||
TEMPLATE_OUTPUT,
|
||||
"Write to the path the caller names, or to your answer if none was named.\n"
|
||||
"It must carry: the bundle ref; the findings, each with a",
|
||||
),
|
||||
("`<CORPUS>` bundle", "bundle you were pointed at"),
|
||||
("# <CORPUS> consumption", "# OKF bundle consumption"),
|
||||
]
|
||||
# The blocks are STRICT: a template that stopped carrying one has drifted,
|
||||
# and rewriting the rest would ship a skill missing a whole section.
|
||||
for old, new in replacements:
|
||||
if old not in text:
|
||||
raise SkillError(
|
||||
f"the template no longer carries the block this generator rewrites: {old[:70]!r}",
|
||||
code="template_drift",
|
||||
)
|
||||
text = text.replace(old, new)
|
||||
# The tokens are LENIENT, and the sweep below is what makes that safe: a
|
||||
# token may already have been consumed by the block that carried it, and a
|
||||
# strict check here would only measure the order of this list.
|
||||
for old, new in (
|
||||
("<PROFILE_NAME>", PROFILE_NAME),
|
||||
("<PRE_PASS_COMMAND>", PRE_PASS_COMMAND),
|
||||
("<BUDGET_LIMIT>", str(okf_consume.DEFAULT_LIMIT)),
|
||||
("<BUDGET_UNIT>", okf_consume.BUDGET_UNIT),
|
||||
("<BUDGET_INSTRUMENT>", okf_consume.BUDGET_INSTRUMENT),
|
||||
("<KNOWN_POSITIVE_CASE>", okf_consume.KNOWN_POSITIVE_CASE),
|
||||
("<KNOWN_POSITIVE_EXPECTED>", str(okf_consume.KNOWN_POSITIVE_EXPECTED)),
|
||||
("<BUNDLE_ROOT>", GENERIC_BUNDLE),
|
||||
("<PAYLOAD_PATH>", "/tmp/payload.json"),
|
||||
("<SKILL_PATH>", "this file"),
|
||||
("<REF>", "the card's `ref`"),
|
||||
("<OUT>", "the path the caller named"),
|
||||
):
|
||||
text = text.replace(old, new)
|
||||
left = sorted(set(_PLACEHOLDER.findall(text)))
|
||||
if left:
|
||||
raise SkillError(
|
||||
f"the generic skill still carries a per-corpus hole: {', '.join(left)}. A hole "
|
||||
"left in a generic document is a number the reader is invited to invent",
|
||||
code="placeholder_unfilled",
|
||||
)
|
||||
description = block_scalar(
|
||||
"Answer one question about ANY OKF bundle from a bounded payload assembled "
|
||||
"by a deterministic pre-pass, marking every claim with its source, its title "
|
||||
"and its provenance locator. Carries no bundle's identity: read the bundle's "
|
||||
f"own card with `{CARD_COMMAND}` first. Use when the user asks a question of, "
|
||||
"or states a hypothesis about, a corpus held as an OKF bundle."
|
||||
)
|
||||
header = f"---\nname: {block_scalar(GENERIC_NAME)}\ndescription: {description}\n---\n"
|
||||
return header + text
|
||||
|
||||
|
||||
def generate_generic(*, out: Path, force: bool = False) -> Path:
|
||||
"""Write the generic skill. Takes no bundle, by construction."""
|
||||
target = out / "SKILL.md"
|
||||
if target.exists() and not force:
|
||||
raise SkillError(
|
||||
f"{target} already exists; pass --force to replace it",
|
||||
code="target_occupied",
|
||||
)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(render_generic(), encoding="utf-8")
|
||||
return target
|
||||
|
||||
|
||||
def card_main(argv: list[str] | None = None) -> int:
|
||||
"""`okf card <bundle>` -- the per-bundle half of a consumption skill, as JSON.
|
||||
|
||||
The generic skill above tells its reader to run this. It is DERIVED on every
|
||||
run and never stored in the bundle: a stored card is one more artefact that
|
||||
can disagree with the bytes beside it, which is the defect the generic skill
|
||||
exists to remove.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="okf card",
|
||||
description=(
|
||||
"Print one bundle's identity, concept count, conditional-field counts "
|
||||
"and whole-bundle cost as JSON. Derived from the bundle on every run."
|
||||
),
|
||||
)
|
||||
parser.add_argument("bundle", type=Path, help="the OKF bundle to describe")
|
||||
args = parser.parse_args(argv)
|
||||
from .mcp_server import card as build_card
|
||||
|
||||
try:
|
||||
payload = build_card(args.bundle.resolve(), profile=okf_consume.DEFAULT_PROFILE)
|
||||
except okf_consume.ConsumeError as exc:
|
||||
print(f"refused ({exc.code}): {exc}", file=sys.stderr)
|
||||
return 1
|
||||
except OSError as exc:
|
||||
print(f"the run did not happen: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue