Files
editor/packages/mcp/test-reports/t4-errors/REPORT.md
T
Adrian PerezandClaude Opus 4.7 b37e88cb83 fix(mcp): apply_patch preserves schema-defaulted ids in multi-op batches
SceneBridge.applyPatch now threads the Zod-parsed node through to the
apply phase instead of the raw input. Previously, when a caller sent a
create patch without an `id` field, the schema's objectId default ran
during dry-run parsing but only in `res.data`; the apply phase pushed
the unparsed `p.node` (no id) to the store, so subsequent tools that
walked `level.children` crashed on undefined entries (e.g.
duplicate_level -> cloneLevelSubtree -> extractIdPrefix(undefined)).

Also adds test-reports/ artefacts from live end-to-end testing:
- t1-stdio: 21/21 tools pass via stdio (~106ms)
- t2-http: connect/single-session behaviour (HTTP transport quirk
  documented)
- t3-scenario: 2-bedroom apartment built end-to-end — 12/12 steps
  after this fix (24 final nodes, validate=true, apartment.json
  exported)
- t4-errors: 24/24 invalid-input cases rejected with proper MCP errors
- t5-resources-prompts: 4/4 resources, 3/3 prompts, dev server
  /api/health 200 OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 18:21:00 +02:00

17 KiB

T4 — Error Contract Verification Report

Server: http://localhost:3917/ Run date: 2026-04-18T16:17:28.757Z

Summary

  • PASS: 24
  • WARN: 0
  • FAIL: 0
  • Total cases: 24

Baseline node count: 3 Final node count: 3 (delta=0) Final validation: valid=true, errors=0

Cases

T4-01 — get_node — nonexistent id

Verdict: PASS

Input:

{
  "id": "node_doesnotexist_xyz"
}

Expected: McpError InvalidParams (-32602) "Node not found" OR structured tool error

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Node not found: node_doesnotexist_xyz",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Node not found: node_doesnotexist_xyz"
    }
  ]
}

T4-02 — describe_node — nonexistent id

Verdict: PASS

Input:

{
  "id": "node_missing_123"
}

Expected: McpError InvalidParams (-32602) "Node not found"

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Node not found: node_missing_123",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Node not found: node_missing_123"
    }
  ]
}

T4-03 — find_nodes — invalid type enum "hamster"

Verdict: PASS

Input:

{
  "type": "hamster"
}

Expected: Zod validation error (MCP InvalidParams -32602)

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Input validation error: Invalid arguments for tool find_nodes: [\n  {\n    \"code\": \"invalid_value\",\n    \"values\": [\n      \"site\",\n      \"building\",\n      \"level\",\n      \"wall\",\n      \"fence\",\n      \"zone\",\n      \"slab\",\n      \"ceiling\",\n      \"roof\",\n      \"roof-segment\",\n      \"stair\",\n      \"stair-segment\",\n      \"item\",\n      \"door\",\n      \"window\",\n      \"scan\",\n      \"guide\"\n    ],\n    \"path\": [\n      \"type\"\n    ],\n    \"message\": \"Invalid option: expected one of \\\"site\\\"|\\\"building\\\"|\\\"level\\\"|\\\"wall\\\"|\\\"fence\\\"|\\\"zone\\\"|\\\"slab\\\"|\\\"ceiling\\\"|\\\"roof\\\"|\\\"roof-segment\\\"|\\\"stair\\\"|\\\"stair-segment\\\"|\\\"item\\\"|\\\"door\\\"|\\\"window\\\"|\\\"scan\\\"|\\\"guide\\\"\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Input validation error: Invalid arguments for tool find_nodes: [\n  {\n    \"code\": \"invalid_value\",\n    \"values\": [\n      \"site\",\n      \"building\",\n      \"level\",\n      \"wall\",\n      \"fence\",\n      \"zone\",\n      \"slab\",\n      \"ceiling\",\n      \"roof\",\n      \"roof-segment\",\n      \"stair\",\n      \"stair-segment\",\n      \"item\",\n      \"door\",\n      \"window\",\n      \"scan\",\n      \"guide\"\n    ],\n    \"path\": [\n      \"type\"\n    ],\n    \"message\": \"Invalid option: expected one of \\\"site\\\"|\\\"building\\\"|\\\"level\\\"|\\\"wall\\\"|\\\"fence\\\"|\\\"zone\\\"|\\\"slab\\\"|\\\"ceiling\\\"|\\\"roof\\\"|\\\"roof-segment\\\"|\\\"stair\\\"|\\\"stair-segment\\\"|\\\"item\\\"|\\\"door\\\"|\\\"window\\\"|\\\"scan\\\"|\\\"guide\\\"\"\n  }\n]"
    }
  ]
}

