stockdata-mcp
# FMP + Qualtrim MCP Server
[](https://m8ven.ai/mcp/glaciannex-stockdata-mcp-1kqwur)
An MCP server for stock research, backed by
**[Financial Modeling Prep](https://site.financialmodelingprep.com/)** for market
and fundamentals data, with **optional** enrichment from
**[Qualtrim](https://www.qualtrim.com/)**.
| Backend | Provides | Required? |
|---|---|---|
| **[Financial Modeling Prep](https://site.financialmodelingprep.com/developer/docs)** (`fmp_*`) | Statements, quotes, ratios, estimates, prices, dividends, screening | **Yes** — needs a free or paid API key |
| **[Qualtrim](https://www.qualtrim.com/)** (`qualtrim_*`) | Their own DCF, curated KPI charts, AI commentary, dip finder, and your portfolios and watchlists | **No** — entirely optional |
## Qualtrim is optional
**The server is fully functional with Financial Modeling Prep alone.** If you
set no Qualtrim credentials, the 22 `qualtrim_*` tools are simply not
registered and the 33 remaining tools work normally. Nothing degrades, and no
feature depends on Qualtrim being present.
Only enable it if you have a paid Qualtrim subscription **and** your own
written permission from Qualtrim to access it programmatically — see
[Qualtrim and their Terms of Service](#qualtrim-and-their-terms-of-service)
below. Without both, leave it off.
Note that Qualtrim is itself a Financial Modeling Prep customer: it licenses
the same underlying data and adds a layer on top. So every raw financial figure
is available from FMP directly, and this server deliberately routes raw data
through FMP even when Qualtrim is configured.
The split is deliberate. Qualtrim is an FMP customer: it buys the raw data and
adds a layer. So this server goes to FMP directly for anything raw, and spends
Qualtrim calls only on what is uniquely Qualtrim's.
**55 tools** — 29 Financial Modeling Prep, 22 Qualtrim, and 4 local (budget, cache and status). Without Qualtrim credentials you get 33.
> ### Before you enable the Qualtrim backend
>
> The FMP half of this server is ordinary: a documented public API, a key you
> sign up for, use it however you like.
>
> The Qualtrim half is not. Qualtrim publishes no API — those routes were read
> off their shipped JavaScript, and their Terms of Service prohibit both
> reverse engineering the platform and using automated means to retrieve data
> from it *without prior written consent*.
>
> That consent is not transferable and this repository does not grant it. If
> you want to run the Qualtrim backend, you need your own paid Qualtrim account
> **and your own written permission from Qualtrim**, obtained yourself. Ask them
> directly at support@qualtrim.com.
>
> Without that, run the server with `QUALTRIM_USERNAME`/`QUALTRIM_PASSWORD`
> unset. The FMP tools work fine on their own and nothing here depends on
> Qualtrim.
>
> The code is published as a reference for how such a client is structured, not
> as an invitation to point it at someone else's service. Qualtrim's stated
> remedy for violations is account termination, and their fees are
> non-refundable.
## Install
```bash
cd ~/Projects/stockdata-mcp
uv venv && uv pip install -e .
./scripts/set-credentials.sh # prompts; password input is hidden
```
`set-credentials.sh` writes `.env` with `0600` permissions. It is gitignored.
Get a free FMP key at
<https://site.financialmodelingprep.com/developer/docs>.
Register once, for every session on the machine:
```bash
claude mcp add stockdata --scope user -- ~/Projects/stockdata-mcp/.venv/bin/stockdata-mcp
```
No `-e` flags — the server reads `.env` itself, so credentials stay out of
`~/.claude.json`. Real environment variables still override the file, so an MCP
client can pass `-e` if you'd rather.
Check it from any session with `backend_status`.
### Guidance for the model
The server ships its own usage instructions over the MCP protocol — backend
choice, free-tier limits, Qualtrim's quirks, how to report figures. Every MCP
client receives these automatically, with no extra setup.
There is also a fuller skill at `.claude/skills/stock-research/` covering
research workflows in more depth. Claude Code reads it from
`~/.claude/skills/` (symlink it there). Claude Desktop manages skills in its own
location and will not pick it up from that directory — it relies on the server
instructions above, which carry the decision-critical parts.
## Caching and budget
Responses are cached in a SQLite store at `~/.cache/fmp-qualtrim-mcp/cache.db`.
The store is **global, not per client** — every server process for this user
opens the same file, so Claude Code and Claude Desktop share one cache and one
daily total. That is required for correctness, not just efficiency: the FMP
quota belongs to the API key, and both apps use the same key.
TTL is chosen per endpoint. Quotes last a minute; profiles a week; reference
data a month. **Historical bars whose date range ended before today never
expire**, because a close that already happened cannot change. A range running
up to today still has a moving edge and gets 15 minutes.
Three tools manage this:
| Tool | Purpose |
|---|---|
| `usage_status` | Requests sent today, remaining, reset time, cache stats |
| `estimate_cost` | What a set of planned calls will cost upstream, before running |
| `cache_clear` | Drop expired entries (safe) or a whole backend |
`estimate_cost` exists because the tool-to-request ratio is not 1:1 and is
invisible from the caller's side: `fmp_search` issues 2 requests,
`fmp_price_target` 3, and `fmp_quote` one per symbol on a free key. It reports
worst-case requests, how many legs are already cached, and the net against
today's remaining budget. Verified: its prediction matched actual spend exactly
on a mixed five-call plan, and returned 0 for the same plan re-run warm.
Measured across six independent processes requesting the same symbol
concurrently: **1 upstream request, 5 cache hits, no lock contention.**
## Configuration
| Variable | Default | Purpose |
|---|---|---|
| `FMP_API_KEY` | — | Required for the `fmp_*` tools |
| `QUALTRIM_USERNAME` / `QUALTRIM_PASSWORD` | — | Enables the `qualtrim_*` tools |
| `QUALTRIM_ENABLE_WRITES` | `false` | Allow tools that modify your portfolios/watchlists |
| `QUALTRIM_MIN_INTERVAL` | `1.0` | Minimum seconds between Qualtrim requests |
| `FMP_MIN_INTERVAL` | `0.1` | Minimum seconds between FMP requests |
| `MAX_RESPONSE_CHARS` | `50000` | Trim oversized tool payloads |
| `FMP_QUALTRIM_ENV_FILE` | `./.env` | Alternate path to the env file |
## FMP tools
Search and reference: `fmp_search`, `fmp_profile`, `fmp_quote`, `fmp_peers`,
`fmp_reference`.
Fundamentals: `fmp_income_statement`, `fmp_balance_sheet`, `fmp_cash_flow`,
`fmp_key_metrics`, `fmp_ratios`, `fmp_financial_growth`, `fmp_financial_scores`,
`fmp_revenue_segmentation`, `fmp_enterprise_values`.
Analysts and valuation: `fmp_analyst_estimates`, `fmp_price_target`,
`fmp_grades`, `fmp_dcf`.
Events and markets: `fmp_dividends`, `fmp_earnings`, `fmp_earnings_calendar`,
`fmp_historical_prices`, `fmp_technical_indicator`, `fmp_screener`,
`fmp_market_movers`, `fmp_sector_performance`, `fmp_insider_trading`, `fmp_news`.
`fmp_request(endpoint, params)` reaches any of FMP's ~250 stable endpoints —
ETF holdings, 13F, senate trades, economic indicators, SEC filings, transcripts —
without a dedicated wrapper.
### What the free tier actually covers
Verified against a live free key: **22 of 29 tools work.**
Paid plan required: `fmp_screener`, `fmp_reference`, `fmp_news`,
`fmp_insider_trading`, `fmp_technical_indicator`. Each returns a clear error
naming the plan as the cause.
Two notes:
- Multi-symbol `fmp_quote` falls back to per-symbol requests, because the batch
route is paid. Capped at 20 symbols to protect the daily quota.
- `fmp_technical_indicator` being paid is easy to route around —
`fmp_historical_prices` is free, so indicators can be computed from OHLCV
locally.
The free tier allows roughly 250 requests/day. Pass `limit` and
`from_date`/`to_date`; the default on price history is the entire series.
## Qualtrim tools
Per-symbol: `qualtrim_overview`, `qualtrim_profile`, `qualtrim_quote`,
`qualtrim_dcf`, `qualtrim_kpi_charts`, `qualtrim_chart`, `qualtrim_ai_analysis`,
`qualtrim_analyst_estimates`.
Comparison: `qualtrim_available_kpis`, `qualtrim_compare`.
Your account: `qualtrim_portfolios`, `qualtrim_portfolio`,
`qualtrim_portfolio_look_through_earnings`, `qualtrim_portfolio_ai_analysis`,
`qualtrim_portfolio_dividends`, `qualtrim_watchlists`, `qualtrim_watchlist`,
`qualtrim_dip_finder`, `qualtrim_watchlist_valuation_ranges`.
Escape hatch: `qualtrim_request(path)`, GET only.
Writes (`qualtrim_add_watchlist_stock`, `qualtrim_add_portfolio_holding`) refuse
to run unless `QUALTRIM_ENABLE_WRITES=true`.
### How the Qualtrim auth actually works
Their shipped bundle looks like bearer-token auth — `authTokens` in
localStorage, `Authorization: Bearer`. It isn't. Login returns the user object
and sets an **Express session cookie** (`connect.sid`); that cookie is what
authorises normal requests. The bundle's token path is exercised only by its
refresh call.
This client keeps a cookie jar, caches the session at
`~/.cache/fmp-qualtrim-mcp/qualtrim-session.json` (`0600`), and logs in again
automatically on a 401.
### Known drift
Their deployed backend has already diverged from the bundle the routes were read
from:
| Route in bundle | Reality |
|---|---|
| `/api/insights/{sym}/overview` | 404 — use `overview-flexible` |
| `/api/insights/{sym}/dcf-calculator` | Returns only a *saved* calculator; use `dcf-calculator-flexible` |
Both tools already use the working variants. Expect more of this over time — a
404 from a Qualtrim tool means the route moved, not that the ticker is wrong.
`qualtrim_dcf` returning empty is not a failure; it means no DCF is saved for
that symbol in your account.
## Qualtrim and their Terms of Service
Qualtrim publishes no API. These routes were read off their shipped JavaScript.
Two consequences:
**It can break without notice.** See "Known drift" above — it already has.
**It is restricted by their terms.** Their acceptable-use section prohibits
using automated means to systematically retrieve data from the site without
prior written consent, and separately prohibits reverse engineering the
platform. Their enforcement lever is account termination, and their fees are
non-refundable.
Consent is obtainable — the prohibition is conditional on it. Ask Qualtrim
directly at support@qualtrim.com, and keep their reply.
Defaults here are conservative: 1 request/sec, 2 concurrent max,
browser-matching user agent, GET by default, no bulk enumeration. Keep it to
interactive research volumes.
## Development
```bash
uv sync # includes the dev group
uv run pytest -q # 126 tests, no network, no API quota spent
```
Tests run against a mocked transport and a throwaway cache, so the suite
exercises every declared tool without spending an upstream request or touching
the Qualtrim account. Notable coverage:
- Every tool is invoked with realistic arguments; a tool added without a test
entry fails the suite.
- Every tool declares all four behaviour hints as booleans, and the values are
asserted against actual behaviour (writes are not marked read-only,
`cache_clear` is destructive but idempotent, local tools are not open-world).
- The write guard holds while `QUALTRIM_ENABLE_WRITES` is unset.
- Cache semantics: closed date ranges never expire, open-ended ones do, repeat
calls send nothing, and POSTs are never served from cache.
- **Registry drift.** `estimate_cost` reads a request plan that lives apart
from the handlers, so it can silently fall out of step. Each tool is run for
real, then priced — if the plan built a different endpoint or params, the
cache keys differ and the test fails. This caught two genuine gaps while
being written.
## Layout
```
src/stockdata_mcp/
server.py tool definitions for both backends
fmp.py FMP stable-API client
qualtrim.py Qualtrim cookie-session client
cache.py shared response cache and usage accounting
plans.py per-tool request plans, for cost preflight
util.py throttling, response trimming, .env loading
tests/ pytest suite, fully mocked
scripts/
set-credentials.sh
.claude/skills/stock-research/SKILL.md
LICENSE MIT
```
## Data sources
- **[Financial Modeling Prep](https://site.financialmodelingprep.com/)** — market
data, financial statements, estimates and screening.
[API docs](https://site.financialmodelingprep.com/developer/docs) ·
[Pricing](https://site.financialmodelingprep.com/pricing-plans)
- **[Qualtrim](https://www.qualtrim.com/)** — optional. Derived analysis and
personal portfolio tracking, built on FMP data.
Neither project is affiliated with this one.
## Not investment advice
Data for your own research. Neither source guarantees accuracy, and nothing here
is a recommendation to buy or sell.
TDQS
Scored across 33 tools
Most tools map cleanly to a distinct data category, and the fmp_ prefixes help separate market data, fundamentals, analyst data, and utilities. A few analyst-related tools and the key_metrics/ratios pair overlap enough that an agent must read the descriptions carefully to pick the right one.
All data tools follow a consistent fmp_<category> snake_case pattern, making the group recognizable and predictable. The operational tools like usage_status and cache_clear break the prefix convention, but they are still readable and clearly grouped, so the deviation is minor.
At 33 tools, the server exceeds the 25+ threshold and presents a heavy surface for an agent to navigate. Many fundamental and analyst tools could be consolidated, and fmp_request already provides a catch-all for less common FMP endpoints.
The set covers the core market-data workflow thoroughly: quotes, historical prices, fundamentals, financial statements, dividends, earnings, analyst data, screening, news, and insider trades. The fmp_request escape hatch also covers any remaining FMP endpoints, so there are no obvious dead ends.