diff --git a/app/__main__.py b/app/__main__.py
index ca261e98..df60de3a 100644
--- a/app/__main__.py
+++ b/app/__main__.py
@@ -8,7 +8,6 @@
from fastapi.staticfiles import StaticFiles
from fluid.utils import log
-from app.api.mcp import create_mcp as _create_mcp
from app.utils.paths import APP_PATH
from quantflow import __version__
@@ -56,17 +55,15 @@ async def api_redoc() -> HTMLResponse:
title="Quantflow API",
)
- api.include_router(cointegration_router)
- api.include_router(heston_router)
- api.include_router(hurst_router)
- api.include_router(rates_router)
- api.include_router(sampling_router)
- api.include_router(smoother_router)
- api.include_router(volatility_router)
+ api.include_router(cointegration_router, tags=["timeseries"])
+ api.include_router(heston_router, tags=["pricing"])
+ api.include_router(hurst_router, tags=["statistics"])
+ api.include_router(rates_router, tags=["pricing"])
+ api.include_router(sampling_router, tags=["statistics"])
+ api.include_router(smoother_router, tags=["timeseries"])
+ api.include_router(volatility_router, tags=["pricing"])
app.include_router(api)
app.include_router(status_router, include_in_schema=False)
- if _create_mcp is not None:
- api.mount("/mcp", _create_mcp().streamable_http_app())
examples_dir = APP_PATH / "examples"
if examples_dir.is_dir():
app.mount(
diff --git a/app/api/cointegration.py b/app/api/cointegration.py
index 53f96c4d..f07cd469 100644
--- a/app/api/cointegration.py
+++ b/app/api/cointegration.py
@@ -5,6 +5,7 @@
from pydantic import BaseModel, Field
from statsmodels.tsa.vector_ar.vecm import coint_johansen
+from app.api.docs import load_description
from quantflow.data.fmp import FMP
from .deps import FMPDep, RedisCache, RedisDep
@@ -20,13 +21,17 @@ class CointegrationResponse(BaseModel):
)
-@cointegration_router.get("/cointegration")
+@cointegration_router.get(
+ "/cointegration",
+ summary="BTC/ETH/SOL cointegration",
+ description=load_description("cointegration.md"),
+)
async def cointegration(
fmp: FMPDep,
redis: RedisDep,
frequency: FMP.freq = Query(
FMP.freq.daily,
- description="Price frequency",
+ description="Price sampling frequency.",
),
) -> CointegrationResponse:
cache = RedisCache(
diff --git a/app/api/docs/__init__.py b/app/api/docs/__init__.py
new file mode 100644
index 00000000..49ae0c54
--- /dev/null
+++ b/app/api/docs/__init__.py
@@ -0,0 +1,7 @@
+from pathlib import Path
+
+_DOCS_PATH = Path(__file__).parent
+
+
+def load_description(filename: str) -> str:
+ return (_DOCS_PATH / filename).read_text(encoding="utf-8")
diff --git a/app/api/docs/cointegration.md b/app/api/docs/cointegration.md
new file mode 100644
index 00000000..d6988735
--- /dev/null
+++ b/app/api/docs/cointegration.md
@@ -0,0 +1,9 @@
+Johansen cointegration analysis of BTC, ETH, and SOL log prices.
+
+Returns the mean-reverting spread (residuals) and the cointegrating vector
+(eigenvector for the largest eigenvalue of the Johansen test).
+
+Log prices are standardised before the test; the returned cointegrating vector
+is rescaled back to the original units and normalised to unit length.
+A residual near zero means the spread is close to its long-run equilibrium.
+Large positive or negative residuals signal potential mean-reversion trades.
diff --git a/app/api/docs/double_exponential_sampling.md b/app/api/docs/double_exponential_sampling.md
new file mode 100644
index 00000000..14681df4
--- /dev/null
+++ b/app/api/docs/double_exponential_sampling.md
@@ -0,0 +1,11 @@
+Sample from a double exponential (asymmetric Laplace) distribution and compare
+the empirical PDF to both the analytical PDF and the PDF recovered from the
+characteristic function.
+
+The double exponential distribution models asymmetric jump sizes in jump-diffusion
+models. The asymmetry is controlled by `log_kappa`: positive values produce
+heavier right tails (upward jumps more likely), negative values heavier left tails.
+At `log_kappa = 0` the distribution is symmetric.
+
+The characteristic function inversion provides an independent check that the
+analytical and numerical PDFs agree.
diff --git a/app/api/docs/gaussian_sampling.md b/app/api/docs/gaussian_sampling.md
new file mode 100644
index 00000000..b47fbfaa
--- /dev/null
+++ b/app/api/docs/gaussian_sampling.md
@@ -0,0 +1,12 @@
+Sample from a Vasicek mean-reverting Gaussian process and compare the empirical
+distribution to the analytical marginal PDF.
+
+The process is run for one unit of time with 1000 time steps. The terminal
+distribution is binned into 50 bins and compared against the analytical Gaussian PDF.
+
+A Kolmogorov-Smirnov test is included to quantify goodness of fit.
+A high p-value (e.g. above 0.05) means the simulation is statistically consistent
+with the analytical distribution.
+
+Antithetic variates halve the variance of Monte Carlo estimates by pairing each
+draw with its negative, without additional model evaluations.
diff --git a/app/api/docs/heston_vol_surface.md b/app/api/docs/heston_vol_surface.md
new file mode 100644
index 00000000..35500003
--- /dev/null
+++ b/app/api/docs/heston_vol_surface.md
@@ -0,0 +1,15 @@
+Generate a theoretical implied volatility surface using a stochastic volatility model.
+
+Two models are available:
+
+- **jd** (Jump Diffusion): a Variance-Gamma-style model with a double-exponential
+ jump distribution. Controlled by `vol`, `jump_fraction`, `jump_intensity`, and
+ `jump_asymmetry`. The `kappa` and `rho` parameters are ignored.
+
+- **hj** (Heston with Jumps): the Heston stochastic volatility model augmented with
+ double-exponential jumps. All parameters are used.
+
+The surface is returned as a grid: 10 maturities evenly spaced from 0.1 to 1.0 years,
+and 51 moneyness values from -0.5 to 0.5 (log-moneyness scaled by sqrt(ttm)).
+Implied volatilities that cannot be computed (e.g. deep in/out of the money) are
+returned as 0.
diff --git a/app/api/docs/hurst_vasicek.md b/app/api/docs/hurst_vasicek.md
new file mode 100644
index 00000000..2aa1a4af
--- /dev/null
+++ b/app/api/docs/hurst_vasicek.md
@@ -0,0 +1,9 @@
+Simulate a Vasicek mean-reverting process and estimate its Hurst exponent.
+
+The Vasicek process reverts to zero at speed `kappa`. Higher values of `kappa`
+produce stronger mean reversion and a lower Hurst exponent (well below 0.5).
+Low values of `kappa` approach Brownian motion (Hurst near 0.5).
+
+The Hurst exponent is estimated from both realized variance and three OHLC-based
+estimators (Parkinson, Garman-Klass, Rogers-Satchell) sampled at periods of
+10min, 20min, 30min, and 1h.
diff --git a/app/api/docs/hurst_wiener.md b/app/api/docs/hurst_wiener.md
new file mode 100644
index 00000000..b11aa894
--- /dev/null
+++ b/app/api/docs/hurst_wiener.md
@@ -0,0 +1,15 @@
+Simulate a Wiener (Brownian motion) process over one day at one-second resolution
+and estimate its Hurst exponent using multiple methods.
+
+The Hurst exponent measures long-range dependence in a time series.
+A value of 0.5 indicates pure Brownian motion (no memory).
+Values above 0.5 indicate trending behaviour; values below 0.5 indicate mean reversion.
+
+Three OHLC-based range estimators are computed across multiple sampling periods:
+
+- **Parkinson**: uses high-low range.
+- **Garman-Klass**: uses open, high, low, close.
+- **Rogers-Satchell**: drift-adjusted version of Garman-Klass.
+
+The Hurst exponent for each estimator is derived from the slope of log(variance)
+vs log(sampling period).
diff --git a/app/api/docs/poisson_sampling.md b/app/api/docs/poisson_sampling.md
new file mode 100644
index 00000000..e3ed62c6
--- /dev/null
+++ b/app/api/docs/poisson_sampling.md
@@ -0,0 +1,10 @@
+Sample from a Poisson jump process and compare the empirical jump count distribution
+to the analytical Poisson PMF.
+
+The process is run for one unit of time with 1000 time steps. Jump counts are
+binned and compared against the analytical distribution using a chi-squared
+goodness-of-fit test. Tail bins with expected count below 5 are merged to satisfy
+the chi-squared validity requirement.
+
+A high p-value (e.g. above 0.05) means the simulation is statistically consistent
+with the analytical Poisson distribution.
diff --git a/app/api/docs/supersmoother.md b/app/api/docs/supersmoother.md
new file mode 100644
index 00000000..5fc90181
--- /dev/null
+++ b/app/api/docs/supersmoother.md
@@ -0,0 +1,12 @@
+Apply SuperSmoother and EWMA filters to two years of historical daily price data
+for a given symbol.
+
+SuperSmoother is an adaptive low-pass filter designed by John Ehlers. It suppresses
+high-frequency noise more aggressively than EWMA while introducing less lag at
+trend inflections.
+
+Both filters use the same `period` parameter, making it easy to compare their
+smoothing characteristics on the same data. Shorter periods follow price more
+closely; longer periods produce smoother output with more lag.
+
+Supported symbols include crypto pairs (e.g. BTCUSD, ETHUSD) and US equities.
diff --git a/app/api/docs/volatility_surface.md b/app/api/docs/volatility_surface.md
new file mode 100644
index 00000000..08327113
--- /dev/null
+++ b/app/api/docs/volatility_surface.md
@@ -0,0 +1,21 @@
+Fetch the live implied volatility surface for a crypto or equity asset and return
+the calibrated surface with discount curves and forward prices.
+
+Data sources:
+
+- **BTC, ETH**: live option chain from Deribit (inverse, crypto-quoted contracts).
+- **SPY, AAPL, NVDA**: option chain from Yahoo Finance (standard equity contracts).
+
+The surface is calibrated using Black-Scholes implied volatilities. Outlier options
+(bid-ask spread too wide, or IV outside plausible bounds) are disabled before
+the surface is returned.
+
+Two discount curves are calibrated from the option data:
+
+- **quote_curve**: discount curve for the numeraire (USD for equity, crypto for inverse).
+- **asset_curve**: discount curve for the underlying asset.
+
+The forward curve and per-maturity implied forwards from put-call parity are also
+included, which are useful for detecting curve arbitrage or funding dislocations.
+
+Responses are cached; live data may be up to a few minutes old.
diff --git a/app/api/docs/yield_curve.md b/app/api/docs/yield_curve.md
new file mode 100644
index 00000000..f7959522
--- /dev/null
+++ b/app/api/docs/yield_curve.md
@@ -0,0 +1,17 @@
+Fit a yield curve model to a set of (time-to-maturity, rate) pairs and return
+the calibrated curve plus interpolated rates.
+
+Four curve types are supported:
+
+- **nelson_siegel**: the Nelson-Siegel parametric model, well suited for smooth
+ yield curves with a single hump. Good default choice.
+- **cir_curve**: Cox-Ingersoll-Ross model, a mean-reverting short-rate model.
+- **vasicek_curve**: Vasicek model, a Gaussian mean-reverting short-rate model.
+- **no_discount**: flat discount curve (no discounting), useful as a baseline.
+
+Rates should be continuously compounded and expressed as decimals (e.g. 0.045 for 4.5%).
+Time to maturity is in years (e.g. 0.25 for 3 months, 2.0 for 2 years).
+
+If `max_ttm` is provided, the response includes `num_points` interpolated rates
+evenly spaced up to that maturity, which is useful for plotting a smooth curve.
+Otherwise, rates are returned at the input maturities only.
diff --git a/app/api/heston.py b/app/api/heston.py
index 7d70914d..ffdcedbe 100644
--- a/app/api/heston.py
+++ b/app/api/heston.py
@@ -2,6 +2,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field
+from app.api.docs import load_description
from quantflow.options.pricer import OptionPricer
from quantflow.sp.heston import HestonJ
from quantflow.sp.jump_diffusion import JumpDiffusion
@@ -18,7 +19,11 @@ class VolSurfaceGridResponse(BaseModel):
)
-@heston_router.get("/heston-vol-surface")
+@heston_router.get(
+ "/heston-vol-surface",
+ summary="Theoretical implied volatility surface",
+ description=load_description("heston_vol_surface.md"),
+)
async def heston_vol_surface(
model: str = Query(
"jd",
diff --git a/app/api/hurst.py b/app/api/hurst.py
index be6bdd13..233e196d 100644
--- a/app/api/hurst.py
+++ b/app/api/hurst.py
@@ -7,6 +7,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field
+from app.api.docs import load_description
from quantflow.sp.ou import Vasicek
from quantflow.sp.wiener import WienerProcess
from quantflow.ta.ohlc import OHLC
@@ -86,7 +87,11 @@ def _ohlc_hurst(df: pd.DataFrame, serie: str, periods: tuple) -> dict[str, float
return result
-@hurst_router.get("/hurst-wiener")
+@hurst_router.get(
+ "/hurst-wiener",
+ summary="Hurst exponent of a Wiener process",
+ description=load_description("hurst_wiener.md"),
+)
async def hurst_wiener(
sigma: float = Query(2.0, description="Wiener process volatility", ge=0.1, le=10),
) -> HurstWienerResponse:
@@ -138,7 +143,11 @@ async def hurst_wiener(
)
-@hurst_router.get("/hurst-vasicek")
+@hurst_router.get(
+ "/hurst-vasicek",
+ summary="Hurst exponent of a Vasicek process",
+ description=load_description("hurst_vasicek.md"),
+)
async def hurst_vasicek(
kappa: float = Query(10.0, description="Mean reversion speed", ge=1.0, le=500.0),
) -> HurstVasicekResponse:
diff --git a/app/api/mcp.py b/app/api/mcp.py
deleted file mode 100644
index 8854a222..00000000
--- a/app/api/mcp.py
+++ /dev/null
@@ -1,90 +0,0 @@
-"""MCP server for quantflow - crypto volatility tools served via HTTP."""
-
-from pathlib import Path
-
-from mcp.server.fastmcp import FastMCP
-
-from quantflow.data.deribit import Deribit, InstrumentKind
-
-
-def create_mcp() -> FastMCP:
- mcp = FastMCP("quantflow")
-
- @mcp.tool()
- async def crypto_instruments(currency: str, kind: str = "spot") -> str:
- """List available instruments for a cryptocurrency on Deribit.
-
- Args:
- currency: Cryptocurrency symbol e.g. BTC, ETH
- kind: Instrument kind - spot, future, option (default: spot)
- """
- async with Deribit() as client:
- data = await client.get_instruments(
- currency=currency, kind=InstrumentKind(kind)
- )
- if not data:
- return f"No instruments found for {currency} ({kind})"
- rows = "\n".join(str(d) for d in data[:20])
- return f"Instruments for {currency} ({kind}):\n{rows}"
-
- @mcp.tool()
- async def crypto_historical_volatility(currency: str) -> str:
- """Get historical volatility for a cryptocurrency from Deribit.
-
- Args:
- currency: Cryptocurrency symbol e.g. BTC, ETH
- """
- async with Deribit() as client:
- df = await client.get_volatility(currency)
- if df.empty:
- return f"No volatility data for {currency}"
- return df.to_csv(index=False)
-
- @mcp.tool()
- async def crypto_term_structure(currency: str) -> str:
- """Get the volatility term structure for a cryptocurrency from Deribit.
-
- Args:
- currency: Cryptocurrency symbol e.g. BTC, ETH
- """
- async with Deribit() as client:
- loader = await client.volatility_surface_loader(currency)
- vs = loader.surface()
- ts = vs.term_structure().round({"ttm": 4})
- return ts.to_csv(index=False)
-
- @mcp.tool()
- async def crypto_implied_volatility(currency: str, maturity_index: int = -1) -> str:
- """Get the implied volatility surface for a cryptocurrency from Deribit.
-
- Args:
- currency: Cryptocurrency symbol e.g. BTC, ETH
- maturity_index: Maturity index (-1 for all maturities)
- """
- async with Deribit() as client:
- loader = await client.volatility_surface_loader(currency)
- vs = loader.surface()
- index = None if maturity_index < 0 else maturity_index
- vs.bs(index=index)
- df = vs.options_df(index=index)
- df["iv"] = df["iv"].map("{:.2%}".format)
- return df.to_csv(index=False)
-
- @mcp.tool()
- async def vol_surface_snapshot(currency: str, path: str) -> str:
- """Fetch a live volatility surface from Deribit and save it as a JSON snapshot.
-
- Args:
- currency: Cryptocurrency symbol e.g. BTC, ETH
- path: File path to write the snapshot to
- """
- async with Deribit() as client:
- loader = await client.volatility_surface_loader(currency)
- vs = loader.surface()
- vs.bs()
- vs.disable_outliers()
- inputs = vs.inputs(converged=True)
- Path(path).write_text(inputs.model_dump_json(indent=2))
- return f"Saved {len(vs.maturities)} maturities to {path}"
-
- return mcp
diff --git a/app/api/rates.py b/app/api/rates.py
index 427c402e..134c2c05 100644
--- a/app/api/rates.py
+++ b/app/api/rates.py
@@ -4,6 +4,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field
+from app.api.docs import load_description
from quantflow.rates import AnyYieldCurve, YieldCurve
rates_router = APIRouter()
@@ -17,7 +18,11 @@ class YieldCurveResponse(BaseModel):
rates: list[float] = Field(description="List of rates for the fitted curve")
-@rates_router.get("/yield-curve")
+@rates_router.get(
+ "/yield-curve",
+ summary="Fit and interpolate a yield curve",
+ description=load_description("yield_curve.md"),
+)
async def yield_curve(
ttm: list[float] = Query(
..., description="List of time to maturities corresponding to the rates"
diff --git a/app/api/sampling.py b/app/api/sampling.py
index 18add3e9..db9a21bc 100644
--- a/app/api/sampling.py
+++ b/app/api/sampling.py
@@ -3,6 +3,7 @@
from pydantic import BaseModel, Field
from scipy.stats import chisquare, ks_1samp
+from app.api.docs import load_description
from quantflow.sp.ou import Vasicek
from quantflow.sp.poisson import PoissonProcess
from quantflow.ta.paths import Paths
@@ -35,7 +36,11 @@ class DoubleExponentialResponse(SamplingResponse):
char_y: list[float] = Field(description="Y values from characteristic function")
-@sampling_router.get("/gaussian-sampling")
+@sampling_router.get(
+ "/gaussian-sampling",
+ summary="Gaussian process sampling vs analytical PDF",
+ description=load_description("gaussian_sampling.md"),
+)
async def gaussian_sampling(
kappa: float = Query(1.0, description="Mean reversion speed", ge=0.1, le=5.0),
samples: int = Query(1000, description="Number of sample paths", ge=100, le=10000),
@@ -61,7 +66,11 @@ async def gaussian_sampling(
)
-@sampling_router.get("/poisson-sampling")
+@sampling_router.get(
+ "/poisson-sampling",
+ summary="Poisson process sampling vs analytical PMF",
+ description=load_description("poisson_sampling.md"),
+)
async def poisson_sampling(
intensity: float = Query(2.0, description="Poisson intensity", ge=2.0, le=20.0),
samples: int = Query(1000, description="Number of sample paths", ge=100, le=10000),
@@ -94,7 +103,11 @@ async def poisson_sampling(
)
-@sampling_router.get("/double-exponential-sampling")
+@sampling_router.get(
+ "/double-exponential-sampling",
+ summary="Double exponential sampling vs analytical PDF",
+ description=load_description("double_exponential_sampling.md"),
+)
async def double_exponential_sampling(
log_kappa: float = Query(
0.1, description="Log of asymmetry parameter", ge=-2.0, le=2.0
diff --git a/app/api/smoother.py b/app/api/smoother.py
index ab27818e..40ebeccc 100644
--- a/app/api/smoother.py
+++ b/app/api/smoother.py
@@ -4,6 +4,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field
+from app.api.docs import load_description
from quantflow.ta.ewma import EWMA
from quantflow.ta.supersmoother import SuperSmoother
@@ -23,7 +24,12 @@ class SmootherResponse(BaseModel):
data: list[SmootherPoint] = Field(description="Time series with smoothed values")
-@smoother_router.get("/supersmoother")
+@smoother_router.get(
+ "/supersmoother",
+ summary="SuperSmoother and EWMA price filter",
+ description=load_description("supersmoother.md"),
+ response_model=SmootherResponse,
+)
async def supersmoother(
redis: RedisDep,
fmp: FMPDep,
diff --git a/app/api/volatility.py b/app/api/volatility.py
index 68fab547..009bfdbe 100644
--- a/app/api/volatility.py
+++ b/app/api/volatility.py
@@ -5,6 +5,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field
+from app.api.docs import load_description
from quantflow.data.deribit import Deribit
from quantflow.data.yahoo import Yahoo
from quantflow.options.inputs import VolSurfaceInputs
@@ -54,7 +55,11 @@ class VolSurfaceResponse(BaseModel):
)
-@volatility_router.get("/volatility-surface")
+@volatility_router.get(
+ "/volatility-surface",
+ summary="Live implied volatility surface",
+ description=load_description("volatility_surface.md"),
+)
async def volatility_surface(
redis: RedisDep,
asset: str = Query(
diff --git a/docs/api/index.md b/docs/api/index.md
index c64cbe60..7dffd0e8 100644
--- a/docs/api/index.md
+++ b/docs/api/index.md
@@ -53,7 +53,6 @@ Time series filters and indicators for financial data.
| Module | Description |
|---|---|
| [EWMA](ta/ewma.md) | Exponentially weighted moving average |
-| [Kalman Filter](ta/kalman.md) | Kalman filter for state estimation |
| [Supersmoother](ta/supersmoother.md) | Ehlers two-pole supersmoother filter |
| [OHLC](ta/ohlc.md) | OHLC bar utilities and resampling |
| [Paths](ta/paths.md) | Simulated path containers and statistics |
diff --git a/docs/api/ta/kalman.md b/docs/api/ta/kalman.md
deleted file mode 100644
index 4f1d395d..00000000
--- a/docs/api/ta/kalman.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Kalman Filter
-
-::: quantflow.ta.kalman.KalmanFilter
diff --git a/docs/mcp.md b/docs/mcp.md
deleted file mode 100644
index 1065e3c9..00000000
--- a/docs/mcp.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# MCP Server
-
-Quantflow exposes its crypto volatility tools as an [MCP](https://modelcontextprotocol.io) server, allowing AI clients such as Claude to query live Deribit data directly.
-
-The server is hosted at:
-
-```
-https://quantflow.quantmind.com/.api/mcp
-```
-
-It uses the [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport. No API key is required — all tools use the public Deribit API.
-
-## Claude Code
-
-```bash
-claude mcp add --transport http quantflow https://quantflow.quantmind.com/.api/mcp
-```
-
-## Claude Desktop
-
-Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
-
-```json
-{
- "mcpServers": {
- "quantflow": {
- "type": "streamable-http",
- "url": "https://quantflow.quantmind.com/.api/mcp"
- }
- }
-}
-```
-
-## Available tools
-
-| Tool | Description |
-|---|---|
-| `crypto_instruments` | List instruments for a currency on Deribit (spot, future, option) |
-| `crypto_historical_volatility` | Historical volatility index from Deribit |
-| `crypto_term_structure` | Volatility term structure across maturities |
-| `crypto_implied_volatility` | Implied volatility surface (all maturities or a single one) |
-| `vol_surface_snapshot` | Fetch a live vol surface and write it as a JSON file |
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 07129dd2..13f7d679 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -154,7 +154,6 @@ and signature changes were made along the way: see **Breaking changes** below.
- New logo set (favicon, lockup, marks, social banners) under
`docs/assets/logos/`. ([#53](https://github.com/quantmind/quantflow/pull/53))
-- New `docs/mcp.md` page covering the MCP server.
- Bibliography rebuilt from BibTeX via `docs/bib2md.py`; glossary expanded;
mathjax tweaks for inline rendering.
([#47](https://github.com/quantmind/quantflow/pull/47),
diff --git a/frontend/observablehq.config.js b/frontend/observablehq.config.js
index 95cdb0b0..7be60b04 100644
--- a/frontend/observablehq.config.js
+++ b/frontend/observablehq.config.js
@@ -13,7 +13,10 @@ export default {
root: "src",
output: "../app/examples",
base: "/examples",
- head: `\n${headSnippet}`,
+ head: `
+
+
+${headSnippet}`,
style: "style.css",
pages: [{name: "Volatility Surface", path: "/volatility-surface"}, {name: "Yield Curve", path: "/yield-curve"}, {name: "Sampling", path: "/sampling"}, {name: "SuperSmoother", path: "/supersmoother"}, {name: "Cointegration", path: "/cointegration"}, {name: "Hurst Exponent", path: "/hurst"}, {name: "Heston Vol Surface", path: "/heston-vol-surface"}],
header: `
@@ -44,5 +47,5 @@ export default {
})();
`,
- footer: "Quantflow live examples"
+ footer: 'Quantflow live examples · API Reference'
};
diff --git a/mkdocs.yml b/mkdocs.yml
index 2bf80493..d9d3ad4a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -94,7 +94,6 @@ nav:
- Timeseries Analysis:
- api/ta/index.md
- EWMA: api/ta/ewma.md
- - Kalman Filter: api/ta/kalman.md
- OHLC: api/ta/ohlc.md
- Paths: api/ta/paths.md
- Supersmoother: api/ta/supersmoother.md
@@ -132,7 +131,6 @@ nav:
- Lévy Process: theory/levy.md
- Option Pricing: theory/option_pricing.md
- Live Examples: https://quantflow.quantmind.com/examples
- - MCP Server: mcp.md
- Glossary: glossary.md
- Contributing: contributing.md
- Bibliography: bibliography.md
diff --git a/pyproject.toml b/pyproject.toml
index 834725a5..a4f609d3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -75,7 +75,6 @@ docs = [
"griffe-pydantic>=1.1.0",
"griffe-typingdoc>=0.2.7",
"kaleido>=1.2.0",
- "mcp>=1.26.0",
"mkdocs-macros-plugin>=1.3.7",
"mkdocs-material>=9.7.0",
"mkdocs-redirects>=1.2.1",
diff --git a/quantflow/ta/kalman.py b/quantflow/ta/kalman.py
deleted file mode 100644
index 4ec5606c..00000000
--- a/quantflow/ta/kalman.py
+++ /dev/null
@@ -1,72 +0,0 @@
-from pydantic import BaseModel, Field, PrivateAttr
-from typing_extensions import Annotated, Doc
-
-
-class KalmanFilter(BaseModel):
- r"""One-dimensional Kalman filter for time series data.
-
- This implementation uses a simple 1D state-space model:
-
- $$
- \begin{align}
- x_t &= x_{t-1} + w_t, \quad w_t \sim \mathcal{N}(0, Q) \\
- z_t &= x_t + v_t, \quad v_t \sim \mathcal{N}(0, R)
- \end{align}
- $$
-
- The Kalman filter estimates the hidden state $x_t$ given noisy measurements $z_t$.
- The ratio $Q/R$ determines the smoothing behavior.
- """
-
- R: float = Field(default=1.0, gt=0.0, description="Measurement noise covariance")
- Q: float = Field(default=0.01, gt=0.0, description="Process noise covariance")
-
- _x: float | None = PrivateAttr(default=None) # State estimate
- _P: float = PrivateAttr(default=1.0) # Error covariance
- _K: float = PrivateAttr(default=0.0) # Kalman Gain
-
- def value(self) -> float | None:
- """Get the most recent smoothed value (state estimate), if available."""
- return self._x
-
- def update(
- self,
- value: Annotated[float, Doc("New noisy measurement to update the filter")],
- ) -> float:
- """Update the filter with a new value and return the smoothed result."""
- # Initialize on first update
- if self._x is None:
- self._x = value
- self._P = self.R
- return value
-
- # Prediction step
- # x_pred = x_prev (Random walk model)
- # P_pred = P_prev + Q
- x_pred = self._x
- P_pred = self._P + self.Q
-
- # Update step
- # K = P_pred / (P_pred + R)
- # x_new = x_pred + K * (measurement - x_pred)
- # P_new = (1 - K) * P_pred
- K = P_pred / (P_pred + self.R)
- x_new = x_pred + K * (value - x_pred)
- P_new = (1 - K) * P_pred
-
- # Update state
- self._x = x_new
- self._P = P_new
- self._K = K
-
- return x_new
-
- @property
- def error_covariance(self) -> float:
- """Current estimated error covariance."""
- return self._P
-
- @property
- def kalman_gain(self) -> float:
- """Most recent Kalman gain."""
- return self._K
diff --git a/quantflow_tests/test_ai.py b/quantflow_tests/test_ai.py
deleted file mode 100644
index ab1d09c8..00000000
--- a/quantflow_tests/test_ai.py
+++ /dev/null
@@ -1,121 +0,0 @@
-"""Tests for the quantflow MCP crypto tools."""
-
-from __future__ import annotations
-
-from pathlib import Path
-from typing import Any
-from unittest.mock import AsyncMock, MagicMock, patch
-
-import pandas as pd
-import pytest
-from mcp.server.fastmcp import FastMCP
-
-from app.api.mcp import create_mcp
-
-
-def text(result: Any) -> str:
- """Extract text from a call_tool result."""
- blocks = result[0] if isinstance(result, tuple) else result
- if blocks and hasattr(blocks[0], "text"):
- return blocks[0].text
- return str(result)
-
-
-@pytest.fixture
-def mcp_server() -> FastMCP:
- return create_mcp()
-
-
-async def test_crypto_instruments(mcp_server: FastMCP) -> None:
- mock_client = AsyncMock()
- mock_client.get_instruments.return_value = [
- MagicMock(__str__=lambda self: "BTC-SPOT")
- ]
- with patch("app.api.mcp.Deribit") as MockDeribit:
- MockDeribit.return_value.__aenter__ = AsyncMock(return_value=mock_client)
- MockDeribit.return_value.__aexit__ = AsyncMock(return_value=False)
- result = await mcp_server.call_tool("crypto_instruments", {"currency": "BTC"})
- assert "BTC" in text(result)
-
-
-async def test_crypto_instruments_empty(mcp_server: FastMCP) -> None:
- mock_client = AsyncMock()
- mock_client.get_instruments.return_value = []
- with patch("app.api.mcp.Deribit") as MockDeribit:
- MockDeribit.return_value.__aenter__ = AsyncMock(return_value=mock_client)
- MockDeribit.return_value.__aexit__ = AsyncMock(return_value=False)
- result = await mcp_server.call_tool("crypto_instruments", {"currency": "BTC"})
- assert "No instruments" in text(result)
-
-
-async def test_crypto_historical_volatility(mcp_server: FastMCP) -> None:
- mock_client = AsyncMock()
- mock_client.get_volatility.return_value = pd.DataFrame(
- [{"date": "2025-01-01", "volatility": 0.8}]
- )
- with patch("app.api.mcp.Deribit") as MockDeribit:
- MockDeribit.return_value.__aenter__ = AsyncMock(return_value=mock_client)
- MockDeribit.return_value.__aexit__ = AsyncMock(return_value=False)
- result = await mcp_server.call_tool(
- "crypto_historical_volatility", {"currency": "BTC"}
- )
- assert "volatility" in text(result)
- assert "2025-01-01" in text(result)
-
-
-async def test_crypto_historical_volatility_empty(mcp_server: FastMCP) -> None:
- mock_client = AsyncMock()
- mock_client.get_volatility.return_value = pd.DataFrame()
- with patch("app.api.mcp.Deribit") as MockDeribit:
- MockDeribit.return_value.__aenter__ = AsyncMock(return_value=mock_client)
- MockDeribit.return_value.__aexit__ = AsyncMock(return_value=False)
- result = await mcp_server.call_tool(
- "crypto_historical_volatility", {"currency": "BTC"}
- )
- assert "No volatility data" in text(result)
-
-
-async def test_crypto_term_structure(mcp_server: FastMCP, vol_surface: Any) -> None:
- mock_loader = MagicMock()
- mock_loader.surface.return_value = vol_surface
- mock_client = AsyncMock()
- mock_client.volatility_surface_loader.return_value = mock_loader
- with patch("app.api.mcp.Deribit") as MockDeribit:
- MockDeribit.return_value.__aenter__ = AsyncMock(return_value=mock_client)
- MockDeribit.return_value.__aexit__ = AsyncMock(return_value=False)
- result = await mcp_server.call_tool(
- "crypto_term_structure", {"currency": "ETH"}
- )
- assert "ttm" in text(result)
-
-
-async def test_crypto_implied_volatility(mcp_server: FastMCP, vol_surface: Any) -> None:
- mock_loader = MagicMock()
- mock_loader.surface.return_value = vol_surface
- mock_client = AsyncMock()
- mock_client.volatility_surface_loader.return_value = mock_loader
- with patch("app.api.mcp.Deribit") as MockDeribit:
- MockDeribit.return_value.__aenter__ = AsyncMock(return_value=mock_client)
- MockDeribit.return_value.__aexit__ = AsyncMock(return_value=False)
- result = await mcp_server.call_tool(
- "crypto_implied_volatility", {"currency": "ETH"}
- )
- assert "iv" in text(result)
-
-
-async def test_vol_surface_snapshot(
- mcp_server: FastMCP, vol_surface: Any, tmp_path: Path
-) -> None:
- mock_loader = MagicMock()
- mock_loader.surface.return_value = vol_surface
- mock_client = AsyncMock()
- mock_client.volatility_surface_loader.return_value = mock_loader
- out = str(tmp_path / "snapshot.json")
- with patch("app.api.mcp.Deribit") as MockDeribit:
- MockDeribit.return_value.__aenter__ = AsyncMock(return_value=mock_client)
- MockDeribit.return_value.__aexit__ = AsyncMock(return_value=False)
- result = await mcp_server.call_tool(
- "vol_surface_snapshot", {"currency": "ETH", "path": out}
- )
- assert "Saved" in text(result)
- assert Path(out).exists()
diff --git a/quantflow_tests/test_ta_filters.py b/quantflow_tests/test_ta_filters.py
index ffc83490..69850ac3 100644
--- a/quantflow_tests/test_ta_filters.py
+++ b/quantflow_tests/test_ta_filters.py
@@ -3,7 +3,6 @@
import pytest
from quantflow.ta.ewma import EWMA
-from quantflow.ta.kalman import KalmanFilter
from quantflow.ta.supersmoother import SuperSmoother
@@ -44,24 +43,6 @@ def test_ewma_from_alpha_validation() -> None:
EWMA.from_alpha(alpha=0.0)
-def test_kalman_initialization_and_first_update() -> None:
- kf = KalmanFilter(R=1.0, Q=0.1)
- assert kf.value() is None
- first = kf.update(5.0)
- assert first == 5.0
- assert kf.value() == 5.0
- assert kf.error_covariance == 1.0
-
-
-def test_kalman_steady_updates_and_properties() -> None:
- kf = KalmanFilter(R=1.0, Q=0.1)
- kf.update(10.0)
- updated = kf.update(12.0)
- assert 10.0 < updated < 12.0
- assert 0.0 < kf.kalman_gain < 1.0
- assert kf.error_covariance > 0.0
-
-
def test_supersmoother_initialization() -> None:
smoother = SuperSmoother(period=10)
assert smoother.raw_value() is None
diff --git a/uv.lock b/uv.lock
index 8e81e96e..80c49876 100644
--- a/uv.lock
+++ b/uv.lock
@@ -679,65 +679,6 @@ toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
-[[package]]
-name = "cryptography"
-version = "48.0.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" },
- { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" },
- { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" },
- { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" },
- { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" },
- { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" },
- { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" },
- { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" },
- { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" },
- { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" },
- { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" },
- { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" },
- { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" },
- { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" },
- { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" },
- { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" },
- { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" },
- { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" },
- { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" },
- { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" },
- { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" },
- { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" },
- { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" },
- { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" },
- { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" },
- { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" },
- { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" },
- { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" },
- { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" },
- { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" },
- { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" },
- { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" },
- { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" },
- { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" },
- { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" },
- { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" },
- { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" },
- { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" },
- { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" },
- { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" },
- { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" },
- { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
- { url = "https://files.pythonhosted.org/packages/be/d2/024b5e06be9d44cb021fb0e1a03d34d63989cf56a0fe62f3dfbab695b9b4/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855", size = 3950391, upload-time = "2026-05-04T22:59:17.415Z" },
- { url = "https://files.pythonhosted.org/packages/bc/17/3861e17c56fa0fd37491a14a8673fdb77c57fc5693cafe745ea8b06dba75/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b", size = 4637126, upload-time = "2026-05-04T22:59:20.197Z" },
- { url = "https://files.pythonhosted.org/packages/f0/0a/7e226dbff530f21480727eb764973a7bff2b912f8e15cd4f129e71b56d1d/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13", size = 4667270, upload-time = "2026-05-04T22:59:22.647Z" },
- { url = "https://files.pythonhosted.org/packages/3b/f2/5a72274ca9f1b2a8b44a662ee0bf1b435909deb473d6f97bcd035bcdbc71/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb", size = 4636797, upload-time = "2026-05-04T22:59:24.912Z" },
- { url = "https://files.pythonhosted.org/packages/b4/e1/48cedb2fe63626e91ded1edad159e2a4fb8b6906c4425eb7749673077ce7/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355", size = 4666800, upload-time = "2026-05-04T22:59:27.474Z" },
- { url = "https://files.pythonhosted.org/packages/a2/ca/7e8365deec19afb2b2c7be7c1c0aa8f99633b54e90c570999acda93260fc/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a", size = 3739536, upload-time = "2026-05-04T22:59:29.61Z" },
-]
-
[[package]]
name = "cuda-bindings"
version = "12.9.6"
@@ -1169,15 +1110,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
-[[package]]
-name = "httpx-sse"
-version = "0.4.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" },
-]
-
[[package]]
name = "hypothesis"
version = "6.152.9"
@@ -1633,31 +1565,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
-[[package]]
-name = "mcp"
-version = "1.27.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "anyio" },
- { name = "httpx" },
- { name = "httpx-sse" },
- { name = "jsonschema" },
- { name = "pydantic" },
- { name = "pydantic-settings" },
- { name = "pyjwt", extra = ["crypto"] },
- { name = "python-multipart" },
- { name = "pywin32", marker = "sys_platform == 'win32'" },
- { name = "sse-starlette" },
- { name = "starlette" },
- { name = "typing-extensions" },
- { name = "typing-inspection" },
- { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/38/83/d1efe7c2980d8a3afa476f4e3d42d53dd54c0ab94c27bee5d755b45c8b73/mcp-1.27.1.tar.gz", hash = "sha256:0f47e1820f8f8f941466b39749eb1d1839a04caddca2bc60e9d46e8a99914924", size = 608458, upload-time = "2026-05-08T16:50:12.601Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/fd/73/42d9596facebdb533b7f0b86c1b0364ef350d1f8ba78b1052e8a58b48b65/mcp-1.27.1-py3-none-any.whl", hash = "sha256:1af3c4203b329430fde7a87b4fcb6392a041f5cb851fd68fc674016ab4e7c06f", size = 216260, upload-time = "2026-05-08T16:50:10.547Z" },
-]
-
[[package]]
name = "mergedeep"
version = "1.3.4"
@@ -2886,20 +2793,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
]
-[[package]]
-name = "pydantic-settings"
-version = "2.14.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pydantic" },
- { name = "python-dotenv" },
- { name = "typing-inspection" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" },
-]
-
[[package]]
name = "pygments"
version = "2.20.0"
@@ -2909,20 +2802,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
-[[package]]
-name = "pyjwt"
-version = "2.13.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
-]
-
-[package.optional-dependencies]
-crypto = [
- { name = "cryptography" },
-]
-
[[package]]
name = "pymdown-extensions"
version = "10.21.3"
@@ -3064,25 +2943,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
]
-[[package]]
-name = "pywin32"
-version = "311"
-source = { registry = "https://pypi.org/simple" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" },
- { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" },
- { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
- { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
- { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
- { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
- { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
- { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
- { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
- { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
- { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
- { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
-]
-
[[package]]
name = "pyyaml"
version = "6.0.3"
@@ -3250,7 +3110,6 @@ docs = [
{ name = "griffe-pydantic" },
{ name = "griffe-typingdoc" },
{ name = "kaleido" },
- { name = "mcp" },
{ name = "mkdocs-macros-plugin" },
{ name = "mkdocs-material" },
{ name = "mkdocs-redirects" },
@@ -3280,7 +3139,6 @@ requires-dist = [
{ name = "isort", marker = "extra == 'dev'", specifier = ">=8.0.0" },
{ name = "kaleido", marker = "extra == 'docs'", specifier = ">=1.2.0" },
{ name = "marimo", marker = "extra == 'book'", specifier = ">=0.23.7" },
- { name = "mcp", marker = "extra == 'docs'", specifier = ">=1.26.0" },
{ name = "mkdocs-macros-plugin", marker = "extra == 'docs'", specifier = ">=1.3.7" },
{ name = "mkdocs-material", marker = "extra == 'docs'", specifier = ">=9.7.0" },
{ name = "mkdocs-redirects", marker = "extra == 'docs'", specifier = ">=1.2.1" },
@@ -3799,19 +3657,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" },
]
-[[package]]
-name = "sse-starlette"
-version = "3.4.4"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "anyio" },
- { name = "starlette" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/f7/2b/58abc2d1fd397e7dde08e947e05c884d8ef2f78d5e2588c17a12d42d6994/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0", size = 31819, upload-time = "2026-05-12T17:37:17.019Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/dc/67/805710444ea8cc75fbf70b920ed431a560c4bf9c57f7d5a3117213189399/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973", size = 16514, upload-time = "2026-05-12T17:37:15.601Z" },
-]
-
[[package]]
name = "starlette"
version = "1.1.0"