MCP 规范《Tools / Error Handling》把「未知工具」归为**协议错误**,
示例即为 {"code": -32602, "message": "Unknown tool: invalid_tool_name"}。
基座原先回 -32601(Method not found),语义偏 JSON-RPC 方法层。
- mcp_configParser.cpp: mcp_Tools::Call 改 -32601 → -32602,
消息改为 "Unknown tool: <name>"。
- 无需改 mcp_server.cpp 的 HandleToolsCall:其协议错误白名单
{-32700,-32600,-32601,-32602} 已含 -32602,分类逻辑不变。
- 同步更新 verify_mcp_frame.py 与 verify_stdio_bridge.py 的断言
(未知「方法」仍是 -32601,语义正确,不动)。
验证(重建基座 + 重启 CAD 后实测):
- 直连 TCP:{"error":{"code":-32602,"message":"Unknown tool: __no_such_tool__"}}
- verify_mcp_frame.py 4/4 PASS;verify_stdio_bridge.py 24/24 PASS
- 回归:verify_annotate.py 9/9、dwg_review --check 正常
- MCP Inspector 冒烟:tools/list 26 个工具、tools/call get_drawing_info 正常
66 lines
4.1 KiB
Markdown
66 lines
4.1 KiB
Markdown
---
|
||
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:-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` 用绝对路径,
|
||
避免依赖客户端的工作目录)。
|