# Cerbero MCP — V2.0.0 Server MCP unificato multi-exchange per la suite Cerbero. Distribuito come singola immagine Docker; testnet e mainnet sono raggiungibili contemporaneamente attraverso un meccanismo di routing per-request basato sul token bearer fornito dal client. ## Caratteristiche - **Una singola immagine Docker** (`cerbero-mcp`) ospita tutti i router exchange in un unico processo FastAPI - **Quattro exchange** (Deribit, Bybit, Hyperliquid, Alpaca) e **due data provider** read-only (Macro, Sentiment) - **Switch testnet/mainnet per-request** tramite header `Authorization: Bearer `: lo stesso container serve entrambi gli ambienti senza riavvii - **Configurazione interamente in `.env`**: nessun file JSON di credenziali separato; le URL upstream (live/testnet) di ciascun exchange sono override-abili tramite variabili dedicate (`DERIBIT_URL_*`, `BYBIT_URL_*`, `HYPERLIQUID_URL_*`, `ALPACA_URL_*`) - **Documentazione interattiva** OpenAPI/Swagger esposta a `/apidocs` - **Qualità verificata**: 310 test (unit + integration + smoke), mypy pulito, ruff pulito ## Avvio rapido (sviluppo, senza Docker) 1. Copiare il template di configurazione e compilarlo: ```bash cp .env.example .env # editare .env con le proprie credenziali e i due token ``` 2. Generare i token bearer: ```bash python -c 'import secrets; print("TESTNET_TOKEN=" + secrets.token_urlsafe(32))' python -c 'import secrets; print("MAINNET_TOKEN=" + secrets.token_urlsafe(32))' ``` 3. Installare le dipendenze e avviare: ```bash uv sync uv run cerbero-mcp ``` 4. Aprire la documentazione interattiva: ## Avvio con Docker ```bash cp .env.example .env # compilare valori docker compose up -d ``` Il container espone la porta indicata da `PORT` in `.env` (default 9000). ## Token bearer e ambienti | Token usato | Ambiente upstream | |---|---| | `Authorization: Bearer $TESTNET_TOKEN` | URL testnet di ciascun exchange | | `Authorization: Bearer $MAINNET_TOKEN` | URL mainnet (live) | | Nessun token / token sconosciuto | 401 Unauthorized | Le tool puramente read-only (`/mcp-macro/*` e `/mcp-sentiment/*`) richiedono comunque un bearer valido, ma il valore (testnet o mainnet) è indifferente perché non hanno endpoint testnet. ### Header X-Bot-Tag (identificazione bot) Tutte le chiamate a `/mcp-*` richiedono inoltre l'header `X-Bot-Tag` con una stringa identificativa del bot chiamante (massimo 64 caratteri). Il valore viene loggato negli audit record per tracciare quale bot ha eseguito ogni operazione write. Esempio di richiesta: Authorization: Bearer $MAINNET_TOKEN X-Bot-Tag: scanner-alpha-prod Se l'header è assente o vuoto la risposta è `400 BAD_REQUEST`. L'header non è richiesto sugli endpoint pubblici (`/health`, `/apidocs`, `/openapi.json`) né sull'endpoint admin `/admin/audit`. ## Endpoint principali | Path | Descrizione | |---|---| | `GET /health` | Liveness check (no auth) | | `GET /health/ready` | Readiness check con ping client exchange (no auth) | | `GET /apidocs` | Swagger UI (no auth) | | `GET /openapi.json` | Schema OpenAPI 3.1 (no auth) | | `POST /mcp-deribit/tools/{tool}` | Tool exchange Deribit | | `POST /mcp-bybit/tools/{tool}` | Tool exchange Bybit | | `POST /mcp-hyperliquid/tools/{tool}` | Tool exchange Hyperliquid | | `POST /mcp-alpaca/tools/{tool}` | Tool exchange Alpaca | | `POST /mcp-macro/tools/{tool}` | Tool macro/market data | | `POST /mcp-sentiment/tools/{tool}` | Tool sentiment/news | | `GET /admin/audit` | Query dell'audit log JSONL (bearer richiesto, no X-Bot-Tag) | ## Observability ### Health check L'applicazione espone due endpoint distinti per il monitoring: - `GET /health` — liveness check semplice. Non richiede autenticazione e ritorna sempre HTTP 200 finché il processo è vivo. Ideale per la liveness probe di Kubernetes o per il pinger di Traefik. - `GET /health/ready` — readiness check evoluto. Itera tutti i client exchange presenti nel registry e per ciascuno tenta una probe leggera (`health()` se disponibile, fallback su `is_testnet()`), con timeout di 2 secondi per client. La risposta contiene il campo `status` con uno dei valori `ready` (tutti i client rispondono), `degraded` (almeno uno fallisce) o `not_ready` (registry vuoto) ed un array `clients` con un record per ogni coppia `(exchange, env)` cached. Per default l'endpoint risponde sempre con HTTP 200; impostando la variabile d'ambiente `READY_FAILS_ON_DEGRADED=true` si forza HTTP 503 quando lo stato non è `ready`, comportamento utile per la readiness probe di Kubernetes. ### Request log Ogni richiesta HTTP attraversa un middleware che emette una riga JSON sul logger `mcp.request` con i seguenti campi: `request_id`, `method`, `path`, `status_code`, `duration_ms`, `actor` (`testnet` o `mainnet`, solo se autenticato), `bot_tag` (header `X-Bot-Tag` se presente), `exchange` (estratto dal path `/mcp-{exchange}/...`), `tool` (nome del tool quando il path è `/mcp-X/tools/Y`), `client_ip`, `user_agent`. Lo stesso `request_id` viene incluso anche nei record dell'audit log `mcp.audit` e nell'envelope di errore restituito al client, in modo da poter correlare le tre tracce a parità di richiesta. ### Audit log Vedi la sezione "Audit query" qui sotto per la consultazione del log strutturato delle operazioni di scrittura. ## Audit query `GET /admin/audit` legge il file JSONL puntato da `AUDIT_LOG_FILE` e restituisce i record filtrati. Richiede un bearer valido (testnet o mainnet); non richiede l'header `X-Bot-Tag`. Parametri di query (tutti opzionali): - `from`, `to`: ISO 8601 datetime (es. `2026-05-01` o `2026-05-01T12:34:56Z`) - `actor`: `testnet` | `mainnet` - `exchange`: nome dell'exchange (`deribit`, `bybit`, `hyperliquid`, `alpaca`) - `action`: nome del tool (es. `place_order`) - `bot_tag`: identificatore del bot - `limit`: massimo record restituiti, default `1000`, massimo `10000` Esempio di chiamata: curl -H "Authorization: Bearer $MAINNET_TOKEN" \ "http://localhost:9000/admin/audit?from=2026-05-01&actor=mainnet&action=place_order&limit=100" Se `AUDIT_LOG_FILE` non è configurata l'endpoint risponde `count: 0` con un campo `warning`. Per abilitare il sink persistente impostare nel `.env`: AUDIT_LOG_FILE=/var/log/cerbero-mcp/audit.jsonl AUDIT_LOG_BACKUP_DAYS=30 ## Tool disponibili ### Common (`cerbero_mcp.common.indicators` + `options` + `microstructure` + `stats`) Tecnici (`sma`, `rsi`, `macd`, `atr`, `adx`), volatilità (`vol_cone`, `garch11_forecast`), statistici (`hurst_exponent`, `half_life_mean_reversion`, `cointegration_test`), risk (`rolling_sharpe`, `var_cvar`), microstructure (`orderbook_imbalance`), options (`oi_weighted_skew`, `smile_asymmetry`, `dealer_gamma_profile`, `vanna_charm_aggregate`). ### Deribit DVOL, GEX, P/C ratio, skew_25d, term_structure, iv_rank, realized_vol, indicatori tecnici, find_by_delta, calculate_spread_payoff, get_dealer_gamma_profile, get_vanna_charm, get_oi_weighted_skew, get_smile_asymmetry, get_atm_vs_wings_vol, get_orderbook_imbalance, place_combo_order. ### Bybit Ticker, orderbook, OHLCV, funding rate, open interest, basis spot/perp, indicatori tecnici, place_batch_order, get_orderbook_imbalance, get_basis_term_structure. ### Hyperliquid Account summary, positions, orderbook, historical, indicators, funding rate, basis spot/perp, place_order, set_stop_loss, set_take_profit. ### Alpaca Account, positions, bars, snapshot, option chain, place_order, amend_order, cancel_order, close_position. ### Macro Treasury yields, FRED indicators, equity futures, asset prices, calendar, get_yield_curve_slope, get_breakeven_inflation, get_cot_tff, get_cot_disaggregated, get_cot_extreme_positioning. ### Sentiment News (CryptoPanic/CoinDesk), social (LunarCrush), funding multi-exchange, OI history, get_funding_arb_spread, get_liquidation_heatmap, get_cointegration_pairs. ## Deploy su VPS con Traefik Sul VPS la rete pubblica (TLS, allowlist IP, rate limit) è gestita da Traefik esterno a questo repository. Il container `cerbero-mcp` non espone porte all'esterno: si registra alla rete docker di Traefik tramite label aggiunte da un override compose esterno (es. `docker-compose.override.yml` versionato fuori da questo repo). La policy di sicurezza pubblica (allowlist IP per gli endpoint write) è responsabilità di Traefik. Esempio label minime per Traefik: ```yaml labels: - "traefik.enable=true" - "traefik.http.routers.cerbero.rule=Host(`cerbero-mcp.tielogic.xyz`)" - "traefik.http.routers.cerbero.entrypoints=websecure" - "traefik.http.routers.cerbero.tls.certresolver=letsencrypt" - "traefik.http.services.cerbero.loadbalancer.server.port=9000" ``` ## Build & deploy pipeline Build dell'immagine eseguita sulla macchina di sviluppo: ```bash export GITEA_PAT='' ./scripts/build-push.sh ``` Lo script tagga `:2.0.0`, `:latest` e `:sha-` per rollback puntuali e pubblica al registry Gitea. Sul VPS Watchtower polla `:latest` e aggiorna il container automaticamente. Smoke test post-deploy: ```bash PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh ``` ## Sviluppo ```bash uv sync uv run pytest # tutta la suite (310 test attesi) uv run pytest tests/unit -v # solo unit uv run pytest tests/integration -v uv run ruff check src/ tests/ uv run mypy src/cerbero_mcp ``` Tutti e quattro i comandi devono ritornare verde prima di committare. ### Layout sorgenti ``` src/cerbero_mcp/ ├── __main__.py # entrypoint cerbero-mcp ├── settings.py # Pydantic Settings (legge .env) ├── auth.py # middleware bearer → request.state.environment ├── server.py # build_app() + Swagger + middleware + handlers ├── client_registry.py # cache lazy {(exchange, env): client} ├── routers/ # un file per exchange (deribit, bybit, ...) ├── exchanges/ # logica per-exchange: client + tools └── common/ # indicators, options, microstructure, stats, ... ``` ## Migrazione da V1 (1.x → 2.0.0) Per chi è in produzione su V1: 1. Backup `secrets/` (V2 non li userà ma servono come fonte di copia). 2. Generare i due nuovi token bearer (vedi sopra). 3. Compilare `.env` mappando i campi V1 ai campi V2: | V1 (file JSON) | V2 (variabile `.env`) | |---|---| | `secrets/deribit.json` `client_id` / `client_secret` | `DERIBIT_CLIENT_ID` / `DERIBIT_CLIENT_SECRET` | | `secrets/bybit.json` `api_key` / `api_secret` | `BYBIT_API_KEY` / `BYBIT_API_SECRET` | | `secrets/hyperliquid.json` `wallet_address` / `private_key` | `HYPERLIQUID_WALLET_ADDRESS` / `HYPERLIQUID_PRIVATE_KEY` | | `secrets/alpaca.json` `api_key_id` / `secret_key` | `ALPACA_API_KEY_ID` / `ALPACA_SECRET_KEY` | | `secrets/macro.json` `fred_api_key` / `finnhub_api_key` | `FRED_API_KEY` / `FINNHUB_API_KEY` | | `secrets/sentiment.json` `cryptopanic_key` / `lunarcrush_key` | `CRYPTOPANIC_KEY` / `LUNARCRUSH_KEY` | 4. Aggiornare i client bot: - i path API restano identici (`/mcp-{exchange}/tools/{tool}`) - sostituire `core.token` / `observer.token` con `TESTNET_TOKEN` o `MAINNET_TOKEN` a seconda dell'ambiente desiderato per la chiamata 5. Spegnere V1 (`docker compose -f down`) e avviare V2 (`docker compose up -d`). 6. Verificare `/health` e `/apidocs`. In caso di necessità è possibile fare rollback pullando i tag immagine V1 (`cerbero-mcp-*:1.x`); si ricordi però che `.env` e `secrets/` sono formati incompatibili tra V1 e V2 — tenere backup separati. ## Architettura Spec di progettazione e plan di implementazione completi in: - [`docs/superpowers/specs/2026-04-30-V2.0.0-unified-image-token-routing-design.md`](docs/superpowers/specs/2026-04-30-V2.0.0-unified-image-token-routing-design.md) - [`docs/superpowers/plans/2026-04-30-V2.0.0-unified-image-token-routing.md`](docs/superpowers/plans/2026-04-30-V2.0.0-unified-image-token-routing.md) Riepilogo del flusso runtime: ``` Bot → Authorization: Bearer _TOKEN ↓ FastAPI middleware auth → request.state.environment ∈ {testnet, mainnet} ↓ Router /mcp-{exchange}/tools/{tool} ↓ ClientRegistry.get(exchange, env) → client cached lazy (HTTP/WS pool riusato) ↓ Tool function (logica pura) → exchange API ``` ### Override URL upstream L'override delle URL upstream da `.env` è completo per Deribit e Hyperliquid. Per Bybit funziona tramite l'attributo `endpoint` interno di pybit (workaround documentato nel client). Per Alpaca l'override è applicato al solo trading endpoint: gli endpoint dati (`data.alpaca.markets`) restano quelli predefiniti dell'SDK. ## Licenza Privato.