llm-ingestion-okf/tests/test_mcp_entry.py
Kjell Tore Guttormsen 570496470b docs(project): the server is the standard way in, the skill the supplement
`okf project`'s closing text and the README's first screen now say it in
that order: register `okf mcp --root` once, and it answers from every
project and reaches subagents; the skill beside the bundle is for someone
who would rather register nothing; neither is made again when a bundle is
rebuilt. Two tests hold the order in both places. The README's stale
`<id>-consume` skill path is corrected to `okf-consume-any`.

v1.1 order F, part F3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 10:36:52 +02:00

200 lines
8.1 KiB
Python

"""MCP as the standard entry: the method travels, and describe fans out.
Subagents inherit a session's MCP tools; they do not inherit its skills. So a
working method that lives only in a skill reaches the main thread and no arm
below it, and the one place it can reach every caller is the server's own
`instructions` and tool descriptions -- both capped by Claude Code at 2 KB
each, which is why what travels is the SHORT version and the long one stays in
the skill.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROJECT_ROOT / "src"))
from llm_ingestion_okf import consume as okf_consume # noqa: E402
from llm_ingestion_okf import mcp_server, project # noqa: E402
GOLDEN = PROJECT_ROOT / "examples" / "ingest-golden-segmented-okf-v0-2" / "expected-bundle"
FIXTURE = PROJECT_ROOT / "tests" / "fixtures" / "consume-bundle"
#: Claude Code truncates server instructions and each tool description at 2 KB
#: (`docs-en-mcp.md`). A description over it is not rejected -- it is CUT, which
#: is worse: the reader gets the first half of a method and no sign that the
#: rest existed.
CLIENT_TRUNCATION_BYTES = 2048
@pytest.fixture
def served(tmp_path: Path) -> mcp_server.Surface:
root = tmp_path / "root"
(root / "a").mkdir(parents=True)
(root / "b").mkdir(parents=True)
for name, source in (("a", GOLDEN), ("b", FIXTURE)):
for path in source.rglob("*"):
if path.is_file():
target = root / name / path.relative_to(source)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(path.read_bytes())
return mcp_server.Surface(roots=(root,), fixed=None, profile=okf_consume.DEFAULT_PROFILE)
def _instructions(surface: mcp_server.Surface) -> str:
result = mcp_server.handle(surface, "initialize", {})
assert isinstance(result["instructions"], str)
return result["instructions"]
def test_the_instructions_carry_the_short_working_method(served: mcp_server.Surface) -> None:
text = _instructions(served)
for mark in ("bundle's own words", "sub-question", "ask again", "outside the cut"):
assert mark in text, f"the instructions do not say {mark!r}"
def test_the_instructions_fit_inside_what_the_client_keeps(
served: mcp_server.Surface,
) -> None:
text = _instructions(served)
assert len(text.encode("utf-8")) <= CLIENT_TRUNCATION_BYTES
# The control: the limit is one this text could realistically cross, so
# the assertion above is a measurement and not a tautology.
assert len(text.encode("utf-8")) > CLIENT_TRUNCATION_BYTES // 4
def test_every_tool_description_fits_and_the_asking_one_carries_the_method(
served: mcp_server.Surface,
) -> None:
by_name = {tool.name: tool for tool in mcp_server.tools(served)}
for name, tool in by_name.items():
assert len(tool.description.encode("utf-8")) <= CLIENT_TRUNCATION_BYTES, name
assert "ask again" in by_name["okf_ask"].description
assert "withheld" in by_name["okf_ask"].description
def test_describe_without_a_bundle_id_answers_for_every_served_bundle(
served: mcp_server.Surface,
) -> None:
"""It refused instead, where `okf_ask` in the same position fans out.
A tool that refuses the call a sibling tool accepts is a shape a client
has to learn out of band, which is the configuration this server exists to
remove.
"""
result = mcp_server.call_describe(served, {})
assert sorted(result["asked"]) == ["b-golden-segmented-okf-v0-2", "consume-fixture"]
cards = result["cards"]
assert isinstance(cards, list) and len(cards) == 2
assert sorted(str(card["bundle_id"]) for card in cards) == sorted(result["asked"])
def test_describe_with_a_bundle_id_is_the_card_it_always_was(
served: mcp_server.Surface,
) -> None:
named = mcp_server.call_describe(served, {"bundle_id": "consume-fixture"})
assert named["bundle_id"] == "consume-fixture"
assert "cards" not in named
def test_a_one_to_one_server_still_answers_with_its_own_card(tmp_path: Path) -> None:
surface = mcp_server.Surface(
roots=(GOLDEN,), fixed="b-golden-segmented-okf-v0-2", profile=okf_consume.DEFAULT_PROFILE
)
assert mcp_server.call_describe(surface, {})["bundle_id"] == "b-golden-segmented-okf-v0-2"
def test_the_ask_answer_carries_the_compact_withheld_block(
served: mcp_server.Surface,
) -> None:
"""A2 reaches the MCP arm because the payload is the payload."""
result = mcp_server.call_ask(served, {"question": "Hva sier veiledningen om krav?"})
for answer in result["answers"]:
block = answer["payload"]["withheld"]
assert set(block) == {"total", "by_rule", "nearest", "complete"}
def test_the_readme_carries_the_one_line_that_registers_the_server() -> None:
"""The user runs it. This package never starts Claude Code."""
readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
assert "claude mcp add --scope user okf -- okf mcp --root" in readme
def test_the_project_summary_says_what_to_do_next_with_the_server(tmp_path: Path) -> None:
folder = tmp_path / "Dokumenter"
folder.mkdir()
(folder / "krav.md").write_text(
"## 4 Grunnforhold\n\nGrunnen er morene over berg.\n", encoding="utf-8", newline=""
)
_, _, summary = project.create(folder, out=tmp_path / "project")
assert "claude mcp add --scope user okf -- okf mcp --root" in summary
def test_the_project_summary_puts_the_server_first_and_the_skill_second(tmp_path: Path) -> None:
"""v1.1 F3: the server is the standard way in, the skill the supplement."""
folder = tmp_path / "Dokumenter"
folder.mkdir()
(folder / "krav.md").write_text(
"## 4 Grunnforhold\n\nGrunnen er morene over berg.\n", encoding="utf-8", newline=""
)
out = tmp_path / "project"
_, _, summary = project.create(folder, out=out)
server = summary.index("claude mcp add --scope user okf -- okf mcp --root")
skill = summary.index(f"start claude in {out}")
assert server < skill
assert "standard" in summary[:server]
assert "supplement" in summary[server:]
assert "Neither has to be made again when a bundle is rebuilt" in summary
def test_the_readme_first_screen_puts_the_server_first_and_the_skill_second() -> None:
readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
first_screen = readme.split("## Known limitations", 1)[0]
server = first_screen.index("claude mcp add --scope user okf -- okf mcp --root")
skill = first_screen.index("The skill is the supplement")
assert server < skill
assert "standard way in" in first_screen[:server]
assert "Neither has to be made again" in first_screen
def test_a_project_bundle_is_where_a_root_server_finds_it(tmp_path: Path) -> None:
"""`--root <the directory holding projects>` must reach `<project>/.okf/<id>`.
Measured rather than reasoned: the walk is bounded at
`MAX_DISCOVERY_DEPTH`, and `.okf` spends one level of it.
"""
projects = tmp_path / "okf"
folder = projects / "Mitt Prosjekt" / "kilder"
folder.mkdir(parents=True)
(folder / "krav.md").write_text(
"## 4 Grunnforhold\n\nGrunnen er morene over berg.\n", encoding="utf-8", newline=""
)
project.create(folder, out=projects / "Mitt Prosjekt", bundle_id="mitt-prosjekt")
found = mcp_server.discover((projects,))
assert [served.bundle_id for served in found.bundles] == ["mitt-prosjekt"]
def test_the_skill_command_refuses_a_missing_out_in_its_own_form(tmp_path: Path) -> None:
"""A5: exit 2 was already right; the TEXT was argparse's, not okf's.
Every other refusal in this chain reads `refused (<code>): <what>`, and a
caller parsing that form got one line that did not match on the one flag
everybody forgets. The code stays 2 -- "the run did not happen" -- because
that is what it is.
"""
import subprocess
result = subprocess.run(
[sys.executable, "-m", "llm_ingestion_okf.cli", "skill"],
capture_output=True,
text=True,
check=False,
cwd=PROJECT_ROOT,
)
assert result.returncode == 2
assert "refused (out_missing)" in result.stderr
assert "--out" in result.stderr