feat: add MCP server, machine-readable output and English UI - #9
Open
Ramos-dev wants to merge 2 commits into
Open
feat: add MCP server, machine-readable output and English UI#9Ramos-dev wants to merge 2 commits into
Ramos-dev wants to merge 2 commits into
Conversation
added 2 commits
August 11, 2026 11:40
The Xueqiu endpoints this tool relies on have been discontinued. Both
`xueqiu.com/stock/search.json` and `xueqiu.com/v4/stock/quote.json` now
return `301 Moved Permanently` and require authentication, so the
response body is HTML rather than JSON.
As a result every command fails:
$ wstock query 茅台
TypeError: Cannot read properties of undefined (reading 'length')
at index.js:118:21
`JSON.parse` receives the redirect page, leaving `data.stocks` undefined.
Data sources
------------
* Quotes now use Tencent `qt.gtimg.cn`
* Search now uses Sina `suggest3.sinajs.cn`
Both are public and need no authentication. Covers A-shares, Hong Kong
and US markets.
Changes
-------
* Add `lib/fetch.js` — a small client over Node's built-in `http`/`https`
handling GBK decoding, gzip/deflate, redirects and a 10s timeout.
* Add `lib/parser.js` — quote/search parsing plus market-prefix
inference, so bare codes such as `600519` or `700` now work.
* Drop the deprecated `request` dependency (49 packages down to 2); the
runtime now has no third-party network dependency.
* Decode GBK responses, which previously rendered Chinese names as
mojibake.
* Align columns by display width, treating full-width characters as two
cells so CJK names line up.
* Stop re-appending `%` to `percentage` on every poll, which accumulated
into `-1.5%%%` while watching.
* Handle rejections in the polling loop; a transient network error
previously stopped updates silently.
* Refresh in place instead of emitting `ESC[2J`, which pushed each frame
into the scrollback buffer so `list` and `show` appeared to append
rather than update. Now uses `cursorTo`/`clearScreenDown`, and skips
control characters entirely when stdout is not a TTY so piped output
stays parseable.
* Disambiguate the `000xxx` code range, where Shanghai indices overlap
Shenzhen tickers (`000001` is the SSE Composite, `000801` is a
Shenzhen listing).
* Normalise timestamps to `YYYY/MM/DD HH:mm:ss` across all markets.
* Replace the deprecated `url.parse()` with the WHATWG `URL` API.
* Add `-s/--single` to print once and exit, for scripting.
* Add tests covering offline parsing, terminal refresh and live
connectivity, and update the README and type definitions.
`config/stock.json` is left untouched, and `add`/`remove` now preserve
its original formatting.
Makes the tool usable by AI agents and scripts, not just interactively, and addresses several open issues. MCP server ---------- `wstock mcp` speaks the Model Context Protocol over stdio, so MCP-capable clients (Claude Desktop, Claude Code, Cursor) can query quotes directly. Five tools are exposed: `get_stock_quote`, `search_stock`, `list_watchlist`, `add_to_watchlist` and `remove_from_watchlist`. The protocol is implemented directly rather than via `@modelcontextprotocol/sdk`, which pulls in ~90 transitive packages (express, cors, zod, jose) for what is a line-delimited JSON-RPC loop. The runtime dependency tree stays at two packages. Machine-readable output ----------------------- * Add `--json` and `--csv` to `list`, `show` and `query`. Both imply `--single`, so they print once and exit instead of hanging in the polling loop. * Numeric fields are emitted as numbers; unparseable values become `null` rather than `NaN`, keeping the JSON valid. * Field names and market labels in these formats are always English and do not follow `--lang`, so parsers stay stable. * Set a non-zero exit code when a lookup fails, so `set -e` scripts behave. English by default ------------------ Table headers and messages were hard-coded Chinese. They now default to English, with `--lang zh` or `WSTOCK_LANG=zh` to switch back. Strings live in `lib/i18n.js`; `config.stockTermMap` becomes `config.stockFields`, which lists the display columns. `lib/parser.js` now returns stable market identifiers instead of localised labels, so search output no longer depends on the UI language. Issues addressed ---------------- * Press `q` to quit the watch view, matching `top`/`htop` (hellopao#4). Only enabled on an interactive TTY, and raw mode is always restored on exit. * Report codes the quote API did not return (hellopao#6). Invalid codes are silently omitted upstream, so a typo such as `sz515050` for `sh515050` previously just disappeared from the table. Documentation ------------- * `AGENTS.md` — layout, conventions, the output contract, and the gotchas that have caused real bugs here (GBK encoding, ambiguous `000xxx` codes, screen-refresh behaviour). `CLAUDE.md` points to it. * `SECURITY.md` — reporting process and the security posture (outbound-only HTTPS, no credentials, no telemetry, small dependency tree). * `CONTRIBUTING.md` — setup, conventions and test layout. * `.mcp.json` — ready-to-use MCP client configuration. * Rewrite the README around the new flags, MCP setup and scripting. Also add `files` to package.json so the published tarball no longer carries tests and development files, and extend the suite to 39 assertions covering i18n, the JSON/CSV contract and the MCP handlers. `config/stock.json` is left untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #8 — that PR restores basic functionality by migrating off the discontinued Xueqiu API; this one makes the tool usable by AI agents and scripts. Please merge #8 first, otherwise this diff will also show its commit.
MCP server
wstock mcpspeaks the Model Context Protocol over stdio, so Claude Desktop, Claude Code and Cursor can query quotes directly:{ "mcpServers": { "wstock": { "command": "wstock", "args": ["mcp"] } } }Five tools:
get_stock_quote,search_stock,list_watchlist,add_to_watchlist,remove_from_watchlist.I implemented the protocol directly instead of using
@modelcontextprotocol/sdk, because the SDK pulls in ~90 transitive packages (express, cors, zod, jose) for what is a line-delimited JSON-RPC loop over stdio. The runtime tree stays at two packages — happy to switch if you would prefer the SDK.Machine-readable output
--jsonand--csvonlist,showandquery. Both imply--single, so they print once and exit rather than hanging in the polling loop:Numeric fields are numbers, not strings; unparseable values become
nullso the JSON stays valid. Field names and market labels are always English here regardless of--lang, so parsers do not break when someone switches language.English by default
Headers and messages were hard-coded Chinese, which made the table unreadable in non-CJK terminals and awkward to parse. They now default to English:
--lang zhorWSTOCK_LANG=zhrestores the Chinese output. Strings live inlib/i18n.js.This replaces
config.stockTermMapwithconfig.stockFields(the column list), since labels now come from the catalogue. Worth flagging as the one behavioural change for existing users — anyone readingstockTermMapfrom the config module would need updating, though it was not part of the documented API.Issues addressed
#4 — press
qto quit. Matchestop/htop. Only enabled on an interactive TTY, and raw mode is always restored so the terminal is not left without echo. Verified in a real PTY.#6 — wrong prefix corrupts the whole table. Already fixed by #8: the parser matches on
v_<code>=rather than splitting positionally, so a bad code can no longer shift other rows. But the invalid code still vanished silently, which is confusing — now it is reported:#3 and #5 need no change after #8 — I verified both.
wstock add 600127infers the market (sh600127), andlistpreservesstock.jsonorder.#7 is promotional and unrelated; I have left it alone.
Documentation
AGENTS.md— layout, conventions, output contract, and the gotchas that have each caused a real bug here: GBK responses vs UTF-8 query keys, the ambiguous000xxxrange, and whyESC[2Jmust not be used for refresh.CLAUDE.mdpoints to it.SECURITY.md— private reporting process plus the actual posture: outbound HTTPS only, no inbound listener, no credentials, no telemetry, two dependencies.CONTRIBUTING.md— setup, conventions, test layout..mcp.json— ready-to-use client config.Other
filesadded topackage.json, so the tarball no longer ships tests and dev files.stockTermMaprename.set -escripts behave.Verification
q-to-quit and in-place refresh verified in a real PTY (4 frames, noESC[2J).config/stock.jsonis byte-identical to master.