diff --git a/src/godotiq/server.py b/src/godotiq/server.py
index b7bf018..0b2b0be 100644
--- a/src/godotiq/server.py
+++ b/src/godotiq/server.py
@@ -1,8 +1,37 @@
 """GodotIQ MCP server — entry point and tool registration."""
 
+from __future__ import annotations
+
+import logging
+from contextlib import asynccontextmanager
+
 from mcp.server.fastmcp import FastMCP
 
-mcp = FastMCP(name="godotiq")
+from godotiq.session import GodotIQSession, discover_project_root
+
+logger = logging.getLogger(__name__)
+
+
+@asynccontextmanager
+async def godotiq_lifespan(server):
+    """Initialize GodotIQ session on server startup."""
+    project_root = discover_project_root()
+    if project_root is not None:
+        session = GodotIQSession(project_root)
+        session.load()
+        logger.info(
+            "GodotIQ session loaded: %s (%d scripts, %d scenes)",
+            session._index.project_name,
+            session._index.total_scripts,
+            session._index.total_scenes,
+        )
+    else:
+        session = None
+        logger.warning("No Godot project found. Tools will return errors.")
+    yield {"session": session}
+
+
+mcp = FastMCP(name="godotiq", lifespan=godotiq_lifespan)
 
 
 @mcp.tool()
@@ -11,6 +40,12 @@ async def godotiq_ping() -> dict:
     return {"status": "ok", "version": "0.1.0"}
 
 
+# Tool imports — each module registers tools on the mcp instance
+from godotiq.tools import memory  # noqa: E402, F401
+from godotiq.tools import code  # noqa: E402, F401
+from godotiq.tools import spatial  # noqa: E402, F401
+
+
 def main() -> None:
     """Start the GodotIQ MCP server with stdio transport."""
     mcp.run(transport="stdio")
diff --git a/tests/test_tools/conftest.py b/tests/test_tools/conftest.py
new file mode 100644
index 0000000..efdd687
--- /dev/null
+++ b/tests/test_tools/conftest.py
@@ -0,0 +1,23 @@
+"""Shared fixtures for Sprint 3 tool tests."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+import pytest
+
+from godotiq.session import GodotIQSession
+
+
+@pytest.fixture
+def sample_root() -> Path:
+    """Path to the sample project fixture."""
+    return Path(__file__).resolve().parent.parent / "fixtures" / "sample_project"
+
+
+@pytest.fixture
+def session(sample_root: Path) -> GodotIQSession:
+    """A loaded GodotIQSession for the sample project."""
+    s = GodotIQSession(sample_root)
+    s.load()
+    return s
diff --git a/tests/test_tools/test_server_integration.py b/tests/test_tools/test_server_integration.py
new file mode 100644
index 0000000..a89ae35
--- /dev/null
+++ b/tests/test_tools/test_server_integration.py
@@ -0,0 +1,69 @@
+"""Tests for server integration — lifespan, context, and tool registration."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+import pytest
+
+
+class TestLifespan:
+    """Test the godotiq_lifespan context manager."""
+
+    @pytest.mark.asyncio
+    async def test_lifespan_creates_session_with_project(self, monkeypatch, sample_root):
+        """Lifespan yields a session when pointed at a valid Godot project."""
+        monkeypatch.setenv("GODOTIQ_PROJECT_ROOT", str(sample_root))
+        from godotiq.server import godotiq_lifespan, mcp
+
+        async with godotiq_lifespan(mcp) as state:
+            assert "session" in state
+            assert state["session"] is not None
+            assert state["session"]._index is not None
+
+    @pytest.mark.asyncio
+    async def test_lifespan_yields_none_without_project(self, monkeypatch, tmp_path):
+        """Lifespan yields None session when no Godot project is found."""
+        monkeypatch.delenv("GODOTIQ_PROJECT_ROOT", raising=False)
+        monkeypatch.chdir(tmp_path)
+        from godotiq.server import godotiq_lifespan, mcp
+
+        async with godotiq_lifespan(mcp) as state:
+            assert "session" in state
+            assert state["session"] is None
+
+
+class TestToolRegistration:
+    """Verify tools are registered after server integration."""
+
+    @pytest.mark.asyncio
+    async def test_ping_still_registered(self):
+        """godotiq_ping must still work after lifespan addition."""
+        from godotiq.server import mcp
+
+        tools = await mcp.list_tools()
+        tool_names = [t.name for t in tools]
+        assert "godotiq_ping" in tool_names
+
+    @pytest.mark.asyncio
+    async def test_no_circular_imports(self):
+        """Importing server should not cause circular import errors."""
+        import importlib
+        import godotiq.server
+        importlib.reload(godotiq.server)
+        # If we reach here, no circular import
+
+
+class TestConftest:
+    """Verify conftest fixtures produce valid objects."""
+
+    def test_session_fixture_loads(self, session):
+        """Session fixture should produce a loaded GodotIQSession."""
+        assert session is not None
+        assert session._index is not None
+        assert session._index.project_name != ""
+
+    def test_sample_root_exists(self, sample_root):
+        """sample_root fixture should point to a real directory."""
+        assert sample_root.exists()
+        assert (sample_root / "project.godot").exists()
