koyu-mcp
koyu-mcp is the MCP server shipped inside koyu. It is the surface through which an LLM agent reads a building, edits it, and confirms what it did — returning the same derivations the koyu command gives you, as JSON.
What it is
| Executable | koyu-mcp — installed alongside koyu by @kensnzk/koyu |
| Transport | stdio. Line-delimited JSON in on stdin, line-delimited JSON out on stdout |
| Protocol | JSON-RPC 2.0 — written by hand. No MCP SDK is used |
| Runtime dependencies | None |
| Environment variables, authentication, network | None |
| Requires | Node 22 or later |
| Tools | Listed below |
serverInfo announces both, separately, because they are separate promises: version is the package's implementation version, and muro is the language ledger it reads and writes. The two move independently, and until they were both stated an agent had no way to ask which language this build spoke.
It is stateless
Every tool takes a required file argument. file is the path of the entry .muro; for a building split into layers with import, pass the base layer's file.
One call goes: resolve the path, read the entry, follow the imports, compose, answer the question, forget. There is no session, no open document, no cache, no undo stack. Call it twice with the same arguments and it composes twice.
So the server never holds a version of the building that the disk does not have. The original is the filesystem, and the history is git's.
Re-composing is cheap. The bundled nine-layer tower takes about 6 ms; the largest bundled example (11 layers, 1,808 spaces, 141,448.56 m² of floor) stays under 100 ms.
Registering it
claude mcp add koyu -- npx -p @kensnzk/koyu koyu-mcp
Per-client instructions, and how to register a development build (node /path/to/koyu/dist/mcp.js), are on Registering it with a client.
Pass the entry as an absolute path. A relative path is resolved against the server process's working directory, and which directory a client starts the server in is up to that client. Get it wrong and you get Cannot read file:.
The standard loop
Shape the agent's work like git's.
model_summary → layers → write_layer → check ──errors──→ fix, write_layer again
│
└──green──→ doors / light / site confirm the consequences
- Grasp the building with
model_summary. Layer composition, levels, zones, assets, areas and thecheckcounts come back in one call, which tells you which file to read next. - Read the original with
layers. The full text of every composed layer comes back. - Write with
write_layer. A whole-file replacement, not a diff. The response carries thecheckresult from immediately after the write, so editing and verifying take one round trip. checkis the gatekeeper. If errors come back, fix them and write again.- Confirm the consequences with
doors/light/site. Move a partition and circulation and daylight change; change an area and coverage changes.checkwas not looking at any of that.
Tools
| Tool | Arguments | What comes back |
|---|---|---|
model_summary | file | Name, unit, layers, levels, zones, assets, areas, check counts |
layers | file | {file, source} for every composed layer |
spaces | file, level? | The spaces — path, type, name, level, area, semi-outdoor flag, originating layer |
canonical_json | file | The canonical JSON (one composed model) |
write_layer | file, layer, content | Replaces a layer wholesale. Returns written plus the check from right after |
new_uids | file, count? | Fresh persistent identity tokens |
check | file | Structural consistency — ok, errors/warnings, diagnostics |
validate | file profile asOf | Architectural verdicts under a named profile — a full AssessmentReport |
doors | file, from, to | The fewest-doors route, or {unreachable: true} |
light | file | Floor area and effective window area for every space written daylight:1 |
site | file | Site area, road frontage, coverage ratio, floor-area ratio |
plan_svg | file, level | The plan SVG for that level, as a string |
section_svg | file, at, look? | The section cut at a grid reference, as a string |
elevation_svg | file, face | The elevation of one face, as a string |
? marks an optional argument. The JSON-RPC surface — what initialize announces, the shape of a tools/call result, how errors come back — is on The protocol.
Do not conflate the two greens
A green check and a usable building are different things. The default between touching spaces is a wall, so a two-storey building that declares no door at all stays green in check while being perfectly sealed. check says only that what is written holds together as data; architectural soundness is what validate says, separately.
They differ down to the type. check's diagnostics carry {code, severity}; validate returns an AssessmentReport whose findings carry {rule, level}. The spellings differ and the two cannot be concatenated. Do not claim a building works because check is green.
validate also refuses to guess its grounds: profile and asOf are required, and a call without them is rejected as invalid arguments before anything is judged. And an empty findings is not by itself a pass — read summary.state, which is complete only when nothing was left indeterminate and nothing errored.
Commit before you let it write
write_layer replaces a file wholesale and has no undo. The server stores not one previous version. Branching, review and rollback are all git's job.
Commit your .muro files before starting work that lets an agent write. The full blast radius is on Writing — write_layer / new_uids.
See also
- Registering it with a client — Claude Code,
.mcp.json, Desktop, any MCP client - The protocol — the JSON-RPC surface and how errors come back
- Reading —
model_summary/layers/spaces/canonical_json - Writing —
write_layer/new_uids, and the safety contract - Verifying —
check/validate - Asking —
doors/light/site/plan_svg - The koyu command — the same derivations by hand
- .muro reference — the notation you are asking the agent to write