diff --git a/src/godotiq/tools/memory/__init__.py b/src/godotiq/tools/memory/__init__.py
index bdc80aa..014ba83 100644
--- a/src/godotiq/tools/memory/__init__.py
+++ b/src/godotiq/tools/memory/__init__.py
@@ -179,6 +179,202 @@ def _build_project_summary(session: GodotIQSession, detail: str = "normal") -> d
     return result
 
 
+def _build_gd_context(session: GodotIQSession, res_path: str, gd) -> dict:
+    """Build context dict for a .gd script file."""
+    idx = session._index
+
+    # Check if this script is an autoload
+    is_autoload = res_path in idx.autoloads.values()
+
+    # Line count
+    abs_path = session._resolve_res_path(res_path)
+    lines = 0
+    if abs_path.exists():
+        lines = len(abs_path.read_text(encoding="utf-8").splitlines())
+
+    # Public API: non-private functions
+    public_funcs = [
+        {
+            "name": f.name,
+            "args": [
+                {"name": a[0], "type": a[1], "default": a[2]} for a in f.args
+            ],
+            "return_type": f.return_type,
+            "is_static": f.is_static,
+            "line": f.line,
+        }
+        for f in gd.functions
+        if not f.is_private
+    ]
+
+    # Public signals
+    public_signals = [
+        {
+            "name": s.name,
+            "args": [{"name": a[0], "type": a[1]} for a in s.args],
+            "line": s.line,
+        }
+        for s in gd.signals
+    ]
+
+    # Public variables (class-scope only)
+    public_vars = [
+        {
+            "name": v.name,
+            "type": v.type_hint,
+            "is_const": v.is_const,
+            "annotation": v.annotation,
+            "line": v.line,
+        }
+        for v in gd.variables
+        if v.scope == "class"
+    ]
+
+    # Enums
+    public_enums = [
+        {
+            "name": e.name,
+            "values": list(e.values.keys()),
+            "line": e.line,
+        }
+        for e in gd.enums
+    ]
+
+    # Signals
+    signals_defined = [s.name for s in gd.signals]
+    signals_emitted = [e.signal_name for e in gd.signal_emissions]
+    signals_connected = [c.signal_source for c in gd.signal_connections]
+
+    # Dependencies
+    autoload_deps = list({r.autoload_name for r in gd.autoload_refs})
+    preload_deps = [r.path for r in gd.preload_refs]
+    used_in_scenes = idx.script_to_scenes.get(res_path, [])
+
+    # Depended by: find scripts that reference this file
+    depended_by: list[str] = []
+    if is_autoload:
+        # Find the autoload name for this script
+        for name, path in idx.autoloads.items():
+            if path == res_path:
+                depended_by = session.get_autoload_dependents(name)
+                break
+    # Also check preload references across all scripts
+    for other_path, other_gd in session._gd_scripts.items():
+        if other_path == res_path:
+            continue
+        for ref in other_gd.preload_refs:
+            if ref.path == res_path and other_path not in depended_by:
+                depended_by.append(other_path)
+
+    # Safety
+    has_serialize = any(
+        f.name in ("serialize", "deserialize") for f in gd.functions
+    )
+
+    return {
+        "file": res_path,
+        "type": "gdscript",
+        "class_name": gd.class_name,
+        "extends": gd.extends,
+        "is_autoload": is_autoload,
+        "lines": lines,
+        "public_api": {
+            "functions": public_funcs,
+            "signals": public_signals,
+            "variables": public_vars,
+            "enums": public_enums,
+        },
+        "signals_defined": signals_defined,
+        "signals_emitted": signals_emitted,
+        "signals_connected": signals_connected,
+        "depends_on": {
+            "autoloads": autoload_deps,
+            "preloads": preload_deps,
+            "scenes": used_in_scenes,
+        },
+        "depended_by": depended_by,
+        "used_in_scenes": used_in_scenes,
+        "safety": {
+            "is_instance_valid_count": len(gd.is_instance_valid_lines),
+            "has_serialize": has_serialize,
+        },
+    }
+
+
+def _build_tscn_context(session: GodotIQSession, res_path: str, scene) -> dict:
+    """Build context dict for a .tscn scene file."""
+    idx = session._index
+
+    # External resources
+    ext_resources = [
+        {"id": rid, "type": r.type, "path": r.path}
+        for rid, r in scene.ext_resources.items()
+    ]
+
+    # Instances and scripts
+    instances = scene.get_all_instances()
+    scripts_used = scene.get_all_scripts()
+
+    # Unique groups across all nodes
+    groups: set[str] = set()
+    for node in scene.nodes:
+        groups.update(node.groups)
+
+    # Connections
+    connections = [
+        {
+            "signal": c.signal,
+            "from": c.from_node,
+            "to": c.to_node,
+            "method": c.method,
+        }
+        for c in scene.connections
+    ]
+
+    # Instanced by: scenes that instance this scene
+    instanced_by = idx.scene_instance_of.get(res_path, [])
+
+    return {
+        "file": res_path,
+        "type": "scene",
+        "total_nodes": len(scene.nodes),
+        "root_type": scene.root.type if scene.root else "",
+        "ext_resources": ext_resources,
+        "instances": instances,
+        "scripts_used": scripts_used,
+        "groups_used": sorted(groups),
+        "connections": connections,
+        "instanced_by": instanced_by,
+    }
+
+
+def _build_file_context(session: GodotIQSession, file_path: str) -> dict:
+    """Build deep context for a single file.
+
+    Args:
+        session: Loaded GodotIQSession.
+        file_path: res:// path or relative path from project root.
+
+    Returns:
+        Dictionary with file context data. Returns error dict if file not found.
+    """
+    res_path = session.resolve_file_path(file_path)
+    if res_path is None:
+        return {"error": "File not found", "file": file_path}
+
+    # Try as GDScript
+    gd = session.get_script(res_path)
+    if gd is not None:
+        return _build_gd_context(session, res_path, gd)
+
+    # Try as scene
+    scene = session.get_scene(res_path)
+    if scene is not None:
+        return _build_tscn_context(session, res_path, scene)
+
+    return {"error": "File not found", "file": file_path}
+
+
 @mcp.tool(
     name="godotiq_project_summary",
     annotations={
@@ -201,3 +397,29 @@ async def godotiq_project_summary(
     if session is None:
         return {"error": "No Godot project loaded. Set GODOTIQ_PROJECT_ROOT or run from project directory."}
     return _build_project_summary(session, detail)
+
+
+@mcp.tool(
+    name="godotiq_file_context",
+    annotations={
+        "readOnlyHint": True,
+        "destructiveHint": False,
+        "idempotentHint": True,
+        "openWorldHint": False,
+    },
+)
+async def godotiq_file_context(
+    file: str,
+    ctx: Context = None,
+) -> dict:
+    """Deep context for a file. Call before editing to understand dependencies and risks.
+
+    Works for both .gd scripts and .tscn scenes.
+
+    Args:
+        file: Path to file, either res:// path or relative path from project root.
+    """
+    session = ctx.request_context.lifespan_context["session"]
+    if session is None:
+        return {"error": "No Godot project loaded"}
+    return _build_file_context(session, file)
diff --git a/tests/test_tools/test_file_context.py b/tests/test_tools/test_file_context.py
new file mode 100644
index 0000000..815fc92
--- /dev/null
+++ b/tests/test_tools/test_file_context.py
@@ -0,0 +1,87 @@
+"""Tests for godotiq_file_context tool."""
+
+from __future__ import annotations
+
+import pytest
+
+from godotiq.session import GodotIQSession
+from godotiq.tools.memory import _build_file_context
+
+
+class TestFileContextGdScript:
+    def test_context_gd_file(self, session: GodotIQSession) -> None:
+        """Basic .gd file returns expected structure."""
+        result = _build_file_context(session, "res://scripts/autoloads/economy_manager.gd")
+        assert result["type"] == "gdscript"
+        assert result["class_name"] == "EconomyManagerClass"
+        assert result["extends"] == "Node"
+        assert "public_api" in result
+
+    def test_context_gd_autoload(self, session: GodotIQSession) -> None:
+        """Autoload script is correctly identified."""
+        result = _build_file_context(session, "res://scripts/autoloads/economy_manager.gd")
+        assert result["is_autoload"] is True
+
+    def test_context_gd_non_autoload(self, session: GodotIQSession) -> None:
+        """Non-autoload script is correctly identified."""
+        result = _build_file_context(session, "res://scripts/entities/worker.gd")
+        assert result["is_autoload"] is False
+
+    def test_context_gd_dependencies(self, session: GodotIQSession) -> None:
+        """Dependencies are correctly listed."""
+        result = _build_file_context(session, "res://scripts/autoloads/progression_manager.gd")
+        assert "depends_on" in result
+        assert "GameManager" in result["depends_on"]["autoloads"]
+        assert "EconomyManager" in result["depends_on"]["autoloads"]
+
+    def test_context_gd_signals(self, session: GodotIQSession) -> None:
+        """Signals are listed."""
+        result = _build_file_context(session, "res://scripts/autoloads/economy_manager.gd")
+        assert "cash_changed" in result["signals_defined"]
+        assert "cash_changed" in result["signals_emitted"]
+
+    def test_context_gd_public_api(self, session: GodotIQSession) -> None:
+        """Public API lists non-private functions."""
+        result = _build_file_context(session, "res://scripts/autoloads/economy_manager.gd")
+        func_names = [f["name"] for f in result["public_api"]["functions"]]
+        assert "add_cash" in func_names
+        assert "spend_cash" in func_names
+        # Private functions should not appear
+        assert "_ready" not in func_names
+
+
+class TestFileContextTscnScene:
+    def test_context_tscn_file(self, session: GodotIQSession) -> None:
+        """Basic .tscn file returns expected structure."""
+        result = _build_file_context(session, "res://scenes/equipment/fdm_printer.tscn")
+        assert result["type"] == "scene"
+        assert result["root_type"] == "Node3D"
+        assert result["total_nodes"] >= 4
+
+    def test_context_tscn_scripts(self, session: GodotIQSession) -> None:
+        """Scene lists attached scripts."""
+        result = _build_file_context(session, "res://scenes/equipment/fdm_printer.tscn")
+        assert "res://scripts/entities/printer.gd" in result["scripts_used"]
+
+    def test_context_tscn_instances(self, session: GodotIQSession) -> None:
+        """Scene lists instanced sub-scenes."""
+        result = _build_file_context(session, "res://scenes/main.tscn")
+        assert "res://scenes/equipment/fdm_printer.tscn" in result["instances"]
+
+
+class TestFileContextPathHandling:
+    def test_context_res_path(self, session: GodotIQSession) -> None:
+        """Accepts res:// paths."""
+        result = _build_file_context(session, "res://scripts/autoloads/economy_manager.gd")
+        assert "error" not in result
+
+    def test_context_relative_path(self, session: GodotIQSession) -> None:
+        """Accepts relative paths from project root."""
+        result = _build_file_context(session, "scripts/autoloads/economy_manager.gd")
+        assert "error" not in result
+        assert result["file"].startswith("res://")
+
+    def test_context_missing_file(self, session: GodotIQSession) -> None:
+        """Returns error dict for missing files."""
+        result = _build_file_context(session, "res://nonexistent.gd")
+        assert "error" in result
