Su errore di Deribit (auth fallita, ecc.) i campi equity/balance/margin/ available/unrealized_pnl/total_pnl ora sono None: signal chiaro di "valore ignoto" vs "saldo realmente a zero". Risolve ambiguità lato client che leggevano equity=0 senza accorgersi del campo error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 <TOKEN>: 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)
- Copiare il template di configurazione e compilarlo:
cp .env.example .env # editare .env con le proprie credenziali e i due token - Generare i token bearer:
python -c 'import secrets; print("TESTNET_TOKEN=" + secrets.token_urlsafe(32))' python -c 'import secrets; print("MAINNET_TOKEN=" + secrets.token_urlsafe(32))' - Installare le dipendenze e avviare:
uv sync uv run cerbero-mcp - Aprire la documentazione interattiva: http://localhost:9000/apidocs
Avvio con Docker
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 suis_testnet()), con timeout di 2 secondi per client. La risposta contiene il campostatuscon uno dei valoriready(tutti i client rispondono),degraded(almeno uno fallisce) onot_ready(registry vuoto) ed un arrayclientscon un record per ogni coppia(exchange, env)cached. Per default l'endpoint risponde sempre con HTTP 200; impostando la variabile d'ambienteREADY_FAILS_ON_DEGRADED=truesi 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-01o2026-05-01T12:34:56Z)actor:testnet|mainnetexchange: nome dell'exchange (deribit,bybit,hyperliquid,alpaca)action: nome del tool (es.place_order)bot_tag: identificatore del botlimit: massimo record restituiti, default1000, massimo10000
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:
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
Il deploy su VPS avviene per clone diretto del repo, senza passare per
un container registry. Lo script scripts/deploy-vps.sh automatizza
l'intero flusso: pull del ramo target, rebuild dell'immagine sulla
macchina VPS, restart del servizio, healthcheck e rollback automatico in
caso di fallimento.
Setup iniziale sul VPS (una sola volta)
# Sul VPS:
sudo mkdir -p /opt/cerbero-mcp
sudo chown -R "$USER":"$USER" /opt/cerbero-mcp
cd /opt/cerbero-mcp
git clone -b V2.0.0 ssh://git@git.tielogic.xyz:222/Adriano/Cerbero-mcp.git .
cp .env.example .env
# editare .env con i token e le credenziali reali
Il branch in produzione è V2.0.0 (non main). Lo script deploy-vps.sh
fa default su questo ramo.
Deploy ricorrente
Da qualunque macchina con accesso SSH al VPS:
ssh user@vps 'cd /opt/cerbero-mcp && bash scripts/deploy-vps.sh'
Oppure direttamente dal VPS:
cd /opt/cerbero-mcp
bash scripts/deploy-vps.sh
Lo script:
- verifica che il working tree sia pulito e che
.envsia presente; - esegue
git fetch + reset --hard origin/V2.0.0; - se la SHA non è cambiata, esce senza fare nulla (override con
FORCE=1); - ricostruisce l'immagine Docker (
docker compose build); - restart graceful del container (
docker compose down --timeout 15seguito dadocker compose up -d); - attende
/health(timeout 30 s di default); - se l'health fallisce, esegue rollback automatico al SHA precedente.
Variabili d'ambiente accettate: BRANCH (default V2.0.0), PORT
(default letto da .env), HEALTH_TIMEOUT_SECONDS, FORCE,
SKIP_ROLLBACK.
Smoke test post-deploy
PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh
Sviluppo
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:
-
Backup
secrets/(V2 non li userà ma servono come fonte di copia). -
Generare i due nuovi token bearer (vedi sopra).
-
Compilare
.envmappando i campi V1 ai campi V2:V1 (file JSON) V2 (variabile .env)secrets/deribit.jsonclient_id/client_secretDERIBIT_CLIENT_ID/DERIBIT_CLIENT_SECRETsecrets/bybit.jsonapi_key/api_secretBYBIT_API_KEY/BYBIT_API_SECRETsecrets/hyperliquid.jsonwallet_address/private_keyHYPERLIQUID_WALLET_ADDRESS/HYPERLIQUID_PRIVATE_KEYsecrets/alpaca.jsonapi_key_id/secret_keyALPACA_API_KEY_ID/ALPACA_SECRET_KEYsecrets/macro.jsonfred_api_key/finnhub_api_keyFRED_API_KEY/FINNHUB_API_KEYsecrets/sentiment.jsoncryptopanic_key/lunarcrush_keyCRYPTOPANIC_KEY/LUNARCRUSH_KEY -
Aggiornare i client bot:
- i path API restano identici (
/mcp-{exchange}/tools/{tool}) - sostituire
core.token/observer.tokenconTESTNET_TOKENoMAINNET_TOKENa seconda dell'ambiente desiderato per la chiamata
- i path API restano identici (
-
Spegnere V1 (
docker compose -f <vecchio compose> down) e avviare V2 (docker compose up -d). -
Verificare
/healthe/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.mddocs/superpowers/plans/2026-04-30-V2.0.0-unified-image-token-routing.md
Riepilogo del flusso runtime:
Bot → Authorization: Bearer <TESTNET|MAINNET>_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.