> ## Documentation Index
> Fetch the complete documentation index at: https://docs.summerengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP tools: run and test

> Run and test tools of the Summer Engine MCP. Play the game, drive input, read the runtime, take screenshots and read errors. For each tool: what it does, what it needs, inputs, output and an example.

Play the game, drive input, read the runtime, take screenshots and read errors. 34 tools. What each column means, and how to connect: [MCP tools reference](/mcp/tools-reference).

<Note>
  Generated from the tools' own definitions: the `summer-engine` npm package `3.3.0` (its MCP server's `tools/list`, `library/tools/*/resource.yaml` and `registry/generated/index.json` in [SummerEngine/summer](https://github.com/SummerEngine/summer)) and the hosted Summer Engine MCP (`src/lib/mcp/hosted` at commit `8c4351ee30`). If something here is wrong, the tool definition is wrong.
</Note>

| Tool | Needs | What it does |
| - | - | - |
| [`summer_camera_bookmark`](#summer_camera_bookmark) | Engine | Save, list, or delete named camera viewpoints for the edited 3D scene. |
| [`summer_clear_console`](#summer_clear_console) | Engine | Clear the editor's Output panel. |
| [`summer_create_debug_report`](#summer_create_debug_report) | Engine | Create a support-ready Markdown report for /summer debug. |
| [`summer_debug_views`](#summer_debug_views) | Engine | One pose rendered as a grid of debug views next to the beauty pass: beauty, lighting (light only: direction, pools, dead-dark areas), unshaded (albedo: texture quality, value grouping), normals (world-space, x red y… |
| [`summer_frame_nodes`](#summer_frame_nodes) | Engine | Frame one or more nodes with a camera fitted to their WORLD bounds and render it with the scene's REAL WorldEnvironment, lights, fog and tonemap (unlike summer\_screenshot nodePath, which only works with the flat… |
| [`summer_frame_shot`](#summer_frame_shot) | Engine | Smart framing: find good camera poses for a shot type automatically, measured in-engine against the real geometry. |
| [`summer_game_control`](#summer_game_control) | Engine | Control the clock of the RUNNING game and list instances. |
| [`summer_game_input`](#summer_game_input) | Engine | Drive the RUNNING game's input like a player would. |
| [`summer_game_probe`](#summer_game_probe) | Engine | State AND pixels of ONE frame of the RUNNING game, atomically (GameProbe): the live scene tree (tree \{path, depth, limit}), up to 64 property reads (props \['/root/Main/Player:position', '/root/Main/HUD/Health:value'])… |
| [`summer_get_board`](#summer_get_board) | Signed in | Read the person's approved planning board for this game: the look (palette, description, picture), the characters, the place, the first-minute storyboard and the cards they picked, with a version that changes when the… |
| [`summer_get_console`](#summer_get_console) | Engine | Read recent messages from the editor's Output panel (print() output, editor-side warnings and errors). |
| [`summer_get_debugger_errors`](#summer_get_debugger_errors) | Engine | Read runtime errors from the debugger. |
| [`summer_get_debugger_warnings`](#summer_get_debugger_warnings) | Engine | Read runtime warnings from the debugger panel. |
| [`summer_get_diagnostics`](#summer_get_diagnostics) | Engine | Quick overview of all errors and warnings from the editor console, the runtime debugger, and script errors together. |
| [`summer_get_runtime_tree`](#summer_get_runtime_tree) | Engine | Scene tree of the RUNNING GAME — live runtime state, not the edited scene. |
| [`summer_get_script_errors`](#summer_get_script_errors) | Engine | Check a GDScript file for parse/compile errors without running the game. |
| [`summer_inspect_runtime_node`](#summer_inspect_runtime_node) | Engine | Live properties of ONE node in the RUNNING GAME: \{node: \{path, class, properties, children\_names}} with a curated common-property set (transform, visibility, physics state, ...). |
| [`summer_is_running`](#summer_is_running) | Engine | Check if the game is currently running. |
| [`summer_play`](#summer_play) | Engine | Start running the game in the engine. |
| [`summer_recent_events`](#summer_recent_events) | Engine | Read the newest engine events in ONE zero-wait poll — what just happened (saves, plays, op receipts, script errors, imports, selection) and, above all, the CURSOR: its next\_seq is the `since` to hand… |
| [`summer_runtime_animate`](#summer_runtime_animate) | Engine | Drive and read animation in the RUNNING game. |
| [`summer_runtime_call`](#summer_runtime_call) | Engine | Call ONE method on a node in the RUNNING game and get its return value: 'take\_damage' \[25], 'get\_velocity', 'has\_method', 'start\_wave' \[3]. |
| [`summer_runtime_set`](#summer_runtime_set) | Engine | Set ONE property on a node in the RUNNING game — the live object, never the scene file (nothing is saved; the change dies with the run). |
| [`summer_runtime_spawn`](#summer_runtime_spawn) | Engine | Spawn a PackedScene into the RUNNING game (action:'spawn' — SpawnRuntimeScene) or free a live node (action:'free' — FreeRuntimeNode). |
| [`summer_scene_audit`](#summer_scene_audit) | Engine | Audit a 3D scene in one fast, read-only call: every node and subnode is walked and likely visual and placement problems come back as a short list, sorted by severity, so you know exactly where to look. |
| [`summer_screenshot`](#summer_screenshot) | Engine | Capture a frame from Summer Engine and return it as an image you can look at directly. |
| [`summer_shot_sheet`](#summer_shot_sheet) | Engine | Render several bookmarks and/or explicit poses into ONE labelled grid image in a single call: same tile size, same view, real lighting. |
| [`summer_snapshot_diff`](#summer_snapshot_diff) | Engine | Diff two world snapshots into exactly what changed: added/removed node paths, changed nodes with the fields that moved (pos, scale, material fingerprint, ...), and per-class count deltas. |
| [`summer_stop`](#summer_stop) | Engine | Stop the running game. |
| [`summer_ui_screenshot`](#summer_ui_screenshot) | Engine | PNG of the editor window (or one dock / dialog / control's rect) returned as an image you can look at — the PIXELS-LAST fallback of the UI ladder: use it to see layout, an unfamiliar panel, or to sanity-check what the… |
| [`summer_ui_tree`](#summer_ui_tree) | Engine | Structured tree of the live editor UI — every visible Control with its class, path, rect, text/tooltip and state (checked, enabled, focused, tabs + current\_tab, value/min/max, selected item) — or, with root:'dialogs',… |
| [`summer_wait_for_event`](#summer_wait_for_event) | Engine | Block until the engine emits a matching EVENT, or a bounded timeout elapses — the replacement for sleeping and re-polling. |
| [`summer_world_snapshot`](#summer_world_snapshot) | Engine | Compact structured snapshot of the whole EDITED scene — the cheap read to run BEFORE and AFTER every mutation batch. |
| [`summer_zoom`](#summer_zoom) | Engine | High-resolution close look at part of a frame: region \[x, y, w, h] (fractions of the frame) or mark N from a marks render of the same pose. |

### summer\_camera\_bookmark

On the local MCP (`summer-engine` npm).

Save, list, or delete named camera viewpoints for the edited 3D scene. A bookmark is a fixed pose (position, look\_at, fov) stored IN THE PROJECT at res\://.summer/camera\_bookmarks.json, so it survives sessions and machines — the scene file is never touched.

WHY: screenshots taken from a preset framing re-fit the scene bounds every time, so a before/after pair shifts whenever anything moves; a bookmark is the same pose every time, which makes before/after comparison real. Save once, then reuse on every capture: [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) target:"scene" framing:"bookmark" bookmark\_name:"\<name>" (add marks:true for numbered labels mapped to node paths).

action:
"save"   — name (1-64 of A-Z a-z 0-9 \\\_ -) plus EITHER position + look\_at as Godot literals ("Vector3(x, y, z)", optional fov, default 60) OR neither: omit both to capture the CURRENT editor 3D viewport camera (result pose\_source: "editor\_viewport" vs "explicit"; overwritten says whether a same-named bookmark was replaced).
"list"   — every saved bookmark (names sorted, poses, created timestamps, file path).
"delete" — remove one by name (result lists the remaining names).

Failures are structured: bad\_args (name grammar, half-given pose, fov outside 1..179, position == look\_at), no\_editor\_camera (no pose given and no 3D viewport camera to capture), not\_found (+ available names), io\_failed (file unreadable/unwritable; a malformed file is reported, never overwritten). Edit-time only — no running game needed. If this engine build predates the bookmark ops, the result is a structured engine\_lacks\_op failure (nothing is sent) naming the fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool camera-bookmark --args '<json>'` |

**Use when:**

* "save this camera angle" / "remember this viewpoint" so later screenshots line up with it
* comparing a scene before and after a change from one fixed, repeatable camera pose
* listing or removing the saved viewpoints of a project

**Do not use when:**

* taking the screenshot itself — [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) target "scene" with framing "bookmark" and bookmark\_name reuses a saved pose
* a one-off pose you will not reuse — [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) framing "free" with camera\_position/camera\_look\_at
* requires an engine build with the camera bookmark ops (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `action` | "save" \| "list" \| "delete" | Yes | "save" a viewpoint, "list" the saved ones, or "delete" one by name. |
| `name` | string | No | Bookmark name for save/delete: 1-64 characters from A-Z a-z 0-9 \\\_ - (e.g. "hero\_closeup"). |
| `position` | string | No | save only. Camera position as a Godot literal, e.g. "Vector3(0, 5, 12)". Goes together with look\_at; omit BOTH to capture the current editor 3D viewport camera. |
| `look_at` | string | No | save only. Point the camera looks at, e.g. "Vector3(0, 1, 0)". Must differ from position. |
| `fov` | number | No | save only. Vertical field of view in degrees (1..179, default 60). With a captured viewport pose the viewport camera's fov wins. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "action": {
        "type": "string",
        "enum": [
          "save",
          "list",
          "delete"
        ],
        "description": "\"save\" a viewpoint, \"list\" the saved ones, or \"delete\" one by name."
      },
      "name": {
        "type": "string",
        "description": "Bookmark name for save/delete: 1-64 characters from A-Z a-z 0-9 _ - (e.g. \"hero_closeup\")."
      },
      "position": {
        "type": "string",
        "description": "save only. Camera position as a Godot literal, e.g. \"Vector3(0, 5, 12)\". Goes together with look_at; omit BOTH to capture the current editor 3D viewport camera."
      },
      "look_at": {
        "type": "string",
        "description": "save only. Point the camera looks at, e.g. \"Vector3(0, 1, 0)\". Must differ from position."
      },
      "fov": {
        "type": "number",
        "description": "save only. Vertical field of view in degrees (1..179, default 60). With a captured viewport pose the viewport camera's fov wins."
      }
    },
    "required": [
      "action"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_camera_bookmark",
  "arguments": {
    "action": "save"
  }
}
```

***

### summer\_clear\_console

On the local MCP (`summer-engine` npm).

Clear the editor's Output panel. Useful before running the game to get a clean slate for error checking.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool clear-console --args '<json>'` |

**Use when:**

* before a play session whose console output you want isolated
* "the Output panel is full of old noise, wipe it"
* resetting before reproducing a bug so only the new messages show

**Do not use when:**

* you still need to read the messages — [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console) first; clearing destroys them

**Inputs:**

No inputs.

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {}
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_clear_console",
  "arguments": {}
}
```

***

### summer\_create\_debug\_report

On the local MCP (`summer-engine` npm).

Create a support-ready Markdown report for /summer debug.

Use this when the user says "/summer debug", asks to send Summer a bug report,
or needs a portable artifact from a failing Codex, cloud or agent session. The
report includes Summer doctor checks, engine health, diagnostics, console
output, debugger errors/warnings, and an agent handoff prompt. It omits auth
tokens and project file contents, but the user should still review it before
sending because local paths and stack traces may appear.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files, changes the open project, uses the network |
| **CLI** | `summer debug` |

**Use when:**

* the user wants to send Summer a bug report
* handing a failing session to another agent as a portable artifact

**Do not use when:**

* running against a hosted/remote MCP endpoint — it probes the local disk (\~/.summer, MCP log, project files) and must run where the CLI is installed

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `issue` | string | No | User-visible issue or repro summary. |
| `output_path` | string | No | Where to write the Markdown report. Defaults to the open project root, or the current working directory if no project is open. |
| `include_play_session` | boolean | No | Launch the game briefly and collect post-play diagnostics. Default `false`. |
| `play_wait_ms` | number | No | Milliseconds to wait after launching the game when include\_play\_session is true. Default `2500`. |
| `max_console_lines` | number | No | Console lines to include after filtering/deduping. Default `200`. |
| `max_debugger_entries` | number | No | Debugger error/warning entries to include after filtering/deduping. Default `100`. |
| `include_doctor` | boolean | No | Include summer doctor checks in the report. Default `true`. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "issue": {
        "type": "string",
        "description": "User-visible issue or repro summary."
      },
      "output_path": {
        "type": "string",
        "description": "Where to write the Markdown report. Defaults to the open project root, or the current working directory if no project is open."
      },
      "include_play_session": {
        "type": "boolean",
        "default": false,
        "description": "Launch the game briefly and collect post-play diagnostics."
      },
      "play_wait_ms": {
        "type": "number",
        "default": 2500,
        "description": "Milliseconds to wait after launching the game when include_play_session is true."
      },
      "max_console_lines": {
        "type": "number",
        "default": 200,
        "description": "Console lines to include after filtering/deduping."
      },
      "max_debugger_entries": {
        "type": "number",
        "default": 100,
        "description": "Debugger error/warning entries to include after filtering/deduping."
      },
      "include_doctor": {
        "type": "boolean",
        "default": true,
        "description": "Include summer doctor checks in the report."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_create_debug_report",
  "arguments": {}
}
```

***

### summer\_debug\_views

On the local MCP (`summer-engine` npm).

One pose rendered as a grid of debug views next to the beauty pass: beauty, lighting (light only: direction, pools, dead-dark areas), unshaded (albedo: texture quality, value grouping), normals (world-space, x red y green z blue: seams, flipped or faceted normals), overdraw (stacked transparent layers), wireframe (triangle density, floating or duplicated pieces).

Use it on the weakest shot of a sheet to see WHY it reads badly. lighting/unshaded/overdraw/wireframe are the engine's Viewport debug draw modes; normals uses an unshaded override material on a private copy (normal maps and alpha cut-outs are not applied) and works on every renderer. The caption names the method per view.

Returns the grid + caption. Read-only: the scene file, the open tab and the undo history are never touched (the render is an offscreen copy of the SAVED scene). The image arrives inline; the caption carries poses, labels and numbers. Failures are structured (failure\_reason), never a silent fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool debug-views --args '<json>'` |

**Use when:**

* understanding WHY a shot reads badly: light direction and pools, texture/value balance, normal seams, overdraw hot spots, triangle density
* checking floating or duplicated geometry, flipped normals or missing light in one framed view
* judging an environment like an artist, on the weakest shot of a sheet

**Do not use when:**

* you only need the final image — [`summer_shot_sheet`](/mcp/tools/run-and-test#summer_shot_sheet) or [`summer_frame_nodes`](/mcp/tools/run-and-test#summer_frame_nodes)
* a pixel-exact close-up of a problem — [`summer_zoom`](/mcp/tools/run-and-test#summer_zoom) with the same pose

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scenePath` | string | No | Scene to look at, e.g. "res\://levels/town.tscn" (the SAVED file). Omit for the scene open in the editor. |
| `bookmark_name` | string | No | Pose from a camera bookmark (summer\_camera\_bookmark). Use INSTEAD of camera\_position/camera\_look\_at. |
| `camera_position` | string | No | Explicit pose: camera position, "Vector3(x, y, z)". Goes with camera\_look\_at. |
| `camera_look_at` | string | No | Explicit pose: point the camera looks at, "Vector3(x, y, z)". |
| `fov` | number | No | Vertical field of view in degrees (1..179). Default: the bookmark's own, or 60. |
| `views` | "beauty" \| "lighting" \| "unshaded" \| "normals" \| "overdraw" \| "wireframe"\[] | No | Which views, in grid order (default all six: beauty, lighting, unshaded, normals, overdraw, wireframe). Items 0 to 8. |
| `update_previous` | boolean | No | bookmark\_name only: replace the bookmark's previous image (compare baseline) with this beauty tile. Default false: only a missing slot is created. |
| `max_size` | integer | No | Longest edge of the returned JPEG in pixels (default 1536). Bigger costs more context; up to 4096 for detail work on a PC. Range 64 to 4096. |
| `aspect` | number | No | Frame width/height (default 1.7778 = 16:9). Every tile uses it. |
| `save_to` | string | No | Also write the returned image to res\://.summer/shots/saved/\<name>.jpg (1-64 of A-Z a-z 0-9 \\\_ -; needs max\_size \<= 1024). Nothing is written without it, except one previous-image slot per rendered bookmark. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scenePath": {
        "type": "string",
        "description": "Scene to look at, e.g. \"res://levels/town.tscn\" (the SAVED file). Omit for the scene open in the editor."
      },
      "bookmark_name": {
        "type": "string",
        "description": "Pose from a camera bookmark (summer_camera_bookmark). Use INSTEAD of camera_position/camera_look_at."
      },
      "camera_position": {
        "type": "string",
        "description": "Explicit pose: camera position, \"Vector3(x, y, z)\". Goes with camera_look_at."
      },
      "camera_look_at": {
        "type": "string",
        "description": "Explicit pose: point the camera looks at, \"Vector3(x, y, z)\"."
      },
      "fov": {
        "type": "number",
        "description": "Vertical field of view in degrees (1..179). Default: the bookmark's own, or 60."
      },
      "views": {
        "type": "array",
        "items": {
          "type": "string",
          "enum": [
            "beauty",
            "lighting",
            "unshaded",
            "normals",
            "overdraw",
            "wireframe"
          ]
        },
        "maxItems": 8,
        "description": "Which views, in grid order (default all six: beauty, lighting, unshaded, normals, overdraw, wireframe)."
      },
      "update_previous": {
        "type": "boolean",
        "description": "bookmark_name only: replace the bookmark's previous image (compare baseline) with this beauty tile. Default false: only a missing slot is created."
      },
      "max_size": {
        "type": "integer",
        "minimum": 64,
        "maximum": 4096,
        "description": "Longest edge of the returned JPEG in pixels (default 1536). Bigger costs more context; up to 4096 for detail work on a PC."
      },
      "aspect": {
        "type": "number",
        "description": "Frame width/height (default 1.7778 = 16:9). Every tile uses it."
      },
      "save_to": {
        "type": "string",
        "description": "Also write the returned image to res://.summer/shots/saved/<name>.jpg (1-64 of A-Z a-z 0-9 _ -; needs max_size <= 1024). Nothing is written without it, except one previous-image slot per rendered bookmark."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns the grid + caption.

```json Example call theme={null}
{
  "name": "summer_debug_views",
  "arguments": {}
}
```

***

### summer\_frame\_nodes

On the local MCP (`summer-engine` npm).

Frame one or more nodes with a camera fitted to their WORLD bounds and render it with the scene's REAL WorldEnvironment, lights, fog and tonemap (unlike [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) nodePath, which only works with the flat preview environment).

Pick the side with direction (front/back/left/right/top/iso) or an explicit from vector; fill sets how much of the frame they span. bookmark\_name also saves the fitted pose so every later render ([`summer_shot_sheet`](/mcp/tools/run-and-test#summer_shot_sheet), [`summer_debug_views`](/mcp/tools/run-and-test#summer_debug_views), [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) framing:"bookmark") lines up with it. marks:true adds numbered labels mapped to node paths.

An explicit from is checked in-engine before you trust the image: if walls block the nodes, or the camera stands behind or inside a one-sided surface (its back is not drawn, so the image would look THROUGH the wall), the caption opens with a WARNING and the nearest valid from (+ fov) to pass back. With marks:true every labelled node gets an occlusion test (centre + 4 bounds points); hidden ones are noted "(hidden behind \<path>)".

Returns the image + caption: the pose as Vector3 literals, the bounds, the environment used, the view check, and the mark list. Read-only: the scene file, the open tab and the undo history are never touched (the render is an offscreen copy of the SAVED scene). The image arrives inline; the caption carries poses, labels and numbers. Failures are structured (failure\_reason), never a silent fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool frame-nodes --args '<json>'` |

**Use when:**

* "show me the houses/props with the real lighting" — fit a camera to one or more nodes and render it with the scene's WorldEnvironment
* framing a node from a chosen side (front/back/left/right/top/iso or an explicit direction) for a lighting or mood check; an explicit direction is checked for walls and back faces between the camera and the nodes
* creating a hero-view bookmark that fits a group of nodes, so later shot sheets and compares line up

**Do not use when:**

* the editor viewport or a game frame is what you need — [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot)
* you do not know a good view — [`summer_frame_shot`](/mcp/tools/run-and-test#summer_frame_shot) finds and scores poses for a shot type
* requires an engine build with ScenePreview fixed-pose framings (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op or framing\_unsupported

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scenePath` | string | No | Scene to look at, e.g. "res\://levels/town.tscn" (the SAVED file). Omit for the scene open in the editor. |
| `nodes` | string\[] | Yes | Node paths relative to the scene root ("House1", "Props/Crate\_02"). The camera fits their combined WORLD bounds (visible meshes, children included). Items 1 to 16. |
| `direction` | "front" \| "back" \| "left" \| "right" \| "top" \| "iso" | No | Side the camera sits on: "front" (+Z), "back" (-Z), "left" (-X), "right" (+X), "top", "iso" (default, 3/4 from -X,+Y,-Z like summer\_screenshot). Use from for anything else. |
| `from` | string | No | Explicit direction FROM the nodes TOWARD the camera, "Vector3(1, 0.4, 2)". Replaces direction. The fitted pose is checked in-engine: when walls block it, or it puts the camera behind or inside a one-sided surface (whose back is not drawn, so the image would look THROUGH it), the caption opens with a WARNING and offers the nearest valid from (+ fov); the requested view is still rendered. |
| `fill` | number | No | Share of the frame the nodes span along their limiting dimension (0.1..1.5, default 0.8). |
| `fov` | number | No | Vertical field of view in degrees (default 50). |
| `max_size` | integer | No | Longest edge of the returned JPEG in pixels (default 1024). Bigger costs more context; up to 4096 for detail work on a PC. Range 64 to 4096. |
| `aspect` | number | No | Frame width/height (default 1.7778 = 16:9). Every tile uses it. |
| `marks` | boolean | No | Numbered Set-of-Mark labels over the largest visible nodes; the caption maps label -> node path (feed a label to summer\_zoom mark). Each labelled node gets an occlusion test (centre + 4 bounds points); a node hidden behind other geometry is noted "(hidden behind \<path>)" — ignore its label. |
| `max_marks` | integer | No | marks only: cap on labels (engine default 32). Range 1 to 128. |
| `bookmark_name` | string | No | Also save the fitted pose as this camera bookmark (1-64 of A-Z a-z 0-9 \\\_ -), so later renders line up with it. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scenePath": {
        "type": "string",
        "description": "Scene to look at, e.g. \"res://levels/town.tscn\" (the SAVED file). Omit for the scene open in the editor."
      },
      "nodes": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "minItems": 1,
        "maxItems": 16,
        "description": "Node paths relative to the scene root (\"House1\", \"Props/Crate_02\"). The camera fits their combined WORLD bounds (visible meshes, children included)."
      },
      "direction": {
        "type": "string",
        "enum": [
          "front",
          "back",
          "left",
          "right",
          "top",
          "iso"
        ],
        "description": "Side the camera sits on: \"front\" (+Z), \"back\" (-Z), \"left\" (-X), \"right\" (+X), \"top\", \"iso\" (default, 3/4 from -X,+Y,-Z like summer_screenshot). Use from for anything else."
      },
      "from": {
        "type": "string",
        "description": "Explicit direction FROM the nodes TOWARD the camera, \"Vector3(1, 0.4, 2)\". Replaces direction. The fitted pose is checked in-engine: when walls block it, or it puts the camera behind or inside a one-sided surface (whose back is not drawn, so the image would look THROUGH it), the caption opens with a WARNING and offers the nearest valid from (+ fov); the requested view is still rendered."
      },
      "fill": {
        "type": "number",
        "description": "Share of the frame the nodes span along their limiting dimension (0.1..1.5, default 0.8)."
      },
      "fov": {
        "type": "number",
        "description": "Vertical field of view in degrees (default 50)."
      },
      "max_size": {
        "type": "integer",
        "minimum": 64,
        "maximum": 4096,
        "description": "Longest edge of the returned JPEG in pixels (default 1024). Bigger costs more context; up to 4096 for detail work on a PC."
      },
      "aspect": {
        "type": "number",
        "description": "Frame width/height (default 1.7778 = 16:9). Every tile uses it."
      },
      "marks": {
        "type": "boolean",
        "description": "Numbered Set-of-Mark labels over the largest visible nodes; the caption maps label -> node path (feed a label to summer_zoom mark). Each labelled node gets an occlusion test (centre + 4 bounds points); a node hidden behind other geometry is noted \"(hidden behind <path>)\" — ignore its label."
      },
      "max_marks": {
        "type": "integer",
        "minimum": 1,
        "maximum": 128,
        "description": "marks only: cap on labels (engine default 32)."
      },
      "bookmark_name": {
        "type": "string",
        "description": "Also save the fitted pose as this camera bookmark (1-64 of A-Z a-z 0-9 _ -), so later renders line up with it."
      }
    },
    "required": [
      "nodes"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns the image + caption: the pose as Vector3 literals, the bounds, the environment used, the view check, and the mark list.

```json Example call theme={null}
{
  "name": "summer_frame_nodes",
  "arguments": {
    "nodes": [
      "<node>"
    ]
  }
}
```

***

### summer\_frame\_shot

On the local MCP (`summer-engine` npm).

Smart framing: find good camera poses for a shot type automatically, measured in-engine against the real geometry.

shot: establishing (wide), eye\_level (from a spawn node at player eye height), low\_angle (hero, near the ground looking up: the camera moves closer and widens its FOV instead of sinking into the ground), detail (close-up), corridor (down a lane or corridor found inside the subject by a free-space scan, preferring the view in from its open end, walls on both sides).

How: candidate poses on a ring/hemisphere (or along the corridor line) at the distance where the subject fills the shot's target share of the frame; each is checked with a THICK sphere sweep to points on the subject (thin rays miss corners), a near-lens sphere (camera inside or touching geometry is nudged forward or rejected), a ray grid through the frame, and a small beauty render per pose. Walls/terrain blocking the subject reject a pose, and so does a camera behind or inside a one-sided surface (a sight line or a quarter of the frame meeting a wall from behind: its back is not drawn, so the image would look through it). Foliage, fences, props and pipes in front are allowed (wanted, up to a limit) as framing; transparent materials (alpha, scissor, glass, foliage cards) are see-through cover at partial weight. Scored on rule-of-thirds placement, frame fill, level horizon, sky share, depth behind the subject (no flat wall right behind it), empty or featureless areas, near-wall clearance, near/far value contrast, foreground framing, surfaces seen from behind, the key light's direction (side or front-side beats a flat front-lit face) and the world edge (sky or void below the horizon).

Returns the top 3 with score breakdowns and poses — three different views (one per side of the subject while the score allows, at least 25 degrees apart) — saves the best as a bookmark (default \<shot>\_\<subject>), and renders the 3 as one sheet with real lighting (render:"best" or "none" to save context). Read-only: the scene file, the open tab and the undo history are never touched (the render is an offscreen copy of the SAVED scene). The image arrives inline; the caption carries poses, labels and numbers. Failures are structured (failure\_reason), never a silent fallback. Saving the bookmark writes res\://.summer/camera\_bookmarks.json.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool frame-shot --args '<json>'` |

**Use when:**

* "find a good view of X" / "frame an establishing shot" / "show it from the player's eye" / "look down the street"
* choosing hero views to bookmark for an environment review loop
* checking whether walls or terrain block the view of a subject, and which props frame it (a camera behind or inside a one-sided wall is rejected; transparent foliage and glass are see-through cover)
* three different establishing views at once (one per side while the score allows), scored on the key light's direction and the world edge below the horizon

**Do not use when:**

* you already have the pose — [`summer_shot_sheet`](/mcp/tools/run-and-test#summer_shot_sheet), [`summer_frame_nodes`](/mcp/tools/run-and-test#summer_frame_nodes) or [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot)
* gameplay camera collision in the running game — that is the camera rig's job (camera-collision-avoidance knowledge)
* a 2D scene

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scenePath` | string | No | Scene to look at, e.g. "res\://levels/town.tscn" (the SAVED file). Omit for the scene open in the editor. |
| `shot` | "establishing" \| "eye\_level" \| "low\_angle" \| "detail" \| "corridor" | Yes | "establishing" (wide, whole subject, 12-40 deg up; a pose showing more than 15% empty ground or world edge ranks below every pose showing less), "eye\_level" (player eye at spawn: its Camera3D kept inside 1.5-1.8 m, or eye\_height, above the walkable surface under the camera), "low\_angle" (hero, camera near the ground looking up), "detail" (close-up), "corridor" (down a lane/corridor inside the subject, found by a free-space scan, at eye height; prefers looking in from its open end). Eye-level and corridor cameras are never raised: a pose that cannot stand at eye height moves horizontally or is rejected. Every top pose states its camera height above the surface below it and its absolute y. |
| `subject` | string\[] | No | Node paths to frame (required except for eye\_level, where it is what the player looks at). Items 0 to 8. |
| `spawn` | string | No | eye\_level only: node the player stands at, e.g. the player or a spawn marker. Its own geometry never counts as an occluder. |
| `eye_height` | number | No | eye\_level/corridor: exact camera height in metres above the walkable surface under the camera (default: 1.6; an eye\_level spawn's own Camera3D keeps its height clamped into 1.5-1.8). |
| `fov` | number | No | Override the shot type's field of view (establishing 55, eye\_level 60/75, low\_angle 60 then widened, detail 40, corridor 60/75). |
| `aspect` | number | No | Frame width/height (default 1.7778 = 16:9). Every tile uses it. |
| `occluders` | object | No | Override the occluder classification. Default: path names (wall/house/terrain... = hard; tree/fence/lamp/prop/pipe... = soft), MultiMesh = soft, else size (\<= 2.5 m = soft). The caption reports the counts by rule. |
| `occluders.hard` | string\[] | No | Path prefixes that must never block the view (walls, terrain): a pose they block is rejected. |
| `occluders.soft` | string\[] | No | Path prefixes that may sit in front as framing (foliage, fences, props). |
| `occluders.ignore` | string\[] | No | Path prefixes left out of the visibility pass entirely. |
| `occluders.hard_layers` | integer | No | Physics layer mask: geometry whose nearest CollisionObject3D is on these layers is hard. |
| `occluders.soft_layers` | integer | No | Physics layer mask for soft geometry. |
| `max_soft_fraction` | number | No | Most of the subject that soft geometry may cover before a pose is rejected (0..1, default 0.67). |
| `bookmark_name` | string | No | Name for the saved best pose (default \<shot>\_\<subject leaf>). |
| `save_bookmark` | boolean | No | Save the best pose as a camera bookmark (default true). |
| `render` | "sheet" \| "best" \| "none" | No | "sheet" (default): the top 3 in one labelled grid with real lighting; "best": the winner only; "none": scores only. |
| `max_size` | integer | No | Longest edge of the returned JPEG in pixels (default 1536). Bigger costs more context; up to 4096 for detail work on a PC. Range 64 to 4096. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scenePath": {
        "type": "string",
        "description": "Scene to look at, e.g. \"res://levels/town.tscn\" (the SAVED file). Omit for the scene open in the editor."
      },
      "shot": {
        "type": "string",
        "enum": [
          "establishing",
          "eye_level",
          "low_angle",
          "detail",
          "corridor"
        ],
        "description": "\"establishing\" (wide, whole subject, 12-40 deg up; a pose showing more than 15% empty ground or world edge ranks below every pose showing less), \"eye_level\" (player eye at spawn: its Camera3D kept inside 1.5-1.8 m, or eye_height, above the walkable surface under the camera), \"low_angle\" (hero, camera near the ground looking up), \"detail\" (close-up), \"corridor\" (down a lane/corridor inside the subject, found by a free-space scan, at eye height; prefers looking in from its open end). Eye-level and corridor cameras are never raised: a pose that cannot stand at eye height moves horizontally or is rejected. Every top pose states its camera height above the surface below it and its absolute y."
      },
      "subject": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "maxItems": 8,
        "description": "Node paths to frame (required except for eye_level, where it is what the player looks at)."
      },
      "spawn": {
        "type": "string",
        "description": "eye_level only: node the player stands at, e.g. the player or a spawn marker. Its own geometry never counts as an occluder."
      },
      "eye_height": {
        "type": "number",
        "description": "eye_level/corridor: exact camera height in metres above the walkable surface under the camera (default: 1.6; an eye_level spawn's own Camera3D keeps its height clamped into 1.5-1.8)."
      },
      "fov": {
        "type": "number",
        "description": "Override the shot type's field of view (establishing 55, eye_level 60/75, low_angle 60 then widened, detail 40, corridor 60/75)."
      },
      "aspect": {
        "type": "number",
        "description": "Frame width/height (default 1.7778 = 16:9). Every tile uses it."
      },
      "occluders": {
        "type": "object",
        "properties": {
          "hard": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Path prefixes that must never block the view (walls, terrain): a pose they block is rejected."
          },
          "soft": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Path prefixes that may sit in front as framing (foliage, fences, props)."
          },
          "ignore": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Path prefixes left out of the visibility pass entirely."
          },
          "hard_layers": {
            "type": "integer",
            "description": "Physics layer mask: geometry whose nearest CollisionObject3D is on these layers is hard."
          },
          "soft_layers": {
            "type": "integer",
            "description": "Physics layer mask for soft geometry."
          }
        },
        "additionalProperties": false,
        "description": "Override the occluder classification. Default: path names (wall/house/terrain... = hard; tree/fence/lamp/prop/pipe... = soft), MultiMesh = soft, else size (<= 2.5 m = soft). The caption reports the counts by rule."
      },
      "max_soft_fraction": {
        "type": "number",
        "description": "Most of the subject that soft geometry may cover before a pose is rejected (0..1, default 0.67)."
      },
      "bookmark_name": {
        "type": "string",
        "description": "Name for the saved best pose (default <shot>_<subject leaf>)."
      },
      "save_bookmark": {
        "type": "boolean",
        "description": "Save the best pose as a camera bookmark (default true)."
      },
      "render": {
        "type": "string",
        "enum": [
          "sheet",
          "best",
          "none"
        ],
        "description": "\"sheet\" (default): the top 3 in one labelled grid with real lighting; \"best\": the winner only; \"none\": scores only."
      },
      "max_size": {
        "type": "integer",
        "minimum": 64,
        "maximum": 4096,
        "description": "Longest edge of the returned JPEG in pixels (default 1536). Bigger costs more context; up to 4096 for detail work on a PC."
      }
    },
    "required": [
      "shot"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns the top 3 with score breakdowns and poses — three different views (one per side of the subject while the score allows, at least 25 degrees apart) — saves the best as a bookmark (default \<shot>\_\<subject>), and renders the 3 as one sheet with real lighting (render:"best" or "none" to save context).

```json Example call theme={null}
{
  "name": "summer_frame_shot",
  "arguments": {
    "shot": "establishing"
  }
}
```

***

### summer\_game\_control

On the local MCP (`summer-engine` npm).

Control the clock of the RUNNING game and list instances. action:'pause' suspends it (GamePause — Engine time frozen, physics inactive; SceneTree.paused untouched), 'resume' lifts the suspension, 'step' advances EXACTLY frames (1..600) of kind 'physics' (default) or 'process' and leaves the game suspended (GameStep), 'speed' sets the user time scale (GameSpeed, 0.25 = quarter speed), 'instances' lists every live game instance (ListGameInstances: name, mode, pid, attached, breaked, scene, seed, fixed\_fps, deterministic, `summer_capture`).

Frame stepping is how you make exact assertions: pause -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) -> act -> step 1 -> probe; the step result reports before/after frame counters, exact, overshoot, and draws the last stepped frame before replying so the following probe shows it. A minimized game window draws no frames and cannot step (timeout). step and pause answer game\_breaked while the game sits at a breakpoint. GameSpeed rides a no-reply channel: acknowledged:false means the engine could not read the new time\_scale back (older game build) — verify with [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) time\_scale.

'instances' is also the boot check after [`summer_play`](/mcp/tools/run-and-test#summer_play) \{instance, mode:'offscreen'}: address an instance only once attached:true (before that the ops answer request\_failed). THE LOOP: [`summer_play`](/mcp/tools/run-and-test#summer_play) (add instance + mode:'offscreen' for a disposable instance; deterministic:true + seed for a reproducible run; fixed\_fps for exact timing) -> wait for boot ([`summer_is_running`](/mcp/tools/run-and-test#summer_is_running), or [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances' showing attached:true) -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) BEFORE (frame-stamped state + pixels) -> act ([`summer_runtime_set`](/mcp/tools/run-and-test#summer_runtime_set) / [`summer_runtime_call`](/mcp/tools/run-and-test#summer_runtime_call) / [`summer_game_input`](/mcp/tools/run-and-test#summer_game_input)) -> [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step' for exact frames, or let it run -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) AFTER -> assert on the two probes. Never claim something moved, spawned or fired without a probe that shows it.

Needs a RUNNING game: failure\_reason game\_not\_running ([`summer_play`](/mcp/tools/run-and-test#summer_play) first), request\_failed (debug session still attaching — wait, retry), unknown\_instance ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances'), game\_breaked (the game sits at a breakpoint — continue it first), timeout (game wedged, minimized or breaked — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors), or [`summer_stop`](/mcp/tools/run-and-test#summer_stop) + [`summer_play`](/mcp/tools/run-and-test#summer_play)), unsupported (the running game predates the summer capture; restart it after updating). All runtime paths are ABSOLUTE ('/root/Main/Player') and come from [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) tree / [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree). On an engine build that predates these ops the result is a structured engine\_lacks\_op failure; the fallback for exact frames is a RunVerification probe awaiting physics\_frame N times.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool game-control --args '<json>'` |

**Use when:**

* pausing the running game and stepping one physics frame at a time to make exact frame-by-frame assertions
* "pause the game and advance a single physics frame" / "run the game at quarter speed" / "which game instances are up?"
* checking that an offscreen playtest instance has attached before addressing it

**Do not use when:**

* starting or stopping the game — [`summer_play`](/mcp/tools/run-and-test#summer_play) / [`summer_stop`](/mcp/tools/run-and-test#summer_stop)
* a probe-based check inside a hidden disposable instance — a RunVerification probe awaiting physics\_frame
* requires an engine build with GamePause / GameStep / GameSpeed / ListGameInstances (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `action` | "pause" \| "resume" \| "step" \| "speed" \| "instances" | Yes | 'pause' suspends the game (Engine time frozen, physics inactive); 'resume' lifts the suspension; 'step' advances exactly N frames and leaves the game suspended; 'speed' sets the user time scale; 'instances' lists the live game instances (main + offscreen). |
| `frames` | integer | No | action:'step' — frames to advance, 1..600 (default 1). |
| `kind` | "physics" \| "process" | No | action:'step' — 'physics' (default: exact physics ticks, max\_physics\_steps\_per\_frame pinned to 1) or 'process' (rendered frames). |
| `speed` | number | No | action:'speed' — user time scale in (0, 100]: 0.25 = quarter speed, 2 = double. |
| `instance` | string | No | Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer\_play \{instance, mode:'offscreen'}; summer\_game\_control action:'instances' lists the live ones. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "action": {
        "type": "string",
        "enum": [
          "pause",
          "resume",
          "step",
          "speed",
          "instances"
        ],
        "description": "'pause' suspends the game (Engine time frozen, physics inactive); 'resume' lifts the suspension; 'step' advances exactly N frames and leaves the game suspended; 'speed' sets the user time scale; 'instances' lists the live game instances (main + offscreen)."
      },
      "frames": {
        "type": "integer",
        "description": "action:'step' — frames to advance, 1..600 (default 1)."
      },
      "kind": {
        "type": "string",
        "enum": [
          "physics",
          "process"
        ],
        "description": "action:'step' — 'physics' (default: exact physics ticks, max_physics_steps_per_frame pinned to 1) or 'process' (rendered frames)."
      },
      "speed": {
        "type": "number",
        "description": "action:'speed' — user time scale in (0, 100]: 0.25 = quarter speed, 2 = double."
      },
      "instance": {
        "type": "string",
        "description": "Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer_play {instance, mode:'offscreen'}; summer_game_control action:'instances' lists the live ones."
      }
    },
    "required": [
      "action"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_game_control",
  "arguments": {
    "action": "pause"
  }
}
```

***

### summer\_game\_input

On the local MCP (`summer-engine` npm).

Drive the RUNNING game's input like a player would. action:'script' schedules up to 1000 timed synthetic events (SimulateInputScript): \[\{at\_frame: 0, type:'action', action:'move\_right', hold\_ms: 500}, \{at\_frame: 30, type:'action', action:'jump', hold\_ms: 50}]; types action | key (keycode / physical\_keycode) | mouse\_click (position \[x,y], button) | axis (action\_negative/action\_positive, signed strength) | raw (\{class, props} — a recorded InputEvent). clock 'frame' (at\_frame, exact) or 'ms' (at\_ms — exact only when the instance runs with fixed\_fps, else approximate; the result reports clock\_mapping). action:'record\_start' / 'record\_stop' capture the game's REAL input into res\://.summer/replays/\<id>.json (InputRecordStart/Stop — cap 20,000 events / \~1 MiB, truncated:true when hit); action:'replay' plays a recording (or inline events) back (InputReplay), with seed asserting reproducibility on a deterministic offscreen instance.

script returns \{scheduled, applied, rejected\[\{index, failure\_reason, error}], first\_frame, last\_frame, completed, clock\_mapping}; per-event rejections (unknown\_action: not in the project InputMap — [`summer_input_map_bind`](/mcp/tools/build#summer_input_map_bind), or use type:'key') are non-fatal unless all\_rejected. ONE script in flight per instance: a second call answers busy — wait for the first. wait:true (default) blocks until the last event fires but the engine caps it at 20 s; a longer script uses wait:false and observes with [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe). replay returns the same shape plus \{recording, deterministic}; seed on an instance not started with [`summer_play`](/mcp/tools/run-and-test#summer_play) \{mode:'offscreen', deterministic:true} answers nondeterministic\_instance.

Scripts vs recordings: a script is the readable, editable repro you write from the spec; a recording is the exact repro of what a human (or a script) actually did — replay it on a deterministic instance for an A/B under identical inputs. Input is an ACTION: prove what it caused with [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) before/after (or [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step' for the exact frame). THE LOOP: [`summer_play`](/mcp/tools/run-and-test#summer_play) (add instance + mode:'offscreen' for a disposable instance; deterministic:true + seed for a reproducible run; fixed\_fps for exact timing) -> wait for boot ([`summer_is_running`](/mcp/tools/run-and-test#summer_is_running), or [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances' showing attached:true) -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) BEFORE (frame-stamped state + pixels) -> act ([`summer_runtime_set`](/mcp/tools/run-and-test#summer_runtime_set) / [`summer_runtime_call`](/mcp/tools/run-and-test#summer_runtime_call) / [`summer_game_input`](/mcp/tools/run-and-test#summer_game_input)) -> [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step' for exact frames, or let it run -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) AFTER -> assert on the two probes. Never claim something moved, spawned or fired without a probe that shows it.

Needs a RUNNING game: failure\_reason game\_not\_running ([`summer_play`](/mcp/tools/run-and-test#summer_play) first), request\_failed (debug session still attaching — wait, retry), unknown\_instance ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances'), game\_breaked (the game sits at a breakpoint — continue it first), timeout (game wedged, minimized or breaked — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors), or [`summer_stop`](/mcp/tools/run-and-test#summer_stop) + [`summer_play`](/mcp/tools/run-and-test#summer_play)), unsupported (the running game predates the summer capture; restart it after updating). All runtime paths are ABSOLUTE ('/root/Main/Player') and come from [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) tree / [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree). engine\_lacks\_op on an older build names the fallback (single SimulateInput ops via [`summer_batch`](/mcp/tools/build#summer_batch), or a RunVerification probe's press()/key()).

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files, changes the open project |
| **CLI** | `summer tool game-input --args '<json>'` |

**Use when:**

* scripting a timed input sequence against the running game (walk right for 500 ms, jump at frame 30, click a button)
* recording my inputs while the game runs and replaying them deterministically for a repro or an A/B
* "record what I do and replay it" / "press jump 10 times in a row in the running game"

**Do not use when:**

* binding or renaming input actions — [`summer_input_map_bind`](/mcp/tools/build#summer_input_map_bind)
* a hidden disposable probe run — a RunVerification probe with press()/key()
* no game is running (failure\_reason game\_not\_running) — [`summer_play`](/mcp/tools/run-and-test#summer_play) first
* requires an engine build with SimulateInputScript / InputRecordStart / InputRecordStop / InputReplay (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `action` | "script" \| "record\_start" \| "record\_stop" \| "replay" | Yes | 'script' schedules a timed sequence of synthetic inputs (SimulateInputScript); 'record\_start'/'record\_stop' capture the game's real input into res\://.summer/replays/\<id>.json; 'replay' plays a recording (or inline events) back. |
| `events` | object\[] | No | action:'script' (required) or 'replay' (instead of recording) — up to 1000 timed events; last event within 36000 frames / 600 s. |
| `events[].at_frame` | integer | No | When to fire, in frames after scheduling (clock:'frame'). Default 0 = the next frame. |
| `events[].at_ms` | integer | No | When to fire, in milliseconds after scheduling (clock:'ms'); mapped through --fixed-fps when the instance has one (clock\_mapping 'exact'), else the physics tick rate ('approximate'). |
| `events[].type` | "action" \| "key" \| "mouse\_click" \| "axis" \| "raw" | Yes | 'action' = InputMap action press/release; 'key' = keycode; 'mouse\_click' = click at a position; 'axis' = analog strength between two actions; 'raw' = a recorded InputEvent replayed as \{class, props}. |
| `events[].action` | string | No | type:'action' — InputMap action name (unknown\_action if not bound). |
| `events[].pressed` | boolean | No | type:'action'\|'key' — press (default true) or release. |
| `events[].strength` | number | No | type:'action' — press strength 0..1 (default 1). type:'axis' — signed strength; negative selects action\_negative. |
| `events[].hold_ms` | integer | No | type:'action'\|'key' — auto-release after this many ms (0 = stays pressed until a release event). |
| `events[].keycode` | integer | No | type:'key' — Godot Key enum value (e.g. 32 = Space, 4194320 = Right arrow). |
| `events[].physical_keycode` | integer | No | type:'key' — physical Key enum value (alternative to keycode). |
| `events[].position` | any\[] | No | type:'mouse\_click' — \[x, y] in window pixels. Items 2 to 2. |
| `events[].button` | integer | No | type:'mouse\_click' — MouseButton (1 = left, 2 = right, 3 = middle). |
| `events[].action_negative` | string | No | type:'axis' — action for negative strength (e.g. 'move\_left'). |
| `events[].action_positive` | string | No | type:'axis' — action for positive strength (e.g. 'move\_right'). |
| `events[].duration_ms` | integer | No | type:'axis' — auto-release after this many ms. |
| `events[].class` | string | No | type:'raw' — InputEvent class to instantiate (e.g. 'InputEventKey'). |
| `events[].props` | object | No | type:'raw' — properties set on the instantiated event. |
| `clock` | "frame" \| "ms" | No | action:'script' — whether at\_frame ('frame', default, exact) or at\_ms ('ms') schedules the events. |
| `wait` | boolean | No | action:'script'\|'replay' — wait for the last event to fire (default true; the engine caps a waited script at 20 s). Scripts longer than that: wait:false and observe with summer\_game\_probe. |
| `include_motion` | boolean | No | action:'record\_start' — also record mouse/joypad motion events (default false; large). |
| `save_as` | string | No | action:'record\_stop' — res\://.summer/replays/\<name>.json to write instead of a generated id. |
| `recording` | string | No | action:'replay' — the res\://.summer/replays/\<id>.json path returned by record\_stop. |
| `seed` | integer | No | action:'replay' — assert the replay is reproducible; only accepted on an instance started with summer\_play \{mode:'offscreen', deterministic:true} (else nondeterministic\_instance). |
| `instance` | string | No | Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer\_play \{instance, mode:'offscreen'}; summer\_game\_control action:'instances' lists the live ones. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "action": {
        "type": "string",
        "enum": [
          "script",
          "record_start",
          "record_stop",
          "replay"
        ],
        "description": "'script' schedules a timed sequence of synthetic inputs (SimulateInputScript); 'record_start'/'record_stop' capture the game's real input into res://.summer/replays/<id>.json; 'replay' plays a recording (or inline events) back."
      },
      "events": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "at_frame": {
              "type": "integer",
              "description": "When to fire, in frames after scheduling (clock:'frame'). Default 0 = the next frame."
            },
            "at_ms": {
              "type": "integer",
              "description": "When to fire, in milliseconds after scheduling (clock:'ms'); mapped through --fixed-fps when the instance has one (clock_mapping 'exact'), else the physics tick rate ('approximate')."
            },
            "type": {
              "type": "string",
              "enum": [
                "action",
                "key",
                "mouse_click",
                "axis",
                "raw"
              ],
              "description": "'action' = InputMap action press/release; 'key' = keycode; 'mouse_click' = click at a position; 'axis' = analog strength between two actions; 'raw' = a recorded InputEvent replayed as {class, props}."
            },
            "action": {
              "type": "string",
              "description": "type:'action' — InputMap action name (unknown_action if not bound)."
            },
            "pressed": {
              "type": "boolean",
              "description": "type:'action'|'key' — press (default true) or release."
            },
            "strength": {
              "type": "number",
              "description": "type:'action' — press strength 0..1 (default 1). type:'axis' — signed strength; negative selects action_negative."
            },
            "hold_ms": {
              "type": "integer",
              "description": "type:'action'|'key' — auto-release after this many ms (0 = stays pressed until a release event)."
            },
            "keycode": {
              "type": "integer",
              "description": "type:'key' — Godot Key enum value (e.g. 32 = Space, 4194320 = Right arrow)."
            },
            "physical_keycode": {
              "type": "integer",
              "description": "type:'key' — physical Key enum value (alternative to keycode)."
            },
            "position": {
              "type": "array",
              "minItems": 2,
              "maxItems": 2,
              "items": [
                {
                  "type": "number"
                },
                {
                  "type": "number"
                }
              ],
              "description": "type:'mouse_click' — [x, y] in window pixels."
            },
            "button": {
              "type": "integer",
              "description": "type:'mouse_click' — MouseButton (1 = left, 2 = right, 3 = middle)."
            },
            "action_negative": {
              "type": "string",
              "description": "type:'axis' — action for negative strength (e.g. 'move_left')."
            },
            "action_positive": {
              "type": "string",
              "description": "type:'axis' — action for positive strength (e.g. 'move_right')."
            },
            "duration_ms": {
              "type": "integer",
              "description": "type:'axis' — auto-release after this many ms."
            },
            "class": {
              "type": "string",
              "description": "type:'raw' — InputEvent class to instantiate (e.g. 'InputEventKey')."
            },
            "props": {
              "type": "object",
              "additionalProperties": {},
              "description": "type:'raw' — properties set on the instantiated event."
            }
          },
          "required": [
            "type"
          ],
          "additionalProperties": false
        },
        "description": "action:'script' (required) or 'replay' (instead of recording) — up to 1000 timed events; last event within 36000 frames / 600 s."
      },
      "clock": {
        "type": "string",
        "enum": [
          "frame",
          "ms"
        ],
        "description": "action:'script' — whether at_frame ('frame', default, exact) or at_ms ('ms') schedules the events."
      },
      "wait": {
        "type": "boolean",
        "description": "action:'script'|'replay' — wait for the last event to fire (default true; the engine caps a waited script at 20 s). Scripts longer than that: wait:false and observe with summer_game_probe."
      },
      "include_motion": {
        "type": "boolean",
        "description": "action:'record_start' — also record mouse/joypad motion events (default false; large)."
      },
      "save_as": {
        "type": "string",
        "description": "action:'record_stop' — res://.summer/replays/<name>.json to write instead of a generated id."
      },
      "recording": {
        "type": "string",
        "description": "action:'replay' — the res://.summer/replays/<id>.json path returned by record_stop."
      },
      "seed": {
        "type": "integer",
        "description": "action:'replay' — assert the replay is reproducible; only accepted on an instance started with summer_play {mode:'offscreen', deterministic:true} (else nondeterministic_instance)."
      },
      "instance": {
        "type": "string",
        "description": "Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer_play {instance, mode:'offscreen'}; summer_game_control action:'instances' lists the live ones."
      }
    },
    "required": [
      "action"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_game_input",
  "arguments": {
    "action": "script"
  }
}
```

***

### summer\_game\_probe

On the local MCP (`summer-engine` npm).

State AND pixels of ONE frame of the RUNNING game, atomically (GameProbe): the live scene tree (tree \{path, depth, limit}), up to 64 property reads (props \['/root/Main/Player:position', '/root/Main/HUD/Health:value']) and a screenshot of the game viewport, all stamped with the SAME frame counters. This is the evidence tool of the playtest loop — the only read where "what the tree says" and "what the screen shows" cannot come from different moments.

You SEE the screenshot as an image block; the text block carries the frame stamp (\{frame: \{process\_frames, physics\_frames, frames\_drawn}, image\_frame, suspended, paused, time\_scale}), values \{key: Godot literal string}, missing\[] (keys that did not resolve — a typo or a node that is gone), tree/total\_nodes/truncated. Two probes around an action are a claim's proof: cite both frame numbers. screenshot:false is the cheap state-only read (works when the window draws nothing). max\_dim (default 1280) bounds the image. Unlike [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) target:'game', the capture happens game-side, so it works for offscreen and floating instances.

THE LOOP: [`summer_play`](/mcp/tools/run-and-test#summer_play) (add instance + mode:'offscreen' for a disposable instance; deterministic:true + seed for a reproducible run; fixed\_fps for exact timing) -> wait for boot ([`summer_is_running`](/mcp/tools/run-and-test#summer_is_running), or [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances' showing attached:true) -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) BEFORE (frame-stamped state + pixels) -> act ([`summer_runtime_set`](/mcp/tools/run-and-test#summer_runtime_set) / [`summer_runtime_call`](/mcp/tools/run-and-test#summer_runtime_call) / [`summer_game_input`](/mcp/tools/run-and-test#summer_game_input)) -> [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step' for exact frames, or let it run -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) AFTER -> assert on the two probes. Never claim something moved, spawned or fired without a probe that shows it.

Needs a RUNNING game: failure\_reason game\_not\_running ([`summer_play`](/mcp/tools/run-and-test#summer_play) first), request\_failed (debug session still attaching — wait, retry), unknown\_instance ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances'), game\_breaked (the game sits at a breakpoint — continue it first), timeout (game wedged, minimized or breaked — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors), or [`summer_stop`](/mcp/tools/run-and-test#summer_stop) + [`summer_play`](/mcp/tools/run-and-test#summer_play)), unsupported (the running game predates the summer capture; restart it after updating). All runtime paths are ABSOLUTE ('/root/Main/Player') and come from [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) tree / [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree). A probe still answers while the game is breaked. On an engine build that predates GameProbe the result is a structured engine\_lacks\_op failure; fall back to [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree) + [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) target:'game' (two calls, two frames).

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool game-probe --args '<json>'` |

**Use when:**

* proving what the running game is actually doing before and after an action — the evidence read of every playtest loop
* "show me the game right now with the player position" / "screenshot the offscreen instance and read the HUD values"
* a claim about motion, spawning, or a state change needs a frame-stamped before/after pair

**Do not use when:**

* the EDITED scene, not the running game — [`summer_world_snapshot`](/mcp/tools/run-and-test#summer_world_snapshot) / [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) target viewport
* no game is running (failure\_reason game\_not\_running) — [`summer_play`](/mcp/tools/run-and-test#summer_play) first
* requires an engine build with GameProbe (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op — fall back to [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree) + [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) target game

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `tree` | object | No | Include the live scene tree (\{name, class, path, children}) built game-side on the SAME frame as the pixels. Omit for no tree. |
| `tree.path` | string | No | Subtree root, ABSOLUTE runtime path (default the scene tree root). |
| `tree.depth` | integer | No | Depth to walk, 1..8 (default 2). |
| `tree.limit` | integer | No | Node cap, 1..4000 (default 200); truncated is reported. |
| `props` | string\[] | No | Up to 64 '\<absolute path>:\<property>' keys read on the same frame, e.g. '/root/Main/Player:position'. Values come back as Godot literal strings; misses land in `missing`. |
| `screenshot` | boolean | No | Capture the game viewport (default true). false = state only, cheaper and works when the window draws nothing. |
| `max_dim` | integer | No | Longest image side in pixels, 16..4096 (default 1280); the frame is downscaled to fit. |
| `instance` | string | No | Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer\_play \{instance, mode:'offscreen'}; summer\_game\_control action:'instances' lists the live ones. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "tree": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Subtree root, ABSOLUTE runtime path (default the scene tree root)."
          },
          "depth": {
            "type": "integer",
            "description": "Depth to walk, 1..8 (default 2)."
          },
          "limit": {
            "type": "integer",
            "description": "Node cap, 1..4000 (default 200); truncated is reported."
          }
        },
        "additionalProperties": false,
        "description": "Include the live scene tree ({name, class, path, children}) built game-side on the SAME frame as the pixels. Omit for no tree."
      },
      "props": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "description": "Up to 64 '<absolute path>:<property>' keys read on the same frame, e.g. '/root/Main/Player:position'. Values come back as Godot literal strings; misses land in `missing`."
      },
      "screenshot": {
        "type": "boolean",
        "description": "Capture the game viewport (default true). false = state only, cheaper and works when the window draws nothing."
      },
      "max_dim": {
        "type": "integer",
        "description": "Longest image side in pixels, 16..4096 (default 1280); the frame is downscaled to fit."
      },
      "instance": {
        "type": "string",
        "description": "Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer_play {instance, mode:'offscreen'}; summer_game_control action:'instances' lists the live ones."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_game_probe",
  "arguments": {}
}
```

***

### summer\_get\_board

On the local MCP (`summer-engine` npm).

Read the person's approved planning board for this game: the look (palette,
description, picture), the characters, the place, the first-minute storyboard
and the cards they picked, with a version that changes when the board does.

The look and the picked cards come back as images you can see. Read the board at
the start of each build step and compare your screenshot against its images
(palette, shapes and proportions, camera angle, composition); fix the biggest
difference. Use the picked card pictures as referenceImageUrl for
[`summer_generate_image`](/mcp/tools/create-assets#summer_generate_image) to make sprites and backgrounds in the same look.

Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
Requires authentication: run 'npx -y summer-engine\@latest login' first.

| | |
| - | - |
| **Needs** | Signed in: `summer login` |
| **Effects** | uses the network |
| **CLI** | `summer tool get-board --args '<json>'` |

**Use when:**

* starting a build step for a game made from a planning board
* "does the game look like the board?"
* comparing a screenshot with the chosen look and picked cards

**Do not use when:**

* the game was not made from a planning board (no project id in the brief)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `project` | string | Yes | The game's project id (given in your build brief) Length 1 to 100. |
| `pictures` | boolean | No | Also return the look and the picked cards as images (default true) |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "project": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100,
        "description": "The game's project id (given in your build brief)"
      },
      "pictures": {
        "type": "boolean",
        "description": "Also return the look and the picked cards as images (default true)"
      }
    },
    "required": [
      "project"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_get_board",
  "arguments": {
    "project": "<project>"
  }
}
```

***

### summer\_get\_console

On the local MCP (`summer-engine` npm).

Read recent messages from the editor's Output panel (print() output, editor-side warnings and errors).

SCOPE: the editor console ONLY. Runtime errors from a played game are collected by the debugger, not the console — right after [`summer_play`](/mcp/tools/run-and-test#summer_play) this tool can honestly report errors 0 while [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors) holds several. Never treat this tool alone as the post-play verdict: read [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics) (console + debugger + script errors together) first, then come here for message bodies. Every result carries a "\_scope" note restating this.

Output is post-processed for token economy: consecutive identical messages collapse into one entry with a "(×N)" count suffix, and the response carries a "\_filter" summary so you can see what was hidden. Message types come straight from the editor log (error / warning / std / editor); errors\_only=true (default) drops the std/editor lines — startup banners and print() output — and keeps errors and warnings. Use errors\_only=false to read print() output, raw=true to bypass all shaping.

Use after [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics) indicates console issues, or to check what your print() statements said.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool get-console --args '<json>'` |

**Use when:**

* diagnostics report console issues and you need the message bodies
* "what did the game print?" / "show me the log output"
* checking whether your print() statements fired

**Do not use when:**

* runtime errors with stack traces — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors)
* compile errors in a script you just edited — [`summer_get_script_errors`](/mcp/tools/run-and-test#summer_get_script_errors)
* the post-play verdict — a played game's runtime errors live in the debugger, so this can report 0 errors after a failing run; read [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics) first

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `max_lines` | number | No | Max lines to return after dedupe (default 100) Default `100`. |
| `filter` | string | No | Only return lines containing this string |
| `type` | "error" \| "warning" \| "std" \| "editor" | No | Filter by message type at the engine level |
| `errors_only` | boolean | No | Drop info/std noise, keep errors and warnings (default true) Default `true`. |
| `strict_errors` | boolean | No | Drop warnings too — return errors only Default `false`. |
| `raw` | boolean | No | Bypass dedupe and level filtering — return engine output verbatim Default `false`. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "max_lines": {
        "type": "number",
        "default": 100,
        "description": "Max lines to return after dedupe (default 100)"
      },
      "filter": {
        "type": "string",
        "description": "Only return lines containing this string"
      },
      "type": {
        "type": "string",
        "enum": [
          "error",
          "warning",
          "std",
          "editor"
        ],
        "description": "Filter by message type at the engine level"
      },
      "errors_only": {
        "type": "boolean",
        "default": true,
        "description": "Drop info/std noise, keep errors and warnings (default true)"
      },
      "strict_errors": {
        "type": "boolean",
        "default": false,
        "description": "Drop warnings too — return errors only"
      },
      "raw": {
        "type": "boolean",
        "default": false,
        "description": "Bypass dedupe and level filtering — return engine output verbatim"
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_get_console",
  "arguments": {}
}
```

***

### summer\_get\_debugger\_errors

On the local MCP (`summer-engine` npm).

Read runtime errors from the debugger. These occur while the game is running (null references, missing nodes, physics errors). Different from console output — these come from the debugger, not print statements.

For warning text, use [`summer_get_debugger_warnings`](/mcp/tools/run-and-test#summer_get_debugger_warnings) (separate tool — engine returns warning count here but not the bodies).

Output is deduped: identical errors firing every frame collapse into one entry with a "(×N)" count suffix. A "\_filter" summary tells you exactly what was collapsed or truncated. Use raw=true to bypass shaping when you really need every entry.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool get-debugger-errors --args '<json>'` |

**Use when:**

* the running game hit failures like null references or missing nodes
* "the game crashed while I was playing, what was it?"
* "why did my scene fail at runtime?"

**Do not use when:**

* the script will not even compile — [`summer_get_script_errors`](/mcp/tools/run-and-test#summer_get_script_errors)
* plain print output — [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `max_errors` | number | No | Max errors to return after dedupe Default `50`. |
| `include_stack` | boolean | No | Include stack traces for each error |
| `include_warnings` | boolean | No | Forward-compat flag — when engine supports it, returns warning bodies in the same call. Today the engine ignores it; use summer\_get\_debugger\_warnings instead. Default `false`. |
| `raw` | boolean | No | Bypass dedupe — return engine output verbatim Default `false`. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "max_errors": {
        "type": "number",
        "default": 50,
        "description": "Max errors to return after dedupe"
      },
      "include_stack": {
        "type": "boolean",
        "description": "Include stack traces for each error"
      },
      "include_warnings": {
        "type": "boolean",
        "default": false,
        "description": "Forward-compat flag — when engine supports it, returns warning bodies in the same call. Today the engine ignores it; use summer_get_debugger_warnings instead."
      },
      "raw": {
        "type": "boolean",
        "default": false,
        "description": "Bypass dedupe — return engine output verbatim"
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_get_debugger_errors",
  "arguments": {}
}
```

***

### summer\_get\_debugger\_warnings

On the local MCP (`summer-engine` npm).

Read runtime warnings from the debugger panel. Warnings are non-fatal issues the game flags during play: missing optional resources, dead signal connections, deprecated API use, large allocations, physics warnings, etc.

Returns structured entries with file/line/function/error\_descr/callstack — same shape as [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors) but filtered to severity = "warning". Engine-internal warnings without a source file are filtered out as noise.

Use this when [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics) shows a non-zero `debugger.warnings` count and you want to see what they actually say.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool get-debugger-warnings --args '<json>'` |

**Use when:**

* diagnostics show a non-zero debugger warning count
* "the debugger shows yellow warnings, what are they?"
* checking for deprecated calls or node configuration warnings after a run

**Do not use when:**

* errors, not warnings — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `max_warnings` | number | No | Max warnings to return after dedupe Default `50`. |
| `include_stack` | boolean | No | Include stack traces Default `true`. |
| `raw` | boolean | No | Bypass dedupe Default `false`. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "max_warnings": {
        "type": "number",
        "default": 50,
        "description": "Max warnings to return after dedupe"
      },
      "include_stack": {
        "type": "boolean",
        "default": true,
        "description": "Include stack traces"
      },
      "raw": {
        "type": "boolean",
        "default": false,
        "description": "Bypass dedupe"
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns structured entries with file/line/function/error\_descr/callstack — same shape as summer\_get\_debugger\_errors but filtered to severity = "warning".

```json Example call theme={null}
{
  "name": "summer_get_debugger_warnings",
  "arguments": {}
}
```

***

### summer\_get\_diagnostics

On the local MCP (`summer-engine` npm).

Quick overview of all errors and warnings from the editor console, the runtime debugger, and script errors together. Returns error counts and a guidance message.

ALWAYS call this FIRST before diving into [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console) or [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors). It tells you where to look. It is also THE post-play read: a played game's runtime errors land in the debugger section here (and in [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors)), never in the editor console — [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console) alone can honestly report errors 0 right after a play session that produced several.

By default the response is a prioritized view: errors first, then warnings, then a small capped tail of recent info/std noise. Counts (console totals, debugger totals) are always complete — only low-severity message bodies are trimmed, and a "\_view" block reports exactly what was suppressed. Pass includeAll: true for the full untrimmed engine payload.

Typical workflow after making changes or playing:
1\. [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics) — are there issues? (after a play session: check debugger.errors)
2\. If errors: [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors) (runtime, with stacks) or [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console) (editor output) for details
3\. Fix the issues
4\. [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics) again to verify

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool get-diagnostics --args '<json>'` |

**Use when:**

* first stop after any change, before targeted console or debugger reads
* verifying a fix removed the errors it targeted
* the post-play read after [`summer_play`](/mcp/tools/run-and-test#summer_play) or a RunVerification probe: runtime errors land in the debugger section here, never in the console

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `includeAll` | boolean | No | Return the full engine diagnostics payload untrimmed (no severity reordering, no info-noise cap). Default `false`. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "includeAll": {
        "type": "boolean",
        "default": false,
        "description": "Return the full engine diagnostics payload untrimmed (no severity reordering, no info-noise cap)."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns error counts and a guidance message.

```json Example call theme={null}
{
  "name": "summer_get_diagnostics",
  "arguments": {}
}
```

***

### summer\_get\_runtime\_tree

On the local MCP (`summer-engine` npm).

Scene tree of the RUNNING GAME — live runtime state, not the edited scene. Use it during playtests to see what actually spawned: dynamically created enemies/projectiles/UI, autoloads, pooled nodes — everything [`summer_get_scene_tree`](/mcp/tools/build#summer_get_scene_tree) (an EDITOR read) can never show. Inspecting live keeps the bug alive; stopping the game to look usually resets it.

Returns \{tree: \{name, class, path, children}, total\_nodes, truncated}. Depth/limit are capped and truncation is declared — never assume a capped tree is complete. Drill into one node's live properties with [`summer_inspect_runtime_node`](/mcp/tools/run-and-test#summer_inspect_runtime_node).

Needs a running game: fails with failure\_reason "game\_not\_running" otherwise — start with [`summer_play`](/mcp/tools/run-and-test#summer_play), then re-run.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool get-runtime-tree --args '<json>'` |

**Use when:**

* inspecting what actually spawned during a playtest without stopping the game
* "which enemies are actually alive right now?"
* "what did the spawner create?" / "is the autoload there?"

**Do not use when:**

* no game is running (failure\_reason game\_not\_running) — [`summer_play`](/mcp/tools/run-and-test#summer_play) first, or read the edited scene with [`summer_get_scene_tree`](/mcp/tools/build#summer_get_scene_tree)
* requires an engine build with GetRuntimeSceneTree (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `path` | string | No | Subtree root to read, e.g. '/root/Main/Enemies'. Omit for the scene tree root. |
| `depth` | integer | No | Maximum depth to walk (engine default 3). |
| `limit` | integer | No | Maximum nodes to return (engine default 500). |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Subtree root to read, e.g. '/root/Main/Enemies'. Omit for the scene tree root."
      },
      "depth": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Maximum depth to walk (engine default 3)."
      },
      "limit": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Maximum nodes to return (engine default 500)."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns \{tree: \{name, class, path, children}, total\_nodes, truncated}.

```json Example call theme={null}
{
  "name": "summer_get_runtime_tree",
  "arguments": {}
}
```

***

### summer\_get\_script\_errors

On the local MCP (`summer-engine` npm).

Check a GDScript file for parse/compile errors without running the game.

Use after writing or editing a .gd file to verify it compiles. Returns line numbers, error messages, and severity. Much faster than running the game to discover script errors.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool get-script-errors --args '<json>'` |

**Use when:**

* after writing or editing a .gd file, to verify it compiles
* "does this script compile?" / "is there a syntax error in player.gd?"
* the editor shows a red error marker on a .gd file

**Do not use when:**

* runtime failures while playing — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `path` | string | Yes | Script path, e.g. 'res\://scripts/player.gd' or 'res\://player\_controller.gd' |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Script path, e.g. 'res://scripts/player.gd' or 'res://player_controller.gd'"
      }
    },
    "required": [
      "path"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns line numbers, error messages, and severity.

```json Example call theme={null}
{
  "name": "summer_get_script_errors",
  "arguments": {
    "path": "res://scripts/player.gd"
  }
}
```

***

### summer\_inspect\_runtime\_node

On the local MCP (`summer-engine` npm).

Live properties of ONE node in the RUNNING GAME: \{node: \{path, class, properties, children\_names}} with a curated common-property set (transform, visibility, physics state, ...). The runtime counterpart of [`summer_inspect_node`](/mcp/tools/build#summer_inspect_node) (which reads the EDITED scene) — use it to answer "what are this enemy's actual stats right now", "where IS the player", "did that flag flip" without stopping the game and losing the state.

Find the path with [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree) first — runtime paths (e.g. '/root/Main/Enemies/Goblin3') often differ from edited-scene paths because nodes are spawned, renamed, or reparented at runtime.

Needs a running game: fails with failure\_reason "game\_not\_running" otherwise — start with [`summer_play`](/mcp/tools/run-and-test#summer_play), then re-run.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool inspect-runtime-node --args '<json>'` |

**Use when:**

* answering "where IS the player" or "what are this enemy's stats right now" during a playtest
* "how much health does the boss have right now?"
* "what is the player's actual velocity mid-jump?"

**Do not use when:**

* no game is running — [`summer_play`](/mcp/tools/run-and-test#summer_play) first; for the edited scene use [`summer_inspect_node`](/mcp/tools/build#summer_inspect_node)
* requires an engine build with GetRuntimeNode (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `path` | string | Yes | Runtime node path, e.g. '/root/Main/Player'. Get it from summer\_get\_runtime\_tree. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Runtime node path, e.g. '/root/Main/Player'. Get it from summer_get_runtime_tree."
      }
    },
    "required": [
      "path"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_inspect_runtime_node",
  "arguments": {
    "path": "/root/Main/Player"
  }
}
```

***

### summer\_is\_running

On the local MCP (`summer-engine` npm).

Check if the game is currently running. Returns the active scene path if running.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool is-running --args '<json>'` |

**Use when:**

* deciding whether to boot, restart, or capture the running game
* "is the game already open / still playing?"
* before [`summer_play`](/mcp/tools/run-and-test#summer_play) or a game-target screenshot, to avoid a double launch

**Do not use when:**

* you need the live scene contents — [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree)

**Inputs:**

No inputs.

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {}
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns the active scene path if running.

```json Example call theme={null}
{
  "name": "summer_is_running",
  "arguments": {}
}
```

***

### summer\_play

On the local MCP (`summer-engine` npm).

Start running the game in the engine. With no extra parameters the game runs inside Summer Engine's viewport (the 'main' instance) QUIETLY — see below.

QUIET BY DEFAULT (focus:false, PlayGame agent:true): the user is usually working on the same machine while you build, so a play must not take over their screen. Quiet play makes the EDITOR stay put: it does not switch the main screen to the Game tab, does not grab keyboard focus for the embedded game, ignores the game's later focus report, and skips the render-health self-check that would otherwise misread the untouched Game view as a GPU failure and flip the user's embed setting. Quiet play does NOT hide the game: it still runs embedded in the Game view (visible if the user already has that tab open), it is the running game for [`summer_is_running`](/mcp/tools/run-and-test#summer_is_running) / [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) target:'game' / [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics), and on engines without the background launch posture it does not change a user who has "Embed Game on Play" turned off — their game opens in its own OS window as always. Engines with the background posture (--summer-background, 0.5.66+) also launch the play child with that flag, so a separate-window game appears without activating or taking focus either. The result echoes agent\_quiet:true when honoured; a launch result WITHOUT agent\_quiet means the engine predates quiet play and most likely took focus — the tool adds posture\_note saying so. focus:true launches like the toolbar Play button (Game tab + focus): use it ONLY when the user is watching and asked to see the game come up.

After starting, confirm boot with [`summer_is_running`](/mcp/tools/run-and-test#summer_is_running) (boot time varies — never sleep a guessed delay), then [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics) for runtime errors. You can run a specific scene instead of the main scene — useful for testing individual levels or UI screens.

DETERMINISTIC RUNS (newer engines): seed / fixed\_fps / time\_scale pin THIS launch only (nothing is persisted). seed pins the game's GLOBAL RNG (randi/randf/randi\_range/randf\_range/randfn, Array.shuffle/pick\_random) — it does NOT pin RandomNumberGenerator instances, scripts that call randomize(), rand\_from\_seed, wall-clock reads, or thread/IO/audio timing. fixed\_fps decouples scene time from the wall clock so frame-count-derived state lands on the same frame run to run. The result's `determinism` block says whether the pins were applied (applied:false carries a reason: already\_running, editor\_run\_args\_override, launch\_not\_started) and restates seed\_scope. If the result has NO determinism block although you sent a pin, the engine predates the params and the run is NOT reproducible — the tool says so; do not claim otherwise. Omitting every pin and instance parameter is exactly the v1 launch.

PLAYTEST LAUNCH (engine runtime-control build): instance + mode:'offscreen' spawn a disposable hidden instance (at most 3) that [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) / [`summer_game_input`](/mcp/tools/run-and-test#summer_game_input) / [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) / `summer_runtime_`\* address by name — run two variants side by side for an A/B. deterministic:true (offscreen only) launches with --fixed-fps 60 --summer-seed --audio-driver Dummy and is what lets [`summer_game_input`](/mcp/tools/run-and-test#summer_game_input) action:'replay' accept a seed; speed sets the user time scale on session start. The instance result reports session\_attached; poll [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances' until attached:true before addressing it. Then: probe -> act -> step/probe -> assert (the agent-playtesting skill). Failure reasons: too\_many\_instances, instance\_exists, session\_timeout (child never attached — check [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console)), unsupported\_mode, main\_scene\_not\_configured. A game already running answers playing:true with determinism.applied:false — [`summer_stop`](/mcp/tools/run-and-test#summer_stop) first to apply seed/fixed\_fps.

MULTIPLAYER — LOCAL PLAY (Summer multiplayer games; engine with Local Play): players:N starts the game's authority headless plus N clients on this machine, each joining through the game's OWN Summer.client.join(SummerJoinTarget.queue(...)) — no account, login, Docker, CLI or local-only game code. The scenes and queue come from the project's summer.build.json and its WorldDefinition (client\_entry\_point + the headless\_engine component entry\_point); queue picks another declared queue — pass queue WITHOUT players to start that queue's minPlayers (a 4-player queue starts 4), or players to choose; spectators adds spectator clients. local\_play.warnings flags a roster outside the queue's minPlayers..maxPlayers. The result's local\_play block lists every process (label, role, persona) and the port; failure\_reason local\_play\_unavailable says exactly what to declare. Every process attaches to the editor debugger: screenshots, input and `summer_game_`\* go to the FIRST client, the authority's output is in [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console), and [`summer_stop`](/mcp/tools/run-and-test#summer_stop) stops them all. A join for a different queue fails with a message naming both queues. It is not a proof of sign-in, matchmaking or hosted admission (staging). No local\_play block although you sent players means the engine predates Local Play and ran ONE client — the tool says so.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool play --args '<json>'` |

**Use when:**

* runtime verification after edits
* the user wants to try the game or one level after a change
* reproducing a bug that only shows at runtime
* a deterministic, repeatable playtest run — pin the global RNG with seed and the timestep with fixed\_fps
* launching a deterministic or parallel offscreen playtest instance (seed, fixed frame rate, named offscreen instance) for the runtime tools to drive
* testing a Summer multiplayer game on this machine — players:2 (Local Play) runs its authority headless and two clients that join through the game's own Summer.client.join; no account, Docker or extra tools
* launching while the user works on the same machine — the default (focus omitted or false) sends PlayGame agent:true, so the editor does not switch to the Game tab or grab focus; the game still runs embedded and is visible to the runtime tools

**Do not use when:**

* the game is already running and needs a restart — [`summer_stop`](/mcp/tools/run-and-test#summer_stop) first (seed/fixed\_fps cannot reach a game that is already up)
* focus: true unless the user is watching and asked to see the game come up — it launches like the toolbar Play button (Game tab + focus). Quiet play does not hide the game; on engines without the background launch posture a user whose Embed Game on Play is off still gets the game in its own OS window as always, while engines with the posture (0.5.66+) also launch that window with --summer-background so it never takes focus
* only checking whether it is running — [`summer_is_running`](/mcp/tools/run-and-test#summer_is_running)
* instance/mode/deterministic need an engine build with the runtime-control ops (Summer Engine 0.5.66 or newer); older engines return for those parameters engine\_lacks\_op (plain play still works)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scene` | string | No | Scene to run instead of main scene, e.g. 'res\://levels/test\_level.tscn' |
| `instance` | string | No | Name a game instance. 'main' (default) = the editor's embedded game. Any other name with mode:'offscreen' spawns a disposable parallel instance (at most 3) that the runtime tools address by this name. |
| `mode` | "embedded" \| "offscreen" | No | 'embedded' (default) plays in the editor; 'offscreen' spawns a hidden child process (requires instance != 'main'). |
| `deterministic` | boolean | No | Offscreen only: launch with --fixed-fps 60 --summer-seed \<seed> --audio-driver Dummy so the run is reproducible and input replays with seed are accepted. |
| `seed` | integer | No | Pin the game's GLOBAL RNG for this launch (child gets --summer-seed \<seed>; default 20260725 when deterministic:true and seed is omitted). Pins randi/randf/randi\_range/randf\_range/randfn, Array.shuffle/pick\_random. Does NOT pin RandomNumberGenerator instances, scripts calling randomize(), or wall-clock reads. Omitted = randomized, as always. |
| `fixed_fps` | integer | No | Fixed timestep for this launch (child gets --fixed-fps \<n>): scene time decouples from the wall clock, the game runs as fast as it renders, frame-count-derived state repeats run to run, and at\_ms input timing / GameStep frames map exactly. Opt-in per launch, never the default. |
| `time_scale` | number | No | Engine time scale for this launch (child gets --time-scale \<f>). Not a determinism pin on its own. |
| `speed` | number | No | User time scale applied on session start, (0, 100] — e.g. 0.5 for half speed. |
| `players` | integer | No | Local Play for a Summer multiplayer game (engine with Local Play): start the game's authority headless plus this many clients on this machine, each joining through the game's own Summer.client.join. The scenes and queue come from the project's summer.build.json WorldDefinition; no account, Docker or game flags are needed. Omit it and pass queue to start the queue's minPlayers from summer.build.json; 0 forces one ordinary run. The result's local\_play block lists every process and persona, plus warnings when the roster is outside the queue's minPlayers..maxPlayers. Range 0 to 64. |
| `spectators` | integer | No | Local Play: also start this many spectator clients (only with players). Range 0 to …. |
| `queue` | string | No | Local Play: the summer.build.json queue the local World stands in for (default: the first declared queue). |
| `focus` | boolean | No | Default false = QUIET play (PlayGame agent:true): the editor does not switch to the Game tab or grab focus for the embedded game, so the user keeps working. Pass true to launch the way the toolbar Play button does (Game tab + focus) — only when the user is watching and asked to see it. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scene": {
        "type": "string",
        "description": "Scene to run instead of main scene, e.g. 'res://levels/test_level.tscn'"
      },
      "instance": {
        "type": "string",
        "description": "Name a game instance. 'main' (default) = the editor's embedded game. Any other name with mode:'offscreen' spawns a disposable parallel instance (at most 3) that the runtime tools address by this name."
      },
      "mode": {
        "type": "string",
        "enum": [
          "embedded",
          "offscreen"
        ],
        "description": "'embedded' (default) plays in the editor; 'offscreen' spawns a hidden child process (requires instance != 'main')."
      },
      "deterministic": {
        "type": "boolean",
        "description": "Offscreen only: launch with --fixed-fps 60 --summer-seed <seed> --audio-driver Dummy so the run is reproducible and input replays with seed are accepted."
      },
      "seed": {
        "type": "integer",
        "description": "Pin the game's GLOBAL RNG for this launch (child gets --summer-seed <seed>; default 20260725 when deterministic:true and seed is omitted). Pins randi/randf/randi_range/randf_range/randfn, Array.shuffle/pick_random. Does NOT pin RandomNumberGenerator instances, scripts calling randomize(), or wall-clock reads. Omitted = randomized, as always."
      },
      "fixed_fps": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Fixed timestep for this launch (child gets --fixed-fps <n>): scene time decouples from the wall clock, the game runs as fast as it renders, frame-count-derived state repeats run to run, and at_ms input timing / GameStep frames map exactly. Opt-in per launch, never the default."
      },
      "time_scale": {
        "type": "number",
        "exclusiveMinimum": 0,
        "description": "Engine time scale for this launch (child gets --time-scale <f>). Not a determinism pin on its own."
      },
      "speed": {
        "type": "number",
        "description": "User time scale applied on session start, (0, 100] — e.g. 0.5 for half speed."
      },
      "players": {
        "type": "integer",
        "minimum": 0,
        "maximum": 64,
        "description": "Local Play for a Summer multiplayer game (engine with Local Play): start the game's authority headless plus this many clients on this machine, each joining through the game's own Summer.client.join. The scenes and queue come from the project's summer.build.json WorldDefinition; no account, Docker or game flags are needed. Omit it and pass queue to start the queue's minPlayers from summer.build.json; 0 forces one ordinary run. The result's local_play block lists every process and persona, plus warnings when the roster is outside the queue's minPlayers..maxPlayers."
      },
      "spectators": {
        "type": "integer",
        "minimum": 0,
        "description": "Local Play: also start this many spectator clients (only with players)."
      },
      "queue": {
        "type": "string",
        "description": "Local Play: the summer.build.json queue the local World stands in for (default: the first declared queue)."
      },
      "focus": {
        "type": "boolean",
        "description": "Default false = QUIET play (PlayGame agent:true): the editor does not switch to the Game tab or grab focus for the embedded game, so the user keeps working. Pass true to launch the way the toolbar Play button does (Game tab + focus) — only when the user is watching and asked to see it."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: The result echoes agent\_quiet:true when honoured; a launch result WITHOUT agent\_quiet means the engine predates quiet play and most likely took focus — the tool adds posture\_note saying so. The result's `determinism` block says whether the pins were applied (applied:false carries a reason: already\_running, editor\_run\_args\_override, launch\_not\_started) and restates seed\_scope. The result's local\_play block lists every process (label, role, persona) and the port; failure\_reason local\_play\_unavailable says exactly what to declare.

```json Example call theme={null}
{
  "name": "summer_play",
  "arguments": {}
}
```

***

### summer\_recent\_events

On the local MCP (`summer-engine` npm).

Read the newest engine events in ONE zero-wait poll — what just happened (saves, plays, op receipts, script errors, imports, selection) and, above all, the CURSOR: its next\_seq is the `since` to hand [`summer_wait_for_event`](/mcp/tools/run-and-test#summer_wait_for_event) BEFORE you trigger the action you will wait on.

since omitted = the newest `limit` sequence numbers (a kinds filter applies inside that window, so fewer may come back). since:0 = the whole retained ring from the oldest, paged — pass next\_seq back as since while truncated is true. since:N = everything after N.

Each event is \{seq, kind, ts, data}; kinds (v1): op.applied, op.failed, script.error, play.started, play.stopped, scene.saved, scene.opened, import.completed, selection.changed, snapshot.published. The engine retains 512 events / 10 minutes — older history is gone, so an empty result is not proof nothing happened earlier. Payloads over 4 KB arrive clamped with truncated:true inside data. On an engine build without the events channel the result is a structured engine\_lacks\_events failure (nothing sent).

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer events` |

**Use when:**

* "what just happened in the engine?" / "show me the recent events" — the last saves, plays, op receipts, script errors, imports
* taking a cursor BEFORE triggering an action you will wait on — pass its next\_seq as `since` to [`summer_wait_for_event`](/mcp/tools/run-and-test#summer_wait_for_event)
* after a [`summer_wait_for_event`](/mcp/tools/run-and-test#summer_wait_for_event) timeout, reading everything that arrived since its next\_seq

**Do not use when:**

* blocking until something happens — [`summer_wait_for_event`](/mcp/tools/run-and-test#summer_wait_for_event)
* full console or debugger bodies — [`summer_get_console`](/mcp/tools/run-and-test#summer_get_console) / [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors) (events carry compact payloads, clamped at 4 KB)
* requires an engine build with the events channel (capabilities.events, /api/events; Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_events

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `since` | integer | No | Deliver events with seq > since. Omit = the newest `limit` sequence numbers. 0 = the whole retained ring from the oldest (paged: pass next\_seq back while truncated is true). Range 0 to …. |
| `kinds` | string\[] | No | Event kinds to deliver (default: all). v1 kinds: op.applied, op.failed, script.error, play.started, play.stopped, scene.saved, scene.opened, import.completed, selection.changed, snapshot.published. Unknown kinds are refused before waiting when the engine advertises its list. |
| `limit` | integer | No | Maximum events to return (default 50, cap 200). |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "since": {
        "type": "integer",
        "minimum": 0,
        "description": "Deliver events with seq > since. Omit = the newest `limit` sequence numbers. 0 = the whole retained ring from the oldest (paged: pass next_seq back while truncated is true)."
      },
      "kinds": {
        "type": "array",
        "items": {
          "type": "string",
          "minLength": 1
        },
        "description": "Event kinds to deliver (default: all). v1 kinds: op.applied, op.failed, script.error, play.started, play.stopped, scene.saved, scene.opened, import.completed, selection.changed, snapshot.published. Unknown kinds are refused before waiting when the engine advertises its list."
      },
      "limit": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Maximum events to return (default 50, cap 200)."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_recent_events",
  "arguments": {}
}
```

***

### summer\_runtime\_animate

On the local MCP (`summer-engine` npm).

Drive and read animation in the RUNNING game. target:'player' = an AnimationPlayer (cmd state|play|pause|stop|seek|speed — RuntimeAnimation); target:'tree' = an AnimationTree state machine (cmd state|travel|start|stop|set\_param|get\_param — RuntimeAnimationTree); target:'bones' = a Skeleton3D's live bone poses (GetRuntimeBones, read-only). Default cmd is 'state' (read-only), so the same tool answers "which clip is playing", "which state is the machine in" and "where is the hand bone" before and after an action.

player returns state \{current\_animation, assigned\_animation, position, length, speed\_scale, playing, animations\[]}. tree returns state \{active, current\_node, travel\_path, playing, position, length, fading\_from, parameters\{}}. bones returns \{skeleton: \{path, bone\_count, motion\_scale}, bones\[\{idx, name, parent, global\_pose\{origin, rotation, scale} | pose | rest}], truncated} — at most 256 bones per call; filter with bones\[] to page larger rigs. unknown\_animation / unknown\_state / unknown\_bone list the valid names in the error.

Animation is motion: one 'state' read proves a clip is assigned, not that it moves. Prove motion with [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step' between two reads (position advances, bone poses change) or two [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) frames. Needs a RUNNING game: failure\_reason game\_not\_running ([`summer_play`](/mcp/tools/run-and-test#summer_play) first), request\_failed (debug session still attaching — wait, retry), unknown\_instance ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances'), game\_breaked (the game sits at a breakpoint — continue it first), timeout (game wedged, minimized or breaked — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors), or [`summer_stop`](/mcp/tools/run-and-test#summer_stop) + [`summer_play`](/mcp/tools/run-and-test#summer_play)), unsupported (the running game predates the summer capture; restart it after updating). All runtime paths are ABSOLUTE ('/root/Main/Player') and come from [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) tree / [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree). target:'bones' and cmd:'state' still answer while the game is breaked; the mutating cmds do not. engine\_lacks\_op on an older build names the fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool runtime-animate --args '<json>'` |

**Use when:**

* checking which clip or state-machine state is actually playing in the running game, then driving it (play, seek, travel, set a blend parameter)
* "which animation is the player in right now?" / "travel the state machine to Attack and see if it blends"
* reading live bone poses to prove a rig moves or an IK target lands

**Do not use when:**

* wiring or authoring animation in the scene — character-animation-wiring / animation-tree skills
* no game is running (failure\_reason game\_not\_running) — [`summer_play`](/mcp/tools/run-and-test#summer_play) first
* requires an engine build with RuntimeAnimation / RuntimeAnimationTree / GetRuntimeBones (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `target` | "player" \| "tree" \| "bones" | Yes | 'player' = AnimationPlayer (RuntimeAnimation), 'tree' = AnimationTree state machine (RuntimeAnimationTree), 'bones' = Skeleton3D poses (GetRuntimeBones). |
| `path` | string | Yes | ABSOLUTE runtime path of the AnimationPlayer / AnimationTree / Skeleton3D. |
| `cmd` | "state" \| "play" \| "pause" \| "stop" \| "seek" \| "speed" \| "travel" \| "start" \| "set\_param" \| "get\_param" | No | Default 'state' (read-only). target:'player': state\|play\|pause\|stop\|seek\|speed. target:'tree': state\|travel\|start\|stop\|set\_param\|get\_param. Ignored for target:'bones'. |
| `name` | string | No | target:'player' play — clip name (unknown\_animation lists the clips). |
| `position` | number | No | target:'player' seek — position in seconds. |
| `speed` | number | No | target:'player' speed — speed\_scale (1.0 = normal, negative plays backwards). |
| `from_end` | boolean | No | target:'player' play — start from the end (backwards). |
| `blend` | number | No | target:'player' play — custom blend time in seconds. |
| `update` | boolean | No | target:'player' seek — apply the pose immediately (default true). |
| `state` | string | No | target:'tree' travel/start — state machine node name (unknown\_state lists them). |
| `reset` | boolean | No | target:'tree' travel/start — reset the destination clip (default true). |
| `param` | string | No | target:'tree' set\_param/get\_param — parameter path, e.g. 'parameters/Blend/blend\_amount'. |
| `value` | string \| number \| boolean | No | target:'tree' set\_param — new value (JSON scalar or Godot literal string). |
| `playback_path` | string | No | target:'tree' — state machine playback parameter (default 'parameters/playback'; nested machines use 'parameters/\<Node>/playback'). |
| `bones` | string\[] | No | target:'bones' — bone names to read (omit for the first 256; page larger rigs with this filter). |
| `space` | "global" \| "local" \| "both" | No | target:'bones' — 'global' (default, skeleton-space global\_pose), 'local' (pose), or 'both'. |
| `include_rest` | boolean | No | target:'bones' — also return each bone's rest Transform3D. |
| `instance` | string | No | Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer\_play \{instance, mode:'offscreen'}; summer\_game\_control action:'instances' lists the live ones. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "target": {
        "type": "string",
        "enum": [
          "player",
          "tree",
          "bones"
        ],
        "description": "'player' = AnimationPlayer (RuntimeAnimation), 'tree' = AnimationTree state machine (RuntimeAnimationTree), 'bones' = Skeleton3D poses (GetRuntimeBones)."
      },
      "path": {
        "type": "string",
        "description": "ABSOLUTE runtime path of the AnimationPlayer / AnimationTree / Skeleton3D."
      },
      "cmd": {
        "type": "string",
        "enum": [
          "state",
          "play",
          "pause",
          "stop",
          "seek",
          "speed",
          "travel",
          "start",
          "set_param",
          "get_param"
        ],
        "description": "Default 'state' (read-only). target:'player': state|play|pause|stop|seek|speed. target:'tree': state|travel|start|stop|set_param|get_param. Ignored for target:'bones'."
      },
      "name": {
        "type": "string",
        "description": "target:'player' play — clip name (unknown_animation lists the clips)."
      },
      "position": {
        "type": "number",
        "description": "target:'player' seek — position in seconds."
      },
      "speed": {
        "type": "number",
        "description": "target:'player' speed — speed_scale (1.0 = normal, negative plays backwards)."
      },
      "from_end": {
        "type": "boolean",
        "description": "target:'player' play — start from the end (backwards)."
      },
      "blend": {
        "type": "number",
        "description": "target:'player' play — custom blend time in seconds."
      },
      "update": {
        "type": "boolean",
        "description": "target:'player' seek — apply the pose immediately (default true)."
      },
      "state": {
        "type": "string",
        "description": "target:'tree' travel/start — state machine node name (unknown_state lists them)."
      },
      "reset": {
        "type": "boolean",
        "description": "target:'tree' travel/start — reset the destination clip (default true)."
      },
      "param": {
        "type": "string",
        "description": "target:'tree' set_param/get_param — parameter path, e.g. 'parameters/Blend/blend_amount'."
      },
      "value": {
        "type": [
          "string",
          "number",
          "boolean"
        ],
        "description": "target:'tree' set_param — new value (JSON scalar or Godot literal string)."
      },
      "playback_path": {
        "type": "string",
        "description": "target:'tree' — state machine playback parameter (default 'parameters/playback'; nested machines use 'parameters/<Node>/playback')."
      },
      "bones": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "description": "target:'bones' — bone names to read (omit for the first 256; page larger rigs with this filter)."
      },
      "space": {
        "type": "string",
        "enum": [
          "global",
          "local",
          "both"
        ],
        "description": "target:'bones' — 'global' (default, skeleton-space global_pose), 'local' (pose), or 'both'."
      },
      "include_rest": {
        "type": "boolean",
        "description": "target:'bones' — also return each bone's rest Transform3D."
      },
      "instance": {
        "type": "string",
        "description": "Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer_play {instance, mode:'offscreen'}; summer_game_control action:'instances' lists the live ones."
      }
    },
    "required": [
      "target",
      "path"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_runtime_animate",
  "arguments": {
    "target": "player",
    "path": "<path>"
  }
}
```

***

### summer\_runtime\_call

On the local MCP (`summer-engine` npm).

Call ONE method on a node in the RUNNING game and get its return value: 'take\_damage' \[25], 'get\_velocity', 'has\_method', 'start\_wave' \[3]. The direct way to trigger gameplay code and read its answer without wiring input or waiting for a timer.

Returns \{path, method, return, return\_type, return\_truncated, frame}; return is a Godot literal string for math values. Arguments are JSON scalars/arrays/objects or Godot literal strings ('Vector3(0, 1, 0)'); Object/RID arguments are refused (bad\_args). failure\_reason call\_error carries call\_error\_detail (the method raised); method\_not\_found means check [`summer_api_docs`](/mcp/tools/build#summer_api_docs) / the node's script.

Calling a method is an ACTION, not evidence: probe after it ([`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe)) to see what it did. Needs a RUNNING game: failure\_reason game\_not\_running ([`summer_play`](/mcp/tools/run-and-test#summer_play) first), request\_failed (debug session still attaching — wait, retry), unknown\_instance ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances'), game\_breaked (the game sits at a breakpoint — continue it first), timeout (game wedged, minimized or breaked — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors), or [`summer_stop`](/mcp/tools/run-and-test#summer_stop) + [`summer_play`](/mcp/tools/run-and-test#summer_play)), unsupported (the running game predates the summer capture; restart it after updating). All runtime paths are ABSOLUTE ('/root/Main/Player') and come from [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) tree / [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree). engine\_lacks\_op on an older build names the fallback; a game whose build predates the summer capture answers unsupported (no legacy fallback carries a return value).

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool runtime-call --args '<json>'` |

**Use when:**

* triggering gameplay code in the running game and reading its answer — take\_damage, start\_wave, get\_velocity
* "call take\_damage(25) on the boss in the running game" / "what does get\_velocity return right now?"
* a method's result is the fact you need and no property exposes it

**Do not use when:**

* a plain property read or write — [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) props / [`summer_runtime_set`](/mcp/tools/run-and-test#summer_runtime_set) are cheaper
* no game is running (failure\_reason game\_not\_running) — [`summer_play`](/mcp/tools/run-and-test#summer_play) first
* requires an engine build with CallRuntimeMethod (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `path` | string | Yes | ABSOLUTE runtime node path, e.g. '/root/Main/Player'. |
| `method` | string | Yes | Method name to call on the node, e.g. 'take\_damage', 'get\_velocity'. |
| `args` | any\[] | No | Positional arguments. JSON scalars, arrays and objects pass as-is; math values as Godot literal strings ('Vector3(0, 1, 0)'). Object and RID arguments are refused (bad\_args). |
| `instance` | string | No | Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer\_play \{instance, mode:'offscreen'}; summer\_game\_control action:'instances' lists the live ones. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "ABSOLUTE runtime node path, e.g. '/root/Main/Player'."
      },
      "method": {
        "type": "string",
        "description": "Method name to call on the node, e.g. 'take_damage', 'get_velocity'."
      },
      "args": {
        "type": "array",
        "items": {},
        "description": "Positional arguments. JSON scalars, arrays and objects pass as-is; math values as Godot literal strings ('Vector3(0, 1, 0)'). Object and RID arguments are refused (bad_args)."
      },
      "instance": {
        "type": "string",
        "description": "Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer_play {instance, mode:'offscreen'}; summer_game_control action:'instances' lists the live ones."
      }
    },
    "required": [
      "path",
      "method"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns \{path, method, return, return\_type, return\_truncated, frame}; return is a Godot literal string for math values.

```json Example call theme={null}
{
  "name": "summer_runtime_call",
  "arguments": {
    "path": "/root/Main/Player",
    "method": "take_damage"
  }
}
```

***

### summer\_runtime\_set

On the local MCP (`summer-engine` npm).

Set ONE property on a node in the RUNNING game — the live object, never the scene file (nothing is saved; the change dies with the run). Use it to put the game into the state you want to test without playing there by hand: teleport the player ('/root/Main/Player' position 'Vector3(0, 2, 0)'), set health to 1, flip a flag, toggle visibility.

Returns \{path, property, value\_before, value\_after, applied, transport, frame}. READ applied: false means the read-back did not match (failure\_reason not\_applied) — a script rewrites the value every frame, or the literal type was wrong. Math values in and out are Godot literal strings ('Vector3(1, 2, 3)'); 'script' and 'owner' are refused. For a persistent change edit the EDITED scene ([`summer_set_prop`](/mcp/tools/build#summer_set_prop)) and restart.

THE LOOP: [`summer_play`](/mcp/tools/run-and-test#summer_play) (add instance + mode:'offscreen' for a disposable instance; deterministic:true + seed for a reproducible run; fixed\_fps for exact timing) -> wait for boot ([`summer_is_running`](/mcp/tools/run-and-test#summer_is_running), or [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances' showing attached:true) -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) BEFORE (frame-stamped state + pixels) -> act ([`summer_runtime_set`](/mcp/tools/run-and-test#summer_runtime_set) / [`summer_runtime_call`](/mcp/tools/run-and-test#summer_runtime_call) / [`summer_game_input`](/mcp/tools/run-and-test#summer_game_input)) -> [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step' for exact frames, or let it run -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) AFTER -> assert on the two probes. Never claim something moved, spawned or fired without a probe that shows it.

Needs a RUNNING game: failure\_reason game\_not\_running ([`summer_play`](/mcp/tools/run-and-test#summer_play) first), request\_failed (debug session still attaching — wait, retry), unknown\_instance ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances'), game\_breaked (the game sits at a breakpoint — continue it first), timeout (game wedged, minimized or breaked — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors), or [`summer_stop`](/mcp/tools/run-and-test#summer_stop) + [`summer_play`](/mcp/tools/run-and-test#summer_play)), unsupported (the running game predates the summer capture; restart it after updating). All runtime paths are ABSOLUTE ('/root/Main/Player') and come from [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) tree / [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree). On an engine build that predates SetRuntimeProp the result is a structured engine\_lacks\_op failure naming the fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool runtime-set --args '<json>'` |

**Use when:**

* putting the running game into a test state without playing there by hand — teleport the player, set health to 1, flip a flag, toggle visibility
* "move the player to (0, 2, 0) in the running game" / "set the boss health to 1 while it is running"
* a runtime experiment whose change should die with the run instead of landing in the scene file

**Do not use when:**

* the change must persist — edit the scene with [`summer_set_prop`](/mcp/tools/build#summer_set_prop) and restart the game
* no game is running (failure\_reason game\_not\_running) — [`summer_play`](/mcp/tools/run-and-test#summer_play) first
* requires an engine build with SetRuntimeProp (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `path` | string | Yes | ABSOLUTE runtime node path, e.g. '/root/Main/Player' (from summer\_game\_probe tree). |
| `property` | string | Yes | Property name, e.g. 'position', 'health', 'visible'. 'script' and 'owner' are refused. |
| `value` | string \| number \| boolean | Yes | New value. JSON scalars for primitives; Godot literal strings for math types ('Vector3(1, 2, 3)', 'Color(1, 0, 0, 1)', 'Vector2(10, 0)'). Compared with Variant equality, so 1 and 1.0 both count as applied. |
| `field` | string | No | Optional sub-field to set instead of the whole value, e.g. 'x' on a Vector3 property. |
| `instance` | string | No | Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer\_play \{instance, mode:'offscreen'}; summer\_game\_control action:'instances' lists the live ones. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "ABSOLUTE runtime node path, e.g. '/root/Main/Player' (from summer_game_probe tree)."
      },
      "property": {
        "type": "string",
        "description": "Property name, e.g. 'position', 'health', 'visible'. 'script' and 'owner' are refused."
      },
      "value": {
        "type": [
          "string",
          "number",
          "boolean"
        ],
        "description": "New value. JSON scalars for primitives; Godot literal strings for math types ('Vector3(1, 2, 3)', 'Color(1, 0, 0, 1)', 'Vector2(10, 0)'). Compared with Variant equality, so 1 and 1.0 both count as applied."
      },
      "field": {
        "type": "string",
        "description": "Optional sub-field to set instead of the whole value, e.g. 'x' on a Vector3 property."
      },
      "instance": {
        "type": "string",
        "description": "Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer_play {instance, mode:'offscreen'}; summer_game_control action:'instances' lists the live ones."
      }
    },
    "required": [
      "path",
      "property",
      "value"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns \{path, property, value\_before, value\_after, applied, transport, frame}.

```json Example call theme={null}
{
  "name": "summer_runtime_set",
  "arguments": {
    "path": "/root/Main/Player",
    "property": "position",
    "value": "<value>"
  }
}
```

***

### summer\_runtime\_spawn

On the local MCP (`summer-engine` npm).

Spawn a PackedScene into the RUNNING game (action:'spawn' — SpawnRuntimeScene) or free a live node (action:'free' — FreeRuntimeNode). Stage a test in seconds: drop three goblins under '/root/Main/Enemies' with props \{position: 'Vector3(4, 0, -2)', health: 10}, or remove the boss to test the empty-arena path. Nothing touches the scene file.

spawn returns \{node: \{path, class}, renamed\_to?, prop\_warnings\[], frame} — use node.path (absolute) for follow-up set/call/probe; unknown props land in prop\_warnings, they never fail the spawn. free returns \{path, queued, freed}: mode 'queue\_free' (default) frees at the end of the frame, so a probe on the SAME frame may still list the node — step one frame ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step') before asserting it is gone; 'free' is immediate. The scene root cannot be freed (refused\_root).

THE LOOP: [`summer_play`](/mcp/tools/run-and-test#summer_play) (add instance + mode:'offscreen' for a disposable instance; deterministic:true + seed for a reproducible run; fixed\_fps for exact timing) -> wait for boot ([`summer_is_running`](/mcp/tools/run-and-test#summer_is_running), or [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances' showing attached:true) -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) BEFORE (frame-stamped state + pixels) -> act ([`summer_runtime_set`](/mcp/tools/run-and-test#summer_runtime_set) / [`summer_runtime_call`](/mcp/tools/run-and-test#summer_runtime_call) / [`summer_game_input`](/mcp/tools/run-and-test#summer_game_input)) -> [`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'step' for exact frames, or let it run -> [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) AFTER -> assert on the two probes. Never claim something moved, spawned or fired without a probe that shows it.

Needs a RUNNING game: failure\_reason game\_not\_running ([`summer_play`](/mcp/tools/run-and-test#summer_play) first), request\_failed (debug session still attaching — wait, retry), unknown\_instance ([`summer_game_control`](/mcp/tools/run-and-test#summer_game_control) action:'instances'), game\_breaked (the game sits at a breakpoint — continue it first), timeout (game wedged, minimized or breaked — [`summer_get_debugger_errors`](/mcp/tools/run-and-test#summer_get_debugger_errors), or [`summer_stop`](/mcp/tools/run-and-test#summer_stop) + [`summer_play`](/mcp/tools/run-and-test#summer_play)), unsupported (the running game predates the summer capture; restart it after updating). All runtime paths are ABSOLUTE ('/root/Main/Player') and come from [`summer_game_probe`](/mcp/tools/run-and-test#summer_game_probe) tree / [`summer_get_runtime_tree`](/mcp/tools/run-and-test#summer_get_runtime_tree). engine\_lacks\_op on an older build names the fallback ([`summer_instantiate_scene`](/mcp/tools/build#summer_instantiate_scene) / [`summer_remove_node`](/mcp/tools/build#summer_remove_node) in the EDITED scene, then restart).

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool runtime-spawn --args '<json>'` |

**Use when:**

* staging a runtime test — place three goblins under the spawner, add a pickup next to the player, remove the boss to test the empty-arena path
* "spawn a goblin at (4, 0, -2) in the running game" / "delete that projectile while the game runs"
* the spawned or freed node must exist only for this run

**Do not use when:**

* the node should be part of the scene permanently — [`summer_instantiate_scene`](/mcp/tools/build#summer_instantiate_scene) / [`summer_remove_node`](/mcp/tools/build#summer_remove_node) in the EDITED scene, then restart
* no game is running (failure\_reason game\_not\_running) — [`summer_play`](/mcp/tools/run-and-test#summer_play) first
* requires an engine build with SpawnRuntimeScene / FreeRuntimeNode (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `action` | "spawn" \| "free" | Yes | 'spawn' instantiates a PackedScene under parent (SpawnRuntimeScene); 'free' removes a node (FreeRuntimeNode). |
| `parent` | string | No | action:'spawn' — ABSOLUTE runtime path of the parent, e.g. '/root/Main/Enemies'. |
| `scene` | string | No | action:'spawn' — PackedScene to instantiate, e.g. 'res\://enemies/goblin.tscn'. |
| `name` | string | No | action:'spawn' — node name; the engine renames on collision and reports renamed\_to. |
| `props` | object | No | action:'spawn' — properties set on the new node before it enters the tree (\{position: 'Vector3(0, 1, 0)', health: 50}). Unknown ones land in prop\_warnings, never fail the spawn. |
| `path` | string | No | action:'free' — ABSOLUTE runtime path of the node to free. |
| `mode` | "queue\_free" \| "free" | No | action:'free' — 'queue\_free' (default, safe: frees at the end of the frame) or 'free' (immediate). |
| `instance` | string | No | Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer\_play \{instance, mode:'offscreen'}; summer\_game\_control action:'instances' lists the live ones. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "action": {
        "type": "string",
        "enum": [
          "spawn",
          "free"
        ],
        "description": "'spawn' instantiates a PackedScene under parent (SpawnRuntimeScene); 'free' removes a node (FreeRuntimeNode)."
      },
      "parent": {
        "type": "string",
        "description": "action:'spawn' — ABSOLUTE runtime path of the parent, e.g. '/root/Main/Enemies'."
      },
      "scene": {
        "type": "string",
        "description": "action:'spawn' — PackedScene to instantiate, e.g. 'res://enemies/goblin.tscn'."
      },
      "name": {
        "type": "string",
        "description": "action:'spawn' — node name; the engine renames on collision and reports renamed_to."
      },
      "props": {
        "type": "object",
        "additionalProperties": {
          "type": [
            "string",
            "number",
            "boolean"
          ]
        },
        "description": "action:'spawn' — properties set on the new node before it enters the tree ({position: 'Vector3(0, 1, 0)', health: 50}). Unknown ones land in prop_warnings, never fail the spawn."
      },
      "path": {
        "type": "string",
        "description": "action:'free' — ABSOLUTE runtime path of the node to free."
      },
      "mode": {
        "type": "string",
        "enum": [
          "queue_free",
          "free"
        ],
        "description": "action:'free' — 'queue_free' (default, safe: frees at the end of the frame) or 'free' (immediate)."
      },
      "instance": {
        "type": "string",
        "description": "Game instance to address (default 'main' = the editor's embedded game). Offscreen instances are named by summer_play {instance, mode:'offscreen'}; summer_game_control action:'instances' lists the live ones."
      }
    },
    "required": [
      "action"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_runtime_spawn",
  "arguments": {
    "action": "spawn"
  }
}
```

***

### summer\_scene\_audit

On the local MCP (`summer-engine` npm).

Audit a 3D scene in one fast, read-only call: every node and subnode is walked and likely visual and placement problems come back as a short list, sorted by severity, so you know exactly where to look. Each issue is a flag to LOOK at, never an auto-fix.

Checks (each can be picked with checks):

* through\_hole: capped ray grids through every facade line (coplanar wall fronts); clusters of rays that pass the wall AND reach the far side of the building (a door insert narrower than its frame, a missing module, a seam). Walls, structure and inserts close an opening; props do not.
* exposed\_edge: open outline edges of walls and facade members (bands, piers, corner blocks; cached per mesh) that nothing covers within 4-7 mm, seen from walkable space (eye points over open-sky floor cells, flood-filled from the scene's cameras and characters, sight lines not through a building), with a 2-60 cm reveal behind them, or a coplanar sheet continuing within 4 cm (seam) or 35 cm (gap). Warn on band pieces and when depth\_step agrees, look otherwise; repeats group per piece type.
* open\_fixture\_end: open ends of run pieces (a mesh with two or more open rims of one size: pipe, duct or gutter sections, elbows, tees) that nothing joins within 2.5 cm (sleeve tolerance), seen from walkable space: a missing elbow, coupler, section or end piece (warn). A piece with one rim or rims of different sizes (a cap, a funnel, an outlet) is open by design.
* depth\_step (look): ray rows across each visible facade side at its band levels and every 1.25 m: band recesses and missing runs, seams, modules standing proud, holes with something behind them; a hole next to a through\_hole confirms it (warn -> error); runs a door-like opening covers are skipped.
* floor\_gap: down rays over the floor tiles, along their seams, and from each tile edge to a wall within 1 m (bare strips at wall bases), classified by the FIRST surface hit: the void (error); an underlay over the floor's own lower surface such as a drain channel ("covers the floor", warn, not a hole); an underlay through a hole or strip. Areas are the missed rays' own footprints with the strip's size; a tile whose own mesh has holes is one issue for the piece.
* floating / sunken: support under each prop's footprint; gap over 2 cm, embed over 3 cm, measured against the first surface from above (the tile it is buried in, not the underlay under it); structure and pieces touching a wall well off the ground excluded.
* interpenetration: each prop's convex hulls against props, walls, structure and inserts; a deepest overlap over 3 cm, then every partner it cuts (up to 3).
* orientation (look): long props within 1.2 m of a wall more than 15 deg off parallel. It never says which way a piece should face.
* uv\_stretch: per mesh resource, large triangles whose UV-to-world mapping is stretched over 8:1 or collapsed to a line, where an instance shows them (a reveal that inserts normally cover = warn).
* duplicate: the same scene at the same transform.
* z\_fight: coplanar overlapping faces anywhere, from each mesh's planar face groups: between any two pieces (props, roofs, ledges, side walls, inserts against hosts, decals; opposite-facing only when both are double-sided) and between two surfaces of one mesh (once per mesh), plus the ray samples. Coplanar = a gap under twice the 24-bit depth step at the view distance (the nearest walkable eye point, camera or bookmark; 30 m without one) for the main camera's near/far; ev carries the overlap, gap, tolerance, surfaces, the plane's normal and the axis to nudge (nudge.world, nudge.local: a window can share its head or a jamb with its host, not its front). Warn over 0.05 m2 seen from a viewpoint; the material decides what is demoted to look, with the reason: render\_priority, depth or normal offsets, a see-through (alpha) material, no depth test or depth writes.
* lights: meshes paired with more than the per-object limit of omni or spot lights (Compatibility: 8 each; light cuts off at seams), spot lights with spot\_angle\_attenuation under 3; shadowed light counts.
* transform: NaN, negative or non-uniform scale, a piece far out of bounds, and pieces left at the origin: an identity LOCAL transform under an identity parent, touching nothing and not one of a row of siblings (several sharing it warn, one alone is look; a module whose corner is the world origin is placed).
* resource: missing dependency files, MeshInstance3D without a mesh, surfaces without a material.

budget\_ms (default 3000) bounds the editor time: each check gets a weighted share of it, and a check past its share stops and is marked counts.\<check>.partial (the share it covered), never clean. The three gap detectors (exposed\_edge, open\_fixture\_end, depth\_step) run last on the time the other checks leave (band pieces and band rows first); rerun them alone when partial.

accept:\[\{key, reason}] (keys from issues\[].key) records look and warn items you judged fine in res\://.summer/audit-accept.json (the only file the audit writes); later audits count them (counts.\<check>.accepted) but hide them until their evidence changes materially (severity rises, or the measured size moves by over 25%: shown again with accept\_stale). Errors cannot be accepted; show\_accepted lists them.

Returns at most 5 KB of JSON: counts per check, time per check (editor ms), the scene size, and ONE page of issues \{n, check, sev (error|warn|look), path, pos (world), why, ev (evidence numbers: sizes, distances, angles, the ray to reproduce), next (the tool to use next), key (for accept)}. Page with offset/limit (next\_offset names the next page); filter with checks, root (a subtree) and min\_severity. render:"sheet" adds ONE inline image of the page's first 6 issues framed up close from their open side (a camera never behind a wall), each tile labelled #n.

Every check works on any scene from geometry, the engine's node and resource data and materials alone: never node or file names, never kit metadata. Each piece's role comes from its shape: see-through cards (every material alpha) are dressing; thin upright sheets are walls (facing their side with more face area); flat slabs whose tops cover their footprint are floors (raised ones with walls under them are roofs, a large one below the rest is the underlay); a piece inside a wall's rectangle that reaches through the wall where it is open is an insert; bands, piers and corner blocks touching a facade wall are structure; the rest are props up to 3.5 m.

Read-only: it audits the SAVED file in a private offscreen copy (ScenePreview), so the open tab never becomes unsaved, undo is untouched and nothing is saved (accept writes only its own file). Save the scene first. Failures are structured (failure\_reason), never a silent fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool scene-audit --args '<json>'` |

**Use when:**

* after each build stage of a 3D level or kit-built street: "what is wrong with this scene and where exactly?"
* before calling a scene done: find see-through walls (an insert narrower than its frame), floor tiles with holes, props floating, sunk or poking into walls
* facade bands with an open end or a gap, seams between modules and modules out of line seen from the street; a duct or downpipe with a missing elbow, coupler or section
* the same look items keep coming back: accept the ones you judged fine (accept:\[\{key, reason}]) so later audits count them but hide them until their evidence changes
* finding props turned the wrong way against a wall (a bench standing perpendicular to it), stretched texture strips, duplicated pieces
* flickering faces: coplanar overlaps anywhere (props, roofs, ledges, inserts against hosts, decals, two surfaces of one mesh), judged by the 24-bit depth precision at the view distance
* bare ground at wall bases, an underlay plane covering drain channels, buried props: floor\_gap and sunken say which surface a ray hits first
* a lit city scene in the Compatibility renderer: meshes lit by more than the per-object light limit, hard spot rims

**Do not use when:**

* you already know the spot: look at it with [`summer_frame_nodes`](/mcp/tools/run-and-test#summer_frame_nodes) or [`summer_zoom`](/mcp/tools/run-and-test#summer_zoom), measure it with [`summer_raycast`](/mcp/tools/build#summer_raycast) or [`summer_measure`](/mcp/tools/build#summer_measure)
* the question is gameplay or reachability: [`summer_navigation_probe`](/mcp/tools/build#summer_navigation_probe), [`summer_play`](/mcp/tools/run-and-test#summer_play)
* the scene has unsaved edits you want audited: it reads the SAVED file, so save first
* requires ScenePreview (any current Summer Engine); an engine without it answers engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scenePath` | string | No | Scene to audit, e.g. "res\://levels/town.tscn" (the SAVED file). Omit for the scene open in the editor. |
| `checks` | "through\_hole" \| "floor\_gap" \| "floating" \| "sunken" \| "interpenetration" \| "exposed\_edge" \| "open\_fixture\_end" \| "orientation" \| "uv\_stretch" \| "duplicate" \| "z\_fight" \| "depth\_step" \| "lights" \| "transform" \| "resource"\[] | No | Only these checks (default: all 15). through\_hole, floor\_gap, floating, sunken, interpenetration, exposed\_edge, open\_fixture\_end, orientation, uv\_stretch, duplicate, z\_fight, depth\_step, lights, transform, resource. Fewer checks run faster (duplicate, lights, transform and resource need no physics); rerun a check budget\_ms left partial on its own. Items 1 to 15. |
| `root` | string | No | Report only issues under this node (subtree), e.g. "Block3". The whole scene is still loaded so walls and floors outside it count as surroundings. |
| `min_severity` | "error" \| "warn" \| "look" | No | "error" (only errors), "warn" (errors and warnings) or "look" (default: everything, including look items that only ask you to look). |
| `offset` | integer | No | Skip this many issues of the sorted, filtered list (paging; the result names the next offset). Range 0 to 100000. |
| `limit` | integer | No | Issues per page (default 15, max 50). The result is capped at 5 KB; a page that does not fit is cut and says so. Range 1 to 50. |
| `budget_ms` | integer | No | Editor time for the whole audit (default 3000 ms, 250-60000). Each check gets a share weighted by its usual cost (unused time passes on); a check past its share stops and counts.\<check>.partial gives the share it covered. Under load, rerun the partial checks with checks:\[...] or a larger budget. Range 250 to 60000. |
| `accept` | object\[] | No | Accept look or warn items you judged fine (up to 50 per call): \[\{key, reason}] with keys from this scene's issues. They are written to res\://.summer/audit-accept.json (the only file the audit writes), then counted (counts.\<check>.accepted) but hidden on every later audit, until their evidence changes materially (severity rises, or the measured size moves by over 25%): then they show again with accept\_stale. Errors cannot be accepted. Items 0 to 50. |
| `accept[].key` | string | Yes | The issue's key from a previous result (issues\[].key). Length 8 to 360. |
| `accept[].reason` | string | Yes | Why it is fine, e.g. "coplanar faces hidden behind a sign". Length 3 to 200. |
| `show_accepted` | boolean | No | List accepted items too (each with its accepted reason). Default false: hidden, only counted. |
| `render` | "sheet" \| "none" | No | "sheet": also return ONE inline image of the first 6 issues of this page framed up close from their open side, each tile labelled with its issue number. Default "none" (no image). |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scenePath": {
        "type": "string",
        "description": "Scene to audit, e.g. \"res://levels/town.tscn\" (the SAVED file). Omit for the scene open in the editor."
      },
      "checks": {
        "type": "array",
        "items": {
          "type": "string",
          "enum": [
            "through_hole",
            "floor_gap",
            "floating",
            "sunken",
            "interpenetration",
            "exposed_edge",
            "open_fixture_end",
            "orientation",
            "uv_stretch",
            "duplicate",
            "z_fight",
            "depth_step",
            "lights",
            "transform",
            "resource"
          ]
        },
        "minItems": 1,
        "maxItems": 15,
        "description": "Only these checks (default: all 15). through_hole, floor_gap, floating, sunken, interpenetration, exposed_edge, open_fixture_end, orientation, uv_stretch, duplicate, z_fight, depth_step, lights, transform, resource. Fewer checks run faster (duplicate, lights, transform and resource need no physics); rerun a check budget_ms left partial on its own."
      },
      "root": {
        "type": "string",
        "description": "Report only issues under this node (subtree), e.g. \"Block3\". The whole scene is still loaded so walls and floors outside it count as surroundings."
      },
      "min_severity": {
        "type": "string",
        "enum": [
          "error",
          "warn",
          "look"
        ],
        "description": "\"error\" (only errors), \"warn\" (errors and warnings) or \"look\" (default: everything, including look items that only ask you to look)."
      },
      "offset": {
        "type": "integer",
        "minimum": 0,
        "maximum": 100000,
        "description": "Skip this many issues of the sorted, filtered list (paging; the result names the next offset)."
      },
      "limit": {
        "type": "integer",
        "minimum": 1,
        "maximum": 50,
        "description": "Issues per page (default 15, max 50). The result is capped at 5 KB; a page that does not fit is cut and says so."
      },
      "budget_ms": {
        "type": "integer",
        "minimum": 250,
        "maximum": 60000,
        "description": "Editor time for the whole audit (default 3000 ms, 250-60000). Each check gets a share weighted by its usual cost (unused time passes on); a check past its share stops and counts.<check>.partial gives the share it covered. Under load, rerun the partial checks with checks:[...] or a larger budget."
      },
      "accept": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "key": {
              "type": "string",
              "minLength": 8,
              "maxLength": 360,
              "description": "The issue's key from a previous result (issues[].key)."
            },
            "reason": {
              "type": "string",
              "minLength": 3,
              "maxLength": 200,
              "description": "Why it is fine, e.g. \"coplanar faces hidden behind a sign\"."
            }
          },
          "required": [
            "key",
            "reason"
          ],
          "additionalProperties": false
        },
        "maxItems": 50,
        "description": "Accept look or warn items you judged fine (up to 50 per call): [{key, reason}] with keys from this scene's issues. They are written to res://.summer/audit-accept.json (the only file the audit writes), then counted (counts.<check>.accepted) but hidden on every later audit, until their evidence changes materially (severity rises, or the measured size moves by over 25%): then they show again with accept_stale. Errors cannot be accepted."
      },
      "show_accepted": {
        "type": "boolean",
        "description": "List accepted items too (each with its accepted reason). Default false: hidden, only counted."
      },
      "render": {
        "type": "string",
        "enum": [
          "sheet",
          "none"
        ],
        "description": "\"sheet\": also return ONE inline image of the first 6 issues of this page framed up close from their open side, each tile labelled with its issue number. Default \"none\" (no image)."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns at most 5 KB of JSON: counts per check, time per check (editor ms), the scene size, and ONE page of issues \{n, check, sev (error|warn|look), path, pos (world), why, ev (evidence numbers: sizes, distances, angles, the ray to reproduce), next (the tool to use next), key (for accept)}.

```json Example call theme={null}
{
  "name": "summer_scene_audit",
  "arguments": {}
}
```

***

### summer\_screenshot

On the local MCP (`summer-engine` npm).

Capture a frame from Summer Engine and return it as an image you can look at directly.

Use this to visually verify your work: scene layout, asset placement, scale, framing, missing/untextured assets, or runtime gameplay state. You see the actual pixels — no description layer in between. Lighting and materials are only truthfully shown by the "viewport" and "game" targets — see the note on "scene" below.

target:
"viewport" (default) — the editor's CURRENT view (whatever scene/tab is open). No game boot. Use for edit-time checks of how the scene looks right now.
"scene" — an OFFSCREEN render of a scene file (no game boot; scripts do not run, physics/particles/animations are static at t=0, so runtime-hidden UI shows as saved). Optionally pass scenePath/framing/size/nodePath. Use for COMPOSITION, SCALE and FRAMING without touching the editor's open tab.
With the preset framings (iso/top/...), it does NOT use the scene's environment/sky, and it injects a synthetic camera and light when the scene has none. The scene's WorldEnvironment — sky, fog, tonemap, glow, SSAO, ambient — is replaced by a flat preview environment. So those framings CANNOT verify lighting, mood, or any material property that depends on the environment: change them and the frame comes back identical.
framing:"camera" is the exception and the trustworthy way to check lighting edit-time: it renders through the scene's OWN current/first Camera3D (or the one named by camera\_path) with the scene's REAL WorldEnvironment — sky, fog, tonemap, glow, ambient all live. Use it before/after any lighting, environment, or emissive-material change, and to see the scene the way the played game will actually frame it.
STABLE VIEWPOINTS (newer engines): framing:"bookmark" + bookmark\_name renders from a pose saved with [`summer_camera_bookmark`](/mcp/tools/run-and-test#summer_camera_bookmark), and framing:"free" from an explicit camera\_position / camera\_look\_at (+ fov). Both are fixed synthetic poses rendered with the scene's REAL WorldEnvironment, and the same pose every time — the way to take before/after frames that actually line up. marks:true draws a Set-of-Mark overlay (numbered tags + boxes over the largest visible 3D nodes, up to max\_marks) and the caption lists label -> node path, so you can say "label 3 (Props/Crate\_02) is floating" instead of guessing. The scene file is never touched. 2D scenes render normally with marks\_unsupported. Older engines resolve the new framings to a preset and ignore marks — the caption says so; a frame from such a build is NOT pose-stable.
"game" — a frame from the RUNNING game (real runtime state). Start the game first ([`summer_play`](/mcp/tools/run-and-test#summer_play)). Works over the plain local connection on current Summer Engine builds (verified on 0.5.65, about 1.4 s); if a build refuses with bridge\_required the result says so and names the alternatives.

BLANK FRAMES ARE A CAPTURE CONDITION, NOT A SCENE FACT. A uniformly black/grey "viewport" frame means the editor had not redrawn its viewport texture when it was read (typically right after a tab switch or a scene mutation) — not that the scene is dark or has no camera. Every frame is content-checked; a flat viewport frame is recaptured once automatically and the caption says what happened. Never conclude anything about lights, cameras, or content from a blank frame: recapture first.

Static frame only — one moment, not motion. For a SEQUENCE of frames over time, or for anything lighting-dependent on an engine build without framing:"camera", use a RunVerification probe's save\_frame(name) — its instance has a real renderer.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool screenshot --args '<json>'` |

**Use when:**

* visually verifying layout, scale, framing, or runtime state
* "show me what the scene looks like right now"
* "did the model land where I put it?" / "does the UI overlap?"
* "take a screenshot of the game" / "screenshot the running game" — target "game" grabs the live frame
* before/after frames from the SAME viewpoint (framing "bookmark" / "free"), or an annotated screenshot with numbered labels mapped to node paths (marks)
* "what changed since the last render of this view?" — framing "bookmark" with compare\_previous returns previous | now | difference map in one image

**Do not use when:**

* verifying lighting, mood, or environment-dependent materials with a preset framing (iso/top/front/...) of the scene target - those replace the WorldEnvironment with a flat preview; use framing "camera" (the scene's own Camera3D + real environment), a fixed pose ("free" / "bookmark", real environment too), or the game target instead
* capturing motion or a sequence of frames (use a RunVerification probe's save\_frame)
* treating a uniformly black or grey "viewport" frame as evidence about lights, cameras, or content - the editor had not redrawn its viewport texture yet; the tool recaptures once and says so in the caption, otherwise recapture yourself
* saving or listing the viewpoints themselves — [`summer_camera_bookmark`](/mcp/tools/run-and-test#summer_camera_bookmark)
* fitting a camera to nodes with the real environment, many views in one image, debug views, zooms or automatic framing — [`summer_frame_nodes`](/mcp/tools/run-and-test#summer_frame_nodes), [`summer_shot_sheet`](/mcp/tools/run-and-test#summer_shot_sheet), [`summer_debug_views`](/mcp/tools/run-and-test#summer_debug_views), [`summer_zoom`](/mcp/tools/run-and-test#summer_zoom), [`summer_frame_shot`](/mcp/tools/run-and-test#summer_frame_shot)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `target` | "viewport" \| "scene" \| "game" | No | "viewport" = editor current view (default), "scene" = offscreen render of a scene file, "game" = running game frame Default `"viewport"`. |
| `scenePath` | string | No | target:"scene" only. Full scene path, e.g. "res\://main.tscn". Omit to render the currently-open scene. |
| `framing` | "auto" \| "iso" \| "top" \| "front" \| "back" \| "left" \| "right" \| "camera" \| "free" \| "bookmark" | No | target:"scene" only, 3D scenes. Camera direction preset: "iso" = 3/4 diagonal view, "top" = straight down, "front" = camera at +Z, "back" = camera at -Z, "left" = camera at -X, "right" = camera at +X. "auto" (default) is an alias of "iso". "camera" = render through the scene's OWN Camera3D with its REAL WorldEnvironment — the only edit-time framing that truthfully shows lighting/mood. "bookmark" (+ bookmark\_name) = the fixed pose saved with summer\_camera\_bookmark; "free" (+ camera\_position, camera\_look\_at, fov) = an explicit fixed pose. Both keep the REAL WorldEnvironment and repeat exactly, so before/after frames line up. The result reports the resolved framing — an older engine echoes a preset instead, and the caption warns. |
| `bookmark_name` | string | No | framing:"bookmark" only (implies it when framing is omitted). Name of a bookmark saved with summer\_camera\_bookmark (list them with action:"list"). Fails with failure\_reason "unknown\_bookmark" (+ the available names) when it does not exist. |
| `size` | integer\[] | No | target:"scene" only. Output image \[width, height] in pixels. Items 2 to 2. |
| `nodePath` | string | No | target:"scene" only. Node path relative to the scene root (e.g. "Player/Mesh") to frame INSTEAD of the whole scene — the camera fits that node's combined bounds (3D visual AABBs or 2D rects, children included). A bare unique name is also found recursively. Fails with failure\_reason "node\_not\_found" when the path does not resolve (no silent whole-scene fallback). Cannot combine with a fixed pose (framing "camera"/"free"/"bookmark"). |
| `camera_path` | string | No | framing:"camera" only. Path of the Camera3D to render through (relative to the scene root) when the scene has several cameras or none marked current. Omit to use the scene's current/first Camera3D. |
| `camera_position` | string | No | framing:"free" only (implies it when framing is omitted). Camera position as a Godot literal, e.g. "Vector3(0, 5, 12)". Goes together with camera\_look\_at. |
| `camera_look_at` | string | No | framing:"free" only. Point the camera looks at, e.g. "Vector3(0, 1, 0)". Must differ from camera\_position. |
| `fov` | number | No | framing:"free" / "bookmark" only. Vertical field of view in degrees (1..179; default 60 for "free", the bookmark's own for "bookmark"). |
| `marks` | boolean | No | target:"scene" only, 3D scenes. Draw a Set-of-Mark overlay: numbered tags + box outlines over the largest visible VisualInstance3D nodes (lights excluded), ranked by projected screen area. The caption lists label -> node path (scene-root-relative) so you can name what you see, and an occlusion test (centre + 4 bounds points from the rendered camera) notes "(hidden behind \<path>)" on labels whose node is hidden: ignore those. Works with every framing. 2D scenes return marks\_unsupported. Default false. |
| `max_marks` | integer | No | marks:true only. Cap on numbered labels (engine default 32, at most 128). The caption says when the cap truncated the list. Range 1 to 128. |
| `compare_previous` | boolean | No | framing:"bookmark" only. Return ONE image of \[previous render \| now \| difference map] for the bookmark, with the share of changed pixels (summer\_shot\_sheet compare\_previous with this one bookmark); this render then replaces the bookmark's previous image in res\://.summer/shots/\<bookmark>.jpg. A plain bookmark render (no marks, at most 1024 px) only creates that image when it is missing and never overwrites the compare baseline. |
| `update_previous` | boolean | No | framing:"bookmark" only. Replace the bookmark's previous image (its compare baseline) with this clean render. Default false: an existing baseline is kept. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "target": {
        "type": "string",
        "enum": [
          "viewport",
          "scene",
          "game"
        ],
        "default": "viewport",
        "description": "\"viewport\" = editor current view (default), \"scene\" = offscreen render of a scene file, \"game\" = running game frame"
      },
      "scenePath": {
        "type": "string",
        "description": "target:\"scene\" only. Full scene path, e.g. \"res://main.tscn\". Omit to render the currently-open scene."
      },
      "framing": {
        "type": "string",
        "enum": [
          "auto",
          "iso",
          "top",
          "front",
          "back",
          "left",
          "right",
          "camera",
          "free",
          "bookmark"
        ],
        "description": "target:\"scene\" only, 3D scenes. Camera direction preset: \"iso\" = 3/4 diagonal view, \"top\" = straight down, \"front\" = camera at +Z, \"back\" = camera at -Z, \"left\" = camera at -X, \"right\" = camera at +X. \"auto\" (default) is an alias of \"iso\". \"camera\" = render through the scene's OWN Camera3D with its REAL WorldEnvironment — the only edit-time framing that truthfully shows lighting/mood. \"bookmark\" (+ bookmark_name) = the fixed pose saved with summer_camera_bookmark; \"free\" (+ camera_position, camera_look_at, fov) = an explicit fixed pose. Both keep the REAL WorldEnvironment and repeat exactly, so before/after frames line up. The result reports the resolved framing — an older engine echoes a preset instead, and the caption warns."
      },
      "bookmark_name": {
        "type": "string",
        "description": "framing:\"bookmark\" only (implies it when framing is omitted). Name of a bookmark saved with summer_camera_bookmark (list them with action:\"list\"). Fails with failure_reason \"unknown_bookmark\" (+ the available names) when it does not exist."
      },
      "size": {
        "type": "array",
        "items": {
          "type": "integer",
          "exclusiveMinimum": 0
        },
        "minItems": 2,
        "maxItems": 2,
        "description": "target:\"scene\" only. Output image [width, height] in pixels."
      },
      "nodePath": {
        "type": "string",
        "description": "target:\"scene\" only. Node path relative to the scene root (e.g. \"Player/Mesh\") to frame INSTEAD of the whole scene — the camera fits that node's combined bounds (3D visual AABBs or 2D rects, children included). A bare unique name is also found recursively. Fails with failure_reason \"node_not_found\" when the path does not resolve (no silent whole-scene fallback). Cannot combine with a fixed pose (framing \"camera\"/\"free\"/\"bookmark\")."
      },
      "camera_path": {
        "type": "string",
        "description": "framing:\"camera\" only. Path of the Camera3D to render through (relative to the scene root) when the scene has several cameras or none marked current. Omit to use the scene's current/first Camera3D."
      },
      "camera_position": {
        "type": "string",
        "description": "framing:\"free\" only (implies it when framing is omitted). Camera position as a Godot literal, e.g. \"Vector3(0, 5, 12)\". Goes together with camera_look_at."
      },
      "camera_look_at": {
        "type": "string",
        "description": "framing:\"free\" only. Point the camera looks at, e.g. \"Vector3(0, 1, 0)\". Must differ from camera_position."
      },
      "fov": {
        "type": "number",
        "description": "framing:\"free\" / \"bookmark\" only. Vertical field of view in degrees (1..179; default 60 for \"free\", the bookmark's own for \"bookmark\")."
      },
      "marks": {
        "type": "boolean",
        "description": "target:\"scene\" only, 3D scenes. Draw a Set-of-Mark overlay: numbered tags + box outlines over the largest visible VisualInstance3D nodes (lights excluded), ranked by projected screen area. The caption lists label -> node path (scene-root-relative) so you can name what you see, and an occlusion test (centre + 4 bounds points from the rendered camera) notes \"(hidden behind <path>)\" on labels whose node is hidden: ignore those. Works with every framing. 2D scenes return marks_unsupported. Default false."
      },
      "max_marks": {
        "type": "integer",
        "minimum": 1,
        "maximum": 128,
        "description": "marks:true only. Cap on numbered labels (engine default 32, at most 128). The caption says when the cap truncated the list."
      },
      "compare_previous": {
        "type": "boolean",
        "description": "framing:\"bookmark\" only. Return ONE image of [previous render | now | difference map] for the bookmark, with the share of changed pixels (summer_shot_sheet compare_previous with this one bookmark); this render then replaces the bookmark's previous image in res://.summer/shots/<bookmark>.jpg. A plain bookmark render (no marks, at most 1024 px) only creates that image when it is missing and never overwrites the compare baseline."
      },
      "update_previous": {
        "type": "boolean",
        "description": "framing:\"bookmark\" only. Replace the bookmark's previous image (its compare baseline) with this clean render. Default false: an existing baseline is kept."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_screenshot",
  "arguments": {}
}
```

***

### summer\_shot\_sheet

On the local MCP (`summer-engine` npm).

Render several bookmarks and/or explicit poses into ONE labelled grid image in a single call: same tile size, same view, real lighting. Use it after every change to see all hero views at once instead of N screenshots.

compare\_previous:true turns each bookmark into a row of \[previous | now | difference map] and reports the share of pixels that changed and where. Each bookmark keeps exactly one previous render (its compare baseline) at res\://.summer/shots/\<bookmark>.jpg (JPEG, \<= 1024 px; the folder is capped at 20 MB, oldest first). Only compare\_previous:true or update\_previous:true replaces it; a plain sheet creates a missing baseline and never overwrites one. view renders every tile as lighting/unshaded/normals/overdraw/wireframe instead of beauty.

Returns the grid + caption (tile number -> label and pose, difference stats). Read-only: the scene file, the open tab and the undo history are never touched (the render is an offscreen copy of the SAVED scene). The image arrives inline; the caption carries poses, labels and numbers. Failures are structured (failure\_reason), never a silent fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool shot-sheet --args '<json>'` |

**Use when:**

* seeing every hero view of an environment at once after a change, in one image
* "did this change make it better?" — compare\_previous puts previous, now and a difference map side by side per bookmark; plain sheets in between never reset that baseline
* rendering several poses in one debug view (lighting, unshaded, normals, overdraw, wireframe) for a quick sweep

**Do not use when:**

* one pose in all debug views — [`summer_debug_views`](/mcp/tools/run-and-test#summer_debug_views)
* motion or a frame sequence — a RunVerification probe's save\_frame
* requires ScenePreview (any current Summer Engine); bookmarks require the camera bookmark ops (0.5.66 or newer)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scenePath` | string | No | Scene to look at, e.g. "res\://levels/town.tscn" (the SAVED file). Omit for the scene open in the editor. |
| `shots` | object\[] | Yes | 1-12 shots, each a bookmark\_name OR camera\_position + camera\_look\_at (+ fov), with an optional label. Rendered in this order, left to right, top to bottom. Items 1 to 12. |
| `shots[].bookmark_name` | string | No | Pose from a camera bookmark (summer\_camera\_bookmark). Use INSTEAD of camera\_position/camera\_look\_at. |
| `shots[].camera_position` | string | No | Explicit pose: camera position, "Vector3(x, y, z)". Goes with camera\_look\_at. |
| `shots[].camera_look_at` | string | No | Explicit pose: point the camera looks at, "Vector3(x, y, z)". |
| `shots[].fov` | number | No | Vertical field of view in degrees (1..179). Default: the bookmark's own, or 60. |
| `shots[].label` | string | No | Tile label (1-64 printable characters, no quotes, backslashes or \$). Default: the bookmark name or 'shot N'. |
| `view` | "beauty" \| "lighting" \| "unshaded" \| "normals" \| "overdraw" \| "wireframe" | No | "beauty" (default: real environment, lights, fog, tonemap), "lighting" (light only), "unshaded" (albedo/texture only), "normals" (world normals, x red y green z blue), "overdraw", "wireframe". |
| `compare_previous` | boolean | No | For bookmark shots: one row per bookmark of \[previous render \| now \| difference map], with the share of changed pixels in the caption. The previous render is the bookmark's slot in res\://.summer/shots/; this render replaces it (it is the next baseline). |
| `update_previous` | boolean | No | Replace each bookmark's previous image (its compare baseline) with this render without comparing. Default false: a sheet without compare\_previous only creates a missing slot and never overwrites an existing baseline. |
| `max_size` | integer | No | Longest edge of the returned JPEG in pixels (default 1536). Bigger costs more context; up to 4096 for detail work on a PC. Range 64 to 4096. |
| `aspect` | number | No | Frame width/height (default 1.7778 = 16:9). Every tile uses it. |
| `save_to` | string | No | Also write the returned image to res\://.summer/shots/saved/\<name>.jpg (1-64 of A-Z a-z 0-9 \\\_ -; needs max\_size \<= 1024). Nothing is written without it, except one previous-image slot per rendered bookmark. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scenePath": {
        "type": "string",
        "description": "Scene to look at, e.g. \"res://levels/town.tscn\" (the SAVED file). Omit for the scene open in the editor."
      },
      "shots": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "bookmark_name": {
              "type": "string",
              "description": "Pose from a camera bookmark (summer_camera_bookmark). Use INSTEAD of camera_position/camera_look_at."
            },
            "camera_position": {
              "type": "string",
              "description": "Explicit pose: camera position, \"Vector3(x, y, z)\". Goes with camera_look_at."
            },
            "camera_look_at": {
              "type": "string",
              "description": "Explicit pose: point the camera looks at, \"Vector3(x, y, z)\"."
            },
            "fov": {
              "type": "number",
              "description": "Vertical field of view in degrees (1..179). Default: the bookmark's own, or 60."
            },
            "label": {
              "type": "string",
              "description": "Tile label (1-64 printable characters, no quotes, backslashes or $). Default: the bookmark name or 'shot N'."
            }
          },
          "additionalProperties": false
        },
        "minItems": 1,
        "maxItems": 12,
        "description": "1-12 shots, each a bookmark_name OR camera_position + camera_look_at (+ fov), with an optional label. Rendered in this order, left to right, top to bottom."
      },
      "view": {
        "type": "string",
        "enum": [
          "beauty",
          "lighting",
          "unshaded",
          "normals",
          "overdraw",
          "wireframe"
        ],
        "description": "\"beauty\" (default: real environment, lights, fog, tonemap), \"lighting\" (light only), \"unshaded\" (albedo/texture only), \"normals\" (world normals, x red y green z blue), \"overdraw\", \"wireframe\"."
      },
      "compare_previous": {
        "type": "boolean",
        "description": "For bookmark shots: one row per bookmark of [previous render | now | difference map], with the share of changed pixels in the caption. The previous render is the bookmark's slot in res://.summer/shots/; this render replaces it (it is the next baseline)."
      },
      "update_previous": {
        "type": "boolean",
        "description": "Replace each bookmark's previous image (its compare baseline) with this render without comparing. Default false: a sheet without compare_previous only creates a missing slot and never overwrites an existing baseline."
      },
      "max_size": {
        "type": "integer",
        "minimum": 64,
        "maximum": 4096,
        "description": "Longest edge of the returned JPEG in pixels (default 1536). Bigger costs more context; up to 4096 for detail work on a PC."
      },
      "aspect": {
        "type": "number",
        "description": "Frame width/height (default 1.7778 = 16:9). Every tile uses it."
      },
      "save_to": {
        "type": "string",
        "description": "Also write the returned image to res://.summer/shots/saved/<name>.jpg (1-64 of A-Z a-z 0-9 _ -; needs max_size <= 1024). Nothing is written without it, except one previous-image slot per rendered bookmark."
      }
    },
    "required": [
      "shots"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns the grid + caption (tile number -> label and pose, difference stats).

```json Example call theme={null}
{
  "name": "summer_shot_sheet",
  "arguments": {
    "shots": [
      {}
    ]
  }
}
```

***

### summer\_snapshot\_diff

On the local MCP (`summer-engine` npm).

Diff two world snapshots into exactly what changed: added/removed node paths, changed nodes with the fields that moved (pos, scale, material fingerprint, ...), and per-class count deltas. Reads like a receipt — no wading through two full dumps.

Standard use: take [`summer_world_snapshot`](/mcp/tools/run-and-test#summer_world_snapshot) BEFORE a mutation batch, mutate, then call this with from\_id (to\_id omitted = the engine takes a fresh snapshot now). Verify the diff matches your INTENT: exactly the nodes you meant to add were added, nothing you didn't touch changed, nothing vanished. An empty diff after a "successful" mutation is a red flag — the change did not land (wrong scene? unsaved? unowned nodes dropped on save?).

The engine retains the last 8 snapshot ids per session; an expired/unknown id fails with failure\_reason "unknown\_snapshot" — take a fresh baseline and re-run the mutation check rather than guessing.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool snapshot-diff --args '<json>'` |

**Use when:**

* verifying a mutation batch against the snapshot\_id taken before it
* "what changed in the scene after that script ran?"
* proving a batch only moved the props it was meant to

**Do not use when:**

* no baseline snapshot exists — take [`summer_world_snapshot`](/mcp/tools/run-and-test#summer_world_snapshot) first
* requires an engine build with DiffWorldSnapshot (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `from_id` | string | Yes | snapshot\_id of the BEFORE snapshot (from summer\_world\_snapshot). |
| `to_id` | string | No | snapshot\_id of the AFTER snapshot. Omit to snapshot the current state now and diff against that. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "from_id": {
        "type": "string",
        "description": "snapshot_id of the BEFORE snapshot (from summer_world_snapshot)."
      },
      "to_id": {
        "type": "string",
        "description": "snapshot_id of the AFTER snapshot. Omit to snapshot the current state now and diff against that."
      }
    },
    "required": [
      "from_id"
    ],
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_snapshot_diff",
  "arguments": {
    "from_id": "<from_id>"
  }
}
```

***

### summer\_stop

On the local MCP (`summer-engine` npm).

Stop the running game. Use after runtime verification or when you intentionally need to restart the running instance; ordinary editor scene mutations do not require a blanket stop. Pass instance to stop ONE offscreen instance started with [`summer_play`](/mcp/tools/run-and-test#summer_play) \{instance, mode:'offscreen'} (the result reports was\_playing and killed); omit it for the editor's main game.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | changes the open project |
| **CLI** | `summer tool stop --args '<json>'` |

**Use when:**

* runtime verification is finished
* the game must restart to pick up scene or script changes
* a runaway or frozen playtest needs killing
* an offscreen playtest instance ([`summer_play`](/mcp/tools/run-and-test#summer_play) with instance + mode offscreen) is no longer needed — pass instance

**Do not use when:**

* you want to undo scene edits — stopping reverts nothing; use editor undo / [`summer_batch`](/mcp/tools/build#summer_batch) Undo
* saving — stopping does not save; [`summer_save_scene`](/mcp/tools/build#summer_save_scene)

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `instance` | string | No | Offscreen instance name to stop (from summer\_play \{instance}); omit for the main embedded game. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "instance": {
        "type": "string",
        "description": "Offscreen instance name to stop (from summer_play {instance}); omit for the main embedded game."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_stop",
  "arguments": {}
}
```

***

### summer\_ui\_screenshot

On the local MCP (`summer-engine` npm).

PNG of the editor window (or one dock / dialog / control's rect) returned as an image you can look at — the PIXELS-LAST fallback of the UI ladder: use it to see layout, an unfamiliar panel, or to sanity-check what the tree described, never to pick click coordinates (there is no coordinate click; [`summer_ui_activate`](/mcp/tools/build#summer_ui_activate) takes paths). For the 3D/2D viewport, a scene render, or the running game use [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) instead — that is the visual-verification tool for scene work, and scene work itself goes through [`summer_run_script`](/mcp/tools/build#summer_run_script) and the scene tools, never through the editor UI. (preview — needs an engine build with UiScreenshot)

Order of preference: a dedicated tool -> [`summer_ui_actions`](/mcp/tools/build#summer_ui_actions) mode:'invoke' by name -> [`summer_ui_tree`](/mcp/tools/run-and-test#summer_ui_tree) + [`summer_ui_activate`](/mcp/tools/build#summer_ui_activate) by control path -> [`summer_ui_screenshot`](/mcp/tools/run-and-test#summer_ui_screenshot) (pixels, last, and only to LOOK — never to pick coordinates). Prefer [`summer_ui_tree`](/mcp/tools/run-and-test#summer_ui_tree) for state: a control's checked/current\_tab/text is exact there and costs a fraction of an image.

root: 'window' (default; the whole editor window incl. embedded dialogs) | 'main' | 'dock:\<title|id>' | 'dialog:\<title>' | 'path:\<node path>' — a Control or embedded dialog is cropped out of the root viewport texture; a native OS sub-window is not in it (native\_subwindow). max\_size caps the longest edge (default 1024, 16-4096). Captured from the editor's own root viewport texture, never the OS screen (safety\_boundary:'no\_os\_screen\_capture').

Headless is honest: under a headless editor (dummy rendering driver) the result is failure\_reason no\_renderer — nothing was drawn, so there are no pixels; [`summer_ui_tree`](/mcp/tools/run-and-test#summer_ui_tree) is the structured view of the same UI and a display (Linux: --xvfb) is needed for pixels. Other failures: texture\_unavailable | zero\_size | native\_subwindow | unknown\_root | not\_found | encode\_failed. Engine builds without the op return a structured engine\_lacks\_op failure (nothing is sent).

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool ui-screenshot --args '<json>'` |

**Use when:**

* "show me what the editor looks like right now" / "screenshot the Inspector dock" — seeing layout or an unfamiliar panel after [`summer_ui_tree`](/mcp/tools/run-and-test#summer_ui_tree) has described it
* sanity-checking an editor-UI step visually when the structured read-back is not enough

**Do not use when:**

* verifying scene work — [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) (viewport, scene render with framing camera, or the running game) is the visual-verification tool for scenes
* reading a control's state (checked, current\_tab, text) — [`summer_ui_tree`](/mcp/tools/run-and-test#summer_ui_tree) is exact and far cheaper
* picking coordinates to click — there is no coordinate click; [`summer_ui_activate`](/mcp/tools/build#summer_ui_activate) takes tree paths
* a headless editor — no renderer, no pixels (no\_renderer); use [`summer_ui_tree`](/mcp/tools/run-and-test#summer_ui_tree)
* requires an engine build with UiScreenshot (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `root` | string | No | Same grammar as summer\_ui\_tree root — 'window' (default; the whole editor window), 'main', 'dock:\<title\|id>', 'dialog:\<title>', 'path:\<node path>'. A Control or embedded dialog is cropped out of the root viewport texture; a native OS sub-window is not in it (native\_subwindow). |
| `max_size` | integer | No | Longest edge in pixels after downscale (engine default 1024, clamped 16-4096). Smaller is cheaper to look at; the result reports scale. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "root": {
        "type": "string",
        "description": "Same grammar as summer_ui_tree root — 'window' (default; the whole editor window), 'main', 'dock:<title|id>', 'dialog:<title>', 'path:<node path>'. A Control or embedded dialog is cropped out of the root viewport texture; a native OS sub-window is not in it (native_subwindow)."
      },
      "max_size": {
        "type": "integer",
        "description": "Longest edge in pixels after downscale (engine default 1024, clamped 16-4096). Smaller is cheaper to look at; the result reports scale."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_ui_screenshot",
  "arguments": {}
}
```

***

### summer\_ui\_tree

On the local MCP (`summer-engine` npm).

Structured tree of the live editor UI — every visible Control with its class, path, rect, text/tooltip and state (checked, enabled, focused, tabs + current\_tab, value/min/max, selected item) — or, with root:'dialogs', every visible dialog/popup and whether one is BLOCKING input. The structured alternative to a screenshot: the tree states outright what exists and what is clickable, at a fraction of the tokens, and its paths are what [`summer_ui_activate`](/mcp/tools/build#summer_ui_activate) takes. (preview — needs an engine build with UiTree/UiDialogs)

SCENE WORK IS NOT UI WORK: to add, move, retune, or read nodes use [`summer_run_script`](/mcp/tools/build#summer_run_script), the scene tools, [`summer_world_snapshot`](/mcp/tools/run-and-test#summer_world_snapshot) and [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) — never by clicking through the editor. UI ops are for editor-workflow steps a human would do with the mouse: open Project Settings or the Import dock, switch the 2D/3D/Script main screen, clear a dialog that is blocking input, read what a dock currently shows. Use this tree to READ editor state (which main screen is active, what a dock shows, what a dialog says and which buttons it has) and to find a control's path when no named action covers the step; use [`summer_get_scene_tree`](/mcp/tools/build#summer_get_scene_tree) / [`summer_world_snapshot`](/mcp/tools/run-and-test#summer_world_snapshot) for the SCENE — the editor's Scene dock is a view of that data, not the data.

root: 'main' (default; the editor chrome) | 'window' (incl. every sub-window) | 'dock:\<title|id>' (file\_system | scene\_tree | inspector or any dock title) | 'dialog:\<title>' | 'path:\<node path>' (zoom into an earlier result) | 'dialogs' -> \{count, blocking, blocking\_dialog?, dialogs:\[\{title, path, class, kind, exclusive, popup, embedded, focused, text?, items?, buttons:\[\{text, path, kind:'ok'|'cancel'|'custom', enabled}]}]}.
Tree results: \{root, root\_path, total\_emitted, truncated, tree:\{name, class, path, visible, rect, text?, tooltip?, enabled?, checked?, focused?, tabs?, current\_tab?, value?, child\_count, children\_truncated?, children}}. Strings clip at 200 chars; icon-only toolbar buttons are named by tooltip; child\_count is always the real count, so a children\_truncated node can be re-read with root:'path:\<its path>' and a larger depth/limit.

Paths (@Panel\@123) are stable within a session, not across builds — re-read instead of persisting them. The blocking-dialog pattern: root:'dialogs' -> blocking:true -> [`summer_ui_activate`](/mcp/tools/build#summer_ui_activate) action:'dismiss\_dialog' path:\<blocking\_dialog.path> -> root:'dialogs' again to confirm blocking:false. Failures: unknown\_root (+available\_docks, dock\_ids) | not\_found (+visible\_titles) | ambiguous\_dialog (+candidates) | editor\_unavailable. Engine builds without these ops return a structured engine\_lacks\_op failure (nothing is sent).

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool ui-tree --args '<json>'` |

**Use when:**

* "is a dialog blocking the editor" / "what does the Inspector dock show" / "which main screen is active" — read editor UI state as structure instead of pixels
* finding a control's path before [`summer_ui_activate`](/mcp/tools/build#summer_ui_activate), and reading its state back afterwards
* listing visible dialogs and popups (root dialogs) before dismissing one with [`summer_ui_activate`](/mcp/tools/build#summer_ui_activate) action dismiss\_dialog

**Do not use when:**

* reading the scene — [`summer_get_scene_tree`](/mcp/tools/build#summer_get_scene_tree) / [`summer_world_snapshot`](/mcp/tools/run-and-test#summer_world_snapshot) read the data the Scene dock merely displays
* a named action already covers the step — [`summer_ui_actions`](/mcp/tools/build#summer_ui_actions) mode invoke needs no tree walk
* judging appearance (layout, colours, an unfamiliar panel) — [`summer_ui_screenshot`](/mcp/tools/run-and-test#summer_ui_screenshot), pixels last
* requires an engine build with UiTree / UiDialogs (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `root` | string | No | Where to start. 'main' (default) = the whole editor chrome; 'window' = the SceneTree root incl. every sub-window; 'dock:\<title\|id>' = one dock (ids file\_system \| scene\_tree \| inspector, or any dock title, case-insensitive); 'dialog:\<title>' = one visible window (exact wins, one substring match accepted); 'path:\<node path>' = a path from an earlier result. 'dialogs' = list every visible dialog/popup with its blocking flag and buttons instead of a tree (UiDialogs). |
| `depth` | integer | No | Tree depth (engine default 4, max 32). Plain Nodes between Controls do not consume depth. |
| `limit` | integer | No | Maximum Control nodes emitted (engine default 500, max 5000). The result carries truncated + child\_count so a cut is visible. |
| `visible_only` | boolean | No | Default true. false also emits hidden controls — readable, but summer\_ui\_activate refuses them with not\_visible. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "root": {
        "type": "string",
        "description": "Where to start. 'main' (default) = the whole editor chrome; 'window' = the SceneTree root incl. every sub-window; 'dock:<title|id>' = one dock (ids file_system | scene_tree | inspector, or any dock title, case-insensitive); 'dialog:<title>' = one visible window (exact wins, one substring match accepted); 'path:<node path>' = a path from an earlier result. 'dialogs' = list every visible dialog/popup with its blocking flag and buttons instead of a tree (UiDialogs)."
      },
      "depth": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Tree depth (engine default 4, max 32). Plain Nodes between Controls do not consume depth."
      },
      "limit": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Maximum Control nodes emitted (engine default 500, max 5000). The result carries truncated + child_count so a cut is visible."
      },
      "visible_only": {
        "type": "boolean",
        "description": "Default true. false also emits hidden controls — readable, but summer_ui_activate refuses them with not_visible."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_ui_tree",
  "arguments": {}
}
```

***

### summer\_wait\_for\_event

On the local MCP (`summer-engine` npm).

Block until the engine emits a matching EVENT, or a bounded timeout elapses — the replacement for sleeping and re-polling. Use it right after [`summer_play`](/mcp/tools/run-and-test#summer_play) to wait for play.started (the game actually booting), after a long op (import, script, save) to wait for op.applied / op.failed by requestId instead of guessing a delay, and during a playtest to catch script.error the moment it fires.

Event kinds (v1): op.applied, op.failed, script.error, play.started, play.stopped, scene.saved, scene.opened, import.completed, selection.changed, snapshot.published. Each event is \{seq, kind, ts, data}; seq is a monotonic cursor.

CURSOR DISCIPLINE: events are delivered live from `since` (omitted = from now). An event that fired BEFORE this call is NOT delivered, so when the moment may come fast, take a cursor first: [`summer_recent_events`](/mcp/tools/run-and-test#summer_recent_events) returns next\_seq — pass it as since BEFORE triggering the action ([`summer_recent_events`](/mcp/tools/run-and-test#summer_recent_events) -> [`summer_play`](/mcp/tools/run-and-test#summer_play) -> [`summer_wait_for_event`](/mcp/tools/run-and-test#summer_wait_for_event) since:\<next\_seq> kinds:\['play.started']). Every result carries next\_seq: pass it as since on the next call to keep waiting with no gap.

match.requestId narrows op.applied / op.failed to one request (other kinds pass through). The call long-polls the engine in slices of at most 25 s until a match or timeout\_seconds (default 30, max 120) elapses.

Returns \{ok, matched, events, next\_seq, since, timed\_out, waited\_ms, polls}. timed\_out:true means NO matching event arrived — it is not evidence the thing did not happen (verify with [`summer_is_running`](/mcp/tools/run-and-test#summer_is_running) / [`summer_get_diagnostics`](/mcp/tools/run-and-test#summer_get_diagnostics)) and you must never claim an event you did not receive. A `gap` field means events between since and the oldest retained were evicted — re-read state instead of trusting the stream. On an engine build without the events channel (no capabilities.events in /api/health) the result is a structured engine\_lacks\_events failure and nothing is sent: fall back to polling the state.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer events --follow` |

**Use when:**

* "wait for the game to start" — right after [`summer_play`](/mcp/tools/run-and-test#summer_play), wait for play.started instead of guessing how long boot takes
* "wait for an event" / "tell me when the import finishes" — block on op.applied / op.failed for one requestId, import.completed, or scene.saved after a long op
* during a playtest, catching script.error the moment it fires instead of reading the console afterwards
* any time you would otherwise sleep and re-check state — take a cursor with [`summer_recent_events`](/mcp/tools/run-and-test#summer_recent_events) first, then wait from it

**Do not use when:**

* reading what already happened — [`summer_recent_events`](/mcp/tools/run-and-test#summer_recent_events) (events fired before `since` are never delivered here)
* checking a fact right now (is the game running, what is in the tree) — [`summer_is_running`](/mcp/tools/run-and-test#summer_is_running) / [`summer_get_scene_tree`](/mcp/tools/build#summer_get_scene_tree) are immediate
* requires an engine build with the events channel (capabilities.events, /api/events; Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_events

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `kinds` | string\[] | No | Event kinds to deliver (default: all). v1 kinds: op.applied, op.failed, script.error, play.started, play.stopped, scene.saved, scene.opened, import.completed, selection.changed, snapshot.published. Unknown kinds are refused before waiting when the engine advertises its list. |
| `since` | integer | No | Deliver events with seq > since. Omit = live from now (an event that already fired is NOT delivered — take a cursor with summer\_recent\_events first and pass its next\_seq here). 0 = replay the retained ring. Range 0 to …. |
| `timeout_seconds` | number | No | How long to wait for a match (default 30, clamped 1-120). The call long-polls in slices of at most 25 s. |
| `match` | object | No | Extra client-side filters applied after the kinds filter. |
| `match.requestId` | string | No | Only op.applied / op.failed events for this requestId match; every other kind passes through. Length 1 to …. |
| `max_events` | integer | No | Maximum matched events to return (default 20, cap 500). |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "kinds": {
        "type": "array",
        "items": {
          "type": "string",
          "minLength": 1
        },
        "description": "Event kinds to deliver (default: all). v1 kinds: op.applied, op.failed, script.error, play.started, play.stopped, scene.saved, scene.opened, import.completed, selection.changed, snapshot.published. Unknown kinds are refused before waiting when the engine advertises its list."
      },
      "since": {
        "type": "integer",
        "minimum": 0,
        "description": "Deliver events with seq > since. Omit = live from now (an event that already fired is NOT delivered — take a cursor with summer_recent_events first and pass its next_seq here). 0 = replay the retained ring."
      },
      "timeout_seconds": {
        "type": "number",
        "description": "How long to wait for a match (default 30, clamped 1-120). The call long-polls in slices of at most 25 s."
      },
      "match": {
        "type": "object",
        "properties": {
          "requestId": {
            "type": "string",
            "minLength": 1,
            "description": "Only op.applied / op.failed events for this requestId match; every other kind passes through."
          }
        },
        "additionalProperties": false,
        "description": "Extra client-side filters applied after the kinds filter."
      },
      "max_events": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Maximum matched events to return (default 20, cap 500)."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns \{ok, matched, events, next\_seq, since, timed\_out, waited\_ms, polls}.

```json Example call theme={null}
{
  "name": "summer_wait_for_event",
  "arguments": {}
}
```

***

### summer\_world\_snapshot

On the local MCP (`summer-engine` npm).

Compact structured snapshot of the whole EDITED scene — the cheap read to run BEFORE and AFTER every mutation batch. Per node: path, class, transform (pos/rot/scale as Godot literal strings, 3-decimal floats), world AABB (3D visuals), visibility, and 8-hex resource fingerprints (script/materials — detect-change markers, never content). Plus a light summary, camera list, environment fingerprint, and per-class counts.

THE LOOP: [`summer_world_snapshot`](/mcp/tools/run-and-test#summer_world_snapshot) (note snapshot\_id) -> mutate ([`summer_run_script`](/mcp/tools/build#summer_run_script) / scene tools / imports) -> [`summer_snapshot_diff`](/mcp/tools/run-and-test#summer_snapshot_diff) from\_id:\<that id> -> [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot). The diff proves exactly what changed structurally; the screenshot proves it looks right. This is how you catch a node that silently vanished on save, a transform that landed at the origin, or an AABB clipping through the floor.

Node lists are path-sorted and truncated DETERMINISTICALLY (result carries total\_nodes + truncated) so two snapshots stay diffable without phantom adds/removes. The engine retains the last 8 snapshots per session, keyed by snapshot\_id. Use this instead of [`summer_get_scene_tree`](/mcp/tools/build#summer_get_scene_tree) when you need transforms/AABBs/fingerprints or a diffable baseline; the tree read remains the hierarchy-shaped view. On an engine build that predates GetWorldSnapshot the result is a structured engine\_lacks\_op failure naming the fallback.

KEEP IT SMALL (about 250 bytes per node): at most 200 nodes are listed by default. path\_prefix reads one subtree ('House3', 'Lane2/Props'), classes keeps some node classes, fields keeps some per-node fields (e.g. \['pos','aabb']), offset/next\_offset page the list. A filtered read adds matched\_nodes and matched\_counts (class counts of the subtree). counts and total\_nodes always describe the whole scene, and snapshot\_id always covers the whole scene, so [`summer_snapshot_diff`](/mcp/tools/run-and-test#summer_snapshot_diff) still sees every change.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | read-only |
| **CLI** | `summer tool world-snapshot --args '<json>'` |

**Use when:**

* taking a diffable baseline before a mutation batch, or verifying transforms/AABBs/fingerprints after one
* "where exactly is everything in this scene?" — positions, sizes, bounds
* "are these two props overlapping?" / "is anything floating?"
* "where did the pieces of House3 land?" — path\_prefix + fields:\[pos, aabb] reads one subtree cheaply
* counting instances per class in one subtree (matched\_counts)

**Do not use when:**

* you only need the hierarchy shape — [`summer_get_scene_tree`](/mcp/tools/build#summer_get_scene_tree)
* requires an engine build with GetWorldSnapshot (Summer Engine 0.5.66 or newer); older engines return engine\_lacks\_op

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scene_path` | string | No | Scene to snapshot, e.g. 'res\://main.tscn'. Omit for the currently edited scene. |
| `max_nodes` | integer | No | Most node entries to return (default 200). The engine still snapshots the whole scene for the diff baseline; the result says when the list is cut (truncated, next\_offset). |
| `offset` | integer | No | Skip this many matching nodes (path-sorted) before listing — page with the result's next\_offset. Range 0 to …. |
| `path_prefix` | string | No | Only this subtree: the node at this scene-relative path and everything below it, e.g. 'House3' or 'Lane2/Props'. |
| `classes` | string\[] | No | Only nodes of these classes, e.g. \['MeshInstance3D', 'OmniLight3D'] (\* wildcards allowed). Items 0 to 32. |
| `fields` | string\[] | No | Only these per-node fields (path is always kept): class, name, pos, rot\_deg, scale, visible, aabb, scene\_file, script\_fp, material\_fps, z\_index, rect, view\_rect, limit\_rect. Items 0 to 16. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scene_path": {
        "type": "string",
        "description": "Scene to snapshot, e.g. 'res://main.tscn'. Omit for the currently edited scene."
      },
      "max_nodes": {
        "type": "integer",
        "exclusiveMinimum": 0,
        "description": "Most node entries to return (default 200). The engine still snapshots the whole scene for the diff baseline; the result says when the list is cut (truncated, next_offset)."
      },
      "offset": {
        "type": "integer",
        "minimum": 0,
        "description": "Skip this many matching nodes (path-sorted) before listing — page with the result's next_offset."
      },
      "path_prefix": {
        "type": "string",
        "description": "Only this subtree: the node at this scene-relative path and everything below it, e.g. 'House3' or 'Lane2/Props'."
      },
      "classes": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "maxItems": 32,
        "description": "Only nodes of these classes, e.g. ['MeshInstance3D', 'OmniLight3D'] (* wildcards allowed)."
      },
      "fields": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "maxItems": 16,
        "description": "Only these per-node fields (path is always kept): class, name, pos, rot_deg, scale, visible, aabb, scene_file, script_fp, material_fps, z_index, rect, view_rect, limit_rect."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`.

```json Example call theme={null}
{
  "name": "summer_world_snapshot",
  "arguments": {}
}
```

***

### summer\_zoom

On the local MCP (`summer-engine` npm).

High-resolution close look at part of a frame: region \[x, y, w, h] (fractions of the frame) or mark N from a marks render of the same pose. The camera renders the EXACT sub-frustum of that region at full output resolution, so you see real texture detail, seams, gaps and floating pieces, not upscaled pixels. A region is honoured exactly (no pad by default, never widened to an aspect ratio: the image takes the region's own aspect, with dark bars only for extreme shapes); a mark gets pad 0.15.

Use after a sheet or debug view shows something suspicious. view picks beauty or a debug view. Returns the zoomed image + caption (the real zoom factor, the rendered region, widened\_because when it is larger than asked, mark -> node path and a warning when that node is hidden). Read-only: the scene file, the open tab and the undo history are never touched (the render is an offscreen copy of the SAVED scene). The image arrives inline; the caption carries poses, labels and numbers. Failures are structured (failure\_reason), never a silent fallback.

| | |
| - | - |
| **Needs** | Summer Engine open with your project |
| **Effects** | writes files |
| **CLI** | `summer tool zoom --args '<json>'` |

**Use when:**

* checking seams, gaps, floating pieces, texture resolution or tiling up close
* looking closer at a numbered mark from a marks render ([`summer_frame_nodes`](/mcp/tools/run-and-test#summer_frame_nodes) marks:true, or [`summer_screenshot`](/mcp/tools/run-and-test#summer_screenshot) marks at the same size)
* inspecting a region a difference map or debug view flagged

**Do not use when:**

* the whole view — [`summer_shot_sheet`](/mcp/tools/run-and-test#summer_shot_sheet) / [`summer_frame_nodes`](/mcp/tools/run-and-test#summer_frame_nodes)
* you have no pose yet — get one from a bookmark or [`summer_frame_shot`](/mcp/tools/run-and-test#summer_frame_shot) first

**Inputs:**

| Input | Type | Required | Description |
| - | - | - | - |
| `scenePath` | string | No | Scene to look at, e.g. "res\://levels/town.tscn" (the SAVED file). Omit for the scene open in the editor. |
| `bookmark_name` | string | No | Pose from a camera bookmark (summer\_camera\_bookmark). Use INSTEAD of camera\_position/camera\_look\_at. |
| `camera_position` | string | No | Explicit pose: camera position, "Vector3(x, y, z)". Goes with camera\_look\_at. |
| `camera_look_at` | string | No | Explicit pose: point the camera looks at, "Vector3(x, y, z)". |
| `fov` | number | No | Vertical field of view in degrees (1..179). Default: the bookmark's own, or 60. |
| `region` | number\[] | No | \[x, y, w, h] as fractions (0..1) of the frame from the same pose, x/y from the TOP-LEFT. Use this OR mark. Rendered EXACTLY (no padding by default, never widened to an aspect ratio): the image takes the region's own aspect, with dark bars only for extreme shapes. Items 4 to 4. |
| `mark` | integer | No | A label number from a marks:true render of the SAME pose and reference\_size (summer\_frame\_nodes, or summer\_screenshot framing free/bookmark at that size). The caption warns when that node is hidden behind other geometry at this pose. |
| `reference_size` | integer\[] | No | \[width, height] of the frame region/mark refer to (default \[1024, 576]). Sets the zoom's aspect. Items 2 to 2. |
| `pad` | number | No | Margin added around the region on each side, as a fraction of its size (0..1; default 0 for region, 0.15 for mark). The caption's widened\_because says when the rendered window is larger than asked. |
| `view` | "beauty" \| "lighting" \| "unshaded" \| "normals" \| "overdraw" \| "wireframe" | No | "beauty" (default: real environment, lights, fog, tonemap), "lighting" (light only), "unshaded" (albedo/texture only), "normals" (world normals, x red y green z blue), "overdraw", "wireframe". |
| `max_size` | integer | No | Longest edge of the returned JPEG in pixels (default 1024). Bigger costs more context; up to 4096 for detail work on a PC. Range 64 to 4096. |
| `save_to` | string | No | Also write the returned image to res\://.summer/shots/saved/\<name>.jpg (1-64 of A-Z a-z 0-9 \\\_ -; needs max\_size \<= 1024). Nothing is written without it, except one previous-image slot per rendered bookmark. |

<Accordion title="Input JSON schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "scenePath": {
        "type": "string",
        "description": "Scene to look at, e.g. \"res://levels/town.tscn\" (the SAVED file). Omit for the scene open in the editor."
      },
      "bookmark_name": {
        "type": "string",
        "description": "Pose from a camera bookmark (summer_camera_bookmark). Use INSTEAD of camera_position/camera_look_at."
      },
      "camera_position": {
        "type": "string",
        "description": "Explicit pose: camera position, \"Vector3(x, y, z)\". Goes with camera_look_at."
      },
      "camera_look_at": {
        "type": "string",
        "description": "Explicit pose: point the camera looks at, \"Vector3(x, y, z)\"."
      },
      "fov": {
        "type": "number",
        "description": "Vertical field of view in degrees (1..179). Default: the bookmark's own, or 60."
      },
      "region": {
        "type": "array",
        "items": {
          "type": "number"
        },
        "minItems": 4,
        "maxItems": 4,
        "description": "[x, y, w, h] as fractions (0..1) of the frame from the same pose, x/y from the TOP-LEFT. Use this OR mark. Rendered EXACTLY (no padding by default, never widened to an aspect ratio): the image takes the region's own aspect, with dark bars only for extreme shapes."
      },
      "mark": {
        "type": "integer",
        "description": "A label number from a marks:true render of the SAME pose and reference_size (summer_frame_nodes, or summer_screenshot framing free/bookmark at that size). The caption warns when that node is hidden behind other geometry at this pose."
      },
      "reference_size": {
        "type": "array",
        "items": {
          "type": "integer"
        },
        "minItems": 2,
        "maxItems": 2,
        "description": "[width, height] of the frame region/mark refer to (default [1024, 576]). Sets the zoom's aspect."
      },
      "pad": {
        "type": "number",
        "description": "Margin added around the region on each side, as a fraction of its size (0..1; default 0 for region, 0.15 for mark). The caption's widened_because says when the rendered window is larger than asked."
      },
      "view": {
        "type": "string",
        "enum": [
          "beauty",
          "lighting",
          "unshaded",
          "normals",
          "overdraw",
          "wireframe"
        ],
        "description": "\"beauty\" (default: real environment, lights, fog, tonemap), \"lighting\" (light only), \"unshaded\" (albedo/texture only), \"normals\" (world normals, x red y green z blue), \"overdraw\", \"wireframe\"."
      },
      "max_size": {
        "type": "integer",
        "minimum": 64,
        "maximum": 4096,
        "description": "Longest edge of the returned JPEG in pixels (default 1024). Bigger costs more context; up to 4096 for detail work on a PC."
      },
      "save_to": {
        "type": "string",
        "description": "Also write the returned image to res://.summer/shots/saved/<name>.jpg (1-64 of A-Z a-z 0-9 _ -; needs max_size <= 1024). Nothing is written without it, except one previous-image slot per rendered bookmark."
      }
    },
    "additionalProperties": false
  }
  ```
</Accordion>

**Output:** MCP text content holding JSON; errors set `isError`. From the tool's own description: Returns the zoomed image + caption (the real zoom factor, the rendered region, widened\_because when it is larger than asked, mark -> node path and a warning when that node is hidden).

```json Example call theme={null}
{
  "name": "summer_zoom",
  "arguments": {}
}
```

***


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.