Skip to main content
Glama
Finviz MCP

Finviz MCP

Finviz MCP — Market Data, Screeners & Fundamentals for AI Assistants

Finviz MCP server gives Claude, ChatGPT, Cursor, Copilot and any MCP client a single, structured way to read everything finviz.com publishes, without a browser, an API key, or a Finviz account.

Every tool is read-only, every failure is a typed error envelope, and every payload carries its own source URL so an answer can be traced back to the page it came from.

NOTE

Independent open-source project —not affiliated with, endorsed by, or associated with Finviz. "Finviz" is a trademark of its respective owner. This server reads publicly reachable, server-rendered pages and the site's own unauthenticated JSON routes; it does not log in, does not use a Finviz account, and does not bypass the Elite paywall (Elite-gated responses are reported as ELITE_ONLY, never faked).

IMPORTANT

Not financial advice. Nothing produced by this software is investment, financial, legal, tax, or accounting advice. Market data may be delayed, inaccurate, or incomplete and is provided without warranty. Do your own research.

License: MIT Python 3.10-3.13 MCP Ready Version


Why this server

Most "Finviz scrapers" stop at the quote header. This one covers the whole page — and, crucially, documents which upstream route each field comes from, because finviz has no official API:

Question

Tool

Upstream source

What is VCIG trading at, and what do its 84 fundamentals say?

finviz_quote

GET /stock?t=VCIG (HTML)

Just the fundamentals, small payload

finviz_snapshot

same page, table.js-snapshot-table

Who are its peers?

finviz_peers

same page, Peers row

Screen the market

finviz_screener

GET /screener/stocks (SSR island)

Which filters exist?

finviz_screener_filters

GET /api/screener-filters?type=stocks

What moved it today?

finviz_news, finviz_why_moving

/api/news/quote/{t}, /api/stocks-why-moving/{t}

What did it file?

finviz_filings

GET /api/quote/filings

Who bought or sold?

finviz_insider_trading, finviz_institutional_ownership

/stock?t={t} HTML + JSON islands

How crowded is the short?

finviz_short_interest

GET /stock?t={t}&ty=si

Earnings history and revisions

finviz_earnings

GET /stock?t={t}&ty=ea

Can it fund itself?

finviz_dilution

GET /stock?t={t}&ty=di

Options chain with greeks

finviz_options

GET /stock?t={t}&ty=oc

A chart image

finviz_chart

charts2-node.finviz.com/chart (PNG)

What is the market doing?

finviz_market_indices

GET /api/indices_data


Related MCP server: xoomar-mcp

Architecture

src/finviz_mcp/
├── server.py                  # MCP routing layer — validation, delegation, error envelope
└── core/
    ├── errors.py              # ErrorCode enum, make_error(), FinvizServiceError
    ├── utils/validators.py    # ticker / filter / order / view / period / scale allow-lists
    └── services/
        ├── finviz_client.py   # httpx transport, TTL cache + stale fallback, retry, CF/Elite detection
        ├── proxy_manager.py   # optional rotating residential proxy (env only)
        ├── html_utils.py      # every HTML → dict parser (pure, synchronous)
        ├── quote_service.py   # quote / snapshot / peers / short interest / earnings / dilution
        ├── screener_service.py# SSR island parsing + filter catalogue
        ├── news_service.py    # /api/news/quote rows + reactions
        ├── filings_service.py # /api/quote/filings paging + form filter
        ├── insider_service.py # insider table + institutional ownership islands
        ├── market_service.py  # indices, why-moving, options chain
        └── chart_service.py   # charts2-node URL builder + PNG download

No business logic lives in server.py. Everything is testable without an MCP client, and the parsers are tested against real captured markup, not hand-written HTML.


Quick start

git clone https://github.com/giraygokirmak/finviz-mcp.git
cd finviz-mcp
uv sync --dev
uv run finviz-mcp            # stdio (what MCP clients launch)

Install as a package

pip install finviz-mcp-server
finviz-mcp                   # stdio

