Files
longbridge__skills/docs/architecture.md
袁章洪 c4e31990e5 docs: drop stale security-list param_error guidance + tighten path-selection rules
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>
2026-04-29 18:33:52 +08:00

17 KiB
Raw Permalink Blame History

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 a scripts/<helper>.py (or commands/<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> --help first — 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:

  1. 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.
  2. Use the canonical Response-language directive verbatim — copy the line from quote / kline / any shipped SKILL.md. Do not rephrase; consistency matters across skills.
  3. Field tables and error tables are three columns — never "Chinese / English"; always 3 columns (Simplified / Traditional / English).
  4. Path rules are declared in SKILL.md according to category:
    • Default read-tier → ## CLI + ## MCP fallback
    • CLI-only → add ## Local-only explaining why MCP has no equivalent
    • MCP-only (analysis tier) → frontmatter requires_mcp: true + ## Prerequisite mentioning claude mcp add longbridge ...
    • Mutating → add ## Two-step protocol (mandatory) describing the dry-run + confirm flow
  5. Default to prompt-only — point the LLM at the raw longbridge <subcommand> and tell it to run longbridge <subcommand> --help whenever flag names might have evolved. scripts/<helper>.py is 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.
  6. Shell command not found is the path-switch signal — when longbridge isn't on PATH, 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.