# Findings & Research > 调研发现、风险分析、决策依据 --- ## 1. 仓库现状 (2026-07-10 调研) **提交历史**: `1dfef0f init` (单提交) **Go 版本**: 1.25.5 **模块名**: `spark-mcp-go` ### 1.1 文件清单 | 文件 | 状态 | 备注 | |---|---|---| | `.gitignore` | 存在 | 包含 Python 模板 + `data/` 目录(Spark 相关) | | `.idea/` | gitignored | JetBrains IDE 配置 | | `go.mod` | 存在 | 所有依赖 `// indirect` 状态 | | `go.sum` | 存在 | — | | `main.go` | GoLand 模板 | "Hello gopher",非业务代码 | | `CLAUDE.md` | 已创建 | 项目元信息 + 待确认决策 | | `task_plan.md` | v0.3 | 7 阶段计划 | | `findings.md` | (本文件) | 调研与决策依据 | | `progress.md` | (会话日志) | — | ### 1.2 关键发现 - `go.mod` 里所有依赖都是 `// indirect` — 还没有任何代码 import 它们 - `mongo-driver/v2` 已 pin 但用户决定改用 SQLite → **Phase 0.1 需要移除** - `.gitignore` 里的 `data/` 注释"persisted Spark connections / pending jobs"暗示原意是用作本地状态存储 → 现统一改用 SQLite - 没有任何 README、文档、测试 → 从零开始 --- ## 2. 架构评审结论 ### 2.1 用户原始方案的关键问题 (v0.1 评审) 用户在初版架构文档里提出的 5 个 Tool + Gin + MCP + MongoDB 方案,经过 Linus 视角评审,发现以下问题: #### 问题 1: 存储选型自我矛盾 - 文档说 "SQLite or GORM" - go.mod pin 了 `mongo-driver/v2` - .gitignore 保留 `data/` 目录 - **结论**: 三选一 → 选 SQLite(用户已确认),`data/` 目录保留作为上传文件存储 #### 问题 2: "元工具" 归属不清 - 文档提到 `fetch_url` / `exec_shell` / `upload_file` 是"底层元工具" - **结论**: 用户确认只暴露 `fetch_url` + `upload_file`(不暴露 `exec_shell`) - `fetch_url` 是 LLM 的 HTTP 出入口 - `upload_file` 是 LLM 的本地文件存储入口 #### 问题 3: Tool 粒度问题 - 5 个 Tool 里 3 个本质都是 `GET ` - **结论**: v0.2 收敛到 4 个 Tool;v0.3 因加入 spark_submit 回到 5 个 #### 问题 4: 示例代码 API 不存在 - `server.NewStreamableHTTPServer` 等在 mcp-go v0.56.0 不存在 - **结论**: Phase 6 实施时**先查 mcp-go 真实 API 再写代码** ### 2.2 v0.3.x 范围变更汇总 | 版本 | 变更 | 触发原因 | |---|---|---| | v0.3 | 加 `spark_submit` Tool,`upload_file` 改本地存储,Cluster 加 binary 路径 | 用户从"纯读"改"读+提交" | | v0.3.1 | 合并 `spark_submit_path` + `spark2_submit_path` → `spark_submit_execute_bin` | 消除 special case | | v0.3.2 | `list_clusters` 标为强制入口,Cluster 加 `is_active` 字段 | LLM 必须能拿到 RM/SHS 地址 | | v0.3.3 | 加回 `fetch_spark_metrics` + `fetch_cluster_env` (高层 Tool,内部调 `fetch_url`) | 业务端点封装,LLM 不必拼 URL | | v0.3.4 | 加回 `analyze_spark_log` + 服务端启发式过滤 (data_skew / gc_pressure / bottleneck) + LLM-Ready Prompt | 用户原 spec 的 [Log Analyzer + Regex Rules + LLM-Ready Prompt] 组件。规则是 first-pass filter,LLM 拿 findings 后做最终判断 | | v0.3.5 | 拆 `yarn_job_manage` → 4 个 RM Tool (list_applications / get_application_status / get_application_logs / kill_application) | 用户参考实现是 4 个独立 Python 入口,1 Tool = 1 端点 | | v0.3.5 | 加鉴权层 (none / basic / kerberos) + SSL + manual redirect | Spark on YARN + Hadoop 2.7,生产环境需要 SPNEGO;`get_application_logs` RM→NM 跨主机 307 必须保留 Authorization | | v0.3.5 | `analyze_spark_log` 日志改走 RM 降级链 | SHS logs 端点不稳定,RM aggregated-logs + amContainerLogs + NM 是生产链路 | | v0.3.6 | 鉴权层简化成 `none` / `simple` / `basic` | 用户环境 simple auth,kerberos 推到 v2 | | v0.3.6 | Hadoop 2.7 兼容性"已验证" | 用户实测 endpoint 通,风险解除 | | v0.3.7 | 加 per-cluster `default_submit_args` + `rate_limit_per_min` | 减 LLM 负担 + 防刷 | | v0.3.7 | 删 `applications` 表,加 `audit_log` 表 | 纯读模式不需要 app 缓存,audit 必需 | | v0.3.7 | 多 `AdminTokens` + analyzer 阈值 env 可配 | 多用户 + 集群特定阈值 | | v0.3.8 | 用 `log/slog` 替换默认 log,加双输出 + per-Tool 独立日志 | 用户要求结构化日志 + 调试 LLM 行为 | **v0.3.3 设计模式确立**: - **通用 HTTP 原语** (`fetch_url`): LLM 自主拼任意 URL - **业务高层封装** (`fetch_spark_metrics`, `fetch_cluster_env`): 内部调 `fetch_url`,处理 URL 拼装、cluster 校验 - 两者并存,LLM 按场景选:常用端点用高层,长尾查询用原语 ### 2.3 待确认项 - (已清空 — v0.3.7 用户接受所有 6 项建议,无未决项) - **环境约束** (用户告知 + 验证): Spark 脚本, YARN-on-Spark, **Hadoop 2.7 (RM/SHS endpoint 已验证工作)** → 无需额外兼容代码 - **v1 鉴权范围**: `none` / `simple` / `basic` (YARN SimpleAuth 用 `user.name` query param)。**Kerberos 不在 v1**(用户环境不需要) 用户中途修正了 "纯 RM/SHS 不支持提交" 决策,改为 "读+本地 spark-submit 提交": - **新增** `spark_submit` Tool,使用 Server 本地 `exec.Command(binary, args...)` - **重新定义** `upload_file`:从"通用 HTTP 上传"改为"本地文件存储" (LLM 把脚本内容传给 Server,Server 写到 `./data/`) - **Cluster 配置新增** 单一 `spark_submit_execute_bin` 字段 (v0.3.1 进一步合并 `spark_submit_path` + `spark2_submit_path`) **重新引入的风险**:命令注入 — 通过 `exec.Command` 切片传参 + Tool 参数 schema 强约束来缓解 --- ## 3. 技术选型依据 ### 3.1 SQLite 驱动对比 | 驱动 | 优点 | 缺点 | |---|---|---| | `modernc.org/sqlite` | 纯 Go,无 CGO,跨平台编译简单 | 性能略低于原生 | | `mattn/go-sqlite3` | CGO 绑定,性能最优 | 需要 C 编译器,跨平台编译复杂 | **选择**: `modernc.org/sqlite` (本项目数据量小,QPS 极低,纯 Go 部署友好) ### 3.2 MCP 传输对比 | 传输 | 标准 | 优点 | 缺点 | |---|---|---|---| | stdio | MCP 基础 | 简单,本地 Agent 首选 | 不能与 HTTP 框架共存 | | HTTP + SSE | MCP legacy | 经典 HTTP 传输 | 仅 server → client 单向 | | **Streamable HTTP** | MCP 2025-03+ | POST + 可选 SSE,现代标准 | 实现稍复杂 | **选择**: Streamable HTTP(用户已确认),与 Gin 路由共存 ### 3.3 HTTP 框架: Gin - 用户原始方案已选 - 适合: admin API + 挂载 MCP HTTP handler - 生态成熟,中间件丰富 - 与 `mark3labs/mcp-go` 的 Streamable HTTP handler 通过 `gin.WrapH` 集成 ### 3.4 共享 HTTP 客户端 **为什么独立包**: - SSRF 防护集中实现,所有 Tool 复用 - Timeout / Size Limit 统一配置 - DNS rebinding 防护只在 client 层做一次 --- ## 4. 安全考量 ### 4.1 SSRF 防护 (核心) **威胁**: `fetch_url` / `upload_file` 被恶意利用访问内网服务或元数据 endpoint **防护策略**: 1. **IP 黑名单**: `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `::1/128`, `fc00::/7`, `fe80::/10` 2. **Hostname allowlist**: 来自 cluster 配置的 `rm_url` / `shs_url` 的 host 走豁免 3. **DNS rebinding 防护**: 解析 hostname 拿 IP,然后 dial 用 IP(不用 hostname 二次解析) 4. **Scheme 校验**: 仅允许 `http` / `https` ### 4.2 鉴权 | 端点 | Token | 来源 | |---|---|---| | `/admin/*` | `ADMIN_TOKEN` | 环境变量,必填 | | `/mcp` | `AGENT_TOKEN` | 环境变量,必填 | | `/healthz` | 公开 | 探活用 | **实现**: `Authorization: Bearer ` Header,中间件统一校验 **未来演进**: 多租户 / OAuth / mTLS(当前计划不包含) ### 4.3 输入校验 | 输入 | 校验 | |---|---| | `cluster.rm_url` / `shs_url` | 必须 `http://` 或 `https://` 开头 | | `cluster.id` | 非空,长度限制 | | `upload_file.file_path` | 必须在 `./data/` 允许目录内 | | `fetch_url.url` | 必须绝对 URL,scheme 校验 | | `yarn_job_manage.action` | enum: `kill` | `status` | ### 4.4 资源限制 | 资源 | 限制 | 默认值 | |---|---|---| | HTTP response body | `MaxResponseBytes` | 1 MB | | HTTP request timeout | `HTTPClientTimeout` | 30s | | Token 大小 | 启动时校验 | < 1 KB | | 集群数量 | 业务上无限制,DB 层建议 1000 | — | --- ## 5. 已知不确定项 ### 5.1 mcp-go v0.56.0 真实 API (Phase 6.1 需要查) - `server.NewMCPServer(name, version string, opts ...ServerOption) *MCPServer` - `mcp.NewTool(name string, opts ...ToolOption) Tool` - `mcp.WithString(name string, opts ...PropertyOption) ToolOption` - `mcp.WithArray(name string, opts ...PropertyOption) ToolOption` (用于 spark_submit 的 args) - `server.NewStreamableHTTPServer(s *MCPServer, opts ...StreamableHTTPOption) *StreamableHTTPServer` - `mcp.NewServer(...)` / `mcp.Server` / `mcp.NewTool` 是 alias? **行动**: Phase 6 实施时先看 GitHub tag 源码或官方 docs ### 5.2 upload_file 的具体业务场景 (已解决) - **确认**: 本地文件存储,供 spark_submit 引用 - **输入**: 文本或 base64 编码的 content - **输出**: 相对 `data_dir` 的路径 ### 5.3 applications 表是否需要 - 当前为"后续阶段"预留(缓存 RM/SHS 返回的 app 状态) - spark_submit 返回的 app_id 是否要入库?本阶段暂不入库,LLM 自行跟踪 - **行动**: Phase 2 末根据实际需求决定保留或删除 ### 5.4 Gin 与 mcp-go Streamable HTTP 的集成方式 - `gin.WrapH(mcpHandler)` 把 http.Handler 适配为 gin.HandlerFunc - 需要确认: mcp-go 的 handler 是否能直接 Wrap,还是需要中间件处理 CORS / Content-Type - **行动**: Phase 6.4 实施时验证 ### 5.5 spark-submit 的 binary 路径在不同环境下的差异 - 开发环境: `/usr/local/bin/spark-submit` - 生产环境: `/opt/cloudera/parcels/SPARK2/bin/spark-submit` 或 `/opt/spark/bin/spark-submit` - 由 admin 在 cluster 配置中指定 — 已经处理 ### 5.6 app_id 解析的 YARN 输出格式 - YARN 提交成功时输出:`Submitted application application_12345_0001` - 不同 YARN 版本可能略有差异(如 Spark Standalone 模式输出不同) - 方案: 用 regex `application_\d+_\d+` 提取,失败则 app_id 为空字符串 - 保留 regex 可配置(可选,本阶段不强制) --- ## 6. 风险矩阵 | 风险 | 概率 | 影响 | 缓解 | |---|---|---|---| | mcp-go v0.56.0 API 与文档不一致 | 中 | 中 | Phase 6.1 先查源码 | | **命令注入 (spark_submit 引入)** | **中** | **高** | **`exec.Command(name, args...)` 切片形式 + submitter enum + path 校验** | | 私网 IP 漏判 (IPv6 段不全) | 低 | 高 | 写测试覆盖 IPv6 段 | | DNS rebinding TOCTOU | 中 | 高 | 解析时锁定 IP,后续 dial 用 IP | | upload_file 路径逃逸 | 中 | 高 | filepath.Clean + 必须在 data_dir 之下 | | Token 通过 env 泄露到日志 | 低 | 高 | 启动时打印配置时隐藏 token 字段 | | spark-submit binary 被替换 | 低 | 高 | 启动时 + runtime 都校验 binary 存在 + 可执行 + 绝对路径 | | SQLite 并发写冲突 | 低 | 中 | 配置 `journal_mode=WAL`, 单写多读 | | YARN 输出格式变化导致 app_id 解析失败 | 中 | 低 | regex 解析失败则 app_id 为空,不影响主流程 | | 内存爆炸 (大响应/大 stdout) | 中 | 中 | HTTP body 1MB 截断,spark stdout 10MB 截断 | --- ## 7. 参考资料 - [mark3labs/mcp-go GitHub](https://github.com/mark3labs/mcp-go) — MCP Server 库 - [Model Context Protocol 规范](https://modelcontextprotocol.io/) — MCP 2025-03+ 标准 - [YARN RM REST API](https://hadoop.apache.org/docs/stable/hadoop-yarn/hadoop-yarn-site/ResourceManagerRest.html) — YARN RM 接口 - [Spark History Server REST API](https://spark.apache.org/docs/latest/monitoring.html#rest-api) — SHS 接口 - [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) — 纯 Go SQLite 驱动