--- 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: "`)→ 桥忠实透传 | ## 关键设计决策 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: "`。改动点:`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` 用绝对路径, 避免依赖客户端的工作目录)。