Files

120 lines
6.7 KiB
Markdown

# MCP 服务(桥内嵌 /mcp 端点)
> 版本: 1.1 (2026-08-27)
> 状态: ✅ 已完成
> 关联: docs/设计/整体设计方案_v3.md(单端口 8610 统一方案)、docs/api_spec/openapi.yaml
> 本次迭代: 在现有 qmt_bridge HTTP 服务器上新增 MCP(Model Context Protocol)端点,使 AI 助手(Claude Desktop / Cursor 等)可直接通过 MCP 调用桥的行情/交易能力
---
## 一、已确认的设计决策
### 1.1 结论:可行,采用"桥内嵌 /mcp 端点"方案
MCP 本质是 **JSON-RPC 2.0 应用协议**,官方两种传输:
- **stdio**:子进程管道,适合"MCP 客户端拉起独立进程"
- **Streamable HTTP**(2025-03-26 起取代旧 SSE):POST JSON-RPC,单请求-响应
桥已经手写 raw-socket HTTP 服务器,并已证明可在同一端口处理 WebSocket 长连接(Upgrade 分流)。因此:
- **选定方案 A(桥内嵌)**:新增 `bridge_mcp_server.py`,在 `POST /mcp` 上实现 JSON-RPC 2.0 分发,工具直接复用 `bridge_data_adapter` / `bridge_util`
- **放弃方案 B(独立 MCP 进程 + 官方 mcp SDK)**:官方 SDK 要求 **Python ≥ 3.10**,QMT 内置是 **Python 3.6.8 纯标准库**(不能装包);方案 B 需额外进程经 HTTP 8610 转发,多一跳且偏离项目"薄桥/零依赖"理念。
- **传输选型**:Streamable HTTP 的"非流式"子集(单请求-响应 `application/json`),覆盖主流 MCP 客户端。流式(SSE)留待后续。
### 1.2 约束核对(逐条过项目规范)
| 硬约束 | 影响 | 结论 |
|--------|------|------|
| 源码 GBK + 纯 ASCII | 新模块同样遵守,无新问题 | ✅ |
| QMT 函数线程约束 | MCP tools/call 跑在 HTTP worker 线程,全部复用现有 adapter 安全模式 | ✅ |
| 单账号 | MCP 工具无 account 参数,与 HTTP 一致 | ✅ |
| 单端口 8610 | `POST /mcp` 复用 8610,不新开端口 | ✅ |
| 热重载 | `qmt_strategy_entry.py``_BRIDGE_MODULES` 需加 `bridge_mcp_server` | ✅ |
| 部署 | `deploy_elevated.bat` 需加 `bridge_mcp_server.py` | ✅ |
### 1.3 协议实现范围(MCP 最小集)
| MCP 方法 | 实现 | 说明 |
|----------|------|------|
| `initialize` | ✅ | 返回协议版本 + `tools` capability,记录协商版本 |
| `notifications/initialized` | ✅ | 空响应(通知类) |
| `ping` | ✅ | 返回空 result |
| `tools/list` | ✅ | 从内置工具注册表返回 `{tools: [...]}`(含 inputSchema) |
| `tools/call` | ✅ | 校验参数 → 调 adapter → 返回 `{content:[{type:"text",text:json}]}` |
> 暂不实现: `resources/*`、`prompts/*`、`logging/*`、`completions/*`、流式(SSE)。纯工具型最小实现与主流客户端兼容。
### 1.4 首批工具(9 个,与 HTTP 接口 1:1)
| 工具名 | 对应 HTTP | 底层 |
|--------|-----------|------|
| `qmt_kline` | GET /data/kline | get_market_data_ex(worker 线程直调 ✅) |
| `qmt_quote` | GET /data/quote | get_full_tick |
| `qmt_tick` | GET /data/tick | get_full_tick(批量) |
| `qmt_instrument` | GET /data/instrument | get_instrument_detail |
| `qmt_trading_dates` | GET /data/calendar/trading_dates | get_trading_dates |
| `qmt_positions` | GET /trade/positions | 读 TRADE_CACHE(策略线程刷新 ✅) |
| `qmt_asset` | GET /trade/asset | 读 TRADE_CACHE |
| `qmt_orders` | GET /trade/orders | 读 TRADE_CACHE(+ 可选过滤) |
| `qmt_trades` | GET /trade/trades | 读 TRADE_CACHE |
后续迭代: `qmt_order`(下单,队列→adjust)、`qmt_cancel`(撤单)、`qmt_subscribe`(订阅)。
### 1.5 工具实现与 HTTP 共用一个内部函数层
为免工具逻辑与 HTTP handler 双写,`bridge_mcp_server.py` 内置"工具名 → 参数校验 → 结果 dict"的映射,直接 import `bridge_data_adapter` 的函数(get_market_data_ex / get_full_tick / get_instrument_detail / get_trading_dates / get_trade_data / get_positions),参数语义与 HTTP 完全一致(code 自动补后缀等)。
> 说明: 本次迭代不把 HTTP handler 重构为共用同一函数层(避免动既有稳定代码);但工具内部复用的 adapter 已是同一函数,行为一致。文档(openapi.yaml)仍是唯一事实来源,工具 schema 与之保持 1:1。
### 1.6 鉴权与 CORS
- 鉴权复用桥 TOKEN 机制: `X-Token` header 或 `?token=`(MCP 客户端如 Claude Desktop 支持自定义 header)。
- 响应带 CORS 头(`Access-Control-Allow-Origin: *` 等),兼容浏览器类 MCP 客户端 / 调试工具。
- `OPTIONS /mcp` 预检返回 204。
### 1.7 错误语义
- 非法 JSON / 非法 JSON-RPC: `-32700` parse error / `-32600` invalid request(HTTP 200, MCP 规范要求 JSON-RPC 错误也走 200)。
- 未知方法: `-32601` method not found。
- 工具参数错误: `-32602` invalid params(带 detail)。
- 工具执行异常: `-32603` internal error(带 detail)。
- 非 JSON-RPC body(如纯文本): 400 + `{"detail": "..."}`(桥统一错误格式)。
---
## 二、实现记录(已完成)
### 2.1 新增文件
- `src/bridge_mcp_server.py`: JSON-RPC 2.0 分发 + 9 个工具(复用 bridge_data_adapter)+ raw-socket 响应 + CORS 头
- `tests/test_mcp.py`: mock 测试(纯标准库,FakeDF 代替 pandas,本机即可跑),覆盖 initialize / tools/list / 9 个 tools/call / 错误路径 / notification 202 / OPTIONS 204
### 2.2 改动文件
- `src/bridge_http_server.py`: `POST /mcp` 路由 + `OPTIONS /mcp` CORS 预检(204)
- `src/qmt_strategy_entry.py`: `_BRIDGE_MODULES` 热重载列表加入 `bridge_mcp_server`
- `deploy_elevated.bat`: 部署文件列表加入 `bridge_mcp_server.py`(7 → 8 个 .py)
- `README.md` / `docs/项目规范.md`(新增 3.8 节)/ `docs/迭代记录/index.md`: 文档同步
### 2.3 实现要点
- **工具与 HTTP 1:1**: qmt_kline / qmt_quote / qmt_tick / qmt_instrument / qmt_trading_dates / qmt_positions / qmt_asset / qmt_orders / qmt_trades
- **协议**: `initialize`(协商 protocolVersion,回显 2025-06-18/2025-03-26/2024-11-05)、`notifications/initialized`(202 空 body)、`ping``tools/list``tools/call`
- **错误**: JSON-RPC 错误走 HTTP 200(parse -32700 / invalid request -32600 / method not found -32601 / invalid params -32602 / internal -32603);非法 body 走 400 `{"detail":...}`
- **鉴权/CORS**: 复用 TOKEN(X-Token);`OPTIONS /mcp` 预检 204;响应带 `Access-Control-Allow-Origin: *`
- **线程约束**: 全部工具经 bridge_data_adapter → 与 HTTP 端点同模式(行情 worker 直调,交易读缓存),无新 QMT 调用形态
### 2.4 验证结果
- 编译 + ASCII 检查: 全部通过(纯 ASCII,0 非 ASCII 字节)
- `python tests/test_mcp.py`: 17 项断言全部 OK(initialize、9 工具、错误路径、202、204)
- 回归: `test_subscription.py` / `test_sub_no_ws.py` / `test_ws_push.py` 全部通过(路由改动无影响)
---
## 三、待讨论事项
- 是否需要 SSE 流式(长连接占线程池 worker,参考 WS 的处理;默认不做)
- 是否需要 `qmt_order` / `qmt_cancel`(依赖交易接口后续迭代)
- 目标 MCP 客户端确认(Claude Desktop / Cursor / DSH 等)