This is an automated email from the ASF dual-hosted git repository. FreeOnePlus pushed a commit to branch agent/remove-internal-audit-report in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git
commit d223925be544f4b5b998ada15c083b3d71f43bfb Author: FreeOnePlus <[email protected]> AuthorDate: Wed Jul 29 18:18:49 2026 +0800 chore: remove internal MCP audit report --- MCP-2026-07-28-AUDIT-REPORT.zh-CN.md | 1020 ---------------------------------- 1 file changed, 1020 deletions(-) diff --git a/MCP-2026-07-28-AUDIT-REPORT.zh-CN.md b/MCP-2026-07-28-AUDIT-REPORT.zh-CN.md deleted file mode 100644 index 57c504e..0000000 --- a/MCP-2026-07-28-AUDIT-REPORT.zh-CN.md +++ /dev/null @@ -1,1020 +0,0 @@ -# Apache Doris MCP Server 对 MCP 2026-07-28 的完整审计报告 - -> 审计日期:2026-07-29 -> 审计对象:`apache/doris-mcp-server` -> 仓库基线:`0e843ae3432115ea5895fbd90a97d495c77f911c`(`master`,2026-06-11) -> 协议基线:MCP `2026-07-28`,规范提交 `5f5440bb26a62e2cf3440b92da5a667efa03b267` -> Python SDK 基线:`mcp` `v2.0.0`,提交 `6f69a3758ebf2ee55ce050f58b470ce11af71133` - -## 一、执行摘要 - -### 1. 最终判断 - -当前 Doris MCP Server **不兼容 MCP 2026-07-28**。这不是“缺少少数新增能力”,而是协议生命周期、传输语义、消息结构、错误模型和鉴权边界都仍停留在旧协议。 - -更严重的是,真实 HTTP 探针证明: - -1. 服务器要求旧版 `Mcp-Session-Id`,无法接受新版无会话请求; -2. 合法的新版 `server/discover` 请求进入旧 SDK 后,会产生未捕获的协议解析异常; -3. 请求返回 HTTP 500 后,服务进程随 AnyIO `TaskGroup` 一起退出; -4. 后续 `/health` 也无法连接。 - -因此,当前版本面对会主动探测 2026-07-28 的新客户端时,不只是“协商失败”,而是存在**远程拒绝服务风险**。 - -### 2. 发布建议 - -在完成迁移和验收前: - -- 不应宣称支持 MCP 2026-07-28; -- 不应把当前 HTTP 模式作为面向公网或不可信网络的生产服务发布; -- 不应继续以“production-ready”“100%”等措辞描述当前质量门禁; -- 可以继续将现版本定位为旧协议兼容版,但必须限定支持范围,并先修复异常请求导致进程退出、默认凭证、CORS 和查询参数令牌问题。 - -### 3. 风险概览 - -本报告的 P0/P1/P2 表示修复与发布优先级,不等同于 CVSS 定级。 - -| 等级 | 数量 | 代表性问题 | -|---|---:|---| -| P0 / 发布阻断 | 4 | 新版合法请求可导致进程退出;完全不支持 2026-07-28;任意 Origin + 凭证式 CORS;已知固定默认令牌与默认匿名访问组合 | -| P1 / 高优先级 | 10 | OAuth audience/resource 边界、外部 OAuth Bearer 链路断裂、错误被包装成成功、明文令牌多进程写入、健康检查失真、客户端包入口损坏、无 CI/一致性门禁 | -| P2 / 中优先级 | 10 | 重复架构、类型和 lint 债务、部署配置冲突、依赖分层、文档漂移、时间 API 弃用等 | - -### 4. 已有优点 - -本报告不是对项目已有工作的否定。当前代码有几项值得保留的基础: - -- Doris OAuth 已实现 PKCE S256、受保护资源元数据、scope、刷新令牌轮换、吊销和速率限制等机制; -- SQL 安全单元测试本次为 `31 passed`; -- OAuth/鉴权策略聚焦测试本次为 `97 passed`; -- 当前 `0.6.1` 已不在 CVE-2025-66335 的已知受影响版本范围内; -- Dockerfile 使用非 root 用户; -- 工具列表当前由固定顺序构造,满足新版“确定性顺序”的方向; -- 包可构建,锁文件可校验。 - -问题主要在于:这些能力尚未被一个可靠的协议核心、鉴权边界和持续质量门禁统一起来。 - ---- - -## 二、审计范围、方法与限制 - -### 1. 审计范围 - -本次覆盖: - -- MCP 2026-07-28 官方规范、Schema、变更日志、弃用清单; -- 官方 Python SDK `v2.0.0` 和迁移指南; -- 官方 MCP Conformance Suite 的服务端无状态场景; -- 当前仓库的协议入口、STDIO、Streamable HTTP、多进程模式; -- tools、resources、prompts、错误语义和缓存语义; -- 静态令牌、JWT、外部 OAuth、Doris OAuth; -- 数据库访问和 SQL 安全边界; -- 配置、Docker、打包、测试、lint、类型检查和文档; -- 实际构建、实际启动和 HTTP 协议探针。 - -### 2. 证据原则 - -结论分为三类: - -- **已验证**:由本地测试、构建、静态工具或真实 HTTP 请求复现; -- **代码确认**:由当前提交中的明确控制流确认; -- **待运行环境验证**:需要真实 Doris、Redis、代理或浏览器环境才能最终确认。 - -### 3. 限制 - -- 本次没有可用的真实 Doris 集群,因此没有验证真实查询正确性、性能、FE/BE 故障恢复和 Doris 权限映射; -- 官方 Conformance npm 包在本机拉取时没有返回,未能完成官方套件执行;已使用精确 Schema 对照和真实线级探针补充验证; -- Bandit 的 B608 等结果是审计线索,不等同于已证明存在 SQL 注入; -- 本报告没有修改业务代码,只新增审计报告。 - ---- - -## 三、MCP 2026-07-28 到底改变了什么 - -官方资料: - -- [MCP 2026-07-28 规范](https://modelcontextprotocol.io/specification/2026-07-28) -- [2026-07-28 变更日志](https://modelcontextprotocol.io/specification/2026-07-28/changelog) -- [规范正式 Release](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2026-07-28) -- [Streamable HTTP 传输规范](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) -- [授权规范](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization) -- [授权安全注意事项](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerations) -- [Python SDK v2.0.0](https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0) -- [Python SDK v2 迁移指南](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md) -- [官方 Conformance Suite](https://github.com/modelcontextprotocol/conformance) - -### 1. 九项主变化 - -| 变化 | 2025-11-25 及以前 | 2026-07-28 | -|---|---|---| -| 会话 | HTTP 可使用 `Mcp-Session-Id` | 删除协议级会话和该 Header | -| 初始化 | `initialize` + `notifications/initialized` | 删除握手;每个请求自描述 | -| 请求元数据 | 初始化时协商 | 每个请求 `_meta` 必须带协议版本和客户端能力 | -| 服务发现 | 无统一前置发现 | 服务器必须实现 `server/discover` | -| 服务端通知 | GET/SSE、资源订阅等多种路径 | 统一为长 POST `subscriptions/listen` | -| 工具性 RPC | `ping`、`logging/setLevel` 等 | 删除;日志级别改为请求级 `_meta` | -| Tasks | 核心实验能力 | 移出核心,成为 `io.modelcontextprotocol/tasks` 扩展 | -| 服务端反向请求 | roots/sampling/elicitation 由服务端发起 | 改为 MRTR:`InputRequiredResult` + 原请求重试 | -| 结果与恢复 | 普通结果无统一类型,SSE 可恢复 | 所有结果有 `resultType`;删除 Last-Event-ID 重放 | - -### 2. 每个请求必须携带的新版元数据 - -新版请求 `_meta` 的关键字段是完整命名空间键: - -```json -{ - "_meta": { - "io.modelcontextprotocol/protocolVersion": "2026-07-28", - "io.modelcontextprotocol/clientCapabilities": {}, - "io.modelcontextprotocol/clientInfo": { - "name": "example-client", - "version": "1.0.0" - } - } -} -``` - -前两个字段为必需;`clientInfo` 为 SHOULD。HTTP 中 `_meta` 协议版本必须与 `MCP-Protocol-Version` Header 一致。 - -### 3. 新版服务端必须处理的基础语义 - -- 必须实现 `server/discover`; -- 普通结果必须包含 `resultType: "complete"`; -- 需要客户端补充信息时返回 `resultType: "input_required"`; -- HTTP POST 必须校验 `Mcp-Method`,带名称的请求还要校验 `Mcp-Name`; -- Header 与请求体不匹配返回 `HeaderMismatch` `-32020`; -- 缺少必要客户端能力返回 `-32021`; -- 不支持协议版本返回 `-32022`,并告知支持版本; -- `tools/list`、`prompts/list`、`resources/list`、`resources/read`、`resources/templates/list` 等可缓存结果必须带 `ttlMs` 和 `cacheScope`; -- tools 应保持确定性顺序; -- JSON Schema 按 2020-12 完整关键字处理,并限制 `$ref`、组合关键字带来的资源消耗。 - -### 4. 授权侧变化 - -- 授权服务器 SHOULD 在授权响应中返回 RFC 9207 `iss`; -- 客户端看到 `iss` 后必须与记录的 issuer 比对; -- Dynamic Client Registration 的 MCP 客户端必须提供适当的 `application_type`; -- 客户端凭证必须按 authorization server issuer 隔离; -- DCR 已进入弃用路径,新实现应优先 Client ID Metadata Documents; -- Bearer token 不得放进 URI query; -- Resource Server 不得接受不是为自己签发的 access token。 - -### 5. 官方 Python SDK v2 的直接影响 - -当前项目使用低层 `Server` API,因此迁移不是只改依赖版本: - -- 旧低层装饰器被移除,改为构造器 `on_*` handlers; -- handler 接收 `(ctx, params)`; -- handler 返回完整协议 Result 类型; -- 字段名改为 Python `snake_case`; -- SDK 不再自动替应用包装结果; -- handler 抛异常不会再自动变成工具的 `isError: true`; -- 推荐用统一高层 `MCPServer`,或按 v2 低层 API 显式实现错误和结果语义; -- v2 Streamable HTTP 可在同一实现中处理新版无状态请求和旧版握手兼容。 - ---- - -## 四、当前项目基线 - -### 1. 仓库状态 - -```text -branch: master -HEAD: 0e843ae3432115ea5895fbd90a97d495c77f911c -pull: Already up to date -``` - -仓库没有 `.codegraph/`,因此本次按普通源码审计执行。 - -### 2. 依赖和协议版本 - -- 项目版本:`0.6.1`; -- Python:`>=3.12`; -- 声明依赖:`mcp>=1.8.0,<2.0.0`; -- 锁定版本:`mcp==1.9.3`; -- 运行时旧服务器协商到:`2025-03-26`; -- 官方支持 2026-07-28 的 Python SDK:`2.0.0`。 - -结论:依赖约束明确排除了实现新协议的 SDK 主版本。 - -### 3. 当前协议结构 - -```mermaid -flowchart TD - A["CLI: stdio / http"] --> B["main.py 单进程实现"] - A --> C["multiworker_app.py 多进程实现"] - B --> D["旧版 mcp.server.lowlevel.Server"] - C --> E["另一份旧版 Server 与 handlers"] - B --> F["Resources / Prompts / Tools managers"] - C --> F - F --> G["Doris 连接池与 SQL 执行"] - B --> H["Static Token / JWT / OAuth 中间件"] - C --> H -``` - -单进程和多进程入口各自复制了服务器构造、协议 handlers、鉴权和 ASGI 组装。两者已经发生行为漂移:单进程 `stateless=False`,多进程 `stateless=True`,但两者都仍是 SDK 1.9 的旧协议实现。 - ---- - -## 五、MCP 2026-07-28 逐项符合性矩阵 - -| 检查项 | 规范要求 | 当前状态 | 判定 | -|---|---|---|---| -| `server/discover` | MUST | 没有实现;合法请求可导致进程退出 | ❌ P0 | -| 无协议会话 | 删除 `Mcp-Session-Id` | 单进程强制旧会话;多进程仍由旧 SDK 处理 | ❌ | -| 删除初始化握手 | 每个请求自描述 | 仍依赖 `initialize`/`initialized` | ❌ | -| 请求 `_meta` | 版本、客户端能力必需 | 无新版元数据解析/验证 | ❌ | -| Result `_meta` | serverInfo SHOULD | 无 | ❌ | -| `resultType` | 所有结果必需 | 全仓未实现 | ❌ | -| `Mcp-Method` / `Mcp-Name` | HTTP POST 必须校验 | 未实现 | ❌ | -| 标准协议错误 | `-32020/-32021/-32022` | 未实现 | ❌ | -| `subscriptions/listen` | 新通知统一入口 | 未实现;仍保留 GET 兼容路径 | ❌ | -| HTTP GET | 新版已移除 | 仍主动改写/兼容 GET | ❌ | -| SSE resumability | 已移除 | 由旧 transport 语义主导 | ❌ | -| 缓存提示 | 指定结果必须有 `ttlMs/cacheScope` | 未实现 | ❌ | -| tools 顺序 | SHOULD 确定 | 手工固定列表 | ✅ | -| MRTR | 需要客户端输入时采用 | 未实现;当前业务暂未依赖 | ⚠️ | -| Tasks 扩展 | 核心已移除 | 当前未实现 Tasks | ✅ N/A | -| JSON Schema 2020-12 | 完整关键字和资源边界 | 手工 schema,未见系统验证和资源上限 | ⚠️ | -| DCR `application_type` | 客户端必须提供,服务端应校验 | 注册实现未校验/持久化 | ❌ | -| 授权响应 `iss` | AS SHOULD 返回 | 未返回 | ❌ | -| audience/resource | 只接受为本 RS 签发的 token | Doris OAuth 存在 resource 校验缺口;外部 OAuth 依赖 userinfo | ❌ | -| Bearer query | MUST NOT | 多条链路接受 query token | ❌ | -| Origin 防护 | 必须校验,防 DNS rebinding | 任意 Origin 被反射并允许 credentials | ❌ P0 | -| 旧版兼容 | 可兼容早期客户端 | `2025-03-26` 初始化可成功 | ✅ 有限 | - ---- - -## 六、关键缺陷详解 - -### P0-1:合法新版请求可让 HTTP 服务进程退出 - -**状态:已验证。** - -复现流程: - -1. 启动当前 HTTP 服务; -2. 旧版 `initialize` 成功,返回 `protocolVersion: 2025-03-26`; -3. 发送符合 2026-07-28 结构的 `server/discover`; -4. 服务器返回 HTTP 500:`No response received`; -5. 旧 SDK 对未知请求产生多分支 Pydantic `ValidationError`; -6. 异常穿透 AnyIO `TaskGroup`,Uvicorn 进程退出; -7. 再访问 `/health`,连接失败。 - -在没有旧会话 ID 时,同一新版请求先返回 HTTP 400 “Missing session ID”,响应仍下发 `mcp-session-id`。这同时证明传输层仍以旧会话为核心。 - -**影响:** - -- 默认匿名 HTTP 服务只要网络可达即可触发; -- 开启鉴权后,任意合法客户端凭证持有者仍可能触发; -- 新客户端的正常兼容性探测本身就可能造成服务中断; -- 健康检查和进程编排会进入重启循环。 - -**修复要求:** - -- 首选直接迁移 SDK v2,让 `server/discover` 由新版协议栈处理; -- 在迁移完成前,协议解析失败必须被请求边界捕获,不能终止 ASGI lifespan; -- 对未知方法返回 JSON-RPC `Method not found`,而不是传播模型解析异常; -- 加一条回归测试:任意未知或新版方法都不得影响下一次 `/health` 和 `tools/list`。 - -### P0-2:协议核心完全不具备 2026-07-28 互操作性 - -**状态:代码确认 + 真实探针验证。** - -主要证据: - -- [`pyproject.toml`](./pyproject.toml) 明确限制 `mcp<2.0.0`; -- [`main.py`](./doris_mcp_server/main.py) 使用旧版低层装饰器和 `InitializationOptions`; -- 单进程 HTTP 使用 `stateless=False`; -- 多进程的 `stateless=True` 只是 SDK 1.9 的传输配置,并不会自动实现 2026-07-28; -- 代码中没有 `server/discover`、新版 `_meta`、`resultType`、`subscriptions/listen`、`ttlMs`、`cacheScope`、MRTR 和新版错误码。 - -**影响:** - -- 新客户端无法使用; -- 协议协商、缓存、安全代理、通知和错误处理均不符合最新版; -- 继续在旧低层 API 上补丁式兼容,会扩大下一轮迁移成本。 - -**修复要求:** - -- 升级 `mcp>=2.0,<3.0`; -- 采用 SDK v2 高层 `MCPServer`,或完整迁移低层 `on_*` handlers; -- 以新版无状态路径为主,利用 SDK 的旧版兼容能力保留现有客户端; -- 删除所有依赖 SDK 私有实现的 monkey patch。 - -### P0-3:Origin/CORS 允许不可信网页携带凭证调用 MCP - -**状态:已验证。** - -当前 [`mcp_cors.py`](./doris_mcp_server/auth/mcp_cors.py) 会反射任意 Origin;两个 ASGI 入口还配置了: - -```python -allow_origins=["*"] -allow_credentials=True -``` - -真实预检请求: - -```text -Origin: https://evil.example -``` - -得到: - -```text -Access-Control-Allow-Origin: https://evil.example -Access-Control-Allow-Credentials: true -``` - -同时允许 `Authorization`、`content-type` 和 MCP 会话 Header。 - -**影响:** - -- 违反 Streamable HTTP 对 Origin 校验和 DNS rebinding 防护的要求; -- 浏览器存在可用凭证时,恶意页面可能跨源调用 MCP; -- 配合监听 `0.0.0.0`、弱/固定令牌或本地代理,会把本地数据库能力暴露给网页。 - -**修复要求:** - -- 默认只绑定 `127.0.0.1`; -- 默认拒绝所有跨域 Origin; -- 显式 allowlist,且只允许可信 HTTPS Origin; -- 不需要浏览器凭证时关闭 `allow_credentials`; -- 使用 SDK v2 的 transport security / DNS rebinding 防护; -- 测试 `Origin: null`、攻击域、同源、伪造 Host 和反向代理场景。 - -### P0-4:固定默认令牌、默认匿名访问和数据库工具形成危险组合 - -**状态:代码确认。** - -当前存在: - -- 鉴权功能默认全部关闭,启动日志明确提示匿名访问; -- [`token_manager.py`](./doris_mcp_server/auth/token_manager.py) 内置可预测的 admin/analyst/readonly 固定令牌; -- 仓库跟踪的 `tokens.json` 保存令牌记录; -- legacy 配置存在 `default_secret`; -- README 直接展示固定默认令牌; -- 文档大量使用 `0.0.0.0` 部署方式。 - -即使单项是为了本地快速体验,组合后仍会形成生产误配置陷阱。 - -**修复要求:** - -- 删除全部可工作的默认令牌和默认 secret; -- 首次启用静态令牌模式时强制显式配置高熵密钥; -- 非 loopback 地址启动且无鉴权时直接拒绝启动,除非显式危险开关; -- `tokens.json` 只能作为无凭证模板,不能包含可用 token; -- 文档把匿名模式标成仅限本机开发。 - ---- - -### P1-1:外部 OAuth 的 Bearer 认证链路字段不一致 - -**状态:代码确认。** - -HTTP 中间件将 `Authorization: Bearer ...` 保存为: - -```python -auth_info["token"] -``` - -但外部 OAuth 分支只接受: - -```python -auth_info["access_token"] -``` - -或 `code + state`。因此普通 Bearer token 进入外部 OAuth 流程时无法被识别。 - -**修复要求:** - -- 只定义一种规范化后的内部凭证结构; -- 在鉴权边界统一解析 Header,后续 provider 不再自行猜字段; -- 用端到端测试覆盖 Bearer → middleware → provider → AuthContext。 - -### P1-2:OAuth audience/resource 边界不完整 - -**状态:代码确认。** - -外部 OAuth 主要通过 provider 的 userinfo 接口判断 token 有效,没有证明 token 的 audience/resource 是 Doris MCP Server。 - -Doris OAuth 的授权事务允许 resource 是 MCP resource 或 issuer,但 `authenticate_access_token` 没有再次确认 token record 的 resource 与当前 MCP resource 一致。 - -**影响:** - -- 可能接受为其他资源签发的 token; -- 多个 Resource Server 共用 Authorization Server 时出现 confused deputy; -- 违反 MCP OAuth 的 resource indicator 边界。 - -**修复要求:** - -- token 入口强制校验 issuer、audience/resource、scope、expiry、revocation; -- access token 记录必须绑定规范化后的 MCP resource URI; -- 不以 userinfo 成功代替 audience 校验; -- 用两个不同 resource 的集成测试证明 token 不可串用。 - -### P1-3:OAuth 2026 细节未跟进 - -**状态:代码确认。** - -- DCR 未验证或持久化 `application_type`; -- 授权成功和错误重定向均不返回 RFC 9207 `iss`; -- token endpoint 没有显式校验请求中的 `resource` 与授权码绑定值; -- 仍只围绕 DCR 设计,没有 Client ID Metadata Documents 路线。 - -**修复要求:** - -- 校验 `application_type` 与 redirect URI 规则; -- 所有授权响应增加 `iss`; -- 授权码兑换再次校验 resource; -- 保留 DCR 兼容,但增加 Client ID Metadata Documents; -- issuer 变化时让客户端重新注册,不复用旧凭证。 - -### P1-4:Bearer 和管理令牌可通过 query string 传递 - -**状态:代码确认。** - -至少三条链路接受 query token: - -- MCP 请求 `?token=...`; -- MCP auth middleware 的 legacy query token; -- 管理页面 `?admin_token=...`。 - -README 也给出管理令牌 query 示例。 - -**影响:** - -令牌会进入浏览器历史、Referer、代理日志、APM、服务器 access log、截图和复制链接。 - -**修复要求:** - -- 删除所有 query token 入口; -- 只接受 `Authorization: Bearer` 或专用管理 Header; -- 管理 UI 改为一次性表单输入并只保存在内存; -- 对旧 query 入口返回明确弃用错误,不能继续静默接受。 - -### P1-5:业务错误被伪装成协议成功 - -**状态:代码确认。** - -当前 handlers 中: - -- `list_resources`、`list_tools`、`list_prompts` 捕获异常后返回空列表; -- resource 和 prompt 异常常被包装为 JSON 字符串内容; -- tool manager 捕获大量异常后返回错误 JSON 字符串; -- 外层仍返回普通 `TextContent`,没有可靠设置 `isError: true`。 - -**影响:** - -- 客户端误把故障当成“没有资源/工具”; -- Agent 可能把错误 JSON 当作业务数据继续推理; -- 监控无法区分空结果、权限拒绝、Doris 不可达和服务器异常; -- SDK v2 取消自动错误包装后,迁移时问题会更明显。 - -**修复要求:** - -- 建立统一错误分类:协议错误、鉴权错误、参数错误、Doris 错误、内部错误; -- list 失败不能返回空列表; -- 工具业务失败返回 `CallToolResult(is_error=True, ...)`; -- 非预期异常转为稳定的内部错误 ID,详细堆栈只写服务端日志; -- 每类错误建立契约测试。 - -### P1-6:令牌持久化不适合多进程 - -**状态:代码确认。** - -静态令牌以 JSON 明文保存;多处直接: - -```python -open(path, "w") -``` - -没有文件锁、临时文件、`fsync`、原子 `os.replace` 或版本冲突控制。多 worker 各自持有 manager 并可能同时写同一文件。 - -另有保存路径会把无法恢复的旧 raw token 替换为类似 `<existing_token_hash_...>` 的占位文本,持久化语义不一致。 - -**影响:** - -- 并发管理操作可能丢更新或损坏文件; -- 容器重启和横向扩容无法保持一致状态; -- 明文 token 和可能的数据库口令扩大泄露面。 - -**修复要求:** - -- 生产模式使用 SQLite/PostgreSQL/Redis 等统一存储; -- 只存不可逆 token digest,展示值仅在创建时返回一次; -- 单机文件方案至少采用文件锁和原子替换; -- 数据库密码进入 Secret Manager/KMS,不写 token JSON; -- 所有 worker 使用同一吊销和权限状态。 - -### P1-7:健康检查只证明 HTTP 进程存在,不证明服务可用 - -**状态:已验证。** - -在 Doris 初始化失败时,`/health` 仍返回: - -```json -{"status": "healthy", "service": "doris-mcp-server"} -``` - -**影响:** - -- Kubernetes/Docker 会把不可查询的实例送入流量; -- 数据库故障无法触发正确摘流; -- 当前新版协议异常导致进程退出前,也没有协议能力检查。 - -**修复要求:** - -- `/live`:只检查进程/event loop; -- `/ready`:检查配置归一化、Doris 最小探针、鉴权依赖和协议服务就绪; -- `/health` 可汇总但必须标注 degraded/unready; -- 对依赖探针增加短超时和缓存,避免健康检查压垮 Doris。 - -### P1-8:发布的 `doris-mcp-client` 命令不可运行 - -**状态:已验证。** - -`pyproject.toml` 声明: - -```toml -doris-mcp-client = "doris_mcp_server.client:main" -``` - -实际客户端在 `doris_mcp_client/client.py`,构建 wheel 只包含 `doris_mcp_server`。安装 wheel 后执行命令得到: - -```text -ModuleNotFoundError: No module named 'doris_mcp_server.client' -``` - -**修复要求:** - -- 将 entry point 改到真实模块; -- 明确把 `doris_mcp_client` 纳入 wheel; -- CI 中对新建虚拟环境安装 wheel,并运行两个 CLI 的 `--help` smoke test。 - -### P1-9:测试套件不是可重复的发布门禁 - -**状态:已验证。** - -完整 pytest: - -```text -384 collected -319 passed -45 failed -12 skipped -8 errors -267 warnings -coverage: 36% -``` - -失败构成: - -- 45 个 SQL injection API 测试硬依赖外部已启动服务,没有 fixture 或条件 skip; -- 8 个 end-to-end setup error 来自 mock config 未执行有效鉴权配置归一化; -- `pyproject.toml` 写的是 `testpaths = ["tests"]`,实际目录是 `test`,pytest 只能 fallback; -- 核心入口覆盖率低:`main.py` 23%、`multiworker_app.py` 22%、`tools_manager.py` 36%。 - -仓库也没有 `.github/workflows`。 - -**修复要求:** - -- 把纯单元、组件、真实 Doris、外部服务安全测试分成明确 marker; -- 外部测试要么自行启动依赖,要么明确 skip,不能把“连接失败”算测试失败; -- 修复 `testpaths`; -- 为 HTTP 生命周期、协议探针、异常隔离和鉴权矩阵补测试; -- 增加 GitHub Actions:Python 3.12、锁文件、测试、ruff、mypy、bandit、wheel smoke、conformance。 - -### P1-10:私有 monkey patch 与双实现让协议升级非常脆弱 - -**状态:代码确认。** - -`main.py` 和 `multiworker_app.py` 都会: - -- 修改 `typing._check_generic`; -- 删除已导入的 MCP 模块; -- 重新加载模块以兼容 `RequestContext`; -- 复制大段相同的 Server/handler 构造。 - -这是对 Python 和 SDK 私有实现的全局进程级修改,在 Uvicorn、测试、插件或其他库已经导入 MCP 时尤其危险。 - -**修复要求:** - -- 删除全部 monkey patch; -- 严格使用 SDK 公共 API; -- 单进程/多进程共享一个 `create_server()` 和一个 `create_asgi_app()`; -- worker 数只影响部署,不应改变协议语义。 - ---- - -## 七、工程、部署与维护问题 - -### P2-1:静态质量债务已经妨碍安全迁移 - -本次结果: - -```text -ruff src: 4,297 errors(3,754 可自动修复) -mypy: 889 errors in 37 files(检查 47 个源码文件) -bandit: 107 findings - High 3 / Medium 65 / Low 39 -``` - -Ruff 主要是空白、旧 typing 写法、未使用 import、import 顺序和未使用变量。Mypy 大量报错意味着协议类型、URI、handler 返回值和配置对象没有可靠静态边界。 - -Bandit 中 65 条 B608 是动态 SQL 审计线索;3 条 High 是 MD5 用于缓存/追踪标识,不能直接解释成密码学漏洞。应逐条分诊,而不是把工具总数等同于可利用漏洞。 - -建议采用“先基线、再收紧”: - -1. 机械格式问题一次性修复; -2. 新增代码要求 ruff 零新增; -3. 先把协议入口、鉴权、query executor 设为 mypy strict; -4. 再逐模块消除存量; -5. Bandit 对确认安全的标识用途写精确注释,对 SQL 构造做 taint 审计和真实回归。 - -### P2-2:数据库与工具层职责过重 - -核心文件体量很大: - -- `tools_manager.py` 约 1,926 行; -- `db.py` 约 2,246 行; -- `schema_extractor.py` 约 2,176 行; -- `security.py` 约 1,559 行; -- `main.py` 约 1,030 行。 - -工具 schema、注册逻辑、执行分派、错误包装和审计混杂;工具定义在装饰器和 `list_tools` 中存在多份来源,容易产生 schema 与执行器漂移。 - -建议建立单一 Tool Definition Registry,由同一对象生成: - -- MCP tool schema; -- 参数验证模型; -- 权限策略; -- 执行 handler; -- 审计字段; -- 文档。 - -### P2-3:Docker Compose 存在可直接导致部署失败的配置问题 - -代码确认: - -- MCP healthcheck 请求 `localhost:8082/health`,服务实际监听 3000; -- metrics 配置和端口映射不一致; -- MCP 与 Grafana 存在主机端口 3000 冲突; -- Compose 固定写入 Doris、Redis、Grafana 弱密码; -- 多个镜像使用可变 `latest`; -- Doris 镜像版本较旧。 - -建议: - -- 端口和环境变量只保留单一来源; -- secrets 改用 `.env` 模板 + Docker Secret/Kubernetes Secret; -- 镜像固定 digest 或明确版本; -- CI 启动 Compose 并执行 readiness; -- `docker compose config` 和端口冲突检查加入门禁。 - -### P2-4:运行时依赖与开发依赖混杂 - -`pytest`、`pytest-cov`、`pytest-asyncio` 等测试包被放进运行时依赖,同时又存在开发依赖重复。 - -影响: - -- wheel 体积和供应链面增加; -- 用户安装服务时带入不必要工具; -- 锁文件和漏洞扫描噪音扩大。 - -应把测试、lint、build 工具全部放到 dev/test dependency group。 - -### P2-5:版本、命令和文档漂移 - -已发现: - -- package 是 0.6.1,README 安装示例仍有 0.6.0; -- 配置中的 server version 仍有 0.4.1; -- 真实旧初始化响应的 `serverInfo.version` 是 SDK 1.9.3,而不是产品版本; -- Makefile `start-sse` 调用 CLI 不支持的 `sse` transport; -- project URL 声明 CHANGELOG,但仓库无 `CHANGELOG.md`; -- README 对生产成熟度的表述与当前失败门禁不一致。 - -版本必须来自一个构建时单一来源,并在 `server/discover` / legacy serverInfo / CLI / package metadata 中一致。 - -### P2-6:`debug=True` 不适合生产 - -两个 Starlette 应用都设置 `debug=True`,同时部分路径把原始异常文本返回客户端。 - -应默认关闭 debug;用相关 ID 连接客户端安全错误和服务端完整堆栈。 - -### P2-7:GET 兼容和宽路径匹配扩大协议面 - -当前为了 Dify 兼容会重写 MCP GET,并接受 `/mcp/...`。新版已移除 GET 入口。 - -建议: - -- 新版端点只接受规范的方法和精确路径; -- 旧 Dify 兼容放到独立、显式启用的 legacy adapter; -- 不让 legacy 行为污染核心协议处理器。 - -### P2-8:没有分页和合理缓存策略 - -大型 Doris 集群的表、视图、分区、物化视图和 schema 可能很大。当前 list 类接口没有形成明确分页策略,新版 `ttlMs/cacheScope` 也未实现。 - -建议: - -- 对可分页协议接口实现 cursor; -- 对业务工具的超大列表设计 limit/cursor/filter; -- 元数据结果可使用短 TTL; -- 任何受用户权限影响的结果必须 `cacheScope: "private"`; -- 缓存键必须包含 principal、scope、Doris endpoint 和数据库上下文。 - -### P2-9:监控工具存在内部网络访问面,需要约束 - -监控功能会根据 Doris 元数据访问 FE/BE HTTP 地址。若这些地址或配置可被低可信输入影响,可能形成 SSRF/内部探测能力。 - -本次未证明可利用,但应: - -- 只允许已配置的 Doris 节点; -- 拒绝 link-local、metadata service 和非预期协议; -- 设置连接/读取超时、响应体上限和重定向策略; -- 把监控类工具纳入单独权限 scope。 - -### P2-10:弃用 API 和告警积累 - -测试中出现 267 条 warning,其中包含 `datetime.utcnow()` 弃用。应统一使用带时区的 UTC 时间,避免 OAuth expiry、审计时间和缓存 TTL 的时区错误。 - ---- - -## 八、安全专项判断 - -### 1. SQL 注入 - -审慎结论: - -- 当前 `0.6.1` 已不在 CVE-2025-66335 公布的受影响范围; -- 聚焦 SQL 安全单元测试 `31 passed`; -- 但 API 级注入测试依赖外部进程,本次完整套件中 45 项全部失败,不能作为可重复门禁; -- Bandit 仍标出 65 个动态 SQL 构造点,其中不少已有 identifier 校验或 quoting,不能直接判成漏洞。 - -所以正确表述不是“已发现 65 个 SQL 注入”,也不是“SQL 注入已经彻底解决”,而是: - -> 已有修复和单元防线,但缺少自包含的真实 API 回归门禁,动态标识符和管理 SQL 仍需按数据流逐项审计。 - -建议新增: - -- 标识符白名单与转义的 property-based tests; -- 每个动态 SQL 构造的 source → validation → sink 清单; -- 真实 Doris 容器的只读和写入权限矩阵; -- 多语句、注释、Unicode、编码、反引号、模板变量和 SSRF 联动测试; -- 禁止凭字符串前缀判断只读语句,使用解析器或强制只读数据库账号。 - -### 2. 权限模型 - -项目已经有 operation policy 和多类认证,但需要把“工具是否可见”和“工具是否可执行”统一: - -- `tools/list` 不能向低权限用户泄露不可执行的敏感工具; -- 缓存不能跨 principal 共享私有工具列表; -- 服务端执行时必须再次授权,不能只靠列表过滤; -- 所有工具应声明最小 scope; -- 数据库账号权限是最后一道边界,MCP admin 不应默认映射 Doris root。 - -### 3. 密钥和敏感信息 - -- 不把 token 放在 query; -- 不记录 Authorization Header; -- 不保存 raw token; -- 数据库密码不进入普通 JSON; -- 日志、错误、追踪和工具结果统一做 secret redaction; -- 测试默认凭证必须与生产配置隔离。 - ---- - -## 九、建议的目标架构 - -```mermaid -flowchart TD - C["MCP Clients\n2026-07-28 + legacy"] --> T["SDK v2 Transport Security\nOrigin / Host / Header validation"] - T --> A["Authentication Boundary\nBearer normalization + issuer/resource/scope"] - A --> P["Single Protocol Core\nserver/discover + stateless + typed results"] - P --> R["Tool Definition Registry\nschema + policy + handler + audit"] - R --> Q["Doris Query Service\nvalidated identifiers + bounded execution"] - P --> S["Subscription Service\nonly when real change events exist"] - A --> I["Durable Identity Store\nhashed tokens + revocation + atomic writes"] - Q --> O["Telemetry\nOpenTelemetry + redaction"] - I --> O -``` - -### 1. 一个协议核心 - -建立: - -```text -create_mcp_server(settings) -> MCPServer -create_asgi_app(settings) -> ASGI app -``` - -STDIO、单 worker、多 worker 只能是 transport/deployment 参数,不再复制 handlers。 - -### 2. SDK v2 优先 - -优先高层 `MCPServer`,原因: - -- 自动支持 `server/discover` 和现代 metadata/header; -- 可同时服务新旧协议; -- 减少手工 Result 包装和底层 transport 细节; -- 避免继续依赖私有 monkey patch。 - -只有确有 Doris 特殊传输需求时才使用低层 `Server`,并严格按 v2 的 `(ctx, params) -> Result` 契约实现。 - -### 3. 显式状态 - -新版协议无会话。需要跨调用状态时: - -- 服务端创建不透明 handle; -- handle 作为普通工具参数返回和传入; -- handle 绑定 principal、scope、expiry 和 resource; -- 状态进入共享存储,不能绑定某个 worker 内存; -- 客户端不可自行伪造或越权复用。 - -### 4. 统一结果和错误 - -所有成功结果: - -```json -{ - "resultType": "complete", - "_meta": { - "io.modelcontextprotocol/serverInfo": { - "name": "doris-mcp-server", - "version": "..." - } - } -} -``` - -需要用户/客户端补充信息时才使用 MRTR。Doris 业务失败与 JSON-RPC 协议失败分开建模。 - -### 5. 多租户安全缓存 - -- 公共静态 schema 才能用 `cacheScope: "public"`; -- 受权限、数据库、用户或行列策略影响的结果必须 `private`; -- cache key 至少包含 issuer、principal、scopes、Doris cluster、catalog/database 和参数; -- 权限变化和 token 吊销应使私有缓存失效。 - ---- - -## 十、迁移路线图 - -### 阶段 0:立即止血 - -目标:当前旧版服务不再因正常或畸形请求退出。 - -- 捕获未知方法/模型解析异常,保持进程存活; -- 默认 loopback;非 loopback + 无鉴权时拒绝启动; -- 关闭任意 Origin、credentials CORS 和 `debug=True`; -- 删除 query token; -- 删除固定默认 token/secret; -- 修复 `/live`、`/ready`; -- 文档明确只支持到当前旧协议,不宣称 2026-07-28。 - -验收:连续发送未知方法、新版 `server/discover`、畸形 JSON 和 Header mismatch 后,服务仍可处理下一请求。 - -### 阶段 1:协议核心迁移 - -- 升级官方 Python SDK v2; -- 删除 monkey patch; -- 合并单/多 worker 实现; -- 实现/启用 `server/discover`; -- 每请求新版 `_meta` 和标准 Header 校验; -- 去除协议会话依赖; -- 全部 Result 增加 `resultType`; -- 实现 `ttlMs/cacheScope`; -- 保持官方 SDK 提供的 legacy compatibility。 - -验收:官方 `server-stateless` 场景通过,手工新旧客户端均成功。 - -### 阶段 2:错误和工具模型重构 - -- 建立 Tool Definition Registry; -- 全部 handler 返回类型化 Result; -- 统一 `isError` 和 JSON-RPC error; -- 增加分页、超时、取消和输出上限; -- 给工具列表和执行同时做权限过滤。 - -验收:Doris 不可达、超时、权限不足、参数错误、内部错误均有不同且稳定的协议表现。 - -### 阶段 3:授权和状态治理 - -- 统一 Bearer 解析; -- 校验 issuer/resource/audience/scope; -- DCR `application_type`、授权响应 `iss`; -- 增加 Client ID Metadata Documents; -- 令牌改为不可逆摘要和共享持久化; -- 明确 handle、缓存和 worker 的 principal 绑定。 - -验收:两个 issuer、两个 resource、三个 scope 的交叉矩阵全部符合最小权限。 - -### 阶段 4:持续交付门禁 - -- GitHub Actions; -- 自包含 pytest; -- 官方 conformance; -- ruff/mypy/bandit; -- wheel 安装 smoke; -- Docker Compose readiness; -- 真实 Doris 集成矩阵; -- 自动生成协议支持矩阵和 CHANGELOG。 - -### 阶段 5:按需求增加新版高级能力 - -- 只有存在真实变更源时才实现 `subscriptions/listen`; -- 交互式工具再引入 MRTR; -- 长任务确有需求时再评估 `io.modelcontextprotocol/tasks` 扩展; -- OpenTelemetry 传播 `traceparent`、`tracestate`、`baggage`,全链路脱敏。 - ---- - -## 十一、建议的发布验收清单 - -以下全部通过,才建议标记“支持 MCP 2026-07-28”: - -### 协议 - -- [ ] `server/discover` 在 STDIO 和 HTTP 均通过; -- [ ] 新版请求不要求 `Mcp-Session-Id`; -- [ ] `_meta` 缺失、版本不支持、Header 不一致返回规范错误; -- [ ] 所有 Result 有 `resultType`; -- [ ] list/read 结果有正确 `ttlMs/cacheScope`; -- [ ] 标准 `Mcp-Method` / `Mcp-Name` 校验; -- [ ] 新版删除的方法不会走旧语义; -- [ ] legacy 客户端兼容路径独立测试; -- [ ] 官方 Conformance `server-stateless` 全通过。 - -### 稳定性 - -- [ ] 未知方法、畸形 JSON、取消、断流不会终止进程; -- [ ] `/live` 与 `/ready` 语义准确; -- [ ] Doris 故障、恢复和超时经过真实环境验证; -- [ ] 多 worker 状态和吊销一致; -- [ ] 输出大小、并发和查询时长有上限。 - -### 安全 - -- [ ] 默认无固定 token/secret; -- [ ] 非 loopback 必须鉴权; -- [ ] query token 全部删除; -- [ ] Origin/Host/DNS rebinding 测试通过; -- [ ] issuer/resource/audience/scope 严格校验; -- [ ] 私有 cache 不跨 principal; -- [ ] token 只存 digest; -- [ ] SQL 注入 API 测试可自包含运行; -- [ ] 真实 Doris 最小权限账号通过。 - -### 工程 - -- [ ] pytest 全绿且无外部隐式前提; -- [ ] ruff 零错误; -- [ ] 协议/鉴权核心 mypy strict; -- [ ] Bandit findings 已逐项分诊; -- [ ] wheel 安装后的 server/client CLI smoke 通过; -- [ ] Docker Compose 无端口冲突、弱密码和错误 healthcheck; -- [ ] 文档版本、命令、能力声明与产物一致。 - ---- - -## 十二、本次执行回执 - -| 检查 | 结果 | -|---|---| -| `git pull --ff-only` | 已是最新 | -| `uv sync --frozen` | 通过 | -| `uv lock --check` | 通过 | -| `uv build` | 通过 | -| 安装 wheel 后 server/client CLI | client 失败,入口模块不存在 | -| 完整 pytest | 319 passed / 45 failed / 12 skipped / 8 errors | -| SQL 安全单测 | 31 passed | -| OAuth/鉴权策略聚焦测试 | 97 passed | -| coverage | 36% | -| Ruff 源码 | 4,297 errors | -| Mypy | 889 errors | -| Bandit | 107 findings | -| 旧协议 initialize | 成功,协商 `2025-03-26` | -| 2026 `server/discover` | HTTP 500,随后进程退出 | -| 恶意 Origin 预检 | 被反射,且允许 credentials | -| 官方 Conformance | npm 包拉取未返回,本次未完成 | - ---- - -## 十三、优先级排序后的行动清单 - -如果只按顺序做十件事,应是: - -1. 修复未知/新版请求导致进程退出; -2. 收紧 bind、Origin/CORS,关闭 debug; -3. 删除默认令牌、默认 secret 和 query token; -4. 升级 MCP Python SDK v2; -5. 合并单进程/多进程协议核心,删除 monkey patch; -6. 完成 `server/discover`、无状态、`resultType`、Header、缓存字段迁移; -7. 修正错误语义,禁止“异常变空列表/成功文本”; -8. 修复 OAuth Bearer 字段和 issuer/resource/audience 边界; -9. 建立自包含测试、官方 conformance 和 GitHub Actions; -10. 修复 wheel 客户端入口、Docker 配置、版本和文档漂移。 - -完成前六项,项目才进入“具备新版协议骨架”;完成前九项,才接近可发布;全部门禁通过后,才适合正式声明支持 MCP 2026-07-28。 --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
