Now I have complete context. Let me produce the section content.

# Section 02: Scene Tree GDScript Handler

## Overview

This section adds the `scene_tree` handler to `godotiq_server.gd`, enabling the Python MCP server to request the live editor scene tree over the WebSocket bridge. The handler reads the currently edited scene via `EditorInterface.get_edited_scene_root()` and recursively walks the tree, returning compact JSON with spatial, script, group, and visibility data per node.

This is a **GDScript-only** section. The corresponding Python bridge tool is implemented in section-03.

## Dependencies

- **section-01-protocol-plugin-wiring** must be completed first. It adds `scene_tree` timeout to `TOOL_TIMEOUTS` in `protocol.py`. However, the GDScript handler itself does not depend on the protocol timeout -- it only needs the dispatch entry point in the server.

## File to Modify

`/Users/salvatorefarruggio/Desktop/godotiq/godot-addon/addons/godotiq/godotiq_server.gd`

## Testing Approach

GDScript handlers are tested **manually** via live bridge connection. There are no automated Python tests for GDScript code in this project. The Python-side bridge tool tests (section-03 and section-09) verify correct parameter forwarding and response handling, providing indirect coverage.

Manual test checklist for the implementer:

1. Open a Godot project with the GodotIQ addon enabled
2. Connect a WebSocket client (or use the Python bridge)
3. Send `{"id": "1", "method": "scene_tree", "params": {}}` -- verify the response contains the scene tree with default depth 3
4. Send with `"depth": 1` -- verify only root-level children are returned
5. Send with `"filter_type": "Node3D"` -- verify only Node3D nodes appear in the tree, but depth still increments correctly through non-matching nodes
6. Send with `"root": "SomeChild"` -- verify the tree starts from that node
7. Send with `"include": ["transform"]` -- verify script/groups/visibility fields are omitted
8. Send with an invalid `"root"` path -- verify an error response is returned

## Implementation Details

### 1. Add `scene_tree` to the `_dispatch()` match block

In the `_dispatch()` method of `godotiq_server.gd`, add a new match case for `"scene_tree"` that calls `_handle_scene_tree(peer_id, id, params)`. Place it after the existing `"editor_context"` case and before the wildcard `_:` case.

```gdscript
"scene_tree":
    _handle_scene_tree(peer_id, id, params)
```

### 2. Implement `_handle_scene_tree()`

This is the main handler function. It should be placed in the `# --- Editor-side handlers ---` section of the file, after the existing handlers.

Signature:

```gdscript
func _handle_scene_tree(peer_id: int, id: String, params: Dictionary) -> void:
```

Logic:

1. Get the edited scene root via `EditorInterface.get_edited_scene_root()`. If null, send an error with code `"NO_SCENE"` and message `"No scene is currently open in the editor"`.

2. Extract parameters from `params`:
   - `root` (String, default `""`): A node path relative to scene root, or a node name. If non-empty, resolve to a start node. If the node cannot be found, send an error with code `"NODE_NOT_FOUND"`.
   - `depth` (int, default 3): Maximum tree traversal depth. Clamp to range [1, 10].
   - `filter_type` (String, default `""`): Only include nodes of this Godot class in the output (e.g., `"Node3D"`, `"Control"`).
   - `include` (Array, default `["transform", "script", "groups", "visibility"]`): Which data categories to include per node.

3. Count total descendants via `_count_descendants()` on the start node.

4. Walk the tree via `_walk_tree()` starting from the start node.