Claude Desktop

claude_desktop_config.json:

{
  "mcpServers": {
    "finviz": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/finviz-mcp", "run", "finviz-mcp"]
    }
  }
}

Or, if you installed the package, just:

{
  "mcpServers": {
    "finviz": { "command": "finviz-mcp" }
  }
}

Docker / Docker Compose

docker compose up -d --build

The container listens on port 8000 internally, published as 6007 (change the left-hand side of the port mapping if that collides on your host):

ports:
  - "6007:8000"

Point any streamable-http MCP client at http://localhost:6007/mcp. A liveness probe is served at http://localhost:6007/health (used by the image's HEALTHCHECK).

Chart PNGs are written to FINVIZ_CHART_DIR (/home/mcpuser/.finviz-mcp/charts in the image) and persisted by the finviz-charts named volume, so finviz_chart keeps returning real file paths across restarts.

Run it directly without Compose:

docker build -t finviz-mcp:latest .
docker run --rm -p 6007:8000 -v finviz-charts:/home/mcpuser/.finviz-mcp/charts finviz-mcp:latest

Streamable HTTP from source

uv run finviz-mcp streamable-http --host 0.0.0.0 --port 6007
# MCP:    http://localhost:6007/mcp
# health: http://localhost:6007/health

The 17 tools

Quote & fundamentals

Tool

What it returns

finviz_quote(ticker)

Last close and after-hours price with change, sector/industry/country/cap class/exchange, the full 84-metric snapshot (snapshot, snapshot_numeric, snapshot_pairs, descriptions), company profile, peers

finviz_snapshot(ticker)

The 84 metrics only — raw strings + coerced numbers + finviz's own metric descriptions

finviz_peers(ticker, limit=12)

The page's real peer row: ticker, company, industry, country, market cap

Screening

Tool

What it returns

finviz_screener(tickers, filters, order, view, page, columns)

One page of results (20 rows upstream), total_count, total_pages, selected_columns, plus the applied filter echo

finviz_screener_filters()

Every filter key with its tab, UI type, title and option values (cached 1h)

News & events

Tool

What it returns

finviz_news(ticker, limit=30, include_reactions)

Up to 100 newest-first headlines with source, URL, day-of-headline move and next-session move

finviz_filings(ticker, page, page_size, forms, sort)

SEC filings with accession numbers, report/filing dates and direct SEC links

finviz_earnings(ticker)

Next earnings date, last 12 quarters (actual vs estimate), annual history, estimate revisions, price reactions, fundamental series

finviz_why_moving(ticker)

Finviz's own explainer: headline, markdown bullet summary, sentiment, catalyst flag

Ownership & risk

Tool

What it returns

finviz_insider_trading(ticker, limit=40)

Form-4 rows (insider, relationship, date, buy/sale, cost, shares, value, resulting holdings, SEC link) + 12-month aggregated buy/sell series

finviz_institutional_ownership(ticker, limit=20)

Managers and funds with percOwnership as both fraction and percent

finviz_short_interest(ticker, limit=60)

Bi-weekly FINRA settlement series + latest reading + computed short-float %

finviz_dilution(ticker)

Dilution model (share count, float, 60-day high, baby-shelf threshold, cash runway, months of cash) — with an income-statement fallback for names finviz does not flag

finviz_options(ticker, expiry)

Chain for one expiry: strike, bid/ask, IV, greeks, open interest, volume

Charts, market & ops

Tool

What it returns

finviz_chart(ticker, period, chart_type, scale, width, height, technicals)

Downloads a PNG to FINVIZ_CHART_DIR and returns its absolute path, size and provenance

finviz_market_indices()

DJI / COMP / SPX / IWM: last, change, OHLC and the intraday series

finviz_health()

Version, cache occupancy, upstream reachability

Try these first

Give me the full Finviz picture on VCIG
Screen technology stocks above $2B, cheapest P/E first
What are the last 10 SEC filings for AAPL, 8-Ks only?
Which insiders bought SNDL this year?
Chart NVDA weekly, 1200x600, with SMAs

