finviz-screener-mcp
by gabriansa
README.md
# finviz-screener-mcp
FastMCP server that exposes **only** Finviz’s stock screener via [`finvizfinance`](https://pypi.org/project/finvizfinance/).
**US-only:** every `screen()` call hardcodes `Country=USA`. No news, maps, insider trading, or Elite API — just screener filters → structured JSON.
## Tools
| Tool | Purpose |
|------|---------|
| `list_filters` | Progressive discovery (index / one category / one section / search) — avoids dumping ~1694 values |
| `screen` | Compact screener results (default limit 25, max 100; offset capped at 400; null fields and the always-USA `Country` column stripped) |
Both tools are annotated read-only (`readOnlyHint`) so clients can skip confirmation prompts. Invalid input and upstream failures are raised as MCP tool errors (`isError: true`) with messages like `[invalid_filter] ...` or `[upstream] ...`, so agents can self-correct.
**Keep agent context small**
```text
list_filters() → category names + counts only
list_filters(category="Sector") → values for Sector only
list_filters(section="signals") → signals only
list_filters(query="rsi") → matching names
screen(..., limit=15, table="custom", columns=[...]) → only needed fields
```
Example `screen` call:
```json
{
"filters": {
"Sector": "Technology",
"Market Cap.": "Large ($10bln to $200bln)"
},
"table": "custom",
"order": "Market Cap.",
"ascend": false,
"columns": ["Ticker", "Market Cap.", "P/E", "RSI", "Price"],
"limit": 15
}
```
## Important caveats
- **Unofficial scrape.** `finvizfinance` parses the free Finviz HTML screener. There is no free official API; Elite’s CSV/API export is a separate paid surface and is **not** used here.
- Quotes on the free site are **delayed**.
- Scraping can break if Finviz changes its DOM, and may be against Finviz Terms of Service. Use responsibly.
## Proxy fallback (for hosted/cloud IPs)
Datacenter hosts (e.g. Horizon) are often IP-blocked by Finviz (`screen` → HTTP 403), even though the same code works fine from a home connection. `screen()` handles this automatically:
1. Reuse the proxy that served the previous request, if there is one — no probing needed.
2. Try the direct connection (the only path taken locally). Once it fails, it's skipped for `FINVIZ_DIRECT_COOLDOWN` seconds (default 600) so a blocked host stops paying the timeout on every call, then retried in case the block was temporary.
3. Rotate through free proxies from a handful of actively-maintained GitHub lists (`proxy_pool.py`), pulling fresh lists as older ones are used up, until a request lands or `FINVIZ_FETCH_BUDGET` seconds (default 45) elapse.
Most free proxies are dead, so trying them one at a time would spend a full connect timeout per corpse. Instead the pool probes candidates **concurrently against Finviz itself** (40 at a time) and only hands back ones that answered HTTP 200 — which also screens out proxies Finviz has already blocked. In practice a cold rotation finds a working route in ~15s and subsequent calls reuse it in ~5s.
No configuration needed. This is still best-effort — free proxies are inherently unreliable. For a guaranteed hosted fix, point `finvizfinance.util.set_proxy` at a paid residential proxy instead (datacenter proxies tend to get 403'd same as the host itself).
## Local setup
Requires Python 3.11+ (3.12 recommended; see `.python-version`).
```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
```
Run the server (stdio):
```bash
fastmcp run main.py:mcp
# or
python main.py
```
Inspect tools:
```bash
fastmcp inspect main.py:mcp
```
### Cursor / Claude Desktop (stdio)
```json
{
"mcpServers": {
"finviz-screener": {
"command": "uv",
"args": ["run", "--with-requirements", "requirements.txt", "fastmcp", "run", "main.py:mcp"],
"cwd": "/absolute/path/to/finviz-screener-mcp"
}
}
}
```
Or with an activated venv:
```json
{
"mcpServers": {
"finviz-screener": {
"command": "/absolute/path/to/finviz-screener-mcp/.venv/bin/fastmcp",
"args": ["run", "main.py:mcp"],
"cwd": "/absolute/path/to/finviz-screener-mcp"
}
}
}
```
No API key or `.env` is required.
## Deploy on Prefect Horizon (FastMCP Cloud)
Horizon is the managed host from the FastMCP team ([docs](https://gofastmcp.com/deployment/fastmcp-cloud); console: [horizon.prefect.io](https://horizon.prefect.io)). URLs look like `https://{name}.fastmcp.app/mcp`.
1. Push this repo to GitHub.
2. Sign in at [horizon.prefect.io](https://horizon.prefect.io) with GitHub and select the repo.
3. Configure:
- **Entrypoint:** `main.py:mcp`
- **Dependencies:** `requirements.txt` (auto-detected)
4. Deploy. Horizon ignores `if __name__ == "__main__"` and installs deps from `requirements.txt`.
5. Connect clients to `https://your-server-name.fastmcp.app/mcp`.
No environment variables or secrets are required for this server. If you add any later: **Settings → Environment Variables**, mark sensitive, then **rebuild and redeploy** (env changes do not hot-reload).
## Smoke check
```bash
python -c "
from main import list_filters, screen
print([c['name'] for c in list_filters()['categories'][:5]])
print(screen(filters={'Sector': 'Technology'}, limit=3)['count'])
"
```
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues