Remove the MCP surface - #28
Merged
Merged
Conversation
The About section still advertised it, and it had stopped being true. The CLI has been the primary surface since the measurement that demoted MCP; it kept the code as the fallback for a client with no shell, and that argument was not wrong — only outweighed. It cost ~2,340 tokens of schema and instructions in every session against ~304 for `knoten --help`, and it was a second surface that had to be kept in step with ops.py forever. Every cross-surface drift bug this project has recorded came from having two: `update`'s refusal built twice with different keys, `--field` coercing `2` to `2.0` on one side and not the other, `ops.index(query=...)` reachable from one surface with no flag on the other. The shell is the interface. Gone: mcp_server.py (327 lines), test_mcp.py, test_mcp_attach.py, test_tool_descriptions.py (~510 lines), the `[mcp]` extra, the `knoten-mcp` entrypoint, `anyio` (a dev dependency that existed only for those async tests), and the README paragraph. The package is 2,432 lines with one runtime dependency and no optional ones. REMOVING THE TRANSPORT REMOVED NO ENFORCEMENT, which is the part worth stating: the gate lives in `commit`, not in the surface, so a `status: alive` node with no `kn:survivedGate` edge is refused exactly as before. That was the point of extracting `commit.py` and `ops.py` out of the server in the first place. Two tests kept their coverage rather than dying with it: the `created` stamping tests now call `commit()` directly, which is where the stamp has lived since the extraction. `test_skill.py` lost the cross-surface binding it existed for — it compared SKILL.md's steps against the server's instructions — so it now pins what SKILL.md can be held to alone: contiguous numbering, every step naming a real command, every command an agent needs present. It also stops being skipped: the suite is 270 passed, 0 skipped, where it was 266 passed and 6 skipped, because every skip was mcp-gated. SPEC §8 records the reversal instead of quietly dropping it, along with the four things the server taught that outlived it — derive the schema rather than write one, state the loop once, put the gate under both doors, and an id authored by an LLM is not a path.
Deleting the module left roughly twenty comments and docstrings across the package and the tests explaining WHY the code is shaped the way it is by naming a thing that no longer exists. Past-tense history is worth keeping — `commit.py` and `ops.py` exist as separate modules because domain logic once sat in a transport layer, and a reader deserves to know that — but the history reads fine without the dead brand name in it. So the reasoning stays and the name goes: "this lived inside the transport layer", "a second surface since removed", "one surface guarded the id and it was not the one people used". SPEC §8 keeps the whole reversal, the token measurements and the four lessons that outlived the server; it just no longer needs the reader to know what the protocol was called. Also untracked `src/knoten.egg-info/`, which was carrying the deleted `knoten-mcp` entrypoint and the `anyio` dependency in committed build metadata. `grep -ri mcp` over the repo now returns nothing.
A simplifier pass mutation-tested every behaviour the four deleted files asserted, and the PR's headline claim turned out to be only half right. "Removing the transport removed no enforcement" is true — every write gate still fails under mutation. But the transport's test files were the ONLY cover for five behaviours, and deleting them deleted the coverage silently, because on a machine without the optional SDK those tests had been skipping all along. Ported, each verified by reintroducing the bug and watching the new test fail: - THE DUPLICATE WARNING, the worst of them. `_similar` carries a 15-line docstring arguing for its exact thresholds — two shared title words, settled claims only, warn but never block — and every one of those four thresholds was free-floating. It is the mechanism that stops the graph holding two answers to one question, which is the failure this project exists to prevent. Also a test that the warning actually reaches the reader; the dict carried it and nothing printed it. - FOUR ATTACHMENT GUARDS: symlink flagged, >1MB flagged, missing source refused with nothing written, unknown node refused. User-facing, not transport-specific. Rewritten against the current API, which returns an `Attached` and raises rather than returning a REJECTED dict. - `killed_by` AND `why_it_died` IN A QUERY RESULT — jointly the answer knoten exists to give, asserted nowhere. - `commit()` NEVER RAISES for a bad candidate. Its docstring still promised it, and that promise matters more now that this is the Python API. - `query`'s OWN RESPONSE CAP. `INDEX_LIMIT` was covered; `QUERY_LIMIT` went with the deleted file. test_skill.py also lost the guarantee it existed for. It bound SKILL.md to a second document; with that gone, nothing checked the skill against the CODE — renaming the `attach` subcommand left the suite green while SKILL.md taught a verb that no longer existed. It now reads the real argparse parser, which is the thing that can drift. Verified: the rename is caught. Its `get`/ALIAS/`knoten[_ ]` machinery existed solely to reconcile a tool name with a CLI verb and is gone. Also: 15 surviving `knoten_commit`-style tool names — worse than the word they replaced, since they read as callables in a repo where `knoten.commit.commit` exists; a comment claiming `frontier` goes through `retrieve()` (it does not, and the anonymised rewrite invented that caller); the id-guard comment that had lost the reason the guard exists; a comparison-table column renamed to "CLI" although it assesses OTHER systems, now "agent-native surface"; two unused imports; and a `KNOTEN_GRAPH` delenv guarding an env var nothing reads any more.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The repo's About section still advertised MCP, and it had stopped being true. Topics and
package keywords are already fixed; this removes the code.
Why
The CLI has been the primary surface since the measurement that demoted MCP. That change
kept the server as the fallback for a client with no shell — not a wrong argument, just an
outweighed one:
knoten --help, and only when asked.ops.pyforever. Every cross-surface drift bugthis project has recorded came from having two:
update's refusal built twice withdifferent keys,
--fieldcoercing2to2.0on one side and not the other,ops.index(query=...)reachable from one surface with no flag on the other.What went
src/knoten/mcp_server.pytest_mcp.py,test_mcp_attach.py,test_tool_descriptions.py[mcp]extra,knoten-mcpentrypoint,anyiodev depThe package is now 2,432 lines, one runtime dependency, no optional ones — verified by
building the wheel and listing it.
Removing the transport removed no enforcement
Worth stating plainly: the gate lives in
commit, not in the surface. Astatus: alivenode with no
kn:survivedGateedge is refused exactly as before. That is what extractingcommit.pyandops.pyout of the server bought.Tests kept their coverage
created-stamping tests called the MCP tool; they now callcommit()directly,which is where the stamp has lived since the extraction.
test_skill.pyexisted to bind SKILL.md's steps to the server's instructions step forstep. With nothing to compare against, it now pins what SKILL.md can be held to alone:
contiguous numbering, every step naming a real command, every command an agent needs
present.
270 passed, 0 skipped, up from266 passed, 6 skipped— every skipwas mcp-gated, so the whole suite now actually runs.
SPEC records the reversal
§8 already documented demoting MCP; it now documents removing it, plus the four things the
server taught that outlived it: derive the schema rather than write one, state the loop
once, put the gate under both doors, and an id authored by an LLM is not a path.