diff --git a/src/cerbero_mcp/__main__.py b/src/cerbero_mcp/__main__.py index f57333f..e8dd7c0 100644 --- a/src/cerbero_mcp/__main__.py +++ b/src/cerbero_mcp/__main__.py @@ -1,9 +1,73 @@ -"""Entrypoint cerbero-mcp.""" +"""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 +from contextlib import asynccontextmanager + +import uvicorn +from fastapi import FastAPI + +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 ( + alpaca, bybit, deribit, hyperliquid, macro, sentiment, +) +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, env) + + app.state.registry = ClientRegistry(builder=builder) + + @asynccontextmanager + async def lifespan(app: FastAPI): + try: + yield + finally: + await app.state.registry.aclose() + + app.router.lifespan_context = lifespan + + app.include_router(deribit.make_router()) + app.include_router(bybit.make_router()) + app.include_router(hyperliquid.make_router()) + app.include_router(alpaca.make_router()) + app.include_router(macro.make_router()) + app.include_router(sentiment.make_router()) + + return app + def main() -> None: - raise NotImplementedError("server da implementare nelle phase successive") + configure_root_logging() + settings = Settings() + app = _make_app(settings) + uvicorn.run( + app, + log_config=None, + host=settings.host, + port=settings.port, + ) if __name__ == "__main__": diff --git a/tests/integration/test_app_boot.py b/tests/integration/test_app_boot.py new file mode 100644 index 0000000..65c510b --- /dev/null +++ b/tests/integration/test_app_boot.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from fastapi.testclient import TestClient + + +def test_app_boots_and_health_responds(monkeypatch): + from tests.unit.test_settings import _minimal_env + for k, v in _minimal_env().items(): + monkeypatch.setenv(k, v) + + from cerbero_mcp.__main__ import _make_app + from cerbero_mcp.settings import Settings + + app = _make_app(Settings()) + c = TestClient(app) + + r = c.get("/health") + assert r.status_code == 200 + assert r.json()["status"] == "healthy" + + r = c.get("/openapi.json") + assert r.status_code == 200 + spec = r.json() + paths = spec["paths"].keys() + assert any(p.startswith("/mcp-deribit/") for p in paths) + assert any(p.startswith("/mcp-bybit/") for p in paths) + assert any(p.startswith("/mcp-hyperliquid/") for p in paths) + assert any(p.startswith("/mcp-alpaca/") for p in paths) + assert any(p.startswith("/mcp-macro/") for p in paths) + assert any(p.startswith("/mcp-sentiment/") for p in paths) + + +def test_apidocs_available_after_boot(monkeypatch): + from tests.unit.test_settings import _minimal_env + for k, v in _minimal_env().items(): + monkeypatch.setenv(k, v) + + from cerbero_mcp.__main__ import _make_app + from cerbero_mcp.settings import Settings + + c = TestClient(_make_app(Settings())) + r = c.get("/apidocs") + assert r.status_code == 200 + assert "Cerbero MCP" in r.text