Upstream data sources (what the server actually calls)

Finviz publishes no documented API. Everything below was reverse-engineered from the rendered pages and the site's own React islands, and is what this server consumes:

JSON routes (undocumented, unversioned, unauthenticated)

GET /api/quote/filings?t={t}&page={n}&pageSize={n}&o={sort}&f={forms}
GET /api/news/quote/{t}                    → {"html": "<news rows>", "timestamp": "..."}
GET /api/stocks-why-moving/{t}             → headline, summary, sentiment, catalyst
GET /api/indices_data                      → DJI, COMP, SPX, IWM intraday OHLCV
GET /api/screener-filters?type=stocks      → 83 filters with options

Document routes (server-rendered HTML; the interesting data lives in <script id="…-init-data"> islands)

GET /stock?t={t}&ta=1&p=d                  → header, 84-field snapshot, profile, peers,
                                             insider table, insider-init-data-0,
                                             institutional-ownership-init-data-0
GET /stock?t={t}&ty=si&ta=1&p=d            → route-init-data: short-interest series
GET /stock?t={t}&ty=ea&ta=1&p=d            → route-init-data: earnings + revisions
                                             fa-init-data-0: EPS/sales series
GET /stock?t={t}&ty=oc&ta=1&p=d[&e=YYYY-MM-DD] → route-init-data: options chain
GET /stock?t={t}&ty=lf&ta=1&p=d            → route-init-data: same shape as /api/quote/filings
GET /stock?t={t}&ty=di&ta=1&p=d            → route-init-data: dilution model (schema_version 1)
GET /screener/stocks?v={view}&f={filters}&o={order}&r={offset}&c={columns}
                                           → <script data-ssr-props="ssr-island-1"> JSON

Binary

GET https://charts2-node.finviz.com/chart?w=&h=&bw=2&bm=1&bb=1&t={t}&tf=d|w|m
    &s=linear|logarithmic&pm=0&am=0&ct=candle_stick|line_chart|heikin_ashi
    &o[0][ot]=sma&o[0][op]=20&o[0][oc]=DC32B363 …

Notes that shaped the implementation:

  • /api/quote does not work. Every parameter spelling returns HTTP 400 {"error":"invalid quote query"}, so the quote is necessarily HTML-scraped.

  • /api/related-tickers is a trap. It ignores its ticker argument and always returns the same mega-cap list, so peers come from the page's Peers row instead.

  • /api/suggestions ignores its query and returns a fixed list — not used.

  • The screener island is the whole result page: data.rows[].text|numbers, totalCount, firstRow, pageSize. Pagination is the 0-based r= offset; upstream caps at 20 rows per page regardless of pageSize.

  • c= is a count-like column selector: c=0,1,…,N progressively adds numeric fields up to 67 in finviz's fixed order.

  • Chart params: s=log and unknown ct= values return HTTP 400 Invalid request — validated client-side instead of forwarded blindly.

  • A real Chrome User-Agent is required; a bare python-httpx/x UA gets a Cloudflare interstitial. No browser is needed (verified with plain urllib).

The full field-level reference — page anatomy, the 84 snapshot labels in page order, every island's shape, the screener row schema, decoded chart parameters and the dead routes — is in docs/FINVIZ_API_MAP.md.

