diff --git a/godot-addon/addons/godotiq/godotiq_server.gd b/godot-addon/addons/godotiq/godotiq_server.gd
index f7b010e..a998b72 100644
--- a/godot-addon/addons/godotiq/godotiq_server.gd
+++ b/godot-addon/addons/godotiq/godotiq_server.gd
@@ -122,6 +122,8 @@ func _dispatch(peer_id: int, id: String, method: String, params: Dictionary) ->
 			_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":
@@ -178,6 +180,117 @@ func _handle_stop(peer_id: int, id: String) -> void:
 	send_response(peer_id, id, {"stopped": true})
 
 
+func _handle_scene_tree(peer_id: int, id: String, params: Dictionary) -> void:
+	var scene_root := EditorInterface.get_edited_scene_root()
+	if scene_root == null:
+		_send_error(peer_id, id, "NO_SCENE", "No scene is currently open in the editor")
+		return
+
+	# Extract parameters
+	var root_param: String = str(params.get("root", ""))
+	var depth: int = clampi(int(params.get("depth", 3)), 1, 10)
+	var filter_type: String = str(params.get("filter_type", ""))
+	var include: Array = params.get("include", ["transform", "script", "groups", "visibility"])
+
+	# Resolve start node
+	var start_node: Node = scene_root
+	if root_param != "":
+		start_node = scene_root.get_node_or_null(NodePath(root_param))
+		if start_node == null:
+			start_node = _find_by_name_recursive(scene_root, root_param)
+		if start_node == null:
+			_send_error(peer_id, id, "NODE_NOT_FOUND", "Node not found: %s" % root_param)
+			return
+
+	var total_nodes := _count_descendants(start_node)
+	var tree: Array = []
+	var returned_nodes := _walk_tree(start_node, scene_root, depth, 0, filter_type, include, tree)
+
+	send_response(peer_id, id, {
+		"root": start_node.name,
+		"scene_path": scene_root.scene_file_path,
+		"total_nodes": total_nodes,
+		"returned_nodes": returned_nodes,
+		"tree": tree,
+	})
+
+
+func _walk_tree(node: Node, scene_root: Node, max_depth: int, current_depth: int, filter_type: String, include: Array, out_nodes: Array) -> int:
+	var count := 0
+	var matches_filter := filter_type == "" or node.is_class(filter_type)
+
+	var node_dict := {}
+	if matches_filter:
+		node_dict["n"] = str(node.name)
+		node_dict["t"] = node.get_class()
+
+		if "transform" in include:
+			if node is Node3D:
+				var n3d: Node3D = node
+				node_dict["p"] = [n3d.position.x, n3d.position.y, n3d.position.z]
+				node_dict["r"] = [n3d.rotation_degrees.x, n3d.rotation_degrees.y, n3d.rotation_degrees.z]
+				node_dict["s"] = [n3d.scale.x, n3d.scale.y, n3d.scale.z]
+			elif node is Node2D:
+				var n2d: Node2D = node
+				node_dict["p"] = [n2d.position.x, n2d.position.y]
+				node_dict["r"] = n2d.rotation_degrees
+				node_dict["s"] = [n2d.scale.x, n2d.scale.y]
+			elif node is Control:
+				var ctrl: Control = node
+				node_dict["p"] = [ctrl.position.x, ctrl.position.y]
+				node_dict["s"] = [ctrl.size.x, ctrl.size.y]
+
+		if "script" in include and node.get_script() != null:
+			node_dict["script"] = node.get_script().resource_path
+
+		if "groups" in include:
+			var groups: Array[String] = []
+			for g in node.get_groups():
+				if not str(g).begins_with("_"):
+					groups.append(str(g))
+			if groups.size() > 0:
+				node_dict["groups"] = groups
+
+		if "visibility" in include and "visible" in node:
+			node_dict["visible"] = node.visible
+
+	# Recurse into children
+	if current_depth < max_depth and node.get_child_count() > 0:
+		var child_nodes: Array = []
+		for child in node.get_children():
+			count += _walk_tree(child, scene_root, max_depth, current_depth + 1, filter_type, include, child_nodes)
+		if matches_filter:
+			node_dict["children"] = child_nodes
+		else:
+			# Promote children up when current node doesn't match filter
+			out_nodes.append_array(child_nodes)
+	elif matches_filter and current_depth == max_depth and node.get_child_count() > 0:
+		node_dict["children_count"] = node.get_child_count()
+
+	if matches_filter:
+		out_nodes.append(node_dict)
+		count += 1
+
+	return count
+
+
+func _count_descendants(node: Node) -> int:
+	var count := 1
+	for child in node.get_children():
+		count += _count_descendants(child)
+	return count
+
+
+func _find_by_name_recursive(node: Node, target_name: String) -> Node:
+	if node.name == target_name:
+		return node
+	for child in node.get_children():
+		var found := _find_by_name_recursive(child, target_name)
+		if found != null:
+			return found
+	return null
+
+
 # --- Game-side forwarding ---
 
 func _handle_game_forward(peer_id: int, id: String, msg_type: String, params: Dictionary, timeout_ms: int) -> void:
