---
date: 2026-05-18
tags: [internal-app-platform, mcp, protocol, p0-blocking-bug]
severity: critical
status: discovered-not-fixed
---

# MCP controller 不遵循 JSON-RPC 2.0 spec，Claude Code 实际跑不通

## 现象

员工拿到正确的 token + 正确格式的 `claude mcp add --transport http ffoa-apps ...` 命令，注册成功（`claude mcp list` 显示 ffoa-apps），但 Claude Code 反复报：

```
ffoa-apps MCP 工具在当前环境中不可用
```

`claude mcp get ffoa-apps` 抓到真实失败原因：

```json
POST /api/v1/internal-apps/mcp
body: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{...}}
response: {"ok":false,"error":{"code":"unknown_method","message":"不支持的 method \"initialize\""}}
```

Claude Code 的 MCP 客户端按 spec 走，握手第一步就发 `initialize`——服务端没实现，客户端判定 server 不合规，断开。**工具列表永远是空的。**

## 根因

`backend/src/modules/internal-app-platform/mcp.controller.ts` 整套自创了一份"MCP 风格"协议，跟 [MCP spec](https://spec.modelcontextprotocol.io/) 完全不兼容：

| 项目实现 | MCP spec 实际要求 |
|---|---|
| 只处理 `tools/list` / `tools/call` | 必须先处理 `initialize`（含 protocolVersion 协商 / serverInfo / capabilities） |
| 响应 `{"ok":true,"data":{...}}` | `{"jsonrpc":"2.0","id":N,"result":{...}}` |
| 错误 `{"ok":false,"error":{"code":"string","message":"..."}}` | `{"jsonrpc":"2.0","id":N,"error":{"code":<integer>,"message":"...","data"?:any}}` |
| `tools/list` 返回 `{name, description}` 仅两字段 | tool 必须含 `inputSchema`（JSON Schema） |
| `tools/call` 返回业务结果直挂 `data` | 必须返 `{"content":[{"type":"text","text":"..."}],"isError"?:bool}` |
| 无版本协商 | 必须返 `protocolVersion`（如 `"2024-11-05"`） |

## 为什么之前没发现

PoC 阶段所有"验证"都是手写 curl + 看 JSON 响应是否合预期。**没有人真的用 Claude Code 客户端走过端到端**——`testing/scripts/contract-check.ts` / 集成测试也都是 fetch 直调 endpoint，绕过 MCP 客户端。

09-test-scenarios.md 的 L3 PoC 剧本说"员工 Claude Code 里说 '帮我部署 hello'"，但 PoC 报告 `testing/reports/internal-app-platform-l3-phase1-podc-2026-05-13-success.md` 里实际验证的是 **deploy.sh 脚本 + webhook**，没截 Claude Code 调 deploy_prepare 的图。

## 正确修复

两条路径：

**A. 用 Anthropic 官方 SDK 重写控制器**（推荐）
- `npm i @modelcontextprotocol/sdk`
- 用 `Server` + `StreamableHTTPServerTransport` 接入
- 工具用 `server.tool(name, schema, handler)` 注册
- SDK 自动处理 initialize / protocolVersion / 错误码 / content 包装

**B. 手写一份合规的 JSON-RPC 2.0 路由**
- 实现 `initialize` / `tools/list` / `tools/call` / `ping`
- 响应严格 `{jsonrpc:"2.0", id, result/error}`
- 错误码用 JSON-RPC 标准（-32xxx）
- tools 加 inputSchema（参数 schema）
- callTool 结果包成 `{content: [{type:"text", text: JSON.stringify(data)}]}`

## 影响范围

- 整个 internal-app-platform 模块 **P0 阻塞**：员工**完全无法使用**，不管 token / mcpAddCommand 格式多对
- 当前 PR #421 修了"命令格式"层但底下 MCP 协议根本通不了
- PRD §F2.x 全部功能（list_apps / deploy_prepare / logs / env / destroy）实际不可用

## 验证方法

修完之后**必须**用真实 Claude Code CLI 走端到端：
1. shell 跑 `claude mcp add --transport http ...`
2. shell 跑 `claude mcp list` → 必须显示 `✓ Connected`（不是只 "Configured"）
3. 启动 `claude` → 输入"列出 ffoa-apps 的所有 MCP 工具"
4. 应该看到 5 个工具具体描述
5. 让 Claude 调 `list_apps` → 返回结果（即使是空数组）不报错

L3 测试报告必须**截图**含这 5 个工具被列出，不能用 curl 替代。

## 相关学习

- 协议级实现不能用 curl 验收——必须用目标客户端的真实实现
- 自创协议变体（`{ok,data}` vs `{jsonrpc,result}`）即使逻辑等价，也会被严格 spec-conformant 客户端拒绝
- AI 协作时代尤其要警惕"看起来工作"：测试覆盖率必须包括"用真实客户端跑一次"
