Skip to content

feat: add MCP server, machine-readable output and English UI - #9

Open
Ramos-dev wants to merge 2 commits into
hellopao:masterfrom
Ramos-dev:feat/agent-friendly
Open

feat: add MCP server, machine-readable output and English UI#9
Ramos-dev wants to merge 2 commits into
hellopao:masterfrom
Ramos-dev:feat/agent-friendly

Conversation

@Ramos-dev

Copy link
Copy Markdown

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 mcp speaks 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

--json and --csv on list, show and query. Both imply --single, so they print once and exit rather than hanging in the polling loop:

$ wstock show 600519 --json
{"name":"贵州茅台","code":"sh600519","current":1346.48,"percentage":-0.18,...}

$ wstock show 600519 --json | jq -r .current
1346.48

Numeric fields are numbers, not strings; unparseable values become null so 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:

$ wstock list
NAME         CODE         OPEN         PREV CLOSE   PRICE        HIGH         LOW          CHANGE
上证指数     sh000001     3950.71      3966.59      3964.79      3966.39      3942.75      -0.05%

--lang zh or WSTOCK_LANG=zh restores the Chinese output. Strings live in lib/i18n.js.

This replaces config.stockTermMap with config.stockFields (the column list), since labels now come from the catalogue. Worth flagging as the one behavioural change for existing users — anyone reading stockTermMap from the config module would need updating, though it was not part of the documented API.

Issues addressed

#4 — press q to quit. Matches top/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:

$ wstock list
...
Unresolved code(s): sz515050 — check the market prefix (sh/sz/bj/hk/us)

#3 and #5 need no change after #8 — I verified both. wstock add 600127 infers the market (sh600127), and list preserves stock.json order.

#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 ambiguous 000xxx range, and why ESC[2J must not be used for refresh. CLAUDE.md points 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.
  • README rewritten around the new flags, MCP setup and scripting.

Other

  • files added to package.json, so the tarball no longer ships tests and dev files.
  • Version bumped to 0.1.0 — new features, plus the stockTermMap rename.
  • Non-zero exit codes on failed lookups, so set -e scripts behave.

Verification

  • Suite extended from 22 to 39 assertions, covering i18n key parity across both languages, the JSON/CSV contract, and the MCP handlers (handshake, tool schemas, notifications, unknown methods, missing arguments).
  • Clean-clone install checked: 2 packages, 39/39 pass, MCP and CLI both work.
  • q-to-quit and in-place refresh verified in a real PTY (4 frames, no ESC[2J).
  • No deprecation warnings on any command.
  • config/stock.json is byte-identical to master.

lirui37 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant