Skip to main content
Glama
hulan-bebe

taiwan-stock-mcp

by hulan-bebe
README.md
# taiwan-stock-mcp

[![CI](https://github.com/hulan-bebe/taiwan-stock-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/hulan-bebe/taiwan-stock-mcp/actions/workflows/ci.yml)

An English-language MCP server for Taiwan listed (TWSE) and OTC (TPEx) stocks,
using official public endpoints without an API key. Four tools provide quotes,
market rankings, valuation screens, and monthly revenue. Every response includes
a concise text summary and structured content with `source`, `as_of`, and
`data_type`. Python 3.10+; MCP SDK 2.x; local stdio transport.

## Example conversation

Live example re-run against official feeds: **latest trading day: 2026-10-22**;
monthly revenue reporting month: **2026-09**. These are the data dates, not
the capture date.

> **You:** Get the latest quote for TSMC (2330), and show the data timestamp.
>
> **Assistant:** TSMC (2330, TWSE): TWD 2,385.00, change -35.00 (-1.45%);
> volume 22651 lots. volume = regular-session lots from TWSE MIS (excludes odd-lot).
> Market closed; as of 2026-10-22T13:30:00+08:00.
>
> **You:** What was its latest reported monthly revenue and growth?
>
> **Assistant:** TSMC (2330): TWD 511.86 billion (511,857,399 thousand),
> MoM -0.57%, YoY +54.65%, cumulative YoY +41.11% for 2026-09.

The quote is a last-session snapshot; the revenue date is the reporting month.
Both responses retain the dates returned by the sources.

## Tools

| Tool | Parameters | Result |
| --- | --- | --- |
| `get_quote` | `code: str` | Auto-detected market, English name, TWD price/change/change %, open/high/low, volume in lots, five bid/ask levels, timestamp and market status. |
| `scan_market` | `sort_by="gainers"` (`gainers`, `losers`, `turnover`), `top_n=20`, `min_price=10.0`, `market="all"` (`all`, `twse`, `tpex`) | Common stocks ranked by percentage change or TWD turnover; `top_n` capped at 100. Daily volume is shares. |
| `screen_valuation` | `pe_max=None`, `pb_max=None`, `yield_min=None`, `industry=None`, `top_n=30` | Inclusive PE/PB/yield filters, ordered by dividend yield descending. Missing PE is excluded when `pe_max` is set. Yield is percent, not a fraction. |
| `get_monthly_revenue` | `code=None`, `industry=None`, `yoy_min=None`, `top_n=30` | Latest month per company, ordered by YoY descending; revenue in **TWD thousands**, MoM/YoY/cumulative YoY in percent. |

Industry accepts English or Chinese categories case-insensitively, including
unique substrings and singular/plural variants such as `Semiconductor` or
`semiconductor industry`. Unknown or ambiguous filters return an error listing
valid English categories. Company
names use English when available, otherwise the official Chinese name. Scan's
common-stock proxy is **exactly four ASCII digits not starting with `00`**; it
excludes ETFs and warrants; **TDRs with `91xx` codes are included**.
`get_quote` also accepts ETFs such as `0050` and bond ETFs, with `.TW`/`.TWO`
suffixes accepted case-insensitively. Ties use code ascending; missing yields/growth sort
last. `top_n` must be positive; only scan has the specified 100-result cap.
Revenue filters apply together, including when `code` is supplied.

Structured quote fields live at the top level; the other tools return `stocks`
and `count`. Empty matches are successful results with feed metadata. Unknown
codes return an error with up to three close code/name suggestions. Source
failures name the upstream feed and return no fabricated or partial numbers.
Error results have `as_of: null` because no usable data date was obtained.

## Install and run

Install from source now:

```sh
python -m pip install "git+https://github.com/hulan-bebe/taiwan-stock-mcp.git"
taiwan-stock-mcp
```

Or install from a clone:

```sh
git clone https://github.com/hulan-bebe/taiwan-stock-mcp.git && cd taiwan-stock-mcp && python -m pip install .
taiwan-stock-mcp
```

After publication to PyPI:

```sh
uvx taiwan-stock-mcp
```

The process waits for MCP messages on stdin; stdout is reserved for the protocol.
No credentials, database, or HTTP hosting are needed.

### Claude Desktop / Claude Code

Claude Desktop configuration file locations:

- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`

With `uvx` installed, add this to Claude Desktop's `claude_desktop_config.json`,
or save it as your Claude Code project's `.mcp.json`:

```json
{
  "mcpServers": {
    "taiwan-stock-mcp": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/hulan-bebe/taiwan-stock-mcp.git",
        "taiwan-stock-mcp"
      ]
    }
  }
}
```

GUI apps may not see `uvx` on PATH even when it works in a terminal. Use its
absolute installed path. For example, on Windows (JSON requires escaped backslashes):

```json
{
  "mcpServers": {
    "taiwan-stock-mcp": {
      "command": "C:\\Users\\YourName\\.local\\bin\\uvx.exe",
      "args": [
        "--from",
        "git+https://github.com/hulan-bebe/taiwan-stock-mcp.git",
        "taiwan-stock-mcp"
      ]
    }
  }
}
```

Use the path where `uvx.exe` is installed on your machine.
For a locally installed package, use the absolute path to `taiwan-stock-mcp`
(`taiwan-stock-mcp.exe` on Windows) as `command` and set `args` to `[]`.
Restart/reconnect the client and check that exactly four tools are available.

## Sources and freshness

| Data | TWSE endpoint | TPEx endpoint | In-memory TTL |
| --- | --- | --- | --- |
| Daily quotes | `exchangeReport/STOCK_DAY_ALL` | `tpex_mainboard_daily_close_quotes` | 1 hour |
| PE/PB/dividend yield | `exchangeReport/BWIBBU_ALL` | `tpex_mainboard_peratio_analysis` | 1 hour |
| Monthly revenue | `opendata/t187ap05_L` | `mopsfin_t187ap05_O` | 6 hours |
| Company reference | `opendata/t187ap03_L` | `mopsfin_t187ap03_O` | 24 hours |
| Quote and book | `mis.twse.com.tw/stock/api/getStockInfo.jsp` (both markets) | Same MIS service | Never cached |

See the [TWSE OpenAPI catalog](https://openapi.twse.com.tw/) and
[TPEx OpenAPI catalog](https://www.tpex.org.tw/openapi/). Base URLs are
`https://openapi.twse.com.tw/v1/` and `https://www.tpex.org.tw/openapi/v1/`.
MIS requests are paced to at most one per second across codes. Requests use a
20-second timeout (40 seconds for the large TPEx daily-quote feed); transport
errors, including timeouts, and server errors retry once after about
one second. Schema errors are explicit.

