# Section 03: Server Integration

## Overview

Modify `src/godotiq/server.py` to add the FastMCP lifespan that creates a GodotIQSession and makes it available to all tools via the MCP context. Establish the tool registration pattern. Create shared test infrastructure for tool testing.

**Critical constraint:** `godotiq_ping` must continue to work exactly as before. This is a purely additive change.

---

## Dependencies

- **Upstream:** Section 02 (session.py must exist)
- **Downstream:** Sections 04-07 (all tools depend on the lifespan and context pattern)

---

## Implementation Details

### Modified: `src/godotiq/server.py`

Three changes were made:

1. **Added lifespan context manager** (`godotiq_lifespan`) that creates GodotIQSession
2. **Passed lifespan to FastMCP constructor**
3. **Kept godotiq_ping unchanged**

**Deviation from plan:** Added `try/except` around `session.load()` to prevent server crash on malformed projects (code review fix #4). Falls back to `session = None` with logged exception.

### IMPORTANT: Correct Lifespan Access Pattern

**The plan originally documented `lifespan_state` but the correct MCP SDK field is `lifespan_context`.**

In tool functions, access the session via:

```python
from mcp.server.fastmcp import Context

@mcp.tool()
async def some_tool(ctx: Context) -> dict:
    session = ctx.request_context.lifespan_context["session"]
    if session is None:
        return {"error": "No Godot project loaded"}
    ...
```

This was verified by reading the MCP SDK source (`RequestContext.lifespan_context` field in `mcp.server.lowlevel.server`).

### Tool Import Pattern

Tools are defined in package directories (`tools/memory/`, `tools/code/`, `tools/spatial/`) which already exist as packages with `__init__.py` files. The imports at the bottom of `server.py` establish the registration pattern:

```python
from godotiq.tools import memory  # noqa: E402, F401
from godotiq.tools import code  # noqa: E402, F401
from godotiq.tools import spatial  # noqa: E402, F401
```

When tool modules need the `mcp` instance:
```python
# In src/godotiq/tools/memory/some_tool.py:
from godotiq.server import mcp
# ... then use @mcp.tool() to register tools
```

### Recommended Tool Pattern (for sections 04-07)

Extract logic into non-MCP functions for testability:

```python
def _build_project_summary(session: GodotIQSession, detail: str) -> dict:
    """Pure logic, no MCP dependency. Easy to test."""
    ...

@mcp.tool()
async def godotiq_project_summary(detail: str = "normal", ctx: Context = None) -> dict:
    """MCP wrapper."""
    session = ctx.request_context.lifespan_context["session"]
    if session is None:
        return {"error": "No Godot project loaded"}
    return _build_project_summary(session, detail)
```

Tests call `_build_project_summary(session, "brief")` directly without needing MCP context.

### Created: `tests/test_tools/conftest.py`

Shared fixtures for Sprint 3 tool tests:
- `sample_root` — Path to sample project fixture
- `session` — A loaded GodotIQSession for the sample project

### Created: `tests/test_tools/test_server_integration.py`

Integration tests covering:
- Lifespan creates session with valid project (via env var)
- Lifespan yields None session when no project found
- `godotiq_ping` still registered after lifespan addition
- No circular import issues
- Conftest fixtures produce valid objects

---

## Files Created/Modified

- **Modified:** `src/godotiq/server.py` — lifespan, tool imports
- **Created:** `tests/test_tools/conftest.py` — shared fixtures
- **Created:** `tests/test_tools/test_server_integration.py` — 6 tests

## Test Results

All 243 tests pass (237 existing + 6 new).

---

## Checklist

- [x] `src/godotiq/server.py` modified with lifespan
- [x] `godotiq_lifespan` creates GodotIQSession and yields it
- [x] `discover_project_root()` used for root discovery
- [x] `godotiq_ping` still works (existing tests pass)
- [x] Tool import pattern established (imports at bottom of server.py)
- [x] `tests/test_tools/conftest.py` created with session fixture
- [x] All existing tests pass: `uv run pytest tests/ -v`
- [x] No circular import issues
- [x] Error handling around `session.load()` (code review fix)
