From c033cf6cb5d2aa0b04fb3291a1950f8973c0a7ca Mon Sep 17 00:00:00 2001 From: claude_dev Date: Thu, 30 Jul 2026 09:39:28 +0800 Subject: [PATCH] =?UTF-8?q?docs(design):=20MCP=20=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E6=9A=B4=E9=9C=B2=20LocalUnifiedProvider=20=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决人肉割裂/手搓查询错/AI不能主动取数: - 14个公开方法分P0/P1/P2优先级(get_price/closes_panel/constituent/fundamentals优先) - 薄server直接@tool注册,跳过FastAPI(YAGNI,不给第三方REST) - 与Vibe-Research分工:实时在线vs本地历史,前缀sanguo_区分 - 配套TET:先治兜底再暴露,AI消费校验过的干净数据 --- .../provider-mcp-expose-design.md | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 docs/design/architecture/provider-mcp-expose-design.md diff --git a/docs/design/architecture/provider-mcp-expose-design.md b/docs/design/architecture/provider-mcp-expose-design.md new file mode 100644 index 0000000..aedb64d --- /dev/null +++ b/docs/design/architecture/provider-mcp-expose-design.md @@ -0,0 +1,149 @@ +# 设计笔记:用 MCP 直接暴露 LocalUnifiedProvider 给 Claude Code + +> 设计日期 2026-07-30。配套 `provider-tet-design.md` + `docs/research/openbb-platform-research.md`。 +> +> 一句话定位:**给 AI 装一只「直接读本项目本地数据」的手**——把 `LocalUnifiedProvider` 的取数方法注册成 Claude Code 可调的 MCP 工具,从「请你跑数据贴给我」变成「我自己拉数据、算指标、给结论」。 + +--- + +## 一、为什么是「直接暴露」而非照搬 OpenBB + +``` +OpenBB 的链路(它需要 REST + Workspace,MCP 是副产品): + provider方法 → @router.command → FastAPI端点 → fastmcp 从 OpenAPI 派生 → MCP工具 + +本项目(不需要 REST/Workspace,跳过中间层): + provider方法 → 直接 @tool 注册成 MCP工具 +``` + +OpenBB 建了 FastAPI 是为了给 Workspace 前端和第三方 REST 用,MCP 顺手派生。**本项目策略层直调 provider、不给第三方 REST**,纯为 MCP 去建一整套 FastAPI + Router 命令树是过度工程(YAGNI)。所以直接拿 MCP server 框架(fastmcp / `mcp` python sdk),把 provider 方法 `@tool` 注册即可。 + +--- + +## 二、解决的本项目痛点(三个实证) + +| 痛点 | 现状 | MCP 暴露后 | +|------|------|-----------| +| **人肉割裂** | 你跑脚本 → 贴数据 → 我分析;或我用 `!` 让你跑命令,每查一次打断一次 | 我直接调工具拿数据,你当中转的环节消失 | +| **手搓查询错**(`feedback-verify-via-provider-exact-query`) | 我手搓 SQL `symbol='000534'` 查 dbbardata(双列)误报「19 股无日线」,实际 provider 数据完整 | 我调用的就是 provider 确切方法,**口径天然一致**,不可能手搓错 | +| **AI 不能主动取数** | 你问「000001 走势如何」,我没法直接拉数据,得绕几道 | 我直接 `get_price_panel("000001")` → 算指标 → 给结论,一句话进一个分析出 | + +--- + +## 三、暴露哪些方法(基于真实公开方法清单) + +源:`sanguo_portfolio/providers/local_unified_provider.py` 的 `LocalUnifiedProvider` 14 个公开方法。按暴露优先级分组: + +### P0 — 高频取数(优先暴露,策略/回测核心) +| 方法 | 行号 | 用途 | 备注 | +|------|------|------|------| +| `get_price` | 170 | 行情(日线/15min,支持复权) | 最高频 | +| `get_closes_panel` | 272 | 多股票收盘价面板 | 回测核心,已优化 fq+UNION ALL | +| `get_constituent` | 512 | 指数成份股(治偏差版) | 选股池 | +| `get_fundamentals_df` | 582 | 基本面(已修前视偏差) | 基本面过滤 | + +### P1 — 估值/风控 +| 方法 | 行号 | 用途 | +|------|------|------| +| `get_value_metrics_batch` | 639 | 估值(pe/pb)批量 | +| `get_limit_status_batch` | 868 | 涨跌停状态(已优化 UNION ALL) | + +### P2 — 元信息/日历 +| 方法 | 行号 | 用途 | +|------|------|------| +| `get_security_info` / `get_security_info_batch` | 746 / 777 | 证券元信息 | +| `get_all_securities` | 998 | 全部证券列表 | +| `get_trade_days` | 723 | 交易日历 | +| `get_split_dividend` | 967 | 除权除息 | + +### 暂不暴露(与 Vibe-Research 重叠或低频) +| 方法 | 原因 | +|------|------| +| `get_current_tick`(947) | 实时 tick,Vibe-Research 的 `query_quote` 已覆盖在线实时 | +| `get_index_stocks`(479) | 与 `get_constituent` 重叠,后者是治偏差权威版,暴露后者即可 | + +--- + +## 四、怎么注册(薄 MCP server) + +核心:server 只做「注册 + 序列化」,不写业务逻辑(业务全在 provider)。 + +```python +# 伪代码:sanguo_mcp/server.py +from mcp.server.fastmcp import FastMCP +from sanguo_portfolio.providers.local_unified_provider import LocalUnifiedProvider + +mcp = FastMCP("sanguo") +provider = LocalUnifiedProvider(...) # 读本地 dbbardata/parquet + +@mcp.tool() +def sanguo_get_price(symbol: str, start: str, end: str, interval: str = "1d") -> list[dict]: + """获取 A 股行情(日线/15min,前复权)。symbol 如 '000001.SZ'。""" + df = provider.get_price(symbol, start, end, interval=interval) + return df.to_dict(orient="records") # JSON records,LLM 友好 + +@mcp.tool() +def sanguo_get_closes_panel(symbols: list[str], start: str, end: str) -> dict: + """多股票收盘价面板(回测用)。""" + return provider.get_closes_panel(symbols, start, end).to_dict() +# ... 其余方法同理 +``` + +### 关键约定 +- **工具命名前缀 `sanguo_`**:和已挂载的 `vibe-research__*` 区分,一看就知道是本地数据 +- **参数 = provider 标准字段**(symbol/start/end/interval),和 TET 笔记的 standard/extra 拆分一致 +- **返回 JSON records**:参考 OpenBB `OBBject.to_llm()`(JSON records 格式),LLM 友好;大表注意别一次返回几万行(加 limit 或采样) +- **数据本地读**:工具内部仍走 `provider-local-data-only` 铁律,读 dbbardata/parquet,不打网络 + +--- + +## 五、和 Vibe-Research 的分工(互补,不重叠) + +| | 数据来源 | 能查什么 | 工具前缀 | +|---|---|---|---| +| **Vibe-Research(已有 5 工具)** | 在线实时 | 当前价、新闻、研报、估值、全球股 | `vibe-research__` / `mcp__vibe-research__` | +| **本项目 MCP(本设计)** | 本地 dbbardata/parquet | 历史日线/15min、成份股、基本面历史、涨跌停、回测取数 | `sanguo_` | + +互补关系:**实时用 Vibe-Research,历史/回测用 sanguo**。例如「000001 现在多少钱」用 `query_quote`,「000001 过去 5 年回测数据」用 `sanguo_get_price`。 + +--- + +## 六、和 TET 的配套(顺序很重要) + +| 阶段 | 做什么 | 为什么 | +|------|--------|--------| +| 先 | **TET 化 provider**(见 `provider-tet-design.md`) | transform_data pydantic 校验,治兜底会乱,保证取数质量 | +| 后 | **MCP 暴露** | 让 AI 消费的是校验过的干净标准数据 | + +**顺序不能反**:如果 provider 兜底还在(补 NaN 当停牌),MCP 暴露出去的也是脏数据,AI 拿着错数据做分析,结论全错。**先治兜底,再暴露**。 + +--- + +## 七、设计原则 + +1. **跳过 FastAPI/Router**(YAGNI):本项目不给第三方 REST,纯为 MCP 建它们是过度工程 +2. **暴露 provider 确切方法**:根治「手搓查询口径错」,我调用的就是策略层用的同一套 API +3. **薄 server**:只注册 + 序列化,业务逻辑零下沉(全留 provider),server 永远是薄薄的胶水层 +4. **本地只读**:工具内部读 dbbardata/parquet,遵守 `provider-local-data-only` 铁律 +5. **大表克制**:MCP 工具面向「对话式查询」,默认带 limit/采样,几万行回测数据不该一次性灌给 LLM + +--- + +## 八、落地步骤 + +1. 选 MCP 框架(`mcp` python sdk 或 fastmcp),起 `sanguo_mcp` server +2. 优先暴露 4 个 P0 方法(get_price / get_closes_panel / get_constituent / get_fundamentals_df) +3. 参数用标准字段,返回 JSON records,大表加 limit +4. 挂到 Claude Code MCP 配置(参考 Vibe-Research 的挂载方式) +5. 验证:对话里调 `sanguo_get_price("000001.SZ", ...)`,确认数据与直接调 provider 一致 +6. 再逐步暴露 P1/P2 + +--- + +## 相关 + +- 配套设计:`provider-tet-design.md`(先治兜底,再暴露) +- 调研:`docs/research/openbb-platform-research.md` +- Vibe-Research 接入先例:`a-stock-data-integration` +- provider 本地只读铁律:`provider-local-data-only` +- 手搓查询错教训:`feedback-verify-via-provider-exact-query`