Quality Screener MCP Server
Enables AI agents to screen and filter stock universe, compute custom quality scores, inspect score history, manage scoring systems, and generate shareable screen links, acting as the signed-in user.
README
Quality Screener MCP server
A standalone Model Context Protocol (MCP) server that exposes the Quality Screener stock-screening engine as tools for AI agents (Claude, Cursor, and any other MCP client).
Once connected, an agent can screen and filter the scored universe, compute custom quality scores, inspect score history, manage saved scoring systems, and generate shareable screen links — acting as the signed-in user, against the same data they see in the web dashboard.
- No SDK dependency. The server is a thin HTTP layer over the public Quality Screener API. It has no dependency on the backend Python package — every tool just calls a REST endpoint and returns the JSON payload.
- Multi-tenant & credential-free. When deployed over HTTP the server stores no credentials of its own. Each request carries the caller's own access token, which the server forwards to the API, so a single public deployment can serve many users without ever acting on a shared account.
Table of contents
- How it works
- Quick start (remote)
- Running locally
- Configuration
- Authentication
- Tools
- Working with
CustomScoreConfig - Connecting an MCP client
- Deployment
- Development
- License
How it works
┌─────────────┐ MCP (stdio | streamable-HTTP) ┌──────────────────┐ HTTPS ┌──────────────────────┐
│ AI agent │ ────────────────────────────────► │ qscreener-mcp │ ────────► │ Quality Screener API │
│ (MCP client)│ ◄──────────────────────────────── │ (this server) │ ◄──────── │ (FastAPI backend) │
└─────────────┘ tool calls / JSON └──────────────────┘ REST └──────────────────────┘
Each MCP tool maps to one Quality Screener REST endpoint. The server attaches
the caller's bearer token to every outbound request (header
X-Stobot-CLI-Token, Authorization: Bearer … also accepted) and returns the
decoded JSON. There is no business logic in the server itself — it is a typed,
authenticated façade over the API.
It runs in two transport modes:
| Transport | Use | Authentication |
|---|---|---|
stdio (default) |
A local agent (e.g. Claude Code) launches the server as a subprocess | Token from $QSCREENER_TOKEN or ~/.config/qscreener/credentials.json |
streamable-http |
A remote, externally reachable deployment (e.g. Railway) | End-to-end MCP OAuth 2.0 — the client opens the browser once, then sends the token automatically; or a per-request X-Stobot-CLI-Token header |
Over HTTP the MCP endpoint is served at /mcp.
Quick start (remote)
The easiest way to use the server is to point your MCP client at the hosted deployment. No token to copy — the client triggers a browser sign-in on first connect:
{
"mcpServers": {
"qscreener": {
"type": "streamable-http",
"url": "https://mcp.qualityscreener.io/mcp"
}
}
}
On first use your browser opens the Quality Screener sign-in page. Approve once, and the agent stays connected. You need a Quality Screener account; the agent inherits exactly your access.
Running locally
Requires uv.
# Install dependencies
uv sync
# stdio — for a local agent that launches this as a subprocess
uv run qscreener-mcp
# streamable-HTTP — mirrors the remote deployment
QSCREENER_MCP_TRANSPORT=streamable-http QSCREENER_MCP_PORT=8080 \
QSCREENER_API_URL=http://localhost:8001 \
uv run qscreener-mcp
# -> MCP endpoint at http://localhost:8080/mcp
With Docker:
docker build -t qscreener-mcp .
docker run --rm -p 8080:8080 \
-e QSCREENER_API_URL=https://your-backend.example.com \
-e QSCREENER_MCP_PUBLIC_URL=http://localhost:8080 \
qscreener-mcp
# -> MCP endpoint at http://localhost:8080/mcp
By default the container runs the streamable-http transport on port 8080.
Configuration
All configuration is via environment variables, resolved at startup.
| Env var | Default | Meaning |
|---|---|---|
QSCREENER_API_URL |
http://localhost:8001 |
Base URL of the Quality Screener backend API the tools call |
QSCREENER_MCP_TRANSPORT |
stdio |
stdio, streamable-http, or sse |
QSCREENER_WEBSITE_URL |
http://localhost:3001 |
Web-app base URL used to build the OAuth browser-login link and shareable screen URLs |
QSCREENER_MCP_PUBLIC_URL |
http://localhost:{PORT|8080} |
Publicly reachable base URL of this server; used to build the OAuth callback URL |
PORT |
— | Bind port for HTTP transports (Railway sets this automatically) |
QSCREENER_MCP_PORT |
8080 |
Bind port fallback when PORT is unset |
QSCREENER_MCP_HOST |
0.0.0.0 |
Bind host for HTTP transports |
QSCREENER_TOKEN |
— | Bearer-token override for stdio mode (single user) |
QSCREENER_CONFIG_DIR |
~/.config/qscreener |
Directory holding credentials.json for stdio mode |
Authentication
The server resolves a bearer token for each call with the following precedence:
- HTTP request header —
X-Stobot-CLI-Token, thenAuthorization: Bearer <token>. $QSCREENER_TOKENenvironment variable.$QSCREENER_CONFIG_DIR/credentials.json— thetokenfield.
Remote (OAuth 2.0)
For a streamable-http deployment, authentication is fully automated via the
MCP OAuth flow:
- The MCP client discovers the authorization server and opens the user's browser.
- The browser lands on the Quality Screener web app, which exchanges the user's
web session for a short-lived CLI token and redirects back to this server's
/oauth/callback. - The server hands the token to the MCP client, which sends it as a bearer token on every subsequent request.
The token is validated on each request by calling the backend's
/v1/cli/auth/whoami endpoint, so a revoked or expired token is rejected
immediately. The server never persists user tokens.
Local (stdio)
Mint a token through the browser login flow and store it locally, then run the server over stdio:
qscreener auth login # opens the web app, stores a token
cat ~/.config/qscreener/credentials.json # the "token" field is your bearer token
Or set QSCREENER_TOKEN directly for CI / scripted use.
Tools
All tools require authentication. Filters use OR logic within a filter and AND logic across filters. Market caps are always in USD.
Account & status
| Tool | Signature | Description |
|---|---|---|
auth_status |
auth_status() |
Whether a token is present and which user it authenticates as. |
account_profile |
account_profile() |
The signed-in user's profile (email, username, organization). |
health |
health() |
API and database health check. |
Scores & screening
| Tool | Signature | Description |
|---|---|---|
scores_top |
scores_top(limit=20) |
Top tickers by quality score, as a {ticker: score} map. |
scores_list |
scores_list(ticker=None, sectors=None, industries=None, countries=None, currencies=None, exchanges=None, min_score=None, max_score=None, min_market_cap_usd=None, max_market_cap_usd=None, sort_by="quality_score", sort_order="desc", offset=0, limit=50, include_duplicates=False) |
List scored tickers with optional filters. |
scores_show |
scores_show(ticker) |
Full score row(s) for a single ticker. |
scores_for_tickers |
scores_for_tickers(tickers, scoring_system_id=None) |
Current scores for a specific list of tickers, under default scoring or a saved scoring system. Unknown tickers are omitted. |
scores_statistics |
scores_statistics(sectors=None, min_score=None, max_score=None, min_market_cap_usd=None, max_market_cap_usd=None) |
Min / max / average score statistics for a filtered universe. |
scores_market_cap |
scores_market_cap(sectors=None, min_score=None) |
Aggregated total market cap (USD) for a filtered universe. |
score_compute |
score_compute(config, sectors=None, industries=None, countries=None, currencies=None, exchanges=None, min_market_cap_usd=None, max_market_cap_usd=None, sort_by="quality_score", sort_order="desc", offset=0, limit=50, include_duplicates=False) |
Compute custom scores from a CustomScoreConfig, restricted to a filtered universe. |
Sharing
| Tool | Signature | Description |
|---|---|---|
screen_share |
screen_share(config) |
Persist a CustomScoreConfig and return a public, copy-pasteable share link (url, slug, created, view_count). Content-addressed: an identical config returns the same link. |
Filters & tickers
| Tool | Signature | Description |
|---|---|---|
filters_list |
filters_list() |
Available filter values (sectors, industries, countries, currencies, exchanges). |
tickers_list |
tickers_list(limit=None) |
Available tickers, optionally truncated to limit. |
tickers_search |
tickers_search(query) |
Search available tickers by case-insensitive substring. |
Score history
Dates are YYYY-MM-DD. Pass scoring_system_id to compute history against a
saved scoring system instead of the default quality score.
| Tool | Signature | Description |
|---|---|---|
history_ticker |
history_ticker(ticker, start=None, end=None, scoring_system_id=None) |
Score history for a single ticker over a date range. |
history_batch |
history_batch(tickers, start=None, end=None, scoring_system_id=None) |
Score history for several tickers at once. |
history_top |
history_top(top=10, scoring_system_id=None) |
Fetch the current top-N tickers and return their score history. |
Saved scoring systems
A scoring system is a named, reusable CustomScoreConfig stored against your
account.
| Tool | Signature | Description |
|---|---|---|
systems_list |
systems_list() |
List your saved scoring systems. |
systems_show |
systems_show(system_id) |
Show a saved scoring system by ID. |
systems_create |
systems_create(name, config, description=None) |
Create a saved scoring system from a config object. |
systems_update |
systems_update(system_id, name=None, config=None, description=None) |
Update a saved scoring system. |
systems_delete |
systems_delete(system_id) |
Delete a saved scoring system. |
systems_apply |
systems_apply(system_id) |
Apply a saved scoring system (increments its usage count). |
Working with CustomScoreConfig
score_compute, screen_share, and the systems_* tools accept a
CustomScoreConfig object describing how to weight financial metrics. Its shape
mirrors the score builder in the web dashboard: weighted metric groups, each
containing weighted metrics, plus scoring parameters and an optional nested
filters block. A minimal example:
{
"name": "My quality screen",
"winsorizePercentile": 5,
"missingDataPercentile": 0.25,
"normalizeGroupZScores": false,
"includeDuplicatesInScoring": false,
"groups": [
{
"id": "returns",
"name": "Returns",
"weight": 0.5,
"metrics": [
{ "id": "roe", "name": "ROE", "weight": 0.5 },
{ "id": "roic", "name": "ROIC", "weight": 0.5 }
]
},
{
"id": "profitability",
"name": "Profitability",
"weight": 0.5,
"metrics": [
{ "id": "profit_margin", "name": "Profit Margin", "weight": 1.0 }
]
}
],
"filters": { "countries": ["Italy"], "min_market_cap": 1 }
}
Scoring parameters use camelCase: winsorizePercentile (1-10), missingDataPercentile
(0.1-0.5), normalizeGroupZScores and includeDuplicatesInScoring (booleans). Universe
filters go inside the nested filters block (market caps in billions USD). Loose
inputs — snake_case keys, the legacy winsorize/zScore flags, or filter keys placed at
the top level — are normalized to this shape automatically, but emitting it directly is
preferred. Use filters_list to discover valid filter values, and build a config
interactively in the dashboard if you want a starting point to copy.
Connecting an MCP client
Remote (recommended)
Any streamable-http MCP client works. No token needed — OAuth handles login:
{
"mcpServers": {
"qscreener": {
"type": "streamable-http",
"url": "https://mcp.qualityscreener.io/mcp"
}
}
}
If your client cannot perform the OAuth flow, send a minted token directly:
{
"mcpServers": {
"qscreener": {
"url": "https://mcp.qualityscreener.io/mcp",
"headers": { "X-Stobot-CLI-Token": "<your token>" }
}
}
}
Local (stdio)
{
"mcpServers": {
"qscreener": {
"command": "uv",
"args": ["run", "--directory", "/path/to/quality-screener-mcp-server", "qscreener-mcp"],
"env": { "QSCREENER_API_URL": "https://your-backend.example.com" }
}
}
}
Deployment
The server deploys as a single container. On Railway:
-
New service → Deploy from repo, pointing at this repository. The Dockerfile is self-contained, so the build context is the repo root.
-
Set environment variables:
QSCREENER_MCP_TRANSPORT=streamable-httpQSCREENER_API_URL=https://<your-backend-domain>QSCREENER_WEBSITE_URL=https://<your-frontend-domain>QSCREENER_MCP_PUBLIC_URL=https://<generated-mcp-domain>
Railway injects
PORTautomatically; the server binds to it. -
Networking → Generate Domain. The MCP endpoint is
https://<generated-domain>/mcp.- Leave the HTTP healthcheck path unset (or use a TCP check):
/mcpanswers406 Not Acceptableto a plainGET, so an HTTP healthcheck expecting200would mark the deploy unhealthy.
- Leave the HTTP healthcheck path unset (or use a TCP check):
-
Connect your MCP client — the OAuth flow triggers automatically on first connection.
Development
uv sync # install dependencies (including dev)
uv run pytest # run the test suite
The codebase is small and self-contained:
| Path | Purpose |
|---|---|
qscreener_mcp/server.py |
FastMCP server, tool definitions, transport entry point |
qscreener_mcp/client.py |
Minimal httpx client that attaches the bearer token |
qscreener_mcp/oauth.py |
MCP OAuth 2.0 provider (token validation, browser flow) |
tests/ |
pytest suite (token resolution, filter forwarding, share-link building) |
License
MIT © Quality Screener.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。