An agent reads an old write-up and concludes that a character port is nearly done. The current build still fails a required check. Both facts are in the repository; the convincing prose was simply easier to find than the blocker.
That became a recurring problem as my CPS-2 work spread across character ports, shared engine changes, emulator scripts, captures, and build receipts. Re-reading more files made the conversation longer without reliably finding the current answer. I built a small MCP service to answer bounded questions against pinned artifacts: what is this candidate, what has been observed, and what still blocks it?
The service can retrieve those answers. It has no tool for launching an emulator, patching a ROM, or publishing a claim. Those operations belong to a separate workflow with its own checks.
The implementation and dated updates follow. The current tool count is 21; the twelve-tool context design and its retrieval measurements are preserved below, followed by the September addition of nine port-analysis tools. A legacy Codex connectivity check is on record, but the current client acceptance matrix and paired token/task-quality trials remain open.
Context is not authority
The most important design decision is what the MCP server cannot do.
Context can inform execution without inheriting it
Pinned facts, not a transcript
Nine narrow verbs
Route the smallest answer
Codex · Claude Code · OpenClaw · local-model hosts
Authority stays explicit
A useful answer can recommend the next gate. It cannot run it.
- AGuarded runtimeexact hash · clean state · 1×
- BHashed receiptobservation · provenance · result
- CHuman-reviewed claimpublish only what the receipt proves
Start from exact catalogs, receipts, ledgers, hashes, blocker fields, and role definitions. Historical prose can explain a fact, but it cannot silently replace the current pin.
Twelve schema-closed tools answer one bounded question at a time. Oversized selections return a digest and preview instead of spilling the estate into the prompt.
The host gives the payload to one scoped position. A handoff carries mission, constraints, current context, and a next-tool sequence—never the entire conversation.
Runtime needs a separate request, an exact candidate, clean state, ordinary controls, and a guarded runner. The context server exposes no edge that can launch or mutate it.
A claim becomes public only after a hashed receipt supports it and a human reviews the scope. Unknown, hard-locked, and diagnostic-only states remain visible.
The MCP process has no shell tool, no subprocess path, no network tool, no emulator method, and no file-write method. Its annotations say read-only, but the implementation also removes the capabilities—because a safety annotation is guidance to a client, while the absence of mutation code is an architectural boundary.
The runtime remains a different system. Evidence execution requires an isolated, pinned Python bootstrap; an exact candidate hash; a separately authorized request; a pinned MAME binary and QSound device image; private ROM staging; clean config and NVRAM; sound on; throttled 1× speed; ordinary input; and an exclusive receipt. Diagnostic mode remains permanently zero-credit.
This is the same principle I use in production AI platforms: retrieval may inform a decision, but it should not quietly grant the authority to execute that decision.
The OS loader, kernel, Python's dependent libraries and standard library, and the invoking process remain trusted. -I -B -S, bounded non-following JSON reads, exact child environments, and pre/post hashes reduce ordinary contamination and drift; they do not claim to defeat an adversarial same-user swap-and-restore race or an already-compromised loader.
The twelve-tool surface
Large tool catalogs often hurt selection. I previously wrote about solving that with a discovery surface in Stop Putting the Whole Tool Catalog in the Prompt. Here, the right answer was smaller: twelve verbs with minimal overlap.
tool surface
Twelve verbs, grouped by the decision they support
The segments add to the entire public tool surface. No execution or arbitrary-path segment exists.
- read-only context plane12 tools3332
Trust and selection: manifest, ranked search, matrix. Routing: snapshot, brief, blockers. Artifacts: lookup, one JSON Pointer, preflight. Domain: list and capped retrieval. Handoff: one compact role packet.
The normal flow is manifest once, search for a compact ranked slice, inspect one port and its evidence matrix, and read one exact artifact field only when the decision needs it. Whole-tree ingestion becomes an exception instead of the default.
There is another subtle rule: a hash-disabled preflight cannot say PASS. It reports that the file is present but the hash was not computed. “I did not check” must remain a first-class state; otherwise performance shortcuts become false evidence.
What Rogue and Cable changed
Rogue supplied the strongest reusable controls in the estate: descriptor-bound O_NOFOLLOW reads, before/open/after identity checks, strict rejection of duplicate JSON keys and non-finite numbers, immutable source snapshots, exclusive outputs, and authority fields that default to false. Those controls now live in the shared read path instead of remaining a Rogue-only advantage.
Cable exposed the semantic failure that mattered most. Its old ledger command could print PASS (0/52 closed) because it validated the shape of the file, not whether any gameplay lane had actually closed. Historical receipts could also masquerade as runtime evidence without exact candidate, contract, lane, and retained-artifact bindings.
The repaired ledger now separates ledger structure: PASS from acceptance: BLOCKED; an explicit acceptance check exits nonzero at 0/52. Closing a lane requires an allowed evidence class, an exact candidate SHA-256, the exact contract and lane, retained-artifact hashes, and a non-promoting receipt. The MCP evidence_matrix reads that pinned state and reports all 52 blockers. It does not turn a static audit into gameplay proof.
Cable is not on Rogue's gameplay level yet. The current result is a better control plane: no sealed playable candidate, no parity percentage, and no release authority. The next work is to consolidate the builder, finish the source denominator, retain an exact candidate, and execute ordinary-control source/target gates—not to rename structural survival as completion.
What recent MCP research changed—and what is still missing
The official MCP tool contract supports declared outputSchema and matching structuredContent; all twelve tools now use that shape while preserving the legacy text result for older hosts. See the MCP 2025-06-18 tool specification.
Recent primary research also makes a useful distinction between exposing tools and retrieving the right tools. MCP-Zero explores hierarchical server-to-tool retrieval and proactive tool requests; ToolRet and MassTool show why selection quality needs its own benchmark. MCP-Bench and MCP-AgentBench push evaluation toward multi-step trajectories and task outcomes. The MCP Security Benchmark, Securing MCP, and a recent STRIDE/DREAD threat model broaden the attack surface beyond path traversal to tool poisoning, shadowing, response impersonation, rug pulls, and decision-trace abuse.
research-to-control
Implemented controls are smaller than the research agenda
A verified cell means the named local control exists—not that this implementation passes the paper's benchmark.
| route | implemented here | measured here | still open |
|---|---|---|---|
| tool contract | verifiedclosed inputs, object outputs, structuredContent | verified12-tool protocol and error regressions | pendingpagination and native 2026 discovery |
| tool retrieval | bridgeddeterministic port-first ranked search | verified3/3 fixed queries at eight | pendingsemantic, fuzzy, multi-hop, cross-server routing |
| agent evaluation | bridgedstable IDs, typed failures, compact handoffs | pendingno paired model task benchmark yet | pendingtrajectory traces and outcome scoring |
| security | verifiedstrict reads, immutable snapshot, data-only results, zero mutation tools | bridgedhostile path/JSON and authority tests | pendingsigned manifests, semantic vetting, collision and rug-pull suite |
Download MVC_HARNESS context plane v2 · SHA-256 1b8e4055c7087b0d08c232d82ce2727eb7317ebae628e5fd4f1700e09aa1d3b9 · 38,961 bytes.
The ZIP contains the dependency-free server, strict read library, a synthetic one-port catalog, a synthetic topic pack, two read-only port-agent profiles for each documented host format, configuration examples, internal SHA-256 inventory, and the seven-test public fixture. It contains no ROM, candidate archive, private evidence tree, workstation path, personal record, runtime authority, or gameplay claim.
Agent positions instead of one giant prompt
The server exports four scoped handoff positions: parity analysis, artifact audit, bounded-domain facilitation, and publication editing. The first finds the smallest evidence-backed blocker for one port. The second checks exact pins and targeted receipt fields. The third restricts a second content type to one capped selection. The fourth drafts only from compact facts while preserving hard-lock and zero-credit language.
Every packet carries a mission, constraints, and next-tool sequence. When a port_id or topic_id is supplied, it also carries compact current context. It never carries mutation authority. That makes the packet useful to a new chat, a subagent, or a different MCP host without copying the entire conversation.
The receiving position gets a mission, hard constraints, current binding facts, and a short next-tool route. It does not inherit ambient conversation history or any capability to mutate the evidence estate.
This is where token reduction and coverage reinforce each other. A compact packet is cheaper than a transcript, but it is also easier to audit. The receiving agent sees the binding state—HARD_LOCKED, diagnostic-only, static contract—not just the most persuasive paragraph from yesterday’s work.
What is measured—and what is not
I froze one exact reverse-engineering source set and measured the compact JSON returned by the smallest corresponding tool sequence:
bounded retrieval
The same Dual Rogue status question carries 35,303 fewer characters
One frozen source set compared with the serialized snapshot → brief → blockers response.
- Dual Rogue status context-35,303 chars / -81.1%whole-file set43,547bounded tools8,244
A 5.28× source-to-payload character ratio. This is not yet a token, cost, or task-quality claim.
progressive retrieval
The context grows only when the decision needs another tool
Cumulative serialized characters across the exact snapshot → brief → blockers sequence, compared with the frozen whole-file source set.
response envelope
The measured Rogue status payload stays inside the hard cap
The cap is enforced on serialized output; unused space remains unused rather than being filled with nearby history.
- snapshot → brief → blockers12,000 chars8,2443,756
8,244 serialized characters returned inside a 12,000-character ceiling. This is a character measurement, not a token estimate.
These timings exclude MCP transport, model inference, filesystem cold cache, and emulator work. They demonstrate that repeated context calls no longer rescan the estate; they do not establish end-to-end agent speed.
The stronger experiment is still open: run identical tasks with and without MCP on the same pinned client and model, then compare uncached and cached tokens, latency, blocker recall, citation precision, false completion claims, and tool-selection accuracy.
The underlying architecture lesson is the distinction between guidance and guarantees. A project instruction can ask an agent to be careful. A denied capability, pre-action guard, exact input schema, or fail-closed state makes a class of unsafe behavior unavailable.
One server, several hosts
MCP standardizes the connection between an AI application and external tools; the model itself is not necessarily the MCP client. The host discovers tools, sends calls, and returns results to the model. That distinction matters when local models enter the picture. The MCP documentation describes this client/server architecture, and the server uses local STDIO transport.
flowchart LR
subgraph H[AI application / MCP host]
C[Codex]
D[Claude Code]
O[OpenClaw]
L[Local-model host]
end
C -->|STDIO| M
D -->|STDIO| M
O -->|STDIO| M
L -->|STDIO| M
subgraph P[Read-only context plane]
M[MVC_HARNESS MCP]
K[(Port catalog)]
A[(Indexed artifacts)]
R[Role handoff packet]
M --> K
M --> A
M --> R
end
M -. no execution edge .- X[Guarded runtime]
X --> E[Hashed evidence receipt]
E --> U[Human-reviewed claim]The solid edges are available context reads. The dotted edge is the point: no MCP method crosses it. Runtime and publication remain separate, deliberately authorized workflows.
This implementation supports the initialize-era protocol through MCP 2025-06-18. A dual-era STDIO client can probe the 2026-07-28 server/discover method, receive exact method-not-found, and fall back; regression tests cover that exchange. Native 2026-07-28 support still requires real discovery, per-request and result metadata, sessionless dispatch, adversarial tests, and pinned client receipts—not a version string changed in one array. The official 2026-07-28 changelog and server/discover contract define that next gate.
host acceptance
Only the historical nine-tool Codex surface has a pinned live receipt
A configuration or recipe is not client support. Each cell reports only the named gate.
| route | project recipe | current 12-tool discovery | bounded calls | seven-check matrix |
|---|---|---|---|---|
| Codex CLI 0.149.0-alpha.4.1 | verifiedtemplate plus equivalent ephemeral override | pendinghistorical 9/9 passed; current 12 pending | verified2 calls returned bounded JSON | pendingpartial acceptance |
| Claude Code | verifiedproject profile parses | pending | pending | pending |
| Cowork | pendingauthenticated remote adapter required | pending | pending | pending |
| Qwen Code | verifiedlegacy negotiation pinned | pending | pending | pending |
| OpenCode V2 | verifiedlocal profile parses | pending | pending | pending |
| OpenClaw | bridgedregistry recipe only | pendingclient not installed | pending | pending |
Codex
Codex supports local STDIO servers through mcp_servers configuration. This public example binds an absolute working directory, launch command, timeouts, and only the ten reverse-engineering-facing verbs. The configuration shape follows the official Codex MCP guide; it does not silently install itself into a user profile.
[mcp_servers.mvc_harness]
command = "python3"
args = ["/path/to/MVC_HARNESS/mcp/server.py", "--root", "/path/to/MVC_HARNESS"]
cwd = "/path/to/MVC_HARNESS"
enabled = true
enabled_tools = [
"server_manifest", "context_search", "evidence_matrix",
"harness_snapshot", "port_brief", "blocker_summary",
"artifact_lookup", "artifact_read_json", "preflight_status",
"handoff_packet"
]I previously ran a pinned, ephemeral Codex CLI 0.149.0-alpha.4.1 connectivity check in a read-only sandbox. It discovered the then-current nine server tools, returned the then-current nine-port snapshot, and completed a second bounded read. That historical receipt proves the client could use that legacy-era STDIO surface; it does not establish acceptance of the current 21-tool surface. Current discovery, manifest/snapshot continuity, annotations, typed errors, native 2026-07-28 negotiation, and automatic project loading remain open.
Claude Code and Cowork
Claude Code documents local STDIO servers and project scope; project entries live in .mcp.json and require workspace trust/approval. A portable setup uses the server’s absolute script path so it does not depend on the client’s current directory. See the official Claude Code MCP guide.
claude mcp add --transport stdio --scope project mvc-harness -- \
python3 /path/to/MVC_HARNESS/mcp/server.py \
--root /path/to/MVC_HARNESS
claude mcp get mvc-harnessI do not assume Cowork loads a Claude Code project's .mcp.json. Anthropic's documented Cowork path is an authenticated remote MCP connector; local MCP applies to Claude Desktop and Claude Code. Its connector deployment guidance is therefore the boundary for the remote phase. Until that adapter passes the matrix, Cowork receives the compact handoff_packet, not a false direct-integration claim.
Protocol compatibility is not the same as a completed client acceptance test. I mark a host verified only after a pinned client starts the process, lists all twelve tools, binds server_manifest to the targeted result's snapshot, preserves safety annotations, and returns a bounded typed error correctly. Claude Code and Cowork remain pending on this workstation.
Qwen Code and OpenCode
Qwen Code and OpenCode now have project-scoped candidates rather than prose-only recipes. Qwen's profile fixes protocol negotiation to legacy, keeps server trust off, exposes exactly the twelve context tools, and exports four project subagents. OpenCode V2 has a local-server profile plus four subagents whose built-in file, shell, web, edit, skill, and child-agent actions are denied. Both still require pinned live client receipts before I call them supported.
The Qwen profile follows its current MCP and subagent contracts. The OpenCode examples follow its V2 MCP and agent schemas. Local configuration narrows the available surface; it does not prove framing, annotations, error behavior, or cleanup in a client that is not installed here.
OpenClaw and adjacent agent hosts
Current OpenClaw documentation exposes an outbound MCP registry with add, set, doctor, and probe; probe is the live connection proof. The least ambiguous configuration is a saved JSON definition followed by a probe. See OpenClaw’s MCP CLI documentation. openclaw mcp serve is deliberately not used here because that exposes OpenClaw conversations; the harness belongs in the outbound registry.
openclaw mcp set mvc-harness \
'{"command":"python3","args":["-m","mcp.server","--root","/path/to/MVC_HARNESS"],"cwd":"/path/to/MVC_HARNESS"}'
openclaw mcp doctor mvc-harness --probeThe same rule applies to “systems similar to OpenClaw”: accepting an MCP-shaped config is not enough. Tool discovery, annotations, framing, error behavior, and process cleanup all need a real smoke test.
Local models on the hardware I actually target
As of August 30, 2026, the exact models requested for this plan are real and available in Ollama. Google positions Gemma 4’s 12B, 26B, and 31B variants for personal computers and agentic workflows; Ollama currently lists their default packaged sizes at approximately 7.6GB, 19GB, and 20GB. Qwen’s official repository lists Qwen3.8-27B as released on August 14, while Ollama packages it at approximately 18GB with tool and thinking support. Sources: Gemma 4, Gemma 4 tags on Ollama, Qwen3.8 official repository, and Qwen3.8 on Ollama.
- Gemma 4 12B7.6GB
roomier starting point when context length matters
- Qwen3.8 27B18GB
tighter fit; measure KV cache and actual offload
- Gemma 4 26B19GB
weights fit the nominal budget more tightly
- Gemma 4 31B20GB
better suited to the 64GB unified-memory profile first
| Host | Start here | Higher-quality profile | Why |
|---|---|---|---|
| Ubuntu + Radeon 7900 XTX 24GB | gemma4:12b | qwen3.8:27b or gemma4:26b, then measure context headroom | The 12B package leaves more room for KV cache; the 18–19GB packages fit the weight budget more tightly |
| M1 Max + 64GB unified memory | qwen3.8:27b-mlx | gemma4:31b-mlx | More unified-memory headroom supports the larger dense profiles and longer experiments |
The RX 7900 XTX has 24GB of VRAM and appears in Ollama’s supported Linux Radeon list; current Ollama documentation requires ROCm 7 on Linux. Ollama supports Apple silicon through Metal and now offers MLX-tagged packages for these models. Sources: AMD’s 7900 XTX specifications, Ollama GPU support, and Ollama macOS support.
Context is the part most likely to break a seemingly good fit. Ollama’s current defaults assign 32K context to 24–48GiB VRAM, while recommending at least 64K for agents and coding tools—and explicitly warning that larger context consumes more memory. Set one model, one request, a conservative context, and inspect the actual offload before increasing it.
ollama pull qwen3.8:27b
OLLAMA_CONTEXT_LENGTH=16384 ollama serve
# In another terminal after a request:
ollama psOn the 64GB Mac, I would begin at 32K or 64K and measure. On the 24GB Radeon, I would begin at 16K for the 27B/26B class and prefer the 12B model when long context matters more than maximum model quality. The exact deployment decision belongs in a measured profile, not a blog-table promise. Ollama’s context-length guide is the source of truth for the runtime controls.
Ollama can launch agent applications with these models—including OpenClaw—but Ollama alone is not the MCP host. The agent application must still discover and call the MVC tools.
The useful boundary
The server makes current blockers easier to retrieve without giving a summary the power to override them. That is a small but useful property in a lab where a stale receipt or a convincing explanation can otherwise stand in for a test that never ran.
It does not make the surrounding agent session read-only. A client may have its own shell and file tools, and those require separate controls. It also does not prove a port works: static analysis, diagnostic observations, and ordinary-control gameplay remain different kinds of evidence.
What I will measure next
The next iteration needs evidence of its own:
- Record baseline tokens and latency for repeated “where is this port stuck?” tasks without MCP.
- Repeat with snapshot → brief → targeted artifact retrieval.
- Measure blocker recall, false parity claims, tool-selection accuracy, and handoff completeness.
- Run the same traces through Codex, Claude Code, OpenClaw, Gemma 4, and Qwen3.8 hosts.
- Promote only client/model profiles that pass the same acceptance matrix.
The success metric is not merely fewer tokens. It is more exact coverage per token, with fewer unsupported claims.
A context service that cannot launch an emulator or write a ROM is a guarantee by construction — the tool simply does not exist to be misused. But the service does not run alone. A full coding-agent session in the same repository still holds a shell and a file writer, and the rules that kept it honest — never launch the emulator ad hoc, never mutate a sealed ledger or a base ROM — lived only in a policy document the model was asked to follow. Asked, not required. That is the gap between guidance and a guarantee, and it is the one an architecture review is supposed to catch.
So the rules moved out of prose and into two independent enforcement layers. A settings deny-list rejects configured emulator commands and writes to base ROMs. A pre-tool hook checks the same actions before they run, and — the part that matters for an evidence system — it refuses with the exact authority gate that is missing rather than a vague denial, so the session is told how to do the thing correctly under a pinned runtime authority instead of simply being stopped. The two layers have different jobs: the permission rule restricts the action, and the hook explains the missing prerequisite and the sanctioned path. Eight enforcement cases and a fail-open guard-can't-brick-the-session test lock the behaviour, and the whole repository still turns over green at 102 tests.
These controls enforce more than a policy paragraph alone, but their reach is bounded by the host permission model, hook coverage, and the tested invocation paths. The regression cases establish those specific behaviors, not universal confinement of an agent that has other tools or can change its own configuration.
Editor's update — September 1, 2026: the harness gets an engineering half
Everything above is a governance harness: contracts, pins, receipts, ledgers, a tier ladder, and a context plane that cannot execute. On September 1 an estate audit measured what that harness does not do, and the answer was blunt: it proves what a port is and offers nothing for building one. The numbers came from the connected repositories, not from memory.
The Cable port made the failure concrete. The donor build renders because it overwrites Ryu's tables in place and inherits a data path; the native build gave Cable his own id, finished the slot machinery, and had no data path at all—its directory rows still pointed at Spider-Man. Two structural facts nobody had measured: stock MAME cannot draw Cable at any address, because his art needs tile-code bit 18 and only the 64 MB core has it (the whole stock ROM holds 5,649 blank tiles against his 14,095); and his data fills the base program's free space almost exactly (244,655 seatable bytes against 234,899 used). Twice in one working session "the missing link" was declared, and it was wrong both times, because the reasoning ran forward from partial structure instead of measuring the artifact that already works. The tier ladder catches over-claiming after the fact; nothing stopped the loop before the build.
So the first thing that shipped was not code. Seven standing rules went into every connected repository's project instructions the same day:
- Measure the working artifact before theorising about the broken one; if a sibling build shows the behaviour, diff it first.
- No "missing link" claims until the screen changes. A fix is necessary until a human sees the effect; only then may it be called sufficient.
- One builder per repo, parameterised.
build_*_vN.pychains are banned; a build is a config plus the shared kernel. - Validate every tool against a known-good control before trusting it, and discard it when it fails. A scanner that finds none of an expected shape is a bug until proven otherwise.
- Census before chasing: table extents, free space (claimed in an allocation ledger) and references are measured before anything is seated.
- Scope and evidence tier in the same sentence as any PASS. Cloud-rig and unpinned runs are
diagnostic_*; only pinned ordinary-input windowed runs close lanes. - Read the facts registry before starting any port task. Copy no script from another repo.
Then the kernel. lib/port/ is the half of the harness that helps build a port, under
one rule: if two repos need it, it lives here with a receipt. Every module carries the
control it is validated against, and those controls run against the real ROMs.
engineering kernel
Ten kernel modules, then four more the same evening
Each segment is a module with its own control; the second row is kernel v3, built against the binary-analysis literature.
- P1–P10 (morning)10 modules3322
25 registry facts (including the contradicted and rejected ones so nobody re-derives them); the ledger refuses overlapping space claims; promote reproduces every hand measurement of the donor build; a new ordinary_control_fbneo64 tier lets 64 MB-core evidence close lanes honestly.
- kernel v3 (evening)4 modules22
Decoder validated against capstone on 2,158 instructions with zero disagreements; references classified by evidence (ddisasm), never by value range—the old value-range scan that reported 2.9% self-containment is on record as REJECTED; the relocation gate proves the moved code's instruction stream is unchanged modulo the patched operands.
The MCP server moved to 1.3.0 and became dual-era—server/discover for
2026-07-28 clients, initialize for everyone else—and exposes the kernel as nine
read-only tools beside the twelve context tools: port_facts, port_games,
port_directory_rows, port_capacity, port_codeblock_survey,
port_merge_classify, port_rebase_plan, port_idtables, and port_ledger. They
measure and plan; they still cannot write a byte, launch an emulator, or promote a
receipt. The context plane's twelve verbs are unchanged.
The kernel's first run produced CABLE_MVC M1 v3: the donor's moveset relocated onto the native seat with a deterministic build, zero clean pc-relative blockers, 521 relocation patches, and a gate that proves the moved instruction stream is identical. That is necessary, and under rule 2 it is only necessary. The trace-diff oracle's first comparison returned no overlapping frames because one side never went live, so it has not yet produced its frame number; until a windowed run on the 64 MB core says otherwise, the native Cable is a static PASS and nothing more.
The same rules governed this week's DHC v6 release: a cloud regression run was diagnostic only, a v5 control run isolated a hook-placement fault before anyone called the fix sufficient, and only the pinned rig closed the lanes.