Skip to main content

Reading — model_summary / layers / spaces / canonical_json

These tools only read. None of them writes anything. Each takes file, the path of the entry .muro, and composes from scratch on every call.

Every piece of output on this page was obtained by actually running it. Absolute paths are shortened to <abs>.

For a spatial model, model_summary includes mode: "spatial" and counts only declared boundaries. canonical_json retains the mode. Space areas describe allocation, not confirmed enclosed floor area.

At a glance​

ToolWhat comes backWhen to call it
model_summaryA summary of the whole buildingFirst, once. It tells you what to read next
layersThe full text of every layerBefore editing, to read the original
spacesThe list of spacesTo enumerate paths, areas and originating layers
canonical_jsonOne composed modelTo hand to a machine, connect outward, or diff

model_summary​

Summary of the building: name, levels, layer composition, zones, reusable assets, placed components, areas, and check counts. Call this first. Pass by:["lease.category"] to also get floor area grouped by those attribute keys

{"name": "model_summary", "arguments": {"file": "<abs>/examples/two-rooms.muro"}}
ArgumentRequiredContents
fileyesThe entry .muro path
bynoAttribute keys to group indoor floor area by. There is no default key: omit it and nothing is grouped
{
 "name": "二室",
 "unit": "mm",
 "layers": [
  "<abs>/examples/two-rooms.muro"
 ],
 "levels": [
  {
   "name": "L1",
   "z": 0,
   "h": 2400,
   "slab": 150
  }
 ],
 "spaces": 3,
 "boundaries": 3,
 "zones": [],
 "assets": [],
 "totalFloorM2": 32.4,
 "semiOutdoorM2": 0,
 "floorsM2": {
  "L1": {
   "rooms": 2,
   "subtotalM2": 32.4
  }
 },
 "check": {
  "errors": 0,
  "warnings": 0
 },
 "hint": "Read layer contents with layers, check with check, and edit with write_layer (check is the gatekeeper). Architectural verdicts come from validate."
}

Pass by and one byM2 entry appears per key.

{"name": "model_summary", "arguments": {"file": "<abs>/examples/office.muro", "by": ["lease.category"]}}
 "byM2": {
  "lease.category": {
   "common": 235.52,
   "rentable": 184.32
  }
 },
FieldContents
name unitThe building name and unit as written
layersAbsolute paths of every composed layer, in strength order — index 0 is the entry (same order as layers)
levelsAscending by z, not declaration order. h and slab appear only when written
spacesHow many spaces (including region-less spaces, exterior and void)
boundariesHow many boundaries after derivation
zonespath, name (only when written), site: true (only for the site zone), areaM2
assetsEach reusable asset's name, kind (door, window or component) and all its attrs
componentPlacementsEach placed component's host space, host area, asset, level, resolved centre and rotation. Absent when none are placed
totalFloorM2Indoor floor area
semiOutdoorM2Semi-outdoor area. Not part of the floor area
floorsM2{rooms, subtotalM2} per level
byM2Indoor floor area by each key in by — the space's own declaration wins, otherwise the deepest zone whose path is a prefix supplies the value. A space carrying no value falls into (unspecified) rather than being dropped. The key is absent when by was not passed
checkJust the {errors, warnings} counts. No messages
sitePolygonsPaths of zones that have a polygon. The key is absent when there are none
originThe written origin — epsg, easting, northing, and elevation/vertical if given. In metres. Absent when not written
azimuthThe written azimuth: the true bearing of +Y, clockwise from true north. Absent means the model has no bearing — it does not mean zero
hintA fixed sentence aimed at the agent

How the counting works​

boundaries is the count after derivation. The default between touching spaces is a wall, so boundaries appear even where no boundary line was written — which means the count can exceed the number of boundary lines layers gives you. For the written side of the composition, use canonical_json.

totalFloorM2 counts indoor only. exterior, void, and anything judged semi-outdoor are excluded; the semi-outdoor share is reported separately in semiOutdoorM2. floorsM2 likewise lists indoor rooms only, and a level with no indoor room gets no key at all.

zones[].areaM2 means two different things. For a site zone (site:1) that has a polygon, it is the polygon's area. Otherwise it is the sum of the indoor floor areas of the spaces under that path. So a site zone whose members are gardens and paths reads 0.

 "zones": [
  {
   "path": "/site",
   "name": "敷地",
   "site": true,
   "areaM2": 0
  },
  {
   "path": "/home",
   "name": "住戸",
   "areaM2": 92.75
  }
 ],

(The zones portion of the same output against examples/house/main.muro. Everything under /site is garden and path, so the indoor sum is 0. To ask for the site area itself, call site — it returns 126.24.)

Run it against an example that has a polygon and the polygon's area comes straight through, along with sitePolygons.

 "zones": [
  {
   "path": "/site",
   "name": "敷地",
   "site": true,
   "areaM2": 1097.8
  },
 "sitePolygons": [
  "/site"
 ],
 "origin": {
  "epsg": 6677,
  "easting": -6250.48,
  "northing": -35720.115,
  "elevation": 3.85,
  "vertical": 6695
 },
 "azimuth": 352.4,

(Excerpts of the same output against examples/tower/main.muro.)

Nothing is derived from origin or azimuth. They are handed back exactly as written, for whatever places the model on a map — koyu itself computes no projection and no meridian convergence. The formula that needs both is on the origin page.


layers​

Returns every layer (.muro file) taking part in the composition, in strength order (later layers are stronger), with its source — this is how you read the original

{"name": "layers", "arguments": {"file": "<abs>/main.muro"}}

file only, required. What comes back is an array of {file, source}, where source is the file's full text, unaltered.

Take these two files.

# main.muro — entry
muro 1.5
name 二層
unit mm

grid X 0 3600 7200
grid Y 0 4500
level L1 0 h:2400 slab:150

import ./L1.muro
# L1.muro
space /L1/a room X1..X2 Y1..Y2 name:居室A
space /L1/b room X2..X3 Y1..Y2 name:居室B
space /out name:外部 outside:1

boundary /L1/b /out t:150
  door w:900 h:2100 edge:S name:玄関

Calling it with main.muro as the entry gives this.

[
 {
  "file": "<abs>/tiny/main.muro",
  "source": "# main.muro — entry\nmuro 1.4\nname 二層\nunit mm\n\ngrid X 0 3600 7200\ngrid Y 0 4500\nlevel L1 0 h:2400 slab:150\n\nimport ./L1.muro\n"
 },
 {
  "file": "<abs>/tiny/L1.muro",
  "source": "# L1.muro\nspace /L1/a room X1..X2 Y1..Y2 name:居室A\nspace /L1/b room X2..X3 Y1..Y2 name:居室B\nspace /out name:外部 outside:1\n\nboundary /L1/b /out t:150\n  door w:900 h:2100 edge:S name:玄関\n"
 }
]

The order is strength order​

The array is in composition strength order — index 0 is the entry and the weakest; later layers are stronger. Nothing is sorted. main.muro comes first above because it is the entry; by absolute path L1.muro would come first.

This order is what decides whose opinion wins, so it is the one an agent needs (the rules of composition). koyu layers prints the same order for a human, and on the same two files it says:

Layers (weakest first — later layers are stronger):
  0	<abs>/tiny/main.muro
  1	<abs>/tiny/L1.muro

Which layer supplied the winning value of which attribute is what koyu layers --attrs shows. That surface does not exist over MCP.

Only composed layers are visible​

A file no import reaches does not come back. A .muro sitting in the directory that nobody imports never appears in layers, and check never looks inside it either.

The entry itself is always included.


spaces​

List of spaces: path, type, level, area, semi-outdoor flag, and originating layer. Optionally filtered by level

{"name": "spaces", "arguments": {"file": "<abs>/examples/two-rooms.muro"}}
ArgumentRequiredContents
fileyesThe entry .muro path
levelnoA level name. Given, it narrows the list to that level
[
 {
  "path": "/L1/a",
  "type": "room",
  "name": "居室A",
  "level": "L1",
  "areaM2": 16.2,
  "semiOutdoor": false,
  "layer": "<abs>/examples/two-rooms.muro"
 },
 {
  "path": "/L1/b",
  "type": "room",
  "name": "居室B",
  "level": "L1",
  "areaM2": 16.2,
  "semiOutdoor": false,
  "layer": "<abs>/examples/two-rooms.muro"
 },
 {
  "path": "/out",
  "name": "外部",
  "outside": true,
  "semiOutdoor": false,
  "layer": "<abs>/examples/two-rooms.muro"
 }
]
FieldContents
pathThe space's path
typeThe room's purpose as written (room, ldk, hall, …). The key is absent when no type was written
nameThe value of name: if there is one, otherwise the last path segment
levelIts level. The key is absent when there is none
areaM2Floor area to wall centrelines. The key is absent for a space with no region
outside voidThe written outside:1 / void:1. Each key is absent when the space does not declare it
semiOutdoorWhether it was judged semi-outdoor
layerAbsolute path of the layer that declared it

/out above shows three absences at once: it declares no type, no region and no level, so type, areaM2 and level are all missing.

Nothing is filtered out. Spaces outside the building, voids and region-less spaces are all in the list. If you want to count what is indoor, read outside, void and semiOutdoor yourself — never the type, which is open vocabulary and decides nothing about floor area. If all you want is the totals, model_summary is faster.

Narrowing by level puts site and building spaces side by side.

[
 {
  "path": "/site/garden",
  "type": "garden",
  "name": "南庭",
  "level": "L1",
  "areaM2": 41.12,
  "semiOutdoor": true,
  "layer": "<abs>/examples/house/site.muro"
 },

(The head of the result for {"file": "<abs>/examples/house/main.muro", "level": "L1"}; the same call returns six entries.)

layer is the key to deciding where an edit goes. The path printed there is exactly what write_layer takes as its layer argument.


canonical_json​

The canonical JSON (machine format — one composed model, byte-stable). The ground for diffing and for external connections

{"name": "canonical_json", "arguments": {"file": "<abs>/examples/two-rooms.muro"}}

file only, required. Returns the composed building as one JSON document a machine can read.

{
 "format": "koyu-canonical/2.0",
 "muro": "1.3",
 "name": "二室",
 "unit": "mm",
 "grid": {
  "X": [
   0,
   3600,
   7200
  ],
  "Y": [
   0,
   4500
  ]
 },
 "levels": {
  "L1": {
   "z": 0,
   "h": 2400,
   "slab": 150
  }
 },

(The head of it; the same call goes on to return spaces and boundaries.)

Only what was written goes in. Derived default walls do not. For the two-room example with no doors written, check answers "boundaries": 1 while canonical_json's boundaries is empty. This is where you count what was actually written.

The indent is one space, so it is not byte-identical to the file koyu json writes — every MCP tool response is written with a one-space indent. Key order and values are the same, so parsing both and comparing agrees. When you need a byte-stable form, use the CLI.

See also​