# Architecture
## Data flow
```{mermaid}
graph TD
Client["MCP Client"]
Server["server.py
FastMCP tools"]
Index["recording_index.py
Directory scanner"]
Reader["mcap_reader.py
Summary & iteration"]
Registry["decoder_registry.py
Encoding dispatch"]
Decoders["decoders/
JSON · Protobuf · ROS1 · ROS2 · FlatBuffer"]
Engine["query_engine.py
DuckDB wrapper"]
Files["MCAP files on disk"]
Client -->|"list_recordings
get_recording_info
get_schema
get_version"| Server
Client -->|load_recording| Server
Client -->|query| Server
Server --> Index
Server --> Reader
Server --> Registry
Server --> Engine
Index --> Files
Reader --> Files
Registry --> Decoders
Engine -->|"register DataFrame
execute SQL
LRU eviction"| Engine
```
## Module responsibilities
| Module | Role |
|--------|------|
| `server.py` | MCP tool registration (6 tools), request orchestration |
| `config.py` | Config loading: defaults → TOML → env vars → CLI. Validates `max_memory_mb >= 64` |
| `recording_index.py` | Scans directories for `.mcap` files, caches summaries, filters by date |
| `mcap_reader.py` | Reads MCAP summary and iterates messages using indexed reader |
| `decoder_registry.py` | Discovers and dispatches to the correct `MessageDecoder` by encoding |
| `decoders/base.py` | `MessageDecoder` protocol and type mappings |
| `decoders/*.py` | One decoder per encoding (JSON, Protobuf, ROS1, ROS2, FlatBuffer) |
| `query_engine.py` | DuckDB connection, table registration, SQL execution, LRU eviction, safety enforcement |
| `flatten.py` | Nested dict flattening for multi-level message schemas |
## Load path
1. `load_recording` receives a filename and optional topic/time filters
2. `mcap_reader.get_summary()` reads the MCAP summary section (end of file, fast)
3. For each channel, `decoder_registry` resolves the decoder by `(message_encoding, schema_encoding)`
4. `mcap_reader.iter_messages()` iterates messages using MCAP chunk indexes
5. Each message is decoded to a flat Python dict via the appropriate decoder
6. Dicts are accumulated into per-topic column lists, then converted to `pd.DataFrame`
7. `query_engine` registers each DataFrame as a named DuckDB table
8. If memory exceeds the configured budget, LRU eviction removes the oldest tables and reports them back to the caller
9. Subsequent `query` calls execute SQL against these tables
## Memory management
The query engine tracks approximate memory usage of registered tables. When `max_memory_mb` is exceeded, the least-recently-used tables are evicted. The `load_recording` response includes `memory_used_mb`, `memory_budget_mb`, and any `evicted_tables` so the LLM can adapt its strategy.
`max_memory_mb` must be at least 64 MB — lower values are rejected at config time.
## Query safety
- DuckDB runs in read-only mode
- File system functions (`read_csv`, `read_parquet`, `COPY`, `EXPORT`) are blocked
- Queries are subject to a configurable timeout (default 30s) and row limit (default 1000)
- When a query references an unloaded table, the error includes `loaded_tables` and a `hint` to guide the LLM toward loading the correct recording