Elite-gated (reported as ELITE_ONLY, never faked): /api/compare-perf/*, /api/ticker-flags*, /api/watchlists*, /export/quote, and full 13F ownership history.


Error envelope

Every tool returns its normal payload on success and this shape on failure — never a raw exception string:

{
  "error": {
    "code": "NO_DATA",
    "message": "finviz.com lists no option contracts for VCIG",
    "ticker": "VCIG",
    "retryable": false
  }
}

Codes: SYMBOL_NOT_FOUND, INVALID_TICKER, INVALID_PARAMETER, UPSTREAM_RATE_LIMIT, UPSTREAM_TIMEOUT, UPSTREAM_BLOCKED, UPSTREAM_ERROR, ELITE_ONLY, NO_DATA, PARTIAL_DATA, PARSE_ERROR, DEPENDENCY_MISSING, INTERNAL_ERROR.

retryable tells the client whether repeating the identical call could plausibly work (rate limit, timeout, Cloudflare challenge) or not (bad ticker, unsupported parameter, no such tab). Branch on code; show message to the user.


Configuration

All settings are optional environment variables (see .env.example):

Variable

Default

Purpose

PORT / HOST

8000 / 127.0.0.1

Streamable-http bind (CLI flags win)

FINVIZ_TIMEOUT

25

Upstream request timeout, seconds

FINVIZ_CACHE_TTL

30

Page/JSON cache lifetime, seconds

FINVIZ_NEWS_TTL

60

Headline cache lifetime, seconds

FINVIZ_MAX_RETRIES

2

Retries for 429 / 5xx / timeouts (4xx never retried)

FINVIZ_CHART_DIR

~/.finviz-mcp/charts

Where finviz_chart writes PNGs

FINVIZ_USER_AGENT

bundled Chrome UA

Override only if finviz starts challenging it

PROXY_ENABLED + PROXY_*

off

Optional rotating residential proxy for rate-limited egress

The 30-second cache is a correctness feature, not just an optimisation: one finviz_quote fans out over a ~240 KB page, and quote + snapshot + peers would otherwise re-download it three times. Stale entries are served when upstream fails, so a rate-limited client still gets numbers.


Development

uv sync --dev
uv run pytest tests/unit -q      # 87 tests, hermetic (no network)

CI never depends on finviz.com being reachable. To exercise the real site end to end (every service, both a small cap and a large cap):

uv run python tools/smoke_live.py VCIG
uv run python tools/smoke_live.py AAPL

Fixtures under tests/fixtures/ are excerpts of real finviz markup captured during development, regenerated with tools/make_fixtures.py; the parsers are therefore exercised against actual tag soup rather than an idealised version of it.

CI: test.yml runs the hermetic suite on Python 3.10–3.13; publish-image.yml re-runs it and then publishes a multi-arch (amd64 + arm64) image to GHCR on main and on v* tags.


Limitations

  • Scraping is inherently fragile. A finviz redesign breaks the parsers in html_utils.py — one file, by design. PARSE_ERROR means "markup changed", not "retry".

  • Rate limiting is real. The cache and the retry/backoff layer are polite by default; do not point a loop at thousands of tickers without pacing.

  • Elite features are not available. Ownership history beyond the two islands, exports and watchlists need a Finviz subscription; this server says so instead of guessing.

  • Foreign private issuers (6-K filers) often have no quarterly earnings rows, no insider table and no short-interest series. That returns NO_DATA with an explanation rather than an empty success.

  • No candle-level JSON. Finviz renders charts as PNGs only, so finviz_chart returns an image path, not a series.


License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables AI assistants to access stock prices, financial statements, earnings call transcripts, and fundamental data for 60,000+ public companies via 25 read-only tools.
    25
    21 npm
    4
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables AI agents to query free public market data through 21 tools spanning 29 datasets sourced from SEC EDGAR/XBRL, FINRA, CFTC, the Federal Reserve, USAspending and exchange APIs. Covers short interest, insider and fund holdings, company financials, macro and policy rates, funding rates, crypto market metrics, IPOs and federal contracts, with no API key required to start.
    22
    104 npm
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to access 37 read-only market-research tools for querying quotes, news, SEC filings, financial statements, ownership and insider activity, earnings, corporate calendars, options, and crypto data.
    49
    22 PyPI
    20
    AGPL 3.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to access and analyze financial data, stock information, company fundamentals, and market insights through 250+ tools spanning stocks, ETFs, crypto, forex, commodities, economics, and more.
    332 npm
    Apache 2.0