diff --git a/src/godotiq/tools/bridge/__init__.py b/src/godotiq/tools/bridge/__init__.py
index 976bae6..61a644d 100644
--- a/src/godotiq/tools/bridge/__init__.py
+++ b/src/godotiq/tools/bridge/__init__.py
@@ -11,6 +11,10 @@ from godotiq.tools.bridge.screenshot import screenshot as _screenshot
 from godotiq.tools.bridge.run import run as _run
 from godotiq.tools.bridge.perf_snapshot import perf_snapshot as _perf_snapshot
 from godotiq.tools.bridge.editor_context import editor_context as _editor_context
+from godotiq.tools.bridge.scene_tree import scene_tree as _scene_tree
+from godotiq.tools.bridge.node_ops import node_ops as _node_ops
+from godotiq.tools.bridge.script_ops import script_ops as _script_ops
+from godotiq.tools.bridge.file_ops import file_ops as _file_ops
 
 
 @mcp.tool(
@@ -143,3 +147,213 @@ async def godotiq_editor_context(ctx: Context = None) -> dict:
         return await _editor_context()
     except Exception as e:
         return {"error": f"Unexpected error: {e}"}
+
+
+@mcp.tool(
+    name="godotiq_scene_tree",
+    annotations={
+        "readOnlyHint": True,
+        "destructiveHint": False,
+        "idempotentHint": True,
+        "openWorldHint": True,
+    },
+)
+async def godotiq_scene_tree(
+    root: str = "",
+    depth: int = 3,
+    filter_type: str = "",
+    include: list[str] | None = None,
+    ctx: Context = None,
+) -> dict:
+    """Read the live scene tree from the Godot editor.
+
+    Returns the editor's active scene tree with spatial, script, group, and
+    visibility context per node. Unlike scene_map (which reads .tscn files
+    from disk), this reads the actual live editor state.
+
+    Args:
+        root: Node path to start from (e.g. "Level/Enemies"). Empty = scene root.
+        depth: Max tree depth (1-10, default 3).
+        filter_type: Only include nodes of this type (e.g. "MeshInstance3D").
+        include: Data fields per node. Default: ["transform","script","groups","visibility"].
+        ctx: MCP context (unused).
+
+    Returns:
+        Dict with root, scene_path, total_nodes, returned_nodes, and tree.
+    """
+    try:
+        return await _scene_tree(root=root, depth=depth, filter_type=filter_type, include=include)
+    except BridgeConnectionError as e:
+        return {"error": str(e), "hint": "Enable the GodotIQ addon in Godot editor and ensure it is running."}
+    except BridgeTimeoutError as e:
+        return {"error": str(e), "hint": "The editor may not be responding or the scene is too large."}
+    except BridgeError as e:
+        return {"error": f"[{e.code}] {e.error_message}"}
+    except Exception as e:
+        return {"error": f"Unexpected error: {e}"}
+
+
+@mcp.tool(
+    name="godotiq_node_ops",
+    annotations={
+        "readOnlyHint": False,
+        "destructiveHint": True,
+        "idempotentHint": False,
+        "openWorldHint": True,
+    },
+)
+async def godotiq_node_ops(
+    operations: list[dict] | None = None,
+    ctx: Context = None,
+) -> dict:
+    """Execute batch node operations in the Godot editor with undo support.
+
+    All operations are grouped as a single undo action (one Ctrl+Z to revert).
+    Maximum 50 operations per call.
+
+    Supported ops: move, rotate, scale, set_property, add_child, delete,
+    duplicate, reparent.
+
+    Args:
+        operations: List of operation dicts, each with an 'op' key.
+        ctx: MCP context (unused).
+
+    Returns:
+        Dict with results array containing per-operation status.
+    """
+    try:
+        return await _node_ops(operations=operations or [])
+    except BridgeConnectionError as e:
+        return {"error": str(e), "hint": "Enable the GodotIQ addon in Godot editor and ensure it is running."}
+    except BridgeTimeoutError as e:
+        return {"error": str(e), "hint": "The operation timed out. Try fewer operations per batch."}
+    except BridgeError as e:
+        return {"error": f"[{e.code}] {e.error_message}"}
+    except ValueError as e:
+        return {"error": str(e)}
+    except Exception as e:
+        return {"error": f"Unexpected error: {e}"}
+
+
+@mcp.tool(
+    name="godotiq_script_ops",
+    annotations={
+        "readOnlyHint": False,
+        "destructiveHint": True,
+        "idempotentHint": False,
+        "openWorldHint": False,
+    },
+)
+async def godotiq_script_ops(
+    op: str = "read",
+    path: str = "",
+    content: str = "",
+    patches: list[dict] | None = None,
+    validate_before_write: bool = True,
+    auto_fix: bool = False,
+    dry_run: bool = False,
+    ctx: Context = None,
+) -> dict:
+    """Read, write, patch, or create GDScript files with validation.
+
+    Modes:
+      - read: Return file content with line count and size.
+      - write: Write entire file (with optional GDScript convention validation).
+      - patch: Find-and-replace with uniqueness checking (safest edit mode).
+      - create: Create new file (fails if file already exists).
+
+    Use this tool for .gd files. For non-script files, use file_ops instead.
+
+    Args:
+        op: Operation mode ("read", "write", "patch", "create").
+        path: File path (supports res:// and relative paths).
+        content: File content for write/create modes.
+        patches: List of {search, replace} dicts for patch mode.
+        validate_before_write: Run GDScript convention checks before writing.
+        auto_fix: Automatically fix detected issues (future feature).
+        dry_run: Validate without writing to disk.
+        ctx: MCP context (unused).
+
+    Returns:
+        Dict with operation result, status, and any validation issues.
+    """
+    try:
+        return await _script_ops(
+            op=op,
+            path=path,
+            content=content,
+            patches=patches,
+            validate_before_write=validate_before_write,
+            auto_fix=auto_fix,
+            dry_run=dry_run,
+        )
+    except Exception as e:
+        return {"error": f"Unexpected error: {e}"}
+
+
+@mcp.tool(
+    name="godotiq_file_ops",
+    annotations={
+        "readOnlyHint": False,
+        "destructiveHint": True,
+        "idempotentHint": False,
+        "openWorldHint": False,
+    },
+)
+async def godotiq_file_ops(
+    op: str = "list",
+    path: str = "",
+    query: str = "",
+    filter: str = "all",
+    recursive: bool = False,
+    context_lines: int = 2,
+    max_depth: int = 3,
+    show_sizes: bool = False,
+    content: str = "",
+    destination: str = "",
+    ctx: Context = None,
+) -> dict:
+    """Execute filesystem operations within the Godot project.
+
+    Operations:
+      - list: List directory entries with optional type filter and recursion.
+      - read: Read text file content (fails on binary files).
+      - write: Write file content (use script_ops for .gd files).
+      - move: Move/rename a file.
+      - delete: Delete a file.
+      - search: Text search across files with context lines.
+      - tree: Directory tree with depth control and optional size info.
+
+    For .gd files, prefer godotiq_script_ops which provides GDScript validation.
+
+    Args:
+        op: Operation ("list", "read", "write", "move", "delete", "search", "tree").
+        path: Target path (supports res:// and relative paths).
+        query: Search query string (for search operation).
+        filter: Type filter ("all", "scenes", "scripts", "images", "audio", etc.).
+        recursive: Include subdirectories in list operation.
+        context_lines: Lines of context around search matches (default 2).
+        max_depth: Maximum depth for tree operation (default 3).
+        show_sizes: Include file sizes in tree output.
+        content: File content for write operation.
+        destination: Destination path for move operation.
+        ctx: MCP context (unused).
+
+    Returns:
+        Dict with operation-specific results.
+    """
+    try:
+        return await _file_ops(
+            op=op,
+            path=path,
+            query=query,
+            filter=filter,
+            recursive=recursive,
+            context_lines=context_lines,
+            max_depth=max_depth,
+            show_sizes=show_sizes,
+            content=content,
+            destination=destination,
+        )
+    except Exception as e:
+        return {"error": f"Unexpected error: {e}"}
diff --git a/tests/test_bridge/test_registration.py b/tests/test_bridge/test_registration.py
new file mode 100644
index 0000000..65c94db
--- /dev/null
+++ b/tests/test_bridge/test_registration.py
@@ -0,0 +1,124 @@
+"""Tests for MCP tool registration wrappers in bridge/__init__.py."""
+
+from __future__ import annotations
+
+import pytest
+from unittest.mock import AsyncMock, patch
+
+from godotiq.tools.bridge import (
+    godotiq_scene_tree,
+    godotiq_node_ops,
+    godotiq_script_ops,
+    godotiq_file_ops,
+)
+
+
+class TestSceneTreeRegistration:
+    """scene_tree MCP wrapper exception handling."""
+
+    async def test_catches_bridge_connection_error(self) -> None:
+        """BridgeConnectionError is caught and returns a user-friendly message with hint."""
+        from godotiq.bridge.connection import BridgeConnectionError
+
+        with patch(
+            "godotiq.tools.bridge.scene_tree.get_bridge",
+            side_effect=BridgeConnectionError("not connected"),
+        ):
+            result = await godotiq_scene_tree()
+
+        assert "error" in result
+        assert "hint" in result
+
+    async def test_catches_bridge_timeout_error(self) -> None:
+        """BridgeTimeoutError is caught and returns a user-friendly message with hint."""
+        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,
+        ):
+            result = await godotiq_scene_tree()
+
+        assert "error" in result
+        assert "hint" in result
+
+    async def test_catches_bridge_error(self) -> None:
+        """BridgeError is caught and returns error with code."""
+        from godotiq.bridge.connection import BridgeError
+
+        mock_bridge = AsyncMock()
+        mock_bridge.request.side_effect = BridgeError("NOT_FOUND", "No scene open")
+        with patch(
+            "godotiq.tools.bridge.scene_tree.get_bridge",
+            return_value=mock_bridge,
+        ):
+            result = await godotiq_scene_tree()
+
+        assert "error" in result
+        assert "NOT_FOUND" in result["error"]
+
+
+class TestNodeOpsRegistration:
+    """node_ops MCP wrapper exception handling."""
+
+    async def test_catches_bridge_connection_error(self) -> None:
+        """BridgeConnectionError is caught and returns a user-friendly message."""
+        from godotiq.bridge.connection import BridgeConnectionError
+
+        with patch(
+            "godotiq.tools.bridge.node_ops.get_bridge",
+            side_effect=BridgeConnectionError("not connected"),
+        ):
+            result = await godotiq_node_ops(
+                operations=[{"op": "move", "node": "X", "position": [0, 0, 0]}]
+            )
+
+        assert "error" in result
+        assert "hint" in result
+
+    async def test_catches_bridge_timeout_error(self) -> None:
+        """BridgeTimeoutError is caught and returns a user-friendly message."""
+        from godotiq.bridge.connection import BridgeTimeoutError
+
+        mock_bridge = AsyncMock()
+        mock_bridge.request.side_effect = BridgeTimeoutError("timed out")
+        with patch(
+            "godotiq.tools.bridge.node_ops.get_bridge",
+            return_value=mock_bridge,
+        ):
+            result = await godotiq_node_ops(
+                operations=[{"op": "move", "node": "X", "position": [0, 0, 0]}]
+            )
+
+        assert "error" in result
+        assert "hint" in result
+
+
+class TestScriptOpsRegistration:
+    """script_ops MCP wrapper exception handling."""
+
+    async def test_catches_generic_exception(self) -> None:
+        """Any unexpected Exception is caught and returns an error dict."""
+        with patch(
+            "godotiq.tools.bridge._script_ops",
+            side_effect=RuntimeError("disk full"),
+        ):
+            result = await godotiq_script_ops(op="read", path="test.gd")
+
+        assert "error" in result
+
+
+class TestFileOpsRegistration:
+    """file_ops MCP wrapper exception handling."""
+
+    async def test_catches_generic_exception(self) -> None:
+        """Any unexpected Exception is caught and returns an error dict."""
+        with patch(
+            "godotiq.tools.bridge._file_ops",
+            side_effect=RuntimeError("permission denied"),
+        ):
+            result = await godotiq_file_ops(op="list", path=".")
+
+        assert "error" in result