T4-04 — measure — nonexistent fromId

Verdict: PASS

Input:

{
  "fromId": "node_nosuch_f",
  "toId": "site_watn4a0qt2xpgri7"
}

Expected: McpError InvalidParams "Node not found"

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Node not found: node_nosuch_f",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Node not found: node_nosuch_f"
    }
  ]
}

T4-05 — apply_patch — patches with one invalid node (missing type)

Verdict: PASS

Input:

{
  "patches": [
    {
      "op": "create",
      "node": {
        "foo": "bar"
      },
      "parentId": "level_tl2aravmn2u9afft"
    }
  ]
}

Expected: McpError InvalidParams, all-or-nothing rollback (no partial state change)

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: invalid patch: patches[0] create node failed schema: [\n  {\n    \"code\": \"invalid_union\",\n    \"errors\": [],\n    \"note\": \"No matching discriminator\",\n    \"discriminator\": \"type\",\n    \"path\": [\n      \"type\"\n    ],\n    \"message\": \"Invalid input\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: invalid patch: patches[0] create node failed schema: [\n  {\n    \"code\": \"invalid_union\",\n    \"errors\": [],\n    \"note\": \"No matching discriminator\",\n    \"discriminator\": \"type\",\n    \"path\": [\n      \"type\"\n    ],\n    \"message\": \"Invalid input\"\n  }\n]"
    }
  ]
}

T4-06 — apply_patch — delete nonexistent id

Verdict: PASS

Input:

{
  "patches": [
    {
      "op": "delete",
      "id": "node_nonexistent_delete_xyz"
    }
  ]
}

Expected: McpError InvalidParams, no state change

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: invalid patch: patches[0] delete id \"node_nonexistent_delete_xyz\" not found",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: invalid patch: patches[0] delete id \"node_nonexistent_delete_xyz\" not found"
    }
  ]
}

T4-07 — create_level — buildingId is not a building (passed a wall/level/site id)

Verdict: PASS

Input:

{
  "buildingId": "level_tl2aravmn2u9afft",
  "elevation": 0
}

Expected: McpError InvalidParams "expected building"

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Node level_tl2aravmn2u9afft is a level, expected building",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Node level_tl2aravmn2u9afft is a level, expected building"
    }
  ]
}

T4-08 — create_wall — levelId doesn't exist

Verdict: PASS

Input:

{
  "levelId": "level_nosuch_999",
  "start": [
    0,
    0
  ],
  "end": [
    5,
    0
  ]
}

Expected: McpError InvalidParams "Level not found"

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Level not found: level_nosuch_999",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Level not found: level_nosuch_999"
    }
  ]
}

T4-09 — create_wall — start not a tuple

Verdict: PASS

Input:

{
  "levelId": "level_tl2aravmn2u9afft",
  "start": "not-a-tuple",
  "end": [
    5,
    0
  ]
}

Expected: Zod validation error (MCP InvalidParams -32602)

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Input validation error: Invalid arguments for tool create_wall: [\n  {\n    \"expected\": \"tuple\",\n    \"code\": \"invalid_type\",\n    \"path\": [\n      \"start\"\n    ],\n    \"message\": \"Invalid input: expected tuple, received string\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Input validation error: Invalid arguments for tool create_wall: [\n  {\n    \"expected\": \"tuple\",\n    \"code\": \"invalid_type\",\n    \"path\": [\n      \"start\"\n    ],\n    \"message\": \"Invalid input: expected tuple, received string\"\n  }\n]"
    }
  ]
}

T4-10 — place_item — targetNodeId doesn't exist

Verdict: PASS

Input:

