quantra-mcp
# quantra-mcp
An [MCP](https://modelcontextprotocol.io) server that is a client of the
[Quantra](https://quantra.io) pricing engine's JSON API (QuantLib-based, open
source): it builds correct requests from trade terms and named market-convention
presets, forwards them, and returns the engine's numbers plus the exact request
that was sent. It computes nothing itself; every number comes from the engine.
```
agent ──(MCP: stdio / streamable HTTP)──▶ quantra-mcp ──(HTTP/JSON)──▶ Quantra engine
```
## Use the public server
Hosted instance, no account: `https://mcp.quantra.io/mcp` (prices on the same
engine as `api.quantra.io`; open, rate-limited per MCP session; session
scratch is per MCP session, in memory, bounded, never persisted).
claude.ai: Settings → Connectors → Add custom connector → URL
`https://mcp.quantra.io/mcp` → ask Claude to price a trade.
- Claude Code: `claude mcp add --transport http quantra https://mcp.quantra.io/mcp`
- Claude Desktop: Settings → Connectors → Add custom connector → URL `https://mcp.quantra.io/mcp` (the same custom connector as claude.ai; no config file)
- Cursor: add `{"mcpServers": {"quantra": {"url": "https://mcp.quantra.io/mcp"}}}` to `.cursor/mcp.json` or `~/.cursor/mcp.json`
Per-client setup, the stdio form for a local engine, and project instructions
for business users are in [docs/clients.md](docs/clients.md).
## Run it yourself
Run an engine (one line, no account):
```bash
docker run -d --name quantra-engine -p 8080:8080 ghcr.io/joseprupi/quantra-server:0.14.0
```
Run the server against it, from PyPI or from a clone:
```bash
QUANTRA_ENGINE_URL=http://localhost:8080 uvx quantra-mcp # stdio
git clone https://github.com/joseprupi/quantra-mcp && cd quantra-mcp && uv sync && uv run quantra-mcp --http --port 8765
```
Or run engine and server together with Docker Compose (only port 8765 is published):
```bash
curl -O https://raw.githubusercontent.com/joseprupi/quantra-mcp/main/docker-compose.example.yml
docker compose -f docker-compose.example.yml up -d
curl http://localhost:8765/readyz
claude mcp add --transport http quantra http://localhost:8765/mcp
```
| Env | Default | Meaning |
|---|---|---|
| `QUANTRA_ENGINE_URL` | `http://localhost:8080` | Engine JSON gateway (self-hosted or `https://api.quantra.io`). |
| `QUANTRA_TIMEOUT_S` | `60` | Per-request engine timeout in seconds. |
| `QUANTRA_MAX_CONCURRENCY` | `4` | Simultaneous engine calls (analytics fan-outs; `tools/call` in `--http` mode). |
| `QUANTRA_RATE_LIMIT_RPM` | `60` | `--http` only: per-client requests per minute (`0` disables). |
| `QUANTRA_ALLOWED_HOSTS` | *(empty)* | `--http` only: `Host` allow-list for `/mcp` behind a public hostname. |
| `QUANTRA_PUBLIC_URL` | *(empty)* | `--http` only: the externally reachable base URL advertised by `GET /`. |
The full table, hosted-mode hardening, security and limits:
[docs/configuration.md](docs/configuration.md).
## What it does
One tool per engine capability. Convenience tools turn trade terms and a
preset into a complete engine request (dates the server cannot compute are
resolved by the engine's calendar endpoints); raw passthrough takes any
request body, validated against the vendored OpenAPI spec first. 44 tools, each
with a title and MCP annotations (read-only / open-world hints):
- Discovery and calendars (6): `quantra_meta`, `engine_schema` (one endpoint, or all 29 without an argument), `list_enums`, `calendar_holidays`, `calendar_business_days`, `calendar_advance`
- Raw passthrough (1): `engine_request` for any of the 29 POST endpoints, including the six without a builder (swaption vol / model calibration, vol-surface sampling, inflation bootstrap, FX forward, cross-currency swap)
- Curves and presets (6): `preset` (one or all), `build_curve`, `build_value_curve`, `curve_from_pasted_table`, `build_query`, `bootstrap_curve`; builder results carry a content `digest`
- Pricing (15): `price_*` for vanilla and OIS swaps, fixed / floating / zero-coupon / callable bonds, FRA, cap/floor, swaption, CDS, equity option, ZC and YoY inflation swaps, YoY cap/floor and structured notes (Monte Carlo, engine >= 0.12.0)
- Option quotes (2): `calibrate_implied_forwards` (put-call-parity forwards, funding, dividend curve) and `calibrate_vol_surface` (Andreasen-Huge or SVI Black surface with arbitrage checks and a repricing report); their dividend curve and VolSurfaceSpec feed `price_structured_note` / `price_equity_option` (engine >= 0.14.0)
- Analytics by composition (4): `swap_dv01`, `key_rate_ladder`, `scenario`, `fair_rate`, all finite differences of engine NPVs (compact by default; `include_calls` for every replayable result)
- Reconciliation (3): `explain_method` (cited engine methodology), `compare_results`, `reprice_with`
- Session scratch (2): `session_put`, `session` (get / list / delete; private to the MCP session, in memory, bounded, never persisted)
- Examples (2): `list_examples`, `get_example` over the engine's 245 vendored example requests
- Resources (docs, schemas, enums, examples, presets, pin, methodology) and 6 prompts
Catalog with every argument: [docs/tools.md](docs/tools.md).
## Rules
- Market data comes from the user or from a cited source. The server has no
market-data source and no vendor data; licensed curves and vols are never
available server-side. Every tool that takes market numbers requires a
`market_data_source` declaration (`user_pasted`, `user_file`, `sourced`,
`engine_example`, `session`); there is no value for invented data. `sourced`
= obtained in the conversation from a trusted public publisher (central bank,
treasury, FRED, exchange, index administrator, regulator filing, fund sponsor;
any other site only after asking the user) or a data connector the user
connected, and requires `sources` (what, value, as_of, url or connector,
derived?), echoed in the result's `provenance`. The assistant will
never type, estimate, recall or invent market data for a trade: without
pasted or cited data, it says what is needed, in paste-able form, and stops.
- Every result echoes the exact `request` sent and the engine's `response`
verbatim, so any number can be replayed with `curl` against the same engine.
Engine errors pass through unchanged (`400` request wrong, `422` unpriceable);
every failed result (`ok: false`) is also an MCP tool error (`isError`).
- Every convention applied is listed in `notes` with its source (a preset
field, an engine fixture, or "market standard, not from an in-repo source").
- The shipped examples are request-shape references only; an example's market
data is never reused for a user's trade.
## Documentation
- [docs/clients.md](docs/clients.md): install and connect each client; project instructions for business users
- [docs/configuration.md](docs/configuration.md): environment variables, hosted-mode settings, security and limits
- [docs/tools.md](docs/tools.md): tools, result shape, analytics, reconciliation, resources, prompts, examples catalog
- [docs/presets.md](docs/presets.md): the market-convention presets and their provenance
- [docs/walkthroughs.md](docs/walkthroughs.md): price a swap in three calls; build a curve from a strip
- [docs/development.md](docs/development.md): gate, live checks, QuantLib parity, engine pin, layout
- [docs/RELEASING.md](docs/RELEASING.md): how a release is cut
- User guide on the site: https://quantra.io/docs/app/agents
## Development
```bash
uv run pytest && uv run ruff check . && uv run ruff format --check . && uv run mypy src # the gate
QUANTRA_ENGINE_URL=http://localhost:18087 uv run python scripts/live_check.py # every tool vs a real engine
```
The engine contract is pinned to the tag in `src/quantra_mcp/schema/PIN`
(v0.14.0); the QuantLib version that engine is built on is in `schema/QUANTLIB_PIN`
(1.41). `scripts/fetch_sources.py` fetches both source trees (text files only, verified
against `schema/SOURCES.lock`) into `vendor/sources/` for the `search_source` /
`read_source` / `list_source` tools; the container image ships them. Requests are unchanged since 0.7.0; responses follow the pinned 0.8.x contract (swaption greek units, theta, zero-valued fields present). Bump it with
`scripts/pin_engine.py`, never by hand ([docs/development.md](docs/development.md)).
## License
BSD-3-Clause. See `LICENSE`.
The server vendors, for the source-reading tools and the examples, two BSD-3-Clause
code bases: the Quantra pricing engine (https://github.com/joseprupi/quantraserver,
`LICENSE.txt`) and QuantLib (https://github.com/lballabio/QuantLib, `LICENSE.TXT`,
copyright the QuantLib Group and contributors). Their licence files are served verbatim
at the roots of the fetched trees (`read_source("engine", "LICENSE.txt")`,
`read_source("quantlib", "LICENSE.TXT")`).
TDQS
Scored across 47 tools
Each tool targets a distinct instrument, engine endpoint, or analytics function, and descriptions clearly distinguish overlapping tools (e.g., build_curve vs build_value_curve vs curve_from_pasted_table; swap_dv01 vs key_rate_ladder vs scenario). Minor ambiguity remains among reprice/analytics tools and curve-building helpers, but an agent can select correctly from descriptions.
All names use snake_case, but the pattern is mixed: many are verb_noun (price_vanilla_swap, build_curve, list_presets), while others are noun-only (fair_rate, scenario, swap_dv01) or have a domain prefix (calendar_advance, session_put, engine_request). The convention is still readable and mostly predictable by domain.
47 tools is well above the 3-15 sweet spot and exceeds the 25+ threshold that signals a heavy surface. While the domain is complex, the sheer number increases selection cost and makes it likely that some tools overlap in agent use; each may be individually justified but the set is too large for smooth MCP use.
The surface covers health/meta, endpoint discovery, calendars, curve construction, bootstrapping, vol calibration/sampling, pricing for all major asset classes, analytics (DV01, key-rate, scenario, fair rate, reprice, compare), session storage, examples, and methodology explanation. The engine_request escape hatch prevents dead ends for any unwrapped endpoint, so coverage is effectively complete.