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>
91 lines
2.4 KiB
Python
91 lines
2.4 KiB
Python
"""Entrypoint cerbero-mcp.
|
|
|
|
Boot:
|
|
- carica Settings da .env
|
|
- costruisce app FastAPI con router per ogni exchange
|
|
- crea ClientRegistry con builder
|
|
- monta lifespan per chiusura pulita
|
|
- avvia uvicorn
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
from contextlib import asynccontextmanager
|
|
from typing import Literal, cast
|
|
|
|
import uvicorn
|
|
from fastapi import FastAPI
|
|
|
|
from cerbero_mcp import admin
|
|
from cerbero_mcp.client_registry import ClientRegistry
|
|
from cerbero_mcp.common.logging import configure_root_logging
|
|
from cerbero_mcp.exchanges import build_client
|
|
from cerbero_mcp.routers import (
|
|
deribit,
|
|
hyperliquid,
|
|
ibkr,
|
|
macro,
|
|
sentiment,
|
|
unified,
|
|
)
|
|
from cerbero_mcp.server import build_app
|
|
from cerbero_mcp.settings import Settings
|
|
|
|
|
|
def _make_app(settings: Settings) -> FastAPI:
|
|
app = build_app(
|
|
testnet_token=settings.testnet_token.get_secret_value(),
|
|
mainnet_token=settings.mainnet_token.get_secret_value(),
|
|
title="Cerbero MCP",
|
|
version="2.0.0",
|
|
)
|
|
|
|
app.state.settings = settings
|
|
|
|
async def builder(exchange: str, env: str):
|
|
return await build_client(
|
|
settings, exchange, cast(Literal["testnet", "mainnet"], env)
|
|
)
|
|
|
|
app.state.registry = ClientRegistry(builder=builder)
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
try:
|
|
yield
|
|
finally:
|
|
# Stop any IBKR WebSocket singletons before closing client registry
|
|
ibkr_ws_dict = getattr(app.state, "ibkr_ws", {}) or {}
|
|
for ws in ibkr_ws_dict.values():
|
|
with contextlib.suppress(Exception):
|
|
await ws.stop()
|
|
await app.state.registry.aclose()
|
|
|
|
app.router.lifespan_context = lifespan
|
|
|
|
app.include_router(deribit.make_router())
|
|
app.include_router(hyperliquid.make_router())
|
|
app.include_router(ibkr.make_router())
|
|
app.include_router(macro.make_router())
|
|
app.include_router(sentiment.make_router())
|
|
app.include_router(unified.make_router())
|
|
app.include_router(admin.make_admin_router())
|
|
|
|
return app
|
|
|
|
|
|
def main() -> None:
|
|
configure_root_logging()
|
|
settings = Settings() # type: ignore[call-arg]
|
|
app = _make_app(settings)
|
|
uvicorn.run(
|
|
app,
|
|
log_config=None,
|
|
host=settings.host,
|
|
port=settings.port,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|