{
  "catalogItemId": "chair-1",
  "targetNodeId": "node_nosuch_target",
  "position": [
    0,
    0,
    0
  ]
}

Expected: McpError InvalidParams "Target node not found"

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Target node not found: node_nosuch_target",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Target node not found: node_nosuch_target"
    }
  ]
}

T4-11 — cut_opening — wallId is not a wall

Verdict: PASS

Input:

{
  "wallId": "site_watn4a0qt2xpgri7",
  "type": "door",
  "position": 0.5,
  "width": 0.8,
  "height": 2
}

Expected: McpError InvalidParams "expected wall"

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Node site_watn4a0qt2xpgri7 is a site, expected wall",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Node site_watn4a0qt2xpgri7 is a site, expected wall"
    }
  ]
}

T4-12 — cut_opening — position out of [0,1]

Verdict: PASS

Input:

{
  "wallId": "missing_wall",
  "type": "door",
  "position": 2.5,
  "width": 0.8,
  "height": 2
}

Expected: Zod validation error (MCP InvalidParams) — position must be <= 1

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Input validation error: Invalid arguments for tool cut_opening: [\n  {\n    \"origin\": \"number\",\n    \"code\": \"too_big\",\n    \"maximum\": 1,\n    \"inclusive\": true,\n    \"path\": [\n      \"position\"\n    ],\n    \"message\": \"Too big: expected number to be <=1\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Input validation error: Invalid arguments for tool cut_opening: [\n  {\n    \"origin\": \"number\",\n    \"code\": \"too_big\",\n    \"maximum\": 1,\n    \"inclusive\": true,\n    \"path\": [\n      \"position\"\n    ],\n    \"message\": \"Too big: expected number to be <=1\"\n  }\n]"
    }
  ]
}

T4-13 — set_zone — polygon with < 3 points

Verdict: PASS

Input:

{
  "levelId": "level_tl2aravmn2u9afft",
  "polygon": [
    [
      0,
      0
    ],
    [
      5,
      0
    ]
  ],
  "label": "Tiny"
}

Expected: Zod validation error (MCP InvalidParams) — polygon must have >= 3 points

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Input validation error: Invalid arguments for tool set_zone: [\n  {\n    \"origin\": \"array\",\n    \"code\": \"too_small\",\n    \"minimum\": 3,\n    \"inclusive\": true,\n    \"path\": [\n      \"polygon\"\n    ],\n    \"message\": \"Too small: expected array to have >=3 items\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Input validation error: Invalid arguments for tool set_zone: [\n  {\n    \"origin\": \"array\",\n    \"code\": \"too_small\",\n    \"minimum\": 3,\n    \"inclusive\": true,\n    \"path\": [\n      \"polygon\"\n    ],\n    \"message\": \"Too small: expected array to have >=3 items\"\n  }\n]"
    }
  ]
}

T4-14 — duplicate_level — levelId is not a level

Verdict: PASS

Input:

{
  "levelId": "site_watn4a0qt2xpgri7"
}

Expected: McpError InvalidParams "expected level"

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Node site_watn4a0qt2xpgri7 is a site, expected level",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Node site_watn4a0qt2xpgri7 is a site, expected level"
    }
  ]
}

T4-15 — delete_node — cascade=false with children (target site site_watn4a0qt2xpgri7 children=1)

Verdict: PASS

Input:

{
  "id": "site_watn4a0qt2xpgri7",
  "cascade": false
}

Expected: McpError InvalidRequest "node has children" (no delete)

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32600: node has 2 descendant(s); pass cascade: true to delete recursively",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32600: node has 2 descendant(s); pass cascade: true to delete recursively"
    }
  ]
}

T4-16a — undo — negative steps

Verdict: PASS

Input:

{
  "steps": -1
}

Expected: Zod validation error (MCP InvalidParams) — steps must be positive int

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Input validation error: Invalid arguments for tool undo: [\n  {\n    \"origin\": \"number\",\n    \"code\": \"too_small\",\n    \"minimum\": 0,\n    \"inclusive\": false,\n    \"path\": [\n      \"steps\"\n    ],\n    \"message\": \"Too small: expected number to be >0\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Input validation error: Invalid arguments for tool undo: [\n  {\n    \"origin\": \"number\",\n    \"code\": \"too_small\",\n    \"minimum\": 0,\n    \"inclusive\": false,\n    \"path\": [\n      \"steps\"\n    ],\n    \"message\": \"Too small: expected number to be >0\"\n  }\n]"
    }
  ]
}

