6faf21369d
Consolidate the cross-cutting data tools onto the unified interface and remove their per-exchange duplicates. - /mcp/tools/get_historical: single call, `exchange` now optional. Set → that venue's candles (native symbol); omitted → cross-exchange consensus (canonical symbol, median OHLC + div_pct). Folds in the former /mcp-cross consensus, whose router is deleted. - /mcp/tools/get_indicators: new common tool computing sma/rsi/atr/macd/adx over the single-or-consensus series. Computation centralized in common/indicators.py::compute_indicators (was duplicated per exchange). - Remove get_historical from /mcp-deribit and /mcp-hyperliquid; remove get_technical_indicators (deribit) and get_indicators (hyperliquid), including the now-dead client methods, Req schemas and tool wrappers. The client get_historical METHODS stay (used by the unified dispatch). Tests: rewrite cross tests into test_unified (instruments, historical single + consensus + failures, indicators single + consensus); drop the old CrossClient test; tighten app-boot surface assertions (/mcp owns the data tools; no /mcp-cross, no per-exchange historical/indicators). Docs: API_REFERENCE / README / CLAUDE updated; smoke run_unified.sh now covers consensus + indicators. 324 passed, ruff clean. Verified live against Deribit/Hyperliquid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
458 lines
18 KiB
Markdown
458 lines
18 KiB
Markdown
# 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
|
|
- **Tre exchange** (Deribit, Hyperliquid, IBKR) e **due data provider**
|
|
read-only (Macro, Sentiment). Bybit e Alpaca sono stati ritirati dalla
|
|
superficie API in V2 (codice archiviato in `old/`)
|
|
- **Interfaccia comune `/mcp`** trasversale agli exchange: `get_instruments`
|
|
(schema uniforme con `exchange`/`fees`/`history_start`), `get_historical` e
|
|
`get_indicators` — singolo exchange o consenso multi-exchange. Storico e
|
|
indicatori vivono **solo** qui, non più per-exchange
|
|
- **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_*`,
|
|
`HYPERLIQUID_URL_*`, `IBKR_URL_*`)
|
|
- **Documentazione interattiva** OpenAPI/Swagger esposta a `/apidocs`
|
|
- **Consenso multi-exchange** integrato in `/mcp/tools/get_historical`: ometti
|
|
`exchange` per il fan-out e il merge per-bar (mediana OHLC + `div_pct` +
|
|
`sources`)
|
|
- **Qualità verificata**: 323 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: <http://localhost:9000/apidocs>
|
|
|
|
## 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/tools/get_instruments` | Lista strumenti uniforme cross-exchange (deribit, hyperliquid) |
|
|
| `POST /mcp/tools/get_historical` | Storico comune: singolo exchange o consenso multi-exchange |
|
|
| `POST /mcp/tools/get_indicators` | Indicatori tecnici sulla stessa serie (single o consenso) |
|
|
| `POST /mcp-deribit/tools/{tool}` | Tool exchange Deribit |
|
|
| `POST /mcp-hyperliquid/tools/{tool}` | Tool exchange Hyperliquid |
|
|
| `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`, `hyperliquid`, `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,
|
|
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, run_backtest, place_combo_order.
|
|
(Storico/indicatori → `/mcp`.)
|
|
|
|
### Hyperliquid
|
|
Account summary, positions, orderbook, funding rate, basis spot/perp,
|
|
place_order, set_stop_loss, set_take_profit. (Storico/indicatori → `/mcp`.)
|
|
|
|
### 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.
|
|
|
|
### Unified (`/mcp`) — interfaccia comune
|
|
Espone gli **unici** tool dati trasversali (storico e indicatori vivono solo
|
|
qui, non per-exchange). Exchange supportati: `deribit`, `hyperliquid` (IBKR non
|
|
ancora integrato — usa `/mcp-ibkr`).
|
|
|
|
- **`get_instruments`** — lista strumenti **uniforme**: ogni elemento porta in
|
|
sé `exchange`, `fees` (maker/taker, live da Deribit, `null` dove l'exchange
|
|
non li espone), `history_start` (data inizio storico, live da Deribit),
|
|
`type`, `tick_size` e un blocco `native` lossless. `exchange` opzionale →
|
|
filtra una sorgente; se omesso fan-out su tutti.
|
|
- **`get_historical`** — `{exchange?, instrument, interval, start_date,
|
|
end_date}`. Con `exchange` ritorna il **singolo** exchange; **omettendolo**
|
|
fa il **consenso** multi-exchange (mediana OHLC, `sources`,
|
|
`div_pct=(max-min)/median` come quality gate) usando il simbolo canonico
|
|
(BTC/ETH via Hyperliquid+Deribit, SOL via Hyperliquid). Fallimento parziale →
|
|
`failed_sources`; tutti gli upstream giù → HTTP 502 retryable.
|
|
- **`get_indicators`** — stessa selezione sorgente; calcola sma/rsi/atr/macd/adx
|
|
sulle candele risultanti.
|
|
|
|
## 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
|
|
|
|
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)
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
ssh user@vps 'cd /opt/cerbero-mcp && bash scripts/deploy-vps.sh'
|
|
```
|
|
|
|
Oppure direttamente dal VPS:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh
|
|
```
|
|
|
|
## Sviluppo
|
|
|
|
```bash
|
|
uv sync
|
|
uv run pytest # tutta la suite (323 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 + unified.py (/mcp) + cross.py
|
|
├── exchanges/ # logica per-exchange: client + tools (+ cross/)
|
|
└── common/ # indicators, options, microstructure, stats, ...
|
|
|
|
old/ # codice ritirato (bybit, alpaca) — fuori dal package
|
|
```
|
|
|
|
## 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/hyperliquid.json` `wallet_address` / `private_key` | `HYPERLIQUID_WALLET_ADDRESS` / `HYPERLIQUID_PRIVATE_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` |
|
|
|
|
> Bybit e Alpaca non sono più esposti in V2 (vedi `old/`); le rispettive
|
|
> variabili `BYBIT_*` / `ALPACA_*` non sono più lette.
|
|
|
|
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:
|
|
|
|
- [`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 <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 (`DERIBIT_URL_*`, `HYPERLIQUID_URL_*`). IBKR usa
|
|
`IBKR_URL_*` / `IBKR_WS_URL_*`.
|
|
|
|
## 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
curl https://cerbero-mcp.<dom>/mcp-ibkr/tools/get_account \
|
|
-H "Authorization: Bearer <TESTNET_TOKEN>" -X POST -d '{}'
|
|
```
|
|
|
|
### Key rotation
|
|
|
|
```bash
|
|
# 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.
|