The longbridge CLI's security-list endpoint no longer hits the intermittent param_error described in earlier docs. The behaviour now is an explicit scope-limit message — "Only US market is supported for security-list (Longbridge API only exposes the Overnight category)" — when invoked with HK / CN / SG. That's a documented limitation, not a bug, so the special-case "prefer MCP for security-list" treatment is no longer warranted. CLAUDE.md §6 (Path selection): - Drop "A specific subcommand has known issues (e.g. security-list param_error)" bullet. - Drop the "Mark exceptions in the SKILL.md explicitly:" intro line — the remaining two exceptions (analysis tier + mutating watchlist-admin) read cleanly without the umbrella phrase. docs/architecture.md: - "Four exceptions" → "Three exceptions" — drop the security-list row. - Decision flow: drop the "Skill declares MCP-preferred?" branch. - Error-handling table: drop the param_error/security-list row (the generic invalid-symbol param_error guidance stays in per-skill error tables where it's still useful). - Maintainer guidelines: drop "MCP-preferred → add Path-selection note" bullet (no skill needs that pattern anymore). - Maintainer guideline §5 wording aligned with the recent prompt-only + scripts-allowed-when-justified convention. docs/install.md: - FAQ entry "param_error on security-list securities" → "Only US market is supported for security-list" with updated guidance (route the user to longbridge-quote for non-US per-symbol lookups). skills/longbridge-security-list/SKILL.md: - Description rewritten to reflect US-only scope (was ambiguously "per market"). Triggers updated to drop "港股一共多少", "list of HK stocks" etc. that no longer apply. - Subcommand table: positional <MARKET> defaulting to HK is gone — the CLI takes no market arg and only returns US overnight names. - Drop "Path-selection note" section. The MCP fallback table no longer marks one entry "prefer MCP". - Error handling: replace param_error wording with the actual scope-limit message and tell the LLM how to redirect the user. Triggered by validation against longbridge v0.7.0+: `longbridge security-list HK --format json` now returns the explicit scope error above; no param_error occurs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
17 KiB
Skill design: multilingual + tool selection
The two cross-cutting design decisions in this repo — trilingual support and CLI vs MCP path selection — are not runtime branches in code. They are written as instructions inside SKILL.md, leaving the decision to the LLM at call time. This document explains how both mechanisms work so future maintainers can stay consistent when adding or rewriting skills.
Cross-references: Agent Skills specification · microsoft/skills convention · this repo's README.md
1. Multilingual (Simplified Chinese / Traditional Chinese / English)
Goals
Mainland-, Hong Kong-, and Taiwan-based users all use these skills. We need to support:
- A user prompt in any of the three languages — "NVDA 现在多少钱" / "NVDA 現在多少錢" / "What's NVDA's price?" — should route correctly to
longbridge-quote. - The reply should match the user's input language, not be hard-coded.
- Field names and error phrasing should follow the same language.
Non-goals: no i18n framework, no code-side language detection, no per-language SKILL.md files. All in prompt — zero code cost.
Implementation: four layers
1️⃣ Description triggers — decide whether the skill activates
Every SKILL.md writes the same semantic concept into multiple language flavours inside the description. Example: longbridge-quote/SKILL.md:
description: |
Real-time quotes, static reference, and valuation indices for stocks listed in HK / US / A-share / Singapore via Longbridge Securities. ... Triggers: "现在多少钱", "股价", "涨跌幅", "成交量", "市值", "市盈率", "PE", "PB", "换手率", "行业", "現在多少", "股價", "成交量", "市值", "市盈率", "stock price", "current price", "quote", "market cap", "PE ratio", "valuation", "NVDA price", "AAPL quote", "茅台市值", "腾讯股价", "700.HK", "600519.SH".
At startup, the agent only loads frontmatter (~100 tokens / skill — this is step 1 of progressive disclosure). The LLM pattern-matches the user's prompt against these triggers — Simplified, Traditional, English, or ticker examples can each independently fire the skill.
Tip: keywords identical across Simplified and Traditional (e.g. "成交量", "市值") only need to be written once. Divergent characters must be written twice (e.g. "现在多少钱" / "現在多少錢", "股价" / "股價").
2️⃣ Response-language directive — decide the output language
Every SKILL.md, immediately below the # <skill-name> heading and the intro paragraph, includes this fixed line:
> **Response language**: match the user's input language —
> Simplified Chinese / Traditional Chinese / English.
This instructs the LLM to detect the user's input language (Simplified, Traditional, or English) and respond in the same language. Modern LLMs already have this ability natively; we just have to surface the instruction inside the SKILL.
The data layer is language-agnostic — both the raw longbridge CLI (used by 17 skills) and the two Python wrappers (longbridge-quote, longbridge-watchlist-admin) emit English JSON only. Language switching happens when the LLM translates that JSON to natural language.
3️⃣ Field translation tables — multilingual ingredients for data → prose
Underlying financial data uses English field names. Each SKILL.md (or, for longer ones, an offloaded references/ file) provides a three-column lookup table; the LLM picks the column for the user's language. Example: longbridge-fundamental/references/field-dictionary.md:
| MCP field | 简体 | 繁體 | English |
|---|---|---|---|
| `revenue` / `total_revenue` | 营业收入 / 营收 | 營業收入 / 營收 | Revenue |
| `cost_of_revenue` | 营业成本 | 營業成本 | Cost of revenue |
| `gross_margin` | 毛利率 | 毛利率 | Gross margin |
| `roe` | 净资产收益率 | 淨資產收益率 | Return on equity |
When rendering the answer, the LLM consults the column matching the language detected in §2.
4️⃣ Error-reply tables — three languages side by side
Each read-tier skill's ## Error handling section maps a class of failure (recognised by either shell behaviour or stderr keyword) to a multilingual reply phrase. Example: longbridge-quote/SKILL.md:
| Situation | LLM response |
|---|---|
| Shell `command not found: longbridge` | Fall back to MCP if configured; otherwise tell the user to install [longbridge-terminal](https://github.com/longportapp/longbridge-terminal). |
| stderr contains `not logged in` / `unauthorized` | Tell the user to run `longbridge auth login`. |
| stderr contains `param_error` or "invalid symbol" | Re-check the `<CODE>.<MARKET>` format with the user. |
The LLM reads longbridge stderr directly and translates the situation into the user's language. There are no canned error_kind enums to look up — the SKILL's table is a recipe for interpreting raw shell output.
End-to-end flow
User: "NVDA 現在股價"
↓
[Claude Code / agent boots up]
1. Loads every SKILL.md frontmatter (~100 tokens / skill)
↓
LLM pattern-matches against description triggers.
"現在股價" hits the zh-Hant trigger of longbridge-quote.
↓
[Activate longbridge-quote SKILL.md]
2. Loads the full SKILL.md (English body + multilingual lookup tables)
3. Response-language directive tells the LLM:
user typed zh-Hant → reply in zh-Hant
↓
longbridge quote NVDA.US --format json (LLM may also call `static`
↓ and `calc-index` and merge by symbol)
longbridge CLI returns a JSON array (English fields)
↓
4. LLM uses zh-Hant + the multilingual tables in §3/§4 to compose:
"NVDA 現在 209.27 美元,當日下跌 -2.86%。數據來源:長橋證券。"
The entire pipeline runs in the prompt layer. The Longbridge CLI itself contains no i18n logic — JSON is always English. Adding a new language (Japanese, Korean, Spanish, …) means adding triggers to the description plus another column to the tables — no code changes required.
2. CLI vs MCP tool selection
Goals
Each capability (quote, positions, capital flow, …) is reachable through two paths:
| Path | Interface | Latency | Dependency |
|---|---|---|---|
| Local CLI | subprocess into the longbridge binary |
Fast (in-process, ~50 ms) | User has installed longbridge-terminal and run longbridge auth login |
| Official MCP | HTTP + OAuth (Anthropic MCP transport) | Slow (network + auth, ~500 ms+) | User has run claude mcp add longbridge ... |
Every SKILL.md tells the LLM the same rule: default to CLI; fall back to MCP under specific conditions.
Default rule
1. LLM calls `longbridge <subcommand> --format json` directly.
2. Shell returns `command not found` → fall back to MCP.
3. Otherwise parse the JSON output (or surface stderr verbatim if non-zero exit).
4. MCP also unconfigured → tell the user: install longbridge-terminal or run `claude mcp add ...`.
Each read-tier SKILL.md ends with an ## MCP fallback section that lists the subcommand ↔ MCP tool mapping. Example: longbridge-quote/SKILL.md:
| CLI subcommand | MCP tool |
|---|---|
| `quote` | `mcp__longbridge__quote` |
| `static` | `mcp__longbridge__static_info` |
| `calc-index` | `mcp__longbridge__calc_indexes` |
Default style: prompt-only. Every SKILL.md tells the LLM what
longbridge <subcommand>to run; the LLM calls it directly and reads the raw JSON. A skill MAY ship ascripts/<helper>.py(orcommands/<slash>.md) when there's a clear runtime need — DOCX/chart generation, format-specific output, or a safety gate. The helper should stay narrow and not re-wrap the longbridge CLI itself.When a SKILL.md is uncertain about exact flag names, defaults, or argument order (because the underlying CLI may have evolved), it instructs the LLM to run
longbridge <subcommand> --helpfirst — the CLI's built-in help is the canonical source.
Three exceptions (each SKILL.md overrides the default explicitly)
| Exception | Default flips to | Why | Where it's written |
|---|---|---|---|
longbridge-subscriptions |
CLI-only | MCP is stateless HTTP — there is no WebSocket session concept, hence no equivalent tool | subscriptions/SKILL.md ## Local-only |
| Six analysis-tier skills (valuation / fundamental / news / peer-comparison / portfolio / catalyst-radar) | MCP-only | requires_mcp: true in frontmatter; they invoke MCP-only tools (valuation_history / profit_analysis / news / topic, …) that the CLI does not expose |
each analysis-tier SKILL.md ## Prerequisite |
longbridge-watchlist-admin |
Two-turn protocol | Mutating writes need an explicit confirmation gate. The SKILL preview the action in plain language and waits for user confirmation before issuing longbridge watchlist <create / update / delete> (or the MCP equivalent). The CLI's delete subcommand also has its own built-in confirmation prompt |
watchlist-admin/SKILL.md ## Two-step protocol |
Decision flow
User prompt arrives
↓
┌─ Analysis-tier skill? (valuation / fundamental / news / peer / portfolio / catalyst-radar)
│ └─ yes ──→ MCP-only — the underlying tools have no CLI equivalent
│ └─ MCP unconfigured? → ask user to run `claude mcp add longbridge ...`
│ └─ no ──→ continue
↓
┌─ Mutating skill? (watchlist-admin — SKILL-layer two-step protocol)
│ └─ yes ──→ ① preview the plan in plain language (no CLI call yet)
│ ② wait for explicit confirmation
│ (matches "确认" / "yes" / "是的" / "confirm")
│ ③ once confirmed → run `longbridge watchlist <create|update|delete> ...`
│ (or the equivalent MCP write tool)
│ └─ no ──→ continue
↓
Run `longbridge <subcommand> ... --format json` directly
↓
Result handling
├─ exit 0, JSON returned ──→ use the result
├─ shell `command not found` ──→ fall back to MCP if configured; otherwise prompt user to install CLI
├─ stderr contains "unauthorized" / "not in authorized scope" ──→ tell user to re-run `longbridge auth login` (MCP shares the same OAuth)
└─ other stderr ──→ surface verbatim; never silently retry
Capability matrix: CLI vs MCP
| Capability | CLI (longbridge binary) |
MCP (mcp__longbridge__*) |
|---|---|---|
| Quote / candlestick / depth / capital flow / options / warrants | ✅ | ✅ |
| Positions / orders / balance | ✅ | ✅ |
| Watchlist (read) | ✅ | ✅ |
| Watchlist (write) | ✅ (with dry-run + confirm gate) | ✅ (raw write; SKILL-layer must add the gate) |
| Historical market-temperature time series | ❌ | ✅ history_market_temperature |
| Finance calendar (earnings / dividends / IPOs / macro) | ❌ | ✅ finance_calendar |
| Short positions | ❌ | ✅ short_positions |
| Options volume analysis | ❌ | ✅ option_volume / option_volume_daily |
| Valuation history + industry distribution | ❌ | ✅ valuation_history / industry_valuation_dist |
| Full IS/BS/CF + analyst consensus | ❌ | ✅ financial_report / forecast_eps / consensus |
| Portfolio P&L analysis | ❌ | ✅ profit_analysis / profit_analysis_detail |
| News + filings + community topics | ❌ | ✅ news / filings / topic / topic_detail / topic_replies |
| Shared watch lists | ❌ | ✅ sharelist_* (8 tools) |
| WebSocket subscription diagnostics | ✅ | ❌ (MCP is stateless) |
| Statement / report exports | ❌ | ✅ statement_* |
Observation: the five analysis-tier skills are MCP-only because the underlying tools they need (valuation history, full financial reports, news, portfolio P&L) simply have no CLI equivalent. It is a capability-difference outcome, not a stylistic choice.
Why default to CLI rather than MCP?
| Dimension | CLI | MCP |
|---|---|---|
| Latency | Local subprocess, ~50 ms | HTTP + OAuth, ~500 ms+ |
| Network | Not required (token cached after longbridge auth login) |
Requires public reachability to openapi.longbridge.com |
| Session state | CLI maintains the WebSocket connection — push / subscribe usable | Stateless HTTP |
| OAuth scope | Decided at longbridge auth login on the user's machine |
Decided when the user authorises in the browser after claude mcp add (re-authorisable for trade scope) |
| Cross-skill consistency | Same --format json flag across all subcommands; behaviour is documented per skill |
MCP responses are whatever the MCP server returns; varies per tool |
In short: when the local CLI is installed, it is faster than MCP. MCP exists to (a) widen capability (analysis / history / async topics) and (b) act as a fallback when CLI is unavailable.
Shell exit / stderr is the glue between CLI and MCP
For prompt-only skills (the default style — no scripts/cli.py between the LLM and longbridge), the LLM reads:
| Signal | LLM response |
|---|---|
Shell command not found (CLI binary missing) |
Path-switch signal — try MCP |
stderr contains unauthorized / not in authorized scope |
Tell user to run longbridge auth logout && longbridge auth login (MCP shares the same OAuth — switching path won't fix scope) |
| Other stderr / non-zero exit | Surface verbatim — never silently retry |
Exit 0, empty JSON [] |
Empty result is success in some skills, ambiguous in others — see each SKILL.md |
A missing binary is the only signal that triggers a path switch. Other errors must not push the LLM to MCP — the underlying error is unrelated to the path.
3. Maintainer guidelines
When adding or rewriting a skill, follow these:
- Trilingual triggers are required — the description must cover Simplified Chinese, Traditional Chinese, and English keywords. Identical-glyph words appear once; divergent ones appear in both forms.
- Use the canonical Response-language directive verbatim — copy the line from
quote/kline/ any shipped SKILL.md. Do not rephrase; consistency matters across skills. - Field tables and error tables are three columns — never "Chinese / English"; always 3 columns (Simplified / Traditional / English).
- Path rules are declared in SKILL.md according to category:
- Default read-tier →
## CLI+## MCP fallback - CLI-only → add
## Local-onlyexplaining why MCP has no equivalent - MCP-only (analysis tier) → frontmatter
requires_mcp: true+## Prerequisitementioningclaude mcp add longbridge ... - Mutating → add
## Two-step protocol (mandatory)describing the dry-run + confirm flow
- Default read-tier →
- Default to prompt-only — point the LLM at the raw
longbridge <subcommand>and tell it to runlongbridge <subcommand> --helpwhenever flag names might have evolved.scripts/<helper>.pyis allowed when there's a clear runtime need (DOCX/chart generation, format helper, safety gate) but should stay narrow and never re-wrap the longbridge CLI by hard-coding its flags. - Shell
command not foundis the path-switch signal — whenlongbridgeisn't onPATH, the LLM falls back to MCP (or asks the user to install longbridge-terminal). Other stderr (auth / etc.) is surfaced verbatim — no silent retries.
One last note: both mechanisms (multilingual + CLI/MCP) share the same essence — express policy through prompt; push the decision to the LLM rather than coding it at runtime. The default style across the repo is prompt-only SKILL.md. A small number of skills may add scripts/ or commands/ subfolders for narrow runtime needs (DOCX/chart generation, format helpers, slash commands) — those are opt-in, not required. Adding or changing a policy almost always means editing a SKILL.md, not touching code.