T4-16b — redo — negative steps

Verdict: PASS

Input:

{
  "steps": -2
}

Expected: Zod validation error (MCP InvalidParams) — steps must be positive int

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Input validation error: Invalid arguments for tool redo: [\n  {\n    \"origin\": \"number\",\n    \"code\": \"too_small\",\n    \"minimum\": 0,\n    \"inclusive\": false,\n    \"path\": [\n      \"steps\"\n    ],\n    \"message\": \"Too small: expected number to be >0\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Input validation error: Invalid arguments for tool redo: [\n  {\n    \"origin\": \"number\",\n    \"code\": \"too_small\",\n    \"minimum\": 0,\n    \"inclusive\": false,\n    \"path\": [\n      \"steps\"\n    ],\n    \"message\": \"Too small: expected number to be >0\"\n  }\n]"
    }
  ]
}

T4-17 — export_json — pretty='yes' (string not bool)

Verdict: PASS

Input:

{
  "pretty": "yes"
}

Expected: Zod validation error (MCP InvalidParams) — pretty must be boolean

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32602: Input validation error: Invalid arguments for tool export_json: [\n  {\n    \"expected\": \"boolean\",\n    \"code\": \"invalid_type\",\n    \"path\": [\n      \"pretty\"\n    ],\n    \"message\": \"Invalid input: expected boolean, received string\"\n  }\n]",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32602: Input validation error: Invalid arguments for tool export_json: [\n  {\n    \"expected\": \"boolean\",\n    \"code\": \"invalid_type\",\n    \"path\": [\n      \"pretty\"\n    ],\n    \"message\": \"Invalid input: expected boolean, received string\"\n  }\n]"
    }
  ]
}

T4-18 — check_collisions — levelId doesn't exist

Verdict: PASS

Input:

{
  "levelId": "level_nosuch_zzz"
}

Expected: Empty collisions result OR graceful error

Actual:

{
  "kind": "unexpected_success",
  "message": "tool returned successfully with no isError flag",
  "structuredContent": {
    "collisions": []
  },
  "rawContent": [
    {
      "type": "text",
      "text": "{\"collisions\":[]}"
    }
  ]
}

Note: returned empty collisions (graceful)

T4-19 — validate_scene — baseline: no args

Verdict: PASS

Input:

{}

Expected: Success — structured { valid, errors[] }

Actual:

{
  "kind": "unexpected_success",
  "message": "tool returned successfully with no isError flag",
  "structuredContent": {
    "valid": true,
    "errors": []
  },
  "rawContent": [
    {
      "type": "text",
      "text": "{\"valid\":true,\"errors\":[]}"
    }
  ]
}

Note: baseline passed

T4-20a — analyze_floorplan_image — image: '' (empty string)

Verdict: PASS

Input:

{
  "image": ""
}

Expected: Validation error OR sampling_unavailable

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32600: sampling_unavailable",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32600: sampling_unavailable"
    }
  ]
}

T4-20b — analyze_floorplan_image — image: 'not-a-url-or-base64'

Verdict: PASS

Input:

{
  "image": "not-a-url-or-base64"
}

Expected: Validation error OR sampling_unavailable

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32600: sampling_unavailable",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32600: sampling_unavailable"
    }
  ]
}

T4-21a — analyze_room_photo — image: '' (empty string)

Verdict: PASS

Input:

{
  "image": ""
}

Expected: Validation error OR sampling_unavailable

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32600: sampling_unavailable",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32600: sampling_unavailable"
    }
  ]
}

T4-21b — analyze_room_photo — image: 'not-a-url-or-base64'

Verdict: PASS

Input:

{
  "image": "not-a-url-or-base64"
}

Expected: Validation error OR sampling_unavailable

Actual:

{
  "kind": "tool_error",
  "message": "MCP error -32600: sampling_unavailable",
  "rawContent": [
    {
      "type": "text",
      "text": "MCP error -32600: sampling_unavailable"
    }
  ]
}