from __future__ import annotations import os from fastapi import Depends, FastAPI, HTTPException from mcp_common.auth import Principal, TokenStore, require_principal from mcp_common.mcp_bridge import mount_mcp_endpoint from mcp_common.server import build_app from pydantic import BaseModel, Field from mcp_macro.fetchers import ( fetch_asset_price, fetch_breakeven_inflation, fetch_cot_disaggregated, fetch_cot_extreme_positioning, fetch_cot_tff, fetch_economic_indicators, fetch_equity_futures, fetch_macro_calendar, fetch_market_overview, fetch_treasury_yields, fetch_yield_curve_slope, ) # --- Body models --- class GetEconomicIndicatorsReq(BaseModel): indicators: list[str] | None = None class GetMacroCalendarReq(BaseModel): days: int = 7 country_filter: list[str] | None = None importance_min: str | None = None start: str | None = None end: str | None = None class GetMarketOverviewReq(BaseModel): pass class GetAssetPriceReq(BaseModel): ticker: str class GetTreasuryYieldsReq(BaseModel): pass class GetEquityFuturesReq(BaseModel): pass class GetYieldCurveSlopeReq(BaseModel): pass class GetBreakevenInflationReq(BaseModel): pass class GetCotTffReq(BaseModel): symbol: str lookback_weeks: int = Field(default=52, ge=4, le=520) class GetCotDisaggregatedReq(BaseModel): symbol: str lookback_weeks: int = Field(default=52, ge=4, le=520) class GetCotExtremeReq(BaseModel): lookback_weeks: int = Field(default=156, ge=4, le=520) # --- ACL helper --- def _check(principal: Principal, *, core: bool = False, observer: bool = False) -> None: allowed: set[str] = set() if core: allowed.add("core") if observer: allowed.add("observer") if not (principal.capabilities & allowed): raise HTTPException(403, f"capability required: {allowed}") # --- App factory --- def create_app(*, fred_api_key: str = "", finnhub_api_key: str = "", token_store: TokenStore) -> FastAPI: app = build_app(name="mcp-macro", version="0.1.0", token_store=token_store) @app.post("/tools/get_economic_indicators", tags=["reads"]) async def t_get_economic_indicators( body: GetEconomicIndicatorsReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_economic_indicators( fred_api_key=fred_api_key, indicators=body.indicators ) @app.post("/tools/get_macro_calendar", tags=["reads"]) async def t_get_macro_calendar( body: GetMacroCalendarReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_macro_calendar( finnhub_api_key=finnhub_api_key, days_ahead=body.days, country_filter=body.country_filter, importance_min=body.importance_min, start=body.start, end=body.end, ) @app.post("/tools/get_market_overview", tags=["reads"]) async def t_get_market_overview( body: GetMarketOverviewReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_market_overview() @app.post("/tools/get_asset_price", tags=["reads"]) async def t_get_asset_price( body: GetAssetPriceReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_asset_price(body.ticker) @app.post("/tools/get_treasury_yields", tags=["reads"]) async def t_get_treasury_yields( body: GetTreasuryYieldsReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_treasury_yields() @app.post("/tools/get_equity_futures", tags=["reads"]) async def t_get_equity_futures( body: GetEquityFuturesReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_equity_futures() @app.post("/tools/get_yield_curve_slope", tags=["reads"]) async def t_get_yield_curve_slope( body: GetYieldCurveSlopeReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_yield_curve_slope() @app.post("/tools/get_breakeven_inflation", tags=["reads"]) async def t_get_breakeven_inflation( body: GetBreakevenInflationReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_breakeven_inflation(fred_api_key=fred_api_key) @app.post("/tools/get_cot_tff", tags=["reads"]) async def t_get_cot_tff( body: GetCotTffReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_cot_tff(body.symbol, body.lookback_weeks) @app.post("/tools/get_cot_disaggregated", tags=["reads"]) async def t_get_cot_disaggregated( body: GetCotDisaggregatedReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_cot_disaggregated(body.symbol, body.lookback_weeks) @app.post("/tools/get_cot_extreme_positioning", tags=["reads"]) async def t_get_cot_extreme( body: GetCotExtremeReq, principal: Principal = Depends(require_principal) ): _check(principal, core=True, observer=True) return await fetch_cot_extreme_positioning(body.lookback_weeks) # ───── MCP endpoint (/mcp) — bridge verso /tools/* ───── port = int(os.environ.get("PORT", "9013")) mount_mcp_endpoint( app, name="cerbero-macro", version="0.1.0", token_store=token_store, internal_base_url=f"http://localhost:{port}", tools=[ {"name": "get_economic_indicators", "description": "FRED economic indicators (Fed rate, CPI, ecc)."}, {"name": "get_macro_calendar", "description": "Eventi macro con filtri country/importance/date range."}, {"name": "get_market_overview", "description": "Snapshot overview mercato macro."}, {"name": "get_asset_price", "description": "Prezzo cross-asset: WTI, DXY, SPX, VIX, yields, FX, ecc."}, {"name": "get_treasury_yields", "description": "Curva US Treasury 2y/5y/10y/30y + shape detection."}, {"name": "get_equity_futures", "description": "Futures ES/NQ/YM/RTY con session status."}, {"name": "get_yield_curve_slope", "description": "Slope 2y10y/5y30y + butterfly + regime (steep/normal/flat/inverted)."}, {"name": "get_breakeven_inflation", "description": "Breakeven inflation 5Y/10Y + 5y5y forward (FRED T5YIE/T10YIE/T5YIFR)."}, {"name": "get_cot_tff", "description": "COT TFF report (CFTC) per equity/financial: ES/NQ/RTY/ZN/ZB/6E/6J/DX. Roles: dealer, asset manager, leveraged funds, other."}, {"name": "get_cot_disaggregated", "description": "COT Disaggregated report (CFTC) per commodities: CL/GC/SI/HG/ZW/ZC/ZS. Roles: producer/merchant, swap dealer, managed money, other."}, {"name": "get_cot_extreme_positioning", "description": "Scanner posizionamento estremo (percentile ≤5 o ≥95) sui simboli watchlist."}, ], ) return app