Files
cad-agent/ai.memory/project_stdio_bridge.md
T

75 lines
5.0 KiB
Markdown
Raw Normal View History

---
name: stdio↔TCP 桥(MCP 标准化)
description: mcp_bridge 包——把标准 MCP 客户端的 stdio 桥接到 CAD 基座裸 TCP;含基座协议缺口、设计决策与用 MCP Inspector 验证的方法
type: project
---
# stdio ↔ TCP 桥(`mcp_bridge/`)
**状态:已实现并验证通过**(2026-10-14),且**已注册进 CodeBuddy**(2026-10-14)。
这是「MCP 标准化」路线的第二步(第一步是基座 B 最小集,见 `project_review_agent_impl.md`)。
## 已接入 CodeBuddy(用户级)
配置写在 `C:\Users\gjm\.codebuddy\.mcp.json`(user 作用域、推荐路径、免首连审批、
不污染仓库)——服务名 `cad-autocad`,`type: stdio`,`command` = 本机 Python314,
`args: ["-m","mcp_bridge"]`,`env` 带 `PYTHONPATH=D:\new-start\CadProject` +
`CAD_MCP_HOST/PORT`。**改配置后需重启 CodeBuddy 才生效**;工具以 `mcp__cad-autocad__<工具名>`
形式出现(26 个)。注意:同目录另有一个**空的**旧文件 `~/.codebuddy/mcp.json`(deprecated),
按优先级被 `.mcp.json` 遮蔽、无害。**前提:CAD 必须在跑(8080 监听)**,否则工具调用报错。
`codebuddy mcp list` 这个 CLI 子命令在本版本会去拉起主程序(撞端口 55187),别用它验证。
## 为什么需要它(非显而易见)
基座是**裸 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:-32602`(`"Unknown tool: <name>"`)→ 桥忠实透传 |
## 关键设计决策
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. **不擅自改写语义**:桥只做忠实透传,错误码一律由基座决定。
(**2026-10-14 修正**:基座原先对未知工具回 `-32601`,语义偏 JSON-RPC 方法层;
按 MCP 规范「未知工具属于**协议错误**,示例码 `-32602`」已改为 `-32602` +
`"Unknown tool: <name>"`。改动点:`mcp_configParser.cpp: mcp_Tools::Call`
—— 只需改码值,`mcp_server.cpp: HandleToolsCall` 的协议错误白名单
`{-32700,-32600,-32601,-32602}` 已含 `-32602`,分类逻辑无需动。)
## 验证方法(重要,含踩坑)
- `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` 用绝对路径,
避免依赖客户端的工作目录)。