Files
cad-agent/ai.memory/project_stdio_bridge.md
T
gjm f33d42afe9 feat(bridge): 新增 stdio↔TCP 桥,让标准 MCP 客户端可直连 CAD 基座
标准 MCP 客户端(Claude Desktop / CodeBuddy / MCP Inspector)只实现 stdio 与
Streamable HTTP,无法直连 cad_mcp_frame 的自定义裸 TCP。新增 mcp_bridge 包在
客户端与基座之间做协议适配,CAD 侧零改动:

- 合成基座缺失的生命周期:initialize(含协议版本协商、capabilities、serverInfo)
  与 ping;notifications/* 按规范不回包。
- tools/list、tools/call 原样转发到 127.0.0.1:8080,result 与顶层 error 忠实透传,
  仅把 id 改写回客户端原始 id。
- 并发:读线程即时应答 initialize/ping,单工作线程串行转发工具调用(保证顺序、
  慢工具不阻塞 ping)。
- stdout 只走 sys.stdout.buffer 并加锁,规避 Windows 的 \n→\r\n 翻译;
  诊断信息一律走 stderr。
- CAD 未启动时桥照常启动,工具调用回 -32603(可重试)。
- 入口:python -m mcp_bridge,支持 --host/--port/--timeout/--verbose/--print-config。

dwg_review/mcp_client.py 增加 raw_rpc()(_rpc 新增 normalize 开关),
返回基座原始响应 dict 供桥透传,避免重复实现 TCP 封装。

验证:
- verify_stdio_bridge.py 24/24 PASS(版本协商、通知不回包、ping、tools/list 与基座
  等价、tools/call、错误透传、-32700、string id、stdout 纯净性、死端口降级)。
- 真实标准客户端 MCP Inspector 冒烟通过:tools/list 拿到 26 个工具,
  tools/list --strict 退出码 0(无 schema 可移植性问题),
  tools/call get_drawing_info 返回标准 content + isError:false。
- 回归:verify_mcp_frame.py 4/4、verify_annotate.py 9/9、dwg_review --list-tools/--check 正常。
2026-10-14 00:44:15 +08:00

63 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: stdio↔TCP 桥(MCP 标准化)
description: mcp_bridge 包——把标准 MCP 客户端的 stdio 桥接到 CAD 基座裸 TCP;含基座协议缺口、设计决策与用 MCP Inspector 验证的方法
type: project
---
# stdio ↔ TCP 桥(`mcp_bridge/`)
**状态:已实现并验证通过**(2026-10-14)。这是「MCP 标准化」路线的第二步
(第一步是基座 B 最小集,见 `project_review_agent_impl.md`)。
## 为什么需要它(非显而易见)
基座是**裸 TCP 自定义传输**,标准 MCP 客户端(Claude Desktop / CodeBuddy / MCP Inspector)
只实现 **stdio** 与 **Streamable HTTP**,因此**无法直连基座**。桥在客户端与基座之间做协议适配,
**CAD 侧零改动**。
## 基座的协议缺口(实测,决定桥要补什么)
| 项 | 事实 |
| --- | --- |
| 连接模型 | 一个连接**只处理一帧**,响应后立即 `closesocket`(`TaskData` 析构)→ 桥每次请求新建连接 |
| 支持方法 | **只有** `tools/list` / `tools/call`;其余回 `-32601` |
| 生命周期 | **没有** `initialize` / `initialized` / `ping` → **必须由桥合成** |
| 通知 | 无 `id` 即不回包 |
| `tools/list` | 26 个工具,`name`/`description`/`inputSchema(type=object)` 已是标准 MCP 形状 → **可原样转发** |
| 未知工具 | 回**顶层** `error:-32601`(不是 `result.isError`)→ 桥忠实透传 |
## 关键设计决策
1. **位置**:顶层包 `mcp_bridge/`;TCP 复用 `dwg_review.mcp_client` 的 `raw_rpc()`
(新增:`_rpc(normalize=False)` 返回基座**原始响应 dict**,不做 error/isError 归一化,
这样桥才能把 `result` 与顶层 `error` 原样透传)。桥调用时 `retries=0`:
"发完即超时"语义有歧义,重试可能**重复执行写类工具**。
2. **并发**:**读线程 + 单工作线程队列**。`initialize`/`ping` 在读线程即时应答,
不会被慢工具阻塞;`tools/list`/`tools/call` 由单工作线程**串行**转发,保证调用顺序。
3. **stdout 纯净性**:只用 `sys.stdout.buffer.write(... + b"\n")` + `flush()`(加锁)。
用 `buffer` 而非文本流,**规避 Windows 的 `\n`→`\r\n` 翻译**;所有日志只走 stderr。
4. **CAD 未启动**:桥照常启动,`initialize`/`ping` 正常,`tools/list` 回 `-32603`(可重试)。
5. **不擅自改写语义**:基座对未知工具回顶层 `-32601`("Method not found")语义上偏 JSON-RPC
方法层,严格说应是 `-32602` 或 `result.isError`。桥**不改写**;要修应改基座
`mcp_server.cpp: HandleToolsCall` 的码值分类。
## 验证方法(重要,含踩坑)
- `verify_stdio_bridge.py`(仓库根):子进程拉起桥,24 项断言全绿(版本协商、通知不回包、
ping、`tools/list` 与基座**等价**、`tools/call`、错误透传、`-32700`、string id、
stdout 纯净性、死端口降级)。
- **用真实标准客户端(MCP Inspector)验证**——这是"标准化"的终极证明:
```bash
npx --yes @modelcontextprotocol/inspector --cli \
--config <配置文件> --server cad-autocad --method tools/list --strict
```
- 实测:`tools/list` 拿到 26 个工具;`--strict` 退出码 **0**(无 schema 可移植性问题);
`--method tools/call --tool-name get_drawing_info` 返回标准 `content` + `isError:false`。
- **踩坑**:Inspector CLI 在 Windows 上**参数解析很别扭**——用 `-- python -m mcp_bridge`
会把 `--method` 一起当成 target 传给 python(argparse 报错 → "Connection closed");
不用 `--` 又报 "Target is required"。
**可靠做法:写一个 `{"mcpServers":{...}}` 配置文件,用 `--config` + `--server`**。
这也顺带验证了用户实际要粘贴的配置格式。
- `python -m mcp_bridge --print-config` 可直接生成该配置片段(`PYTHONPATH` 用绝对路径,
避免依赖客户端的工作目录)。