diff --git a/src/godotiq/tools/bridge/scene_tree.py b/src/godotiq/tools/bridge/scene_tree.py
new file mode 100644
index 0000000..ce85f12
--- /dev/null
+++ b/src/godotiq/tools/bridge/scene_tree.py
@@ -0,0 +1,44 @@
+"""Scene tree tool -- read the live editor scene tree via the bridge."""
+
+from __future__ import annotations
+
+from godotiq.bridge.session import get_bridge
+
+DEFAULT_INCLUDE: list[str] = ["transform", "script", "groups", "visibility"]
+
+
+async def scene_tree(
+    root: str = "",
+    depth: int = 3,
+    filter_type: str = "",
+    include: list[str] | None = None,
+) -> dict:
+    """Get the live scene tree from the Godot editor.
+
+    Args:
+        root: Optional node path to start from (e.g. "Level/Enemies").
+              Empty string means the edited scene root.
+        depth: Maximum tree depth to return. Clamped to [1, 10].
+        filter_type: Only include nodes of this type (e.g. "MeshInstance3D").
+                     Empty string means all types.
+        include: Which optional data fields to include per node.
+                 Defaults to ["transform", "script", "groups", "visibility"].
+
+    Returns:
+        Dict with root, scene_path, total_nodes, returned_nodes, and tree.
+    """
+    depth = max(1, min(10, depth))
+
+    if include is None:
+        include = list(DEFAULT_INCLUDE)
+
+    bridge = await get_bridge()
+    return await bridge.request(
+        "scene_tree",
+        params={
+            "root": root,
+            "depth": depth,
+            "filter_type": filter_type,
+            "include": include,
+        },
+    )
diff --git a/tests/test_bridge/test_scene_tree.py b/tests/test_bridge/test_scene_tree.py
new file mode 100644
index 0000000..fafdf33
--- /dev/null
+++ b/tests/test_bridge/test_scene_tree.py
@@ -0,0 +1,115 @@
+"""Tests for the scene_tree bridge tool."""
+
+from __future__ import annotations
+
+import pytest
+from unittest.mock import AsyncMock, patch
+
+from godotiq.tools.bridge.scene_tree import scene_tree
+
+DEFAULT_INCLUDE = ["transform", "script", "groups", "visibility"]
+
+
+class TestSceneTree:
+    """scene_tree parameter validation and bridge delegation."""
+
+    async def test_default_params(self) -> None:
+        """Default call forwards depth=3 and default include list to bridge."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {"root": "Root", "tree": []}
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            result = await scene_tree()
+
+        mock_bridge.request.assert_called_once_with(
+            "scene_tree",
+            params={
+                "root": "",
+                "depth": 3,
+                "filter_type": "",
+                "include": DEFAULT_INCLUDE,
+            },
+        )
+        assert result == {"root": "Root", "tree": []}
+
+    async def test_depth_clamped_below_1(self) -> None:
+        """Depth values below 1 are clamped to 1."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {}
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            await scene_tree(depth=0)
+
+        assert mock_bridge.request.call_args.kwargs["params"]["depth"] == 1
+
+    async def test_depth_clamped_above_10(self) -> None:
+        """Depth values above 10 are clamped to 10."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {}
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            await scene_tree(depth=50)
+
+        assert mock_bridge.request.call_args.kwargs["params"]["depth"] == 10
+
+    async def test_root_param_forwarded(self) -> None:
+        """Custom root path is forwarded to the bridge."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {}
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            await scene_tree(root="Level/Enemies")
+
+        assert mock_bridge.request.call_args.kwargs["params"]["root"] == "Level/Enemies"
+
+    async def test_filter_type_forwarded(self) -> None:
+        """filter_type string is forwarded to the bridge."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {}
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            await scene_tree(filter_type="MeshInstance3D")
+
+        assert mock_bridge.request.call_args.kwargs["params"]["filter_type"] == "MeshInstance3D"
+
+    async def test_custom_include_list(self) -> None:
+        """A caller-supplied include list overrides the default."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {}
+        custom = ["transform", "script"]
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            await scene_tree(include=custom)
+
+        assert mock_bridge.request.call_args.kwargs["params"]["include"] == custom
+
+    async def test_returns_bridge_response(self) -> None:
+        """The bridge response dict is returned unmodified."""
+        mock_bridge = AsyncMock()
+        expected = {
+            "root": "Main",
+            "scene_path": "res://main.tscn",
+            "total_nodes": 42,
+            "returned_nodes": 10,
+            "tree": [{"n": "Player", "t": "CharacterBody3D"}],
+        }
+        mock_bridge.request.return_value = expected
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            result = await scene_tree()
+
+        assert result == expected
+
+    async def test_raises_bridge_connection_error(self) -> None:
+        """BridgeConnectionError from get_bridge propagates."""
+        from godotiq.bridge.connection import BridgeConnectionError
+
+        with patch(
+            "godotiq.tools.bridge.scene_tree.get_bridge",
+            side_effect=BridgeConnectionError("not connected"),
+        ):
+            with pytest.raises(BridgeConnectionError):
+                await scene_tree()
+
+    async def test_raises_bridge_timeout_error(self) -> None:
+        """BridgeTimeoutError from bridge.request propagates."""
+        from godotiq.bridge.connection import BridgeTimeoutError
+
+        mock_bridge = AsyncMock()
+        mock_bridge.request.side_effect = BridgeTimeoutError("timed out")
+        with patch("godotiq.tools.bridge.scene_tree.get_bridge", return_value=mock_bridge):
+            with pytest.raises(BridgeTimeoutError):
+                await scene_tree()
