Model Context Protocol server exposing UCITS ETF analytics — compare, factor metrics, Monte Carlo projection — with formulas, sources, and disclaimers in every payload.
quant-mcp demonstrates solid definition quality with well-structured tools, comprehensive docstrings, and explicit formulas. All 4 tools have descriptions (194 - 500+ chars, well above the 10 - 1024 baseline). Input schemas are present with type definitions and parameter descriptions. However, output schemas are documented in docstrings rather than as formal JSON Schema, and error handling lacks actionable recovery guidance. Tool naming follows verb_noun convention (compare_, get_, monte_carlo_). The server prioritizes transparency (formulas, disclaimers, sources) over LLM-optimized brevity.
Compare two UCITS ETFs side by side using curated metadata. Inputs ------ isin_a, isin_b : str 12-character ISINs (e.g. 'IE00BFMXXD54' for VUAA). Case-insensitive. Both ISINs must exist in the curated list — call this tool with no args to retrieve the supported set if unsure (or pass an unsupported ISIN and read the `supported_isins` field of the error response). Output (dict) ------------- On success: - etf_a, etf_b: per-ETF metadata (name, ISIN, ticker, OCF, AUM, domicile, replication, dividend treatment, inception, index). - comparison: ocf_delta_bps = (etf_a.ocf_pct - etf_b.ocf_pct) * 10000 fund_size_ratio_a_over_b = etf_a.fund_size_aum_eur / etf_b.fund_size_aum_eur same_index = case-insensitive equality of `index_tracked` tracking_difference_delta_pct = null in v0 — fetch via the issuer factsheet or trackingdifferences.com - dataset_as_of: ISO date the curated metadata snapshot was taken. - sources: per-ETF KID + justETF profile URLs for audit. - disclaimer: identical to etf-dashboard's DISCLAIMER_TEXT. On unsupported ISIN: - error: 'unsupported_isin' - unsupported: [list of the ISINs the caller passed that aren't curated] - supported_isins: full list of currently supported ISINs. Compliance notes ---------------- - Returns data only. Never recommends one ETF over the other. - `same_index` is a factual string comparison, not a "they're equivalent" claim — two physical-replication funds tracking the same index can still have different OCF, tracking difference, and AUM. - AUM figures are approximate quarterly snapshots — for live values, follow the linked justETF URL.
Compute rolling 1y / 3y / 5y / 10y annualised return, volatility, Sharpe, max drawdown, and Calmar ratio for a curated UCITS ETF. Inputs ------ isin : str 12-character ISIN. Resolved via the curated list to a Yahoo Finance ticker; prices are fetched (cached) via yfinance. Output (dict) ------------- On success: - isin, name, ticker_yahoo: identity fields. - lookback_period: "10y" — the window pulled from yfinance. - n_observations, first_obs_date, last_obs_date: covered range. - factors: "1y" / "3y" / "5y" / "10y" -> {available, n_observations, annualised_return, annualised_volatility, sharpe_ratio, max_drawdown, calmar_ratio} or {available: false, note}. - rf_annual_assumed: 0.02 (used in Sharpe excess-return calc). - formulas: explicit string formula for every numeric output. - sources: KID + justETF + Yahoo Finance URLs for audit. - disclaimer: same DISCLAIMER_TEXT as compare_etfs. On unsupported ISIN: structured error mirroring compare_etfs. Formulas (also returned in the payload's `formulas` field) ---------------------------------------------------------- - daily_returns = P_t / P_{t-1} - 1 - annualised_return = (1 + mean(daily_returns)) ** 252 - 1 - annualised_vol = std(daily_returns) * sqrt(252) - sharpe_ratio = mean(excess) / std(excess) * sqrt(252) excess = daily_returns - rf_daily rf_daily = (1 + 0.02) ** (1/252) - 1 - max_drawdown = min(prices / running_max - 1) (negative number) - calmar_ratio = annualised_return / abs(max_drawdown)
Output schemas documented in docstrings, not as formal JSON Schema. LLMs cannot parse docstring-embedded schemas to plan downstream calls or validate responses.
Error responses lack actionable recovery guidance. Unsupported ISIN errors return supported_isins list (good), but other failure modes (e.g., yfinance data gaps, invalid portfolio weights) have no documented error structure or next-step hints.
monte_carlo_projection accepts portfolio as array of objects with 'isin' and 'weight_pct' keys, but schema does not enforce required properties or validate weight_pct range [0, 100] at schema level. Validation happens at runtime only.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 66 | 2026-07-28+ | v2 |
Project a portfolio of curated UCITS ETFs forward using Monte Carlo simulation. Inputs ------ portfolio : list[dict] Each dict must have keys: - isin: 12-character ISIN (case-insensitive). - weight_pct: float in [0, 100]. Weights need not sum to 100 (will be normalised). Negative weights (shorts) are allowed. months_ahead : int Projection horizon in months. Must be in [1, 600] (i.e. up to 50 years). n_simulations : int Number of Monte Carlo paths. Must be in [1, 10000]. Output (dict) ------------- On success: - portfolio_composition: normalised weights, per-ETF metadata. - projection_horizon_months: the input. - n_simulations: the input. - lookback_period: "10y" — the window used to estimate mean/vol. - mean_return_annual, volatility_annual: portfolio-level annualised metrics (weighted average of per-ETF factors). - simulated_paths: list of n_simulations paths, each a list of monthly portfolio values (starting from 1.0). - percentile_bands: {"10": [...], "50": [...], "90": [...]} — monthly portfolio values at the 10th, 50th, 90th percentiles across all simulations. - formulas: explicit string formulas for mean/vol aggregation. - disclaimer: MONTE_CARLO_DISCLAIMER (fat tails warning). On unsupported ISIN or invalid input: structured error. Formulas -------- - portfolio_mean_annual = sum(weight_i * mean_i) - portfolio_vol_annual = sqrt(sum(weight_i^2 * vol_i^2)) (assumes zero correlation — conservative for diversified portfolios). - monthly_mean = portfolio_mean_annual / 12 - monthly_vol = portfolio_vol_annual / sqrt(12) - simulated_return_t = monthly_mean + monthly_vol * Z_t where Z_t ~ N(0, 1) (normal random variable). - portfolio_value_t = portfolio_value_{t-1} * (1 + simulated_return_t).
Return 'pong' to verify the quant-mcp server is reachable from a client.
Tool descriptions are lengthy (200 - 500+ chars) and optimized for human compliance/transparency rather than LLM selection efficiency. Baseline for A+ tools is 50 - 200 chars. Docstrings prioritize formulas and disclaimers over concise intent statements.
No tool composition guidance. Docstrings do not hint at multi-step workflows (e.g., 'Call compare_etfs first to identify candidates, then get_etf_factors for detailed metrics'). Agents must infer tool sequencing.