Quality Screener

io.github.quality-screener由 GitHub 帳號 quality-screener 發布v0.1.0更新於 Oct 24, 2026

Screen and score stocks with the Quality Screener engine: filters, custom scores, and history.

已驗證Streamable HTTP可網頁執行金融資料與分析

概覽

AI 產生的概覽

遠端 MCP 伺服器,讓助理以登入使用者身分使用 Quality Screener 引擎篩選、評分與比較股票。

可以做什麼
  • 篩選與排名股票:列出最高分或依條件過濾的已評分股票、查看單一股票的完整評分、取得指定股票清單的評分,並統計分數分布或彙總市值。
  • 依加權指標群組建立自訂品質評分,並指定評分所依據的同類股票範圍。
  • 查看單一、多檔或目前前 N 名股票在指定日期區間的評分歷史。
  • 管理已儲存的評分系統:列出、查看、建立、更新、刪除與套用。
  • 產生可分享的篩選連結,並查詢可用的篩選值與股票代號。
  • 檢查驗證狀態、帳戶資料與 API 健康狀態。
適用情境
適合讓助理執行股票品質篩選、依自訂加權指標比較公司,或追蹤評分隨時間的變化,所用資料與 Quality Screener 網頁儀表板一致。較適合投資研究與分析類對話,而非一般用途任務。
執行需求
遠端 streamable-HTTP 端點,不需本機執行環境或安裝套件。需要 Quality Screener 帳戶,用戶端首次連線時須完成 OAuth 2.0 瀏覽器登入,或在 X-Stobot-CLI-Token 標頭提供已簽發的權杖(也接受 Authorization: Bearer)。本機自架需 uv 或 Docker,並設定 QSCREENER_API_URL 等環境變數。
安裝前請注意
助理完全以你的帳戶權限運作,它能讀取或修改的都是你的資料。有寫入操作:可建立、更新與刪除已儲存的評分系統,screen_share 會發布公開、可直接複製的篩選設定連結。OAuth 權杖會在每次請求中轉送至 Quality Screener API;託管伺服器本身不保存憑證,但電子郵件地址用於識別帳戶。評分範圍過小時可能得出誤導性的分數。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Quality Screener,將其新增到工作區。
  2. 連接 所需的 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/io.github.quality-screener%2Fqscreener/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。 客戶端會在首次連接時完成 OAuth 登入。

{
  "mcpServers": {
    "quality-screener": {
      "type": "http",
      "url": "https://mcp.qualityscreener.io/mcp"
    }
  }
}

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

┌─────────────┐   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:

TransportUseAuthentication
stdio (default)A local agent (e.g. Claude Code) launches the server as a subprocessToken from $QSCREENER_TOKEN or ~/.config/qscreener/credentials.json
streamable-httpA 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:

json
{  "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.

bash
# Install dependenciesuv sync
# stdio — for a local agent that launches this as a subprocessuv run qscreener-mcp
# streamable-HTTP — mirrors the remote deploymentQSCREENER_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:

bash
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 varDefaultMeaning
QSCREENER_API_URLhttp://localhost:8001Base URL of the Quality Screener backend API the tools call
QSCREENER_MCP_TRANSPORTstdiostdio, streamable-http, or sse
QSCREENER_WEBSITE_URLhttp://localhost:3001Web-app base URL used to build the OAuth browser-login link and shareable screen URLs
QSCREENER_MCP_PUBLIC_URLhttp://localhost:{PORT|8080} (or https://$VERCEL_URL on Vercel)Publicly reachable base URL of this server; used as the OAuth issuer and callback URL
QSCREENER_OAUTH_SIGNING_KEY—HMAC key for signed OAuth state, authorization codes, and client registrations. Required on Vercel; optional on a single long-lived process
PORT—Bind port for HTTP transports (Railway sets this automatically)
QSCREENER_MCP_PORT8080Bind port fallback when PORT is unset
QSCREENER_MCP_HOST0.0.0.0Bind host for HTTP transports
QSCREENER_TOKEN—Bearer-token override for stdio mode (single user)
QSCREENER_CONFIG_DIR~/.config/qscreenerDirectory holding credentials.json for stdio mode

Authentication

The server resolves a bearer token for each call with the following precedence:

  1. HTTP request header — X-Stobot-CLI-Token, then Authorization: Bearer <token>.
  2. $QSCREENER_TOKEN environment variable.
  3. $QSCREENER_CONFIG_DIR/credentials.json — the token field.

Remote (OAuth 2.0)

For a streamable-http deployment, authentication is fully automated via the MCP OAuth flow:

  1. The MCP client discovers the authorization server and opens the user's browser.
  2. 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.
  3. 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. Access tokens are the CLI token, so they need no local store. Pending login state, authorization codes, and dynamically registered clients are HMAC-signed JWTs when QSCREENER_OAUTH_SIGNING_KEY is set, which is what a multi-instance host (Vercel) needs; a single process without the key keeps the original in-memory behaviour.

Local (stdio)

Mint a token through the browser login flow and store it locally, then run the server over stdio:

bash
qscreener auth login                          # opens the web app, stores a tokencat ~/.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

ToolSignatureDescription
auth_statusauth_status()Whether a token is present and which user it authenticates as.
account_profileaccount_profile()The signed-in user's profile (email, username, organization).
healthhealth()API and database health check.

Scores & screening

ToolSignatureDescription
scores_topscores_top(limit=20)Top tickers by quality score, as a {ticker: score} map.
scores_listscores_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_showscores_show(ticker)Full score row(s) for a single ticker.
scores_for_tickersscores_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_statisticsscores_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_capscores_market_cap(sectors=None, min_score=None)Aggregated total market cap (USD) for a filtered universe.
score_computescore_compute(config, scoring_universe=None, sectors=None, industries=None, regions=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. scoring_universe picks the peer group (changes the scores); the other filters select rows (do not).

Sharing

ToolSignatureDescription
screen_sharescreen_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

ToolSignatureDescription
filters_listfilters_list()Available filter values (sectors, industries, countries, currencies, exchanges).
tickers_listtickers_list(limit=None)Available tickers, optionally truncated to limit.
tickers_searchtickers_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.

ToolSignatureDescription
history_tickerhistory_ticker(ticker, start=None, end=None, scoring_system_id=None)Score history for a single ticker over a date range.
history_batchhistory_batch(tickers, start=None, end=None, scoring_system_id=None)Score history for several tickers at once.
history_tophistory_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.

ToolSignatureDescription
systems_listsystems_list()List your saved scoring systems.
systems_showsystems_show(system_id)Show a saved scoring system by ID.
systems_createsystems_create(name, config, description=None)Create a saved scoring system from a config object.
systems_updatesystems_update(system_id, name=None, config=None, description=None)Update a saved scoring system.
systems_deletesystems_delete(system_id)Delete a saved scoring system.
systems_applysystems_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:

json
{  "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 }      ]    }  ],  "scoringUniverseFilters": { "countries": ["Italy"], "min_market_cap": 1 },  "filters": { "min_score": 1.2 }}

Scoring parameters use camelCase: winsorizePercentile (1-10), missingDataPercentile (0.1-0.5), normalizeGroupZScores and includeDuplicatesInScoring (booleans). scoringUniverseFilters defines the peer group the scores are computed against; the nested filters block holds saved-screen state. Market caps are in billions USD inside both blocks (the tool arguments take 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.

Two stages: score against, then filter

Quality scores are relative — every company is winsorized and z-scored against a population — so who is in the peer group and which rows you look at are different questions, and score_compute takes them separately.

StageWhereEffect
1. Scoring universescoring_universe argument, or config.scoringUniverseFiltersapplied before winsorize/z-score — changes every score
2. Result filtersthe sectors / countries / … argumentsapplied after scoring — never changes a score

"Best European tech judged against European tech" and "best European tech judged against the world" are different lists, not the same list rescaled — narrowing the universe moves each metric's bounds, mean and σ by different amounts, so companies genuinely reorder:

jsonc
// judged against European tech — the peer group is European techscore_compute(config, scoring_universe={"sectors": ["Technology"], "regions": ["Europe"]})
// judged against the world — the peer group is everyone, then Europe is shownscore_compute(config, sectors=["Technology"], regions=["Europe"])

Stage 1 accepts sectors, industries, regions, countries, currencies, exchanges, min_market_cap_usd and max_market_cap_usd. It rejects min_score, max_score, ticker and tickers with an error rather than ignoring them: the first two filter on the very scores being computed, the rest select rows.

Every response carries a scoring_universe field naming the peer group and its size. Scores computed against different peer groups are not comparable — do not mix them in one table.

Two edges worth knowing:

  • min_market_cap_usd as a stage-2 argument also floors the scoring population. This is long-standing backend behaviour, kept for compatibility. Set min_market_cap_usd inside scoring_universe to control the peer group explicitly; it overrides the stage-2 floor. max_market_cap_usd filters rows only unless you set it in scoring_universe.
  • A very small universe still scores. There is no minimum-population guard yet, so winsorizing at the 5th/95th percentile of a dozen companies returns confident nonsense.

What the nested filters block does

It is saved-screen state. screen_share and systems_create/systems_update persist it so a shared screen or saved scoring system restores its filter selections when reopened in the dashboard.

It does not define the peer group — scoringUniverseFilters does. Passing a saved config to score_compute applies its filters block as stage-2 filters (an explicit argument wins), matching what the dashboard does, so re-scoring a saved system keeps its view.


Connecting an MCP client

Remote (recommended)

Any streamable-http MCP client works. No token needed — OAuth handles login:

json
{  "mcpServers": {    "qscreener": {      "type": "streamable-http",      "url": "https://mcp.qualityscreener.io/mcp"    }  }}

If your client cannot perform the OAuth flow, send a minted token directly:

json
{  "mcpServers": {    "qscreener": {      "url": "https://mcp.qualityscreener.io/mcp",      "headers": { "X-Stobot-CLI-Token": "<your token>" }    }  }}

Local (stdio)

json
{  "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:

  1. New service → Deploy from repo, pointing at this repository. The Dockerfile is self-contained, so the build context is the repo root.

  2. Set environment variables:

    • QSCREENER_MCP_TRANSPORT=streamable-http
    • QSCREENER_API_URL=https://<your-backend-domain>
    • QSCREENER_WEBSITE_URL=https://<your-frontend-domain>
    • QSCREENER_MCP_PUBLIC_URL=https://<generated-mcp-domain>

    Railway injects PORT automatically; the server binds to it.

  3. Networking → Generate Domain. The MCP endpoint is https://<generated-domain>/mcp.

    • Leave the HTTP healthcheck path unset (or use a TCP check): /mcp answers 406 Not Acceptable to a plain GET, so an HTTP healthcheck expecting 200 would mark the deploy unhealthy.
  4. Connect your MCP client — the OAuth flow triggers automatically on first connection.


Deploy on Vercel

The same ASGI app that Docker/Railway serve via mcp.run() is exported from app.py as a FastAPI app so Vercel’s Python runtime can host it as one Function. The FastMCP app already listens on /mcp, /.well-known/*, /authorize, /token, /register, and /oauth/callback; Vercel routes every request to that Function with the original path, so those URLs resolve without remapping. vercel.json only sets maxDuration (60s) on app.py.

Do not rewrite /mcp (or the OAuth paths) onto / — FastMCP matches the literal paths.

Create a Vercel project pointed at this repository (Framework Preset: FastAPI / Other, Root Directory: repository root). Set these environment variables — names only; never commit values:

Env varRequiredWhat it is for
QSCREENER_API_URLyesBase URL of the Quality Screener backend the tools call
QSCREENER_WEBSITE_URLyesWeb-app origin used for the OAuth browser login and shareable screen links
QSCREENER_MCP_PUBLIC_URLyes on a custom domainPublic origin of this deployment (OAuth issuer + /oauth/callback). Preview deploys can omit it and will use https://$VERCEL_URL
QSCREENER_OAUTH_SIGNING_KEYyesHMAC secret used to sign OAuth pending-state, authorization codes, and dynamic client registrations so they survive across Function invocations. Use a long random string (32+ bytes)

Vercel injects VERCEL_URL automatically (hostname, no scheme). It is only a fallback issuer when QSCREENER_MCP_PUBLIC_URL is unset. Do not use it as the production issuer if the MCP client will connect via a custom domain.

These are not required on Vercel (the app.py entrypoint is always HTTP, and the Function does not bind a port):

  • QSCREENER_MCP_TRANSPORT
  • QSCREENER_MCP_HOST
  • QSCREENER_MCP_PORT / PORT
  • QSCREENER_TOKEN
  • QSCREENER_CONFIG_DIR

After deploy the MCP endpoint is https://<vercel-domain>/mcp. Point an MCP client at that URL the same way as the Railway deployment. Raise functions["app.py"].maxDuration in vercel.json if custom scoring hits the 60s cap.


Development

bash
uv sync            # install dependencies (including dev)uv run pytest      # run the test suite

The codebase is small and self-contained:

PathPurpose
qscreener_mcp/server.pyFastMCP server, tool definitions, transport entry point
qscreener_mcp/client.pyMinimal httpx client that attaches the bearer token
qscreener_mcp/oauth.pyMCP OAuth 2.0 provider (token validation, browser flow)
app.pyVercel FastAPI entrypoint that mounts the streamable-HTTP ASGI app
tests/pytest suite (token resolution, filter forwarding, share-link building)

Privacy Policy

The full privacy policy is published at PRIVACY.md (https://github.com/quality-screener/quality-screener-mcp-server/blob/main/PRIVACY.md).

In short:

  • The only personal data retained is your email address, which identifies your account. Username and organization are optional profile fields.
  • The MCP server is a stateless proxy — it holds no database and writes no personal data to storage of its own. Each request is forwarded to the Quality Screener API using your access token, never a shared account.
  • Your data is not sold, not used for advertising, and not used to train machine-learning models.
  • Operational logs reference accounts by a pseudonymous user ID, not by email.
  • Account data is deleted when you delete your account.

See the policy for retention periods, third-party recipients, international transfers, and your GDPR rights.


Support

ChannelUse it for
[email protected]Support requests, security reports, privacy and data-subject requests
GitHub IssuesBug reports and feature requests

This README is the canonical documentation for the MCP server: https://github.com/quality-screener/quality-screener-mcp-server

Please report suspected security vulnerabilities privately by email rather than opening a public issue.


License

MIT © Quality Screener.

來源:README.md,提交 2515e5f

工具

0
該伺服器需要登入,因此 SourceWeft 無法在沒有帳號的情況下讀取其工具,也未收錄。已連接帳號所能看到的內容可能因帳號及其權限而異。

版本歷史

1
  1. v0.1.0最新Sep 16, 2026

評分

已滿足適用檢查項的 85%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分