diff --git a/README.md b/README.md index 98100e2..8a1766e 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,215 @@ -# Cerbero_mcp +# Cerbero MCP — V2.0.0 -Server MCP riusabili (exchange + market data) per la suite Cerbero. -Spinta da `Cerbero/` (commit `pre-split-2026-04-27`) come parte dello -split documentato in `docs/superpowers/specs/2026-04-27-split-mcp-core-design.md` -(nel repo storico). +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. -## Servizi -- `mcp-alpaca`, `mcp-bybit`, `mcp-deribit`, `mcp-hyperliquid` — exchange - con `place_order`, `environment_info`, leverage cap server-side -- `mcp-deribit` e `mcp-bybit` espongono inoltre `place_combo_order`: - - Deribit: `private/create_combo` + ordine sul combo → 1 sola crociata - di spread invece di N (slippage atteso ridotto su strutture liquide). - - Bybit: `place_batch_order` su `category=option` → multi-leg atomico - in un solo round-trip API (no sconto fee, solo atomicità + latenza). -- `mcp-macro`, `mcp-sentiment` — read-only market data +## Caratteristiche -## Indicatori quantitativi disponibili +- **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 +- **Documentazione interattiva** OpenAPI/Swagger esposta a `/apidocs` -### Common (`mcp_common.indicators` + `options` + `microstructure` + `stats`) -- Tecnici: `sma`, `rsi`, `macd`, `atr`, `adx` -- Volatilità: `vol_cone` (RV multi-window con percentili), `garch11_forecast` -- Statistici: `hurst_exponent`, `half_life_mean_reversion`, `autocorrelation`, - `cointegration_test` (Engle-Granger) -- Risk: `rolling_sharpe` (Sharpe + Sortino), `var_cvar` (historical VaR/ES) -- Microstructure: `orderbook_imbalance` (ratio + microprice + slope) -- Options: `oi_weighted_skew`, `smile_asymmetry`, `atm_vs_wings_vol`, - `dealer_gamma_profile`, `vanna_charm_aggregate` +## Avvio rapido (sviluppo, senza Docker) -### Deribit (esposti come tool MCP) +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. + +## Endpoint principali + +| Path | Descrizione | +|---|---| +| `GET /health` | Healthcheck (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 | + +## 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. -**Nuovi**: `get_dealer_gamma_profile`, `get_vanna_charm`, -`get_oi_weighted_skew`, `get_smile_asymmetry`, `get_atm_vs_wings_vol`, -`get_orderbook_imbalance`. +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 (current+history), open interest, -basis spot/perp, indicatori tecnici. **Nuovi**: `get_orderbook_imbalance`, -`get_basis_term_structure`. +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. -**Nuovi**: `get_yield_curve_slope` (slope 2y10y/5y30y + butterfly + regime), -`get_breakeven_inflation` (T5YIE/T10YIE/T5YIFR), `get_cot_tff` (TFF report -CFTC equity/financial: ES/NQ/RTY/ZN/ZB/6E/6J/DX), `get_cot_disaggregated` -(Disaggregated report CFTC commodities: CL/GC/SI/HG/ZW/ZC/ZS), -`get_cot_extreme_positioning` (scanner percentile ≤5/≥95 su watchlist). +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. **Nuovi**: `get_funding_arb_spread` (opportunità arb compatte), -`get_liquidation_heatmap` (heuristic da OI delta + funding extreme), -`get_cointegration_pairs` (Engle-Granger su coppie crypto). +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 -Niente CI/CD su Gitea: la build delle 8 image è responsabilità della -macchina di sviluppo, fatta da `scripts/build-push.sh`. Il flusso è: - -1. **Quality gate locale** (sul laptop, prima di pushare): - - `uv run ruff check services/` - - `uv run mypy services/common/src/mcp_common` - - `uv run pytest services/` - - `docker compose -f docker-compose.prod.yml config -q` -2. **Build & push** (sul laptop): - ```bash - export GITEA_PAT='' - ./scripts/build-push.sh # tutte le 8 image - ./scripts/build-push.sh base mcp-bybit # solo specifiche - ``` - Tagga `:latest` + `:sha-` per rollback puntuali. Cache - buildx via registry stesso (run successivi 5-10× più veloci). -3. **Auto-rollover su VPS**: Watchtower polla il registry ogni 5 min e - aggiorna i container quando il digest del tag `:latest` cambia. - -Vedi [`DEPLOYMENT.md`](DEPLOYMENT.md) per build & push, deploy VPS -no-clone (`scripts/deploy-noclone.sh`), smoke test, rollback. - -## Avvio locale (dev) +Build dell'immagine eseguita sulla macchina di sviluppo: ```bash -docker compose up -d -bash tests/smoke/run.sh +export GITEA_PAT='' +./scripts/build-push.sh ``` -## Configurazione +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. -Vedi `secrets/*.json` e variabili `*_TESTNET` / `ALPACA_PAPER` in -`docker-compose.yml` per override ambiente. - -### Deploy su VPS pubblica (`cerbero-mcp.tielogic.xyz`) - -Vedi [`DEPLOYMENT.md`](DEPLOYMENT.md) per il runbook completo end-to-end. - -Il gateway Caddy è configurato per: -- TLS automatico via Let's Encrypt (richiede DNS A/AAAA che punti al - VPS e porte 80+443 raggiungibili). -- HSTS preload, header di sicurezza (`X-Content-Type-Options`, - `X-Frame-Options`, `Referrer-Policy`). -- Rate limit per IP (60 req/min su read, 10 req/min su write) tramite - plugin `mholt/caddy-ratelimit`. -- Allowlist IP sui write endpoint (`place_*`, `cancel_*`, `set_*`, - `close_*`, `transfer_*`, `amend_*`, `switch_*`): IP non presenti in - `WRITE_ALLOWLIST` ricevono `403 forbidden`. - -Variabili d'ambiente per il deploy: +Smoke test post-deploy: ```bash -# .env (su VPS) -ACME_EMAIL=adrianodalpastro@tielogic.com -GATEWAY_HTTP_PORT=80 -GATEWAY_HTTPS_PORT=443 - -# Allowlist write endpoint (CIDR space-separated). Default copre: -# - loopback IPv4/IPv6 (bot sull'host VPS chiama http://localhost) -# - Docker bridge 172.16.0.0/12 (bot in container nella stessa compose network) -# Aggiungi gli IP pubblici dei tuoi bot esterni se li hai. -WRITE_ALLOWLIST="127.0.0.1/32 ::1/128 172.16.0.0/12 1.2.3.4/32" +PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh ``` -Tre scenari per il trading bot: -1. Bot container nella stessa compose network → chiama `http://gateway:80` - internamente. Source IP = Docker bridge → coperto dalla default. -2. Bot processo sull'host VPS → chiama `http://localhost`. Source IP = - `127.0.0.1` → coperto dalla default. -3. Bot esterno (laptop, altro server) → chiama - `https://cerbero-mcp.tielogic.xyz` con TLS. Devi aggiungere l'IP - pubblico del bot in `WRITE_ALLOWLIST`. - -Senza configurare `WRITE_ALLOWLIST` la default è loopback + Docker bridge: -nessun IP pubblico esterno può triggerare ordini. - -Sull'host VPS i secret devono avere permessi restrittivi: +## Sviluppo ```bash -chmod 600 secrets/*.json secrets/*.token +uv sync +uv run pytest # tutta la suite +uv run pytest tests/unit -v # solo unit +uv run ruff check src/ +uv run mypy src/cerbero_mcp ``` -### Risoluzione environment (testnet/mainnet) +## Migrazione da V1 (1.x → 2.0.0) -Ogni servizio exchange usa `mcp_common.environment.resolve_environment()` -che applica la precedenza: +Per chi è in produzione su V1: -1. env var di override (`DERIBIT_TESTNET`, `BYBIT_TESTNET`, - `HYPERLIQUID_TESTNET`, `ALPACA_PAPER`) -2. flag nel secret JSON (`testnet` o `paper` per alpaca) -3. default `testnet` +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: -Gli URL canonici live/testnet sono passati come kwargs -`default_base_url_live` / `default_base_url_testnet` direttamente al -resolver — non serve duplicarli nel secret JSON, ma se presenti -prevalgono sui default del codice. + | 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 completa 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). + +Riepilogo: + +``` +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 +``` + +## Licenza + +Privato.