# CLAUDE.md Guida per Claude Code che lavora su **Cerbero MCP** — server MCP multi-exchange (FastAPI) distribuito come singola immagine Docker, con routing testnet/mainnet per-request basato sul token bearer. ## Comandi Gestione progetto con `uv` (Python ≥ 3.11): ```bash uv sync # installa dipendenze (incl. dev) uv run cerbero-mcp # avvia il server (porta da .env, default 9000) uv run pytest # suite completa (~323 test attesi) uv run pytest tests/unit -v # solo unit uv run pytest tests/unit/exchanges/cross/test_unified.py::test_get_historical_single_exchange # singolo test uv run ruff check src tests # lint (E,F,I,W,UP,B,SIM; line-length 100) uv run mypy src/cerbero_mcp # type check (non-strict) ``` Tutti e quattro (pytest, ruff, mypy) devono essere verdi prima di committare. `testpaths = ["tests"]` → `old/` non viene mai raccolto dai test. ## Architettura Flusso runtime: ``` Bot → Authorization: Bearer _TOKEN (+ header X-Bot-Tag) → auth middleware → request.state.environment ∈ {testnet, mainnet} → Router (/mcp/... , /mcp-{exchange}/... , /mcp-cross/...) → ClientRegistry.get(exchange, env) → client cached lazy per (exchange, env) → tool function (logica pura) → exchange API ``` File chiave: - `src/cerbero_mcp/__main__.py` — `_make_app()` registra tutti i router. - `src/cerbero_mcp/server.py` — `build_app()`, Swagger, exception handler, e `_TimestampInjectorMiddleware` che inietta `data_timestamp` (ISO UTC) nel top level di **ogni** risposta JSON sotto `/tools/*` (+ header `X-Data-Timestamp`). - `src/cerbero_mcp/auth.py` — bearer → env, e `X-Bot-Tag` obbligatorio (path whitelisted: `/health`, `/health/ready`, `/apidocs`, `/openapi.json`). - `src/cerbero_mcp/client_registry.py` — cache `{(exchange, env): client}`. - `src/cerbero_mcp/exchanges/__init__.py` — `build_client()`: factory per exchange. - `src/cerbero_mcp/settings.py` — Pydantic Settings da `.env`. - `src/cerbero_mcp/routers/` — un file per exchange + `unified.py` (`/mcp`) + `cross.py` (`/mcp-cross`). - `src/cerbero_mcp/exchanges//{client.py,tools.py}` — client HTTP + schemi Pydantic / wrapper tool. `cross/` contiene aggregatore consensus, interfaccia unificata, normalizzatori e symbol-map. ## Exchange attivi Trading: **deribit, hyperliquid, ibkr**. Data-provider read-only: **macro, sentiment**. **Bybit e Alpaca sono ritirati** dalla superficie API in V2: codice archiviato in `old/` (fuori dal package, non importato, non testato). Nota: nel modulo `sentiment` "bybit" resta come **fonte dati pubblica** (funding/OI), non come client di trading. ## Interfacce dati - **`/mcp` (comune, raccomandata)** — `get_instruments` con schema uniforme (ogni elemento porta `exchange`, `fees`, `history_start`, `type`, `tick_size`, `native`); `get_historical` con `exchange` selezionabile (singolo venue). Supporta deribit + hyperliquid (IBKR non ancora integrato). - **`/mcp-cross`** — `get_historical` a consenso multi-exchange (mediana OHLC, `sources`, `div_pct`). - **`/mcp-{exchange}`** — router per-exchange legacy, ancora attivi. Convenzione: ogni `get_historical` ritorna la chiave uniforme `candles: [{timestamp(ms), open, high, low, close, volume}]` (validatore in `common/candles.py`). `fees`/`history_start` sono popolati **live dove l'upstream li espone** (oggi: Deribit), altrimenti `null`. ## Convenzioni - Schemi richiesta = modelli Pydantic in `/tools.py`; il router fa solo dependency-injection di env + client e chiama il wrapper del tool. - Errori applicativi: sollevare `fastapi.HTTPException`; gli handler in `server.py` li avvolgono in un envelope con `request_id`. - Firme/crittografia: il signing L1 Hyperliquid usa `eth_utils.to_hex` come l'SDK ufficiale → `r`/`s` hex possono essere < 66 char (byte alto a zero, ~1/256); non assumere lunghezza fissa. - Branch di produzione: **`V2.0.0`** (non `main`). Deploy via `scripts/deploy-vps.sh`. ## Documentazione - `README.md` — overview, setup, deploy, migrazione V1→V2, IBKR OAuth. - `docs/API_REFERENCE.md` — catalogo completo endpoint/tool. Aggiornarlo quando si aggiungono/rimuovono tool o si cambiano gli schemi.