# Phase 4 集成验收 — MCP Server

对应 plan §4.7（`docs/plan/001-agent-mcp-development-plan.md`）。验收分两层：**自动化**（子进程 + mock 后端，协议级断言）与**人工**（真实 Claude Desktop/Cursor + 真实后端）。

## 1. 自动化验收（已跑通）

测试文件：`security-center-mcp/acceptance_test.go`（main 包测试扩展，复用 P4-T6 `main_test.go` 的帧驱动模式：`go build` 出真实二进制 → 子进程启动 → httptest mock 后端 → stdin/stdout 驱动 MCP 协议帧）。

运行方式：

```bash
cd security-center-mcp
go build ./... && go vet ./... && go test ./...
# 单跑验收用例：go test -run TestAcceptance -v .
```

最近执行结果：5 个用例全部 PASS（连同既有 P4-T6 冒烟与 internal/ 各包测试，全仓 `go test ./...` 绿）。

| 用例 | 覆盖点 | 关键断言 |
| --- | --- | --- |
| `TestAcceptanceToolList` | tools/list | 返回恰好 14 个 tool，集合精确匹配 plan §4.3 名单（测试内硬编码名单，不引用 `tools.Registry()`，避免同义反复） |
| `TestAcceptanceExposureToVulnerabilityChain` | §4.7 "查某 IP 暴露面 → 关联漏洞"链路 | ① `search_assets(q=IP, exposure=public)` 打到 `/api/v1/caasm/ip-assets` 且 q/exposure 透传、data 透传；② `list_ports(q=IP, exposure=public)` 打到 `/api/v1/caasm/ports` 且 data 透传（注意：`/caasm/ports` 在 swagger 中未声明 `q` 参数，dispatch 按设计丢弃未映射参数，测试显式断言 q 不进查询串——按 IP 定位暴露面由第①步完成）；③ `search_vulnerabilities(search=OpenSSL)` 打到 `/api/v1/vulnerabilities` 且 search 透传；④ `get_vulnerability(public_id)` 依次请求 detail/events/comments 三个端点并聚合出 `vulnerability`/`events`/`comments` 三键；⑤ 全链路 6 个后端请求的路径序列精确匹配 |
| `TestAcceptanceMissingAPIKey` | 错误路径：无 `SECURITY_CENTER_API_KEY` 启动 | 进程 exit code = 1，stderr 含 `SECURITY_CENTER_API_KEY is required` 提示文案 |
| `TestAcceptanceInvalidAPIKey` | 错误路径：无效 key（mock 回 401 信封，HTTP 200 + body code 401） | tools/call 结果 `IsError=true`，payload 为 `{"error":{"code":401,...}}` |
| `TestAcceptanceMissingRequiredArgument` | 错误路径：缺必填参数（`get_vulnerability` 缺 `public_id`） | JSON-RPC `-32602`（非 tool 结果），错误消息点名缺失参数，且后端零请求（参数校验先于任何 HTTP 调用） |

**审计归因（plan §4.5，静态核验）**：链路用例的 mock 后端记录每个请求的 `X-API-Key` 头，逐请求断言其恒等于子进程 env 配置的 `SECURITY_CENTER_API_KEY`——即 MCP 转发的是调用方自己的 key，后端 AgentAuth 据此把调用归因到 key owner（代码侧：`internal/client/client.go` `Get()` 无条件 `req.Header.Set("X-API-Key", c.APIKey)`，`c.APIKey` 仅来自 env 配置）。

已知限制：自动化层用 mock 后端，不验证真实后端的认证/授权/审计落库——这部分在下方人工清单中覆盖。

## 2. 人工验收清单（需真实环境执行）

前置条件：后端已部署且 `agent.enabled: true`；你已在 Security Center 前端 **API Keys** 页创建**自己的** key（形如 `sc_...`）；已按 `docs/client-setup.md` 构建二进制。

### 2.1 客户端接入

- [ ] 按 `docs/client-setup.md` §3/§4 把 MCP server 配入 Claude Desktop（或 Cursor），`SECURITY_CENTER_API_KEY` 填**自己的** key，`SECURITY_CENTER_BASE_URL` 指向真实后端
  - 预期：客户端完全重启后 MCP server 显示已启用，无启动报错
- [ ] 查看客户端的工具列表
  - 预期：恰好 14 个 tool（plan §4.3 名单），每个 tool 有清晰的中文描述与参数 schema
- [ ] 故意把 `SECURITY_CENTER_API_KEY` 配成空/过短，重启客户端
  - 预期：server 启动即退出，客户端 MCP 日志（stderr）出现 `SECURITY_CENTER_API_KEY is required ...` 提示；改回正确 key 后恢复

### 2.2 对话式验收（LLM 自主选 tool）

- [ ] 对话提问："查一下生产环境有哪些资产"（或"搜一下 xxx 相关的资产"）
  - 预期：LLM 自主选择 `search_assets` 并填入合理过滤参数，返回资产清单可读摘要
- [ ] 对话提问："有哪些严重（critical/high）未修复的漏洞"
  - 预期：LLM 自主选择 `search_vulnerabilities` 并带上 `severity`/`status` 过滤，返回漏洞清单
- [ ] 对话提问："先查一下有哪些漏洞标签/类型可用，再按标签筛漏洞"
  - 预期：LLM 先调 `list_vuln_lookup_options`（kind=tags/types）再调 `search_vulnerabilities`，选 tool 顺序合理

### 2.3 链路验收："查某 IP 暴露面 → 关联漏洞"

- [ ] 一次对话完成："查一下 `<某真实 IP>` 的公网暴露面，再看看有没有和它相关的漏洞"
  - 预期：LLM 依次完成——
    1. `search_assets`（q=该 IP，IP 资产维度）定位资产；
    2. `list_ports` 查该资产/相关端口暴露（或 `list_high_risk_ports` 补充高危端口）；
    3. `search_vulnerabilities`（search=资产名/服务名/IP 等关键词）找关联漏洞；
    4. 对命中的漏洞调 `get_vulnerability`（public_id）给出详情+事件流+评论的完整档案；
    5. 最终输出把"暴露面 → 漏洞"串成可读结论，中途不缺环节、不虚构数据
- [ ] 链路中的每次 tool 调用结果与后端真实数据一致（抽查 1-2 条与 Web 前端页面比对）

### 2.4 审计归因验收（后端侧）

- [ ] 完成上述对话后，在后端 MySQL 查询审计表：

  ```sql
  SELECT id, actor_type, actor_user_id, actor_email, action, resource_type, metadata, created_at
  FROM audit_logs
  WHERE actor_type = 'agent'
  ORDER BY id DESC
  LIMIT 20;
  ```

  - 预期：刚才的 MCP 调用全部落库为 `actor_type='agent'`；`actor_user_id`/`actor_email` 等于**该 key 的 owner**（归因到 key owner，而非某个共享账号）；`metadata` 含 `agent_id`/`agent_name`/`auth_method` 等字段
- [ ] 在前端 API Keys 页吊销该 key，再回到客户端对话查一次资产
  - 预期：tool 调用返回 `{"error":{"code":401,...}}`（LLM 能读懂并提示 key 失效）；重新签发 key 并更新配置后恢复

### 2.5 收尾

- [ ] 确认客户端配置文件中未把真实 key 提交进任何仓库（`client-setup.md` 已要求 key 不入库）
- [ ] 以上全部勾选后，Phase 4 验收完成