5. Send the response with:
   - `root`: The name of the start node
   - `scene_path`: The scene file path from `scene_root.scene_file_path`
   - `total_nodes`: Total descendant count
   - `returned_nodes`: Number of nodes in the returned tree (from the walker's counter)
   - `tree`: The nested array of node dicts produced by `_walk_tree()`

### 3. Implement `_walk_tree()`

This is the recursive tree walker. It builds a nested structure of node data dictionaries.

Signature:

```gdscript
func _walk_tree(node: Node, scene_root: Node, max_depth: int, current_depth: int, filter_type: String, include: Array, out_nodes: Array) -> int:
```

Parameters:
- `node`: Current node being visited
- `scene_root`: The scene root (for relative path calculation)
- `max_depth`: Maximum allowed depth
- `current_depth`: Current recursion depth (starts at 0)
- `filter_type`: Type filter string (empty means include all)
- `include`: Array of data category strings to include
- `out_nodes`: Output array that receives node data dicts

Returns: The count of nodes added to `out_nodes` (including children added recursively).

Logic:

1. Determine if the current node matches the filter. A node matches if `filter_type` is empty OR `node.is_class(filter_type)` returns true.

2. If the node matches the filter, build a node data dictionary with short keys for token efficiency:
   - `n` (String): `node.name`
   - `t` (String): `node.get_class()`
   - If `"transform"` is in `include`:
     - For `Node3D`: `p` = position as `[x, y, z]`, `r` = rotation_degrees as `[x, y, z]`, `s` = scale as `[x, y, z]`
     - For `Node2D`: `p` = position as `[x, y]`, `r` = rotation_degrees (single float), `s` = scale as `[x, y]`
     - For `Control`: `p` = position as `[x, y]`, `s` = size as `[x, y]`
   - If `"script"` is in `include` and `node.get_script()` is not null: `script` = the script's resource path
   - If `"groups"` is in `include` and node has groups: `groups` = array of group name strings (exclude internal groups starting with `_`)
   - If `"visibility"` is in `include` and node has the `visible` property: `visible` = boolean

3. If `current_depth < max_depth` and the node has children, recurse into children. **Critical**: Always increment `current_depth` when recursing, regardless of whether the current node matched `filter_type`. Skipping the increment on filtered-out nodes can cause unbounded recursion because depth never increases.

4. For the children:
   - If the node matched the filter, add a `children` key to the node dict containing child node dicts
   - If `current_depth == max_depth` and the node has children, instead add `children_count` with the total child count (so the agent knows there is more below)
   - If the node did not match the filter, the children's results go directly into `out_nodes` (they are "promoted" up)

5. Add the node dict to `out_nodes` (if it matched the filter) and return the running count.

### 4. Implement `_count_descendants()`

Simple recursive counter for total nodes under a given node.

Signature:

```gdscript
func _count_descendants(node: Node) -> int:
```

Logic: Return 1 (for the node itself) plus the recursive count of all children. This gives the `total_nodes` value for the response.

### 5. Node resolution for the `root` parameter

When the `root` parameter is non-empty, the handler needs to find the target node. Use a two-step approach:

1. Try `scene_root.get_node_or_null(NodePath(root))` -- this handles full paths like `"Level/Enemies/Boss"`
2. If that returns null, try a recursive name search through the scene tree looking for a node whose `name` matches the `root` string exactly

If neither approach finds a node, send an error response with code `"NODE_NOT_FOUND"` and a descriptive message.

### 6. Complete dispatch integration

The final `_dispatch()` method should look like this (showing only the relevant additions):

```gdscript
func _dispatch(peer_id: int, id: String, method: String, params: Dictionary) -> void:
    match method:
        "ping":
            _handle_ping(peer_id, id)
        "editor_context":
            _handle_editor_context(peer_id, id)
        "scene_tree":
            _handle_scene_tree(peer_id, id, params)
        "run":
            _handle_run(peer_id, id, params)
        "stop":
            _handle_stop(peer_id, id)
        "screenshot":
            _handle_game_forward(peer_id, id, "godotiq:screenshot", params, SCREENSHOT_TIMEOUT_MS)
        "perf_snapshot":
            _handle_game_forward(peer_id, id, "godotiq:query_perf", params, PERF_TIMEOUT_MS)
        _:
            _send_error(peer_id, id, "UNKNOWN_METHOD", "Unknown method: %s" % method)
```

## Response Format

Example response for a scene with a few nodes, depth=2, all includes enabled:

```json
{
  "id": "req-1",
  "status": "ok",
  "result": {
    "root": "Main",
    "scene_path": "res://scenes/main.tscn",
    "total_nodes": 47,
    "returned_nodes": 5,
    "tree": [
      {
        "n": "Main",
        "t": "Node3D",
        "p": [0, 0, 0],
        "r": [0, 0, 0],
        "s": [1, 1, 1],
        "visible": true,
        "children": [
          {
            "n": "Player",
            "t": "CharacterBody3D",
            "p": [5.0, 0.0, 3.0],
            "r": [0, 90, 0],
            "s": [1, 1, 1],
            "script": "res://scripts/player.gd",
            "groups": ["controllable"],
            "visible": true,
            "children_count": 12
          },
          {
            "n": "WorldEnvironment",
            "t": "WorldEnvironment",
            "visible": true
          }
        ]
      }
    ]
  }
}
```

## Key Design Decisions

- **Short keys** (`n`, `t`, `p`, `r`, `s`): Reduces token count when the AI agent processes the response. Node names and types are the most common fields, so single-character keys save significant tokens for large trees.

- **Always increment depth**: The `current_depth` counter must increment on every recursion regardless of filter matching. If a `filter_type` is set and a non-matching node is encountered, the walker still increments depth when visiting that node's children. Without this, a deep tree of non-matching nodes could recurse indefinitely until the stack overflows.

- **Promote filtered-out nodes' children**: When a node does not match `filter_type`, its children that do match are added directly to the parent's output array. This ensures the agent sees all matching nodes without empty wrapper levels.

- **`children_count` at depth boundary**: When the walker reaches `max_depth`, it includes a `children_count` integer instead of recursing further. This tells the agent "there are N more nodes below" so it can make an informed decision about whether to request a deeper tree or a subtree starting at that node.

- **Internal group filtering**: Groups starting with `_` are excluded from the output. These are Godot-internal groups not meaningful to the agent.

---

## Implementation Notes

Implemented exactly as specified. Two post-review auto-fixes applied:
1. Added `str()` wrapper for `start_node.name` in response (StringName consistency)
2. Only emit `children` key when child array is non-empty (cleaner output)

`children_count` uses immediate child count (`get_child_count()`) rather than total descendants — the plan was ambiguous and immediate count avoids an expensive recursive call at the depth boundary.