diff --git a/tests/test_bridge/test_tools.py b/tests/test_bridge/test_tools.py
new file mode 100644
index 0000000..0edfc53
--- /dev/null
+++ b/tests/test_bridge/test_tools.py
@@ -0,0 +1,108 @@
+"""Tests for bridge MCP tool implementations with mocked bridge."""
+
+from __future__ import annotations
+
+from unittest.mock import AsyncMock, patch
+
+from godotiq.tools.bridge.screenshot import screenshot
+from godotiq.tools.bridge.run import run
+from godotiq.tools.bridge.perf_snapshot import perf_snapshot
+from godotiq.tools.bridge.editor_context import editor_context
+
+
+class TestScreenshotTool:
+    async def test_default_params(self):
+        mock_bridge = AsyncMock()
+        mock_bridge.request_with_retry.return_value = {
+            "image": "base64data",
+            "format": "webp",
+            "width": 960,
+            "height": 540,
+        }
+        with patch("godotiq.tools.bridge.screenshot.get_bridge", return_value=mock_bridge):
+            result = await screenshot()
+
+        mock_bridge.request_with_retry.assert_called_once_with(
+            "screenshot",
+            params={"viewport": "game", "scale": 0.5, "format": "webp"},
+            max_retries=3,
+            retry_delay=0.5,
+        )
+        assert result["image"] == "base64data"
+        assert result["format"] == "webp"
+        assert result["width"] == 960
+        assert result["height"] == 540
+
+    async def test_invalid_scale_clamped(self):
+        mock_bridge = AsyncMock()
+        mock_bridge.request_with_retry.return_value = {}
+        with patch("godotiq.tools.bridge.screenshot.get_bridge", return_value=mock_bridge):
+            await screenshot(scale=-1.0)
+
+        call_kwargs = mock_bridge.request_with_retry.call_args
+        assert call_kwargs[1]["params"]["scale"] == 0.5
+
+    async def test_invalid_format_fallback(self):
+        mock_bridge = AsyncMock()
+        mock_bridge.request_with_retry.return_value = {}
+        with patch("godotiq.tools.bridge.screenshot.get_bridge", return_value=mock_bridge):
+            await screenshot(format="bmp")
+
+        call_kwargs = mock_bridge.request_with_retry.call_args
+        assert call_kwargs[1]["params"]["format"] == "webp"
+
+
+class TestRunTool:
+    async def test_play_main(self):
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {}
+        with patch("godotiq.tools.bridge.run.get_bridge", return_value=mock_bridge):
+            result = await run(action="play", scene="main")
+
+        mock_bridge.request.assert_called_once_with("run", params={"scene": "main"})
+        assert result == {"action": "play", "scene": "main"}
+
+    async def test_stop(self):
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {}
+        with patch("godotiq.tools.bridge.run.get_bridge", return_value=mock_bridge):
+            result = await run(action="stop")
+
+        mock_bridge.request.assert_called_once_with("stop")
+        assert result == {"action": "stop"}
+
+
+class TestPerfSnapshotTool:
+    async def test_basic_call(self):
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {
+            "fps": 60,
+            "draw_calls": 120,
+            "triangles": 50000,
+        }
+        with patch("godotiq.tools.bridge.perf_snapshot.get_bridge", return_value=mock_bridge):
+            result = await perf_snapshot()
+
+        mock_bridge.request.assert_called_once_with("perf_snapshot")
+        assert result["fps"] == 60
+
+
+class TestEditorContextTool:
+    async def test_connected(self):
+        mock_bridge = AsyncMock()
+        mock_bridge.request.return_value = {
+            "editor_version": "4.3",
+            "open_scenes": ["res://main.tscn"],
+        }
+        with patch("godotiq.tools.bridge.editor_context.get_bridge_or_none", return_value=mock_bridge):
+            result = await editor_context()
+
+        assert result["addon_connected"] is True
+        assert result["editor_version"] == "4.3"
+
+    async def test_disconnected(self):
+        with patch("godotiq.tools.bridge.editor_context.get_bridge_or_none", return_value=None):
+            result = await editor_context()
+
+        assert result["addon_connected"] is False
+        assert "note" in result
