Files
Cerbero-mcp/README.md
T
root 110ca7f5cf docs(V2): update README for IBKR integration
Add IBKR to the exchange list, endpoint table, audit filter values, and
Tool disponibili. Bump test count to 366 and reorder IBKR Setup before
Licenza.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 08:54:54 +00:00

16 KiB

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
  • Cinque exchange (Deribit, Bybit, Hyperliquid, Alpaca, IBKR) 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: 366 test (unit + integration + smoke), mypy pulito, ruff pulito

Avvio rapido (sviluppo, senza Docker)

  1. Copiare il template di configurazione e compilarlo:
    cp .env.example .env
    # editare .env con le proprie credenziali e i due token
    
  2. 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))'
    
  3. Installare le dipendenze e avviare:
    uv sync
    uv run cerbero-mcp
    
  4. 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-ibkr/tools/{tool} Tool exchange Interactive Brokers
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, ibkr)
  • 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.

IBKR (Interactive Brokers)

Account, positions, activities, ticker, bars, snapshot, option chain, search_contracts, clock, streaming (tick + depth via WebSocket singleton), place_order, amend_order, cancel_order, close_position, bracket/OCO/OTO orders. Auth via OAuth 1.0a Self-Service con minting session token unattended (vedi sezione "IBKR Setup" più sotto).

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:

  1. verifica che il working tree sia pulito e che .env sia presente;
  2. esegue git fetch + reset --hard origin/V2.0.0;
  3. se la SHA non è cambiata, esce senza fare nulla (override con FORCE=1);
  4. ricostruisce l'immagine Docker (docker compose build);
  5. restart graceful del container (docker compose down --timeout 15 seguito da docker compose up -d);
  6. attende /health (timeout 30 s di default);
  7. 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 (366 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 <vecchio compose> 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:

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.

IBKR Setup

IBKR uses OAuth 1.0a Self-Service for fully unattended runtime auth. Setup is manual one-time per account (paper + live), then the container mints live session tokens autonomously.

One-time setup

  1. Login to https://www.interactivebrokers.com → User Settings → Self-Service OAuth

  2. Generate keypairs locally:

    uv run python scripts/ibkr_oauth_setup.py --env testnet
    

    This writes RSA keys under secrets/ and prints SHA-256 fingerprints.

  3. Register the two fingerprints in the IBKR portal. Receive a consumer_key.

  4. Get a request token + authorization URL:

    uv run python scripts/ibkr_oauth_setup.py --env testnet \
        --consumer-key <K> --request-token
    
  5. Open the URL, authorize, copy the verifier_code.

  6. Exchange verifier for long-lived access token (~5 years validity):

    uv run python scripts/ibkr_oauth_setup.py --env testnet --verifier <V>
    
  7. Copy the printed values into .env:

    • IBKR_CONSUMER_KEY_TESTNET
    • IBKR_ACCESS_TOKEN_TESTNET
    • IBKR_ACCESS_TOKEN_SECRET_TESTNET
    • IBKR_SIGNATURE_KEY_PATH_TESTNET
    • IBKR_ENCRYPTION_KEY_PATH_TESTNET
    • IBKR_ACCOUNT_ID_TESTNET (e.g., DU1234567 for paper)
    • IBKR_DH_PRIME (hex from portal; shared paper/live)
  8. Repeat with --env mainnet for live trading.

Smoke test

curl https://cerbero-mcp.<dom>/mcp-ibkr/tools/get_account \
  -H "Authorization: Bearer <TESTNET_TOKEN>" -X POST -d '{}'

Key rotation

# 1. Generate new keypairs alongside existing
uv run python scripts/ibkr_oauth_setup.py --env testnet --rotate

# 2. Register new fingerprints in IBKR portal, get new consumer_key + tokens

# 3. Confirm rotation (atomic swap with auto-rollback on validation fail)
curl -X POST "https://cerbero-mcp.<dom>/admin/ibkr/rotate-keys/confirm?env=testnet" \
  -H "Authorization: Bearer <ADMIN_TOKEN>" -H "Content-Type: application/json" \
  -d '{"new_consumer_key":"...","new_access_token":"...","new_access_token_secret":"..."}'

Licenza

Privato.