Cache age and data age differ: prior-session data is never relabelled as today.
For daily/valuation lists, top-level `as_of` is the oldest input data date.
For revenue, it is the oldest month among returned rows, falling back to the
input feeds when no rows match;
`as_of_by_source` and each row's `as_of` retain individual dates/months.
Monthly reporting months may differ between companies.

Market status uses Taipei weekday session hours (09:00–13:30) and the MIS feed
date; it is not an exchange holiday-calendar service. Outside that session,
quotes retain the last trade and report `closed`. MIS uses `z` first, then `pz`
(last trade) with `price_basis: "pz_last_trade"` and `price_is_fallback: true`.
If both MIS trade fields are missing,
the tool uses the daily close with `price_basis: "daily_close"`, the daily
trade's actual `as_of`, and `price_is_fallback: true`. Book, OHLC and volume
remain the MIS snapshot; `snapshot_as_of` labels their timestamp. Unavailable
numeric values remain null. Volume and book sizes are lots (1,000 shares per
standard stock lot). MIS volume is regular-session lots and excludes odd-lot
trades; daily scan volume is shares. Empty MIS books return empty lists.

Data is for informational purposes, not investment advice. The MIT license
covers this software; data remains subject to provider terms. Review the
[TWSE terms and open-data exception](https://www.twse.com.tw/en/terms/use.html),
[TPEx terms and open-data exception](https://www.tpex.org.tw/en-us/gtsm_disclaimer.html),
and [Taiwan Open Government Data License](https://data.gov.tw/license).
MIS is a separate public quote service; availability without an API key does
not grant blanket redistribution rights.

## Data quality

Outliers are flagged, never silently dropped or corrected. Valuation rows flag
`unusual_yield` above 15% and additionally `likely_one_off_dividend` above 50%;
the text summary calls out the reported yields and their exchange. Revenue rows
flag `low_base_effect` when YoY exceeds 300% and include prior-year revenue for
context. These flags describe unusual reported values, not a verified cause.
Missing or corporate-action price changes remain null. Summary tables show at
most ten rows, with remaining results available in structured content.

## Development

```sh
python -m pip install -e ".[dev]"
python -m ruff check .
python -m pytest -q
python -m pytest -q -m live
```

Offline tests use recorded official responses in `tests/fixtures/`; they cover
parsing, units, filters, ordering, cache expiry, source errors, and a real stdio
round trip through the MCP 2.x client. Live tests are skipped by default and
not run in CI. CI runs ruff and offline pytest on Python 3.10 and 3.13.
To print actual live stdio summaries:

```sh
python -m pytest -q -s -m live tests/live/test_live_tools.py
```

`sources/` owns HTTP, parsing and feed caching; `reference.py` owns company and
industry mapping; `tools/` holds MCP-independent results and pure record
transformations; `server.py` only registers and presents the four tools.
The stdio tests use anonymous pipes on Windows to work in restricted
environments; other platforms use the SDK's stdio transport directly.

See [the design spec](docs/SPEC.md) and [two-minute demo script](docs/DEMO_SCRIPT.md).

## Roadmap and license

v0.1: Taiwan stocks. v0.2: US stocks. v0.3: cross-market queries.

[MIT](LICENSE).