TEST_PLAN.md 21 KB

FWH MCP Server 接口测试方案

被测对象:service/cmd/mcp(MCP Server,名称 fwh-tools,version 2.0.0) 传输方式:stdio JSON-RPC(server.ServeStdio) 底层框架:github.com/mark3labs/mcp-go 编写时间:2026-08-25


1. 被测对象概况

fwh-mcp-server 是一个通过 stdin/stdout 走 JSON-RPC 的 MCP Server。 入口 cmd/mcp/main.go 在启动时会初始化与 HTTP Server 相同的基础设施:

  • database.Init → MySQL / MongoDB / PgSQL
  • cache.Init → Redis
  • minioUtil.InitClient → MinIO
  • repositories.InitPlaneClient → Plane Web API

随后调用 tools.RegisterAll(s) 注册全部工具,分为四层:

层级 前缀 说明 读写
业务逻辑层 service_* 调用后端 Service 层,含业务校验/聚合 读为主,含写
数据直查层 data_* 直接读 DB/Cache/远程 API 只读为主
对象存储层 storage_* MinIO / 本地 FileStore 读写
辅助工具层 util_* 纯函数 只读

注意:data_plane_v2_api_write.go 中的写工具全部处于注释状态(未注册); registerPlaneApiTools(Plane V1)在 data/register.go 中已被停用,不会注册。 本方案以「实际注册的工具」为测试范围。


2. 运行路径与配置文件(关键)

测试运行路径固定为:/var/www/fwh-toolbox/server

fwh-mcp-server 必须在该目录下作为当前工作目录(cwd)启动,原因见下方配置加载逻辑。

该目录实际内容(编写方案时探测):

文件 说明
fwh-mcp-server 已构建的被测二进制(约 50MB,构建于 8月21日)
fwh-toolbox-server HTTP Server 二进制(非本次测试对象)
config.env 配置文件(本部署使用的是 config.env,不是 .env)
cert/ FWH MQTT / TLS 证书目录(相对路径引用)
.kiro/ Agent / MCP 设置

配置加载机制(config.LoadConfig)

config.go 中按相对当前工作目录依次尝试:

paths := []string{"./.env", "./config.env"}   // 先 .env,后 config.env
// 命中即停;随后 v.AutomaticEnv() 允许环境变量覆盖文件值

关键结论:

  1. 该目录下没有 .env,只有 config.env → 会加载 ./config.env。
  2. 因为是相对路径,server 与测试驱动都必须把 cwd 设为 /var/www/fwh-toolbox/server,否则读不到配置、会 fallback 到默认值(Mongo/Redis 走 localhost 默认、MySQL 为空、FWH 凭据缺失)。
  3. 环境变量可覆盖文件配置(AutomaticEnv)——如需临时改依赖地址,可通过 env 注入。

⚠️ stdout 污染风险(必须处理)

LoadConfig 在成功/失败时会向 stdout 打印:

✅ Using config file: ./config.env
⚠️ No config file found, using default values & environment variables

而 MCP 走 stdio JSON-RPC,这几行会污染通道。测试驱动(mcp-go client)在初始化握手时必须容忍/跳过这几行非 JSON 输出,或确认当前 mcp-go 版本对启动前的 stdout 噪声是否敏感。若握手失败,优先排查此处。

运行约定(方式 A 落地到该路径)

# 全部在测试运行路径下操作
cd /var/www/fwh-toolbox/server

# 复用已存在的二进制,或重新构建覆盖:
#   (构建需在源码目录,产物移动到此路径)
#   cd <源码>/service && go build -o fwh-mcp-server ./cmd/mcp/ && mv fwh-mcp-server /var/www/fwh-toolbox/server/

# 测试驱动以本目录为 cwd 启动 server(command="./fwh-mcp-server"),
# 与 .kiro/settings/mcp.json 中 "command": "./fwh-mcp-server" 一致。

测试驱动程序读取配置也应以 /var/www/fwh-toolbox/server/config.env 为准, 保证「驱动断言用的依赖地址」与「server 实际连接的依赖地址」完全一致。


3. 环境前置(本机探测结果)

编写方案时对本机 127.0.0.1 的探测结果:

依赖 端口 状态
Redis 6379 OPEN
MongoDB 27017 OPEN
MinIO 9198 OPEN
PgSQL 5432 OPEN
Plane Web 8600 OPEN
Go 工具链 - go1.26.4 可用
二进制 fwh-mcp-server - 已存在于运行路径(构建于 8月21日),可复用或重建

配置来自 /var/www/fwh-toolbox/server/config.env(已核对实际内容):

  • MYSQL_DSN → 远程 34.211.183.235:3308/home_energy_storage_server(站点/SIM 等业务数据依赖)
  • MONGO_DSN → 127.0.0.1:27017/FwhToolboxDb(注意:生产库名 FwhToolboxDb,非 dev 库)
  • POSTGRES_DSN → localhost:5432/fwh_toolbox_db
  • REDIS_ADDR → localhost:6379
  • MINIO_ENDPOINT → localhost:9198,bucket fwh-toolbox
  • FWH API → energy.franklinwh.com,账号 FMU006(含真实密码,凭据可用性需实测)
  • Plane → http://localhost:8600,workspace fwh(含真实 Web/API 凭据)

结论:Mongo/Redis/MinIO/PgSQL/Plane 均可连通且有真实凭据;FWH 远程 API 与远程 MySQL 有真实凭据,但可用性/网络连通性仍需在测试时实际验证。

⚠️ 数据敏感性:Mongo 指向生产库 FwhToolboxDb,MySQL 为生产业务库。因此 data_mongo_*、业务 service_* 查询会读到真实数据,L3 写操作尤其需谨慎——默认一律不真实写入。


3. 测试分层策略

按依赖强度从低到高分为 L0–L3,逐层推进。低层是高层的前置门禁。

L0 —— 协议与工具清单(无外部数据依赖,必测)

目的:验证 server 能正常握手、所有工具正确注册、schema 合法。

用例 方法 断言
L0-1 初始化握手 initialize 返回 serverInfo.name == "fwh-tools"、version == "2.0.0"、protocolVersion 合法
L0-2 工具清单 tools/list 返回工具数 > 0,且能全部列出(处理分页 nextCursor)
L0-3 工具命名规范 解析清单 每个工具名前缀属于 service_/data_/storage_/util_/plane_ 集合
L0-4 Schema 完整性 解析清单 每个工具含 name/description/inputSchema,required 字段声明合法
L0-5 未知工具 tools/call name=不存在 返回错误(非崩溃)

L0 全部不依赖任何后端数据,是回归测试的核心。

L1 —— 纯函数与参数校验(可精确断言)

目的:在不依赖外部服务的前提下验证工具逻辑与错误处理。

用例 工具 输入 断言
L1-1 SN 解析正确性 util_parse_sn sn=10060006A03X25310091 productName 含 aGate、year=2025、week=31、serialNumber=0091
L1-2 SN 缺参 util_parse_sn 无 sn isError,提示 参数 sn 必填
L1-3 SN 长度不足 util_parse_sn sn=1006 isError,提示长度不足
L1-4 必填缺失(抽样) service_site_query_info 等 省略 required isError,参数 xxx 必填
L1-5 格式校验 service_site_power_flow date=2025/01/01(错误格式) isError,提示需 yyyy-MM-dd

说明:L1-4/L1-5 只验证「参数校验分支」,在 handler 调用外部服务之前返回,因此不依赖后端。

L2 —— 只读工具连通性冒烟(依赖外部服务)

目的:对依赖可用的只读工具做真实调用,验证「能连通 + 返回结构合法 + 无 panic」。 断言以结构合法性为主(返回是 text content、可 JSON 解析或为结构化错误),不强断言具体业务数据。

分组(按依赖):

  • Redis 依赖:data_redis_get、data_redis_exists
  • Mongo 依赖:data_mongo_find、data_mongo_count(注意 collection 白名单)
  • PgSQL 依赖:data_pgsql_query_snapshot_file_record、data_pgsql_query_snapshot_data
  • MinIO 依赖:storage_minio_list_objects、storage_minio_read_object、storage_file_read、storage_file_list
  • Plane 依赖:plane_list_workspaces、plane_list_projects、plane_list_issues、plane_list_labels、plane_list_states、service_feedback_history 等
  • MySQL/业务依赖:service_site_search、service_sim_search、service_monitor_alarm_stats、service_dashboard_*、service_tou_template_list、service_background_task_list、service_site_monitor_config_list 等
  • FWH 远程 API 依赖:data_fwh_*、service_site_query_info、service_site_power_flow、data_sim_*

每个用例三态判定:

  • PASS:返回 content 且结构合法(成功数据 或 业务空结果)
  • FAIL:进程 panic / 返回非法结构 / 超时无响应
  • SKIP(dependency):依赖不可用导致的连通错误(记录原因,不计失败)

凭据类失败(如 FWH 密码占位、Plane 登录失败)归为 SKIP 并标注,不算 FAIL。

L3 —— 写操作工具(默认只做参数校验,不真实写入)

写操作工具(描述带 [写操作])默认只测参数校验分支,不触发真实写入,避免污染数据。 仅在你显式确认使用隔离/测试环境后,才追加"真实写入 + 回滚/清理"用例。

已注册的写操作工具(全部为 service_*):

工具 动作
service_feedback_create 在 Plane 创建 Intake Issue
service_site_info_trigger_update 触发站点信息同步任务
service_site_info_tag_update 创建/更新站点标签
service_auto_update_create_package / _update_package / _delete_package 升级包增改删
service_auto_update_update_config / _delete_config 升级配置改删
service_auto_update_create_file 创建升级文件
service_batch_download_create / _update_desc / _update_owner / _delete 批量下载任务增改删
service_site_analysis_create / _update_desc / _delete 站点分析任务增改删
service_site_monitor_config_create / _update / _delete 站点监控配置增改删

storage 写:storage_file_write、storage_file_upload、storage_file_read_to_local —— 默认只测参数校验。

L4 —— 敏感数据脱敏验证(PII,重点,必测)

背景:MCP 输出会经过 helper 层的 PII 脱敏后才返回给 Agent(JSONResult / JSONResultOrFile / BytesResultOrFileMasked)。 由于 Mongo/MySQL 指向生产库,含真实用户 PII,脱敏是否生效直接关系到数据是否泄露给上层 LLM/日志。这一层必须验证。

脱敏机制(cmd/mcp/tools/helper/)

双路径 + fail-closed:

  • 优化 A(强类型源头脱敏):命中 *dto.SiteInfoDTO、domain.SimCardInfo、dto.SimCardInfo、ResPage[...] 等已知类型时,对副本做字段级脱敏后序列化(不污染上游共享对象)。
  • 优化 B(JSON 流式脱敏):未命中强类型时,序列化后按 token 流式扫描,命中敏感 key 则脱敏其 value;敏感 key 对应对象/数组时整个子树完全脱敏。
  • fail-closed:脱敏解析失败时拒绝输出(返回 error),绝不泄露原始数据。

脱敏规则(pii_rules.go):

类别 字段(小写匹配) 方式 结果
手机号 phone/mobile/tel/contactnumber/msisdn/financiercontractnumber MaskFull 固定 6 位 ******
姓名 username/firstname/lastname/accountname/financiername/display_name/displayname MaskName 保留首字符 + ***
邮箱 email MaskEmail 首字符 + *** + 完整域名
账号 account/installeraccount MaskAccount 前2 + *** + 后2
地址串 address/deviceaddress/addresssecondlast/gpsaddress MaskFull ******
经纬度 longitude/latitude/lng/lat MaskFull ******
设备/网络标识 imei/imsi/mac/deviceip/gatewayip MaskFull ******
保留(不脱敏) iccid/giccid/sn、city/province/country — 原值

已有单元测试(前置门禁,先跑)

cmd/mcp/tools/helper/pii_test.go 已覆盖掩码函数、流式脱敏(扁平/嵌套/子树/非法JSON fail-closed/null保留)、强类型脱敏与上游不污染、A/B 一致性。

  • L4-0:先运行 go test ./cmd/mcp/tools/helper/...,全部 PASS 是后续端到端脱敏验证的前提。

端到端脱敏用例(现有单测未覆盖,本方案重点)

通过 MCP 真实调用,验证「返回给 Agent 的内容确实已脱敏」。断言方式:对返回的 content 文本做敏感值检测——不得出现原始 PII,且应出现对应掩码形态。

用例 工具 断言(对返回文本)
L4-1 站点详情脱敏 service_site_query_info(真实 SN) 不含明文手机号/邮箱/完整地址/经纬度/IMEI/MAC/IP;contactNumber/address/longitude/latitude/imei 值为 ******;userName 形如 X***;email 形如 x***@域名;保留 sn、iccId、city 原值
L4-2 站点列表脱敏 service_site_search 分页 Data[] 内每个站点均脱敏(验证 ResPage 强类型路径生效)
L4-3 SIM 详情脱敏 service_sim_query(真实 GICCID) imei/imsi/msisdn 为 ******;保留 giccid
L4-4 SIM 列表脱敏 service_sim_search Data[] 内每张卡的 imei/imsi/msisdn 均脱敏,giccid 保留
L4-5 直查层脱敏(B 路径) data_mongo_find(含 PII 的白名单 collection) 走流式脱敏;返回中敏感 key 的 value 已脱敏(验证未命中强类型时 B 路径仍生效)
L4-6 FWH 原始响应脱敏 data_fwh_*(返回上游原始 JSON 的工具) 走 BytesResultOrFileMasked,敏感字段已脱敏
L4-7 落盘结果脱敏 触发大结果落盘(spilled:true)的查询 读取 localPath 文件内容,文件内也是脱敏后的数据(脱敏发生在落盘之前,见 marshalSanitized → BytesResultOrFile)
L4-8 fail-closed 行为 见风险说明 脱敏失败时返回 error 而非原始数据(此项主要靠单测 L4-0 保证,端到端难构造,标注为设计验证)

敏感值检测清单(供 L4-1~L4-7 自动断言)

驱动程序应对返回文本执行如下"负向断言"(这些明文模式不得出现):

  • 手机号:连续 11 位数字(\b1\d{10}\b)
  • 邮箱明文:[\w.+-]+@[\w.-]+(除已知保留域名场景外,本地部分应已被 x*** 替换)
  • IMEI/IMSI:连续 14–15 位数字
  • MAC:([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}
  • IP:\b\d{1,3}(\.\d{1,3}){3}\b(排除脱敏后的 ******)
  • 经纬度:字段 longitude/latitude 的 value 不应为数字

并执行"正向断言":保留字段(sn/iccid/giccid/city)应保持原值。

注意:L4 端到端用例依赖能查到含 PII 的真实记录。需要你提供 1 个真实站点 SN 和 1 个真实 GICCID 作为测试样本;若不便,可退化为"L4-0 单测 + L4-5 直查一条已知含 PII 的 Mongo 文档"。


4. 执行方式

推荐 方式 A:独立 Go 测试驱动,最适合"所有接口都测一遍"且可重复、可 CI。

方式 A:Go 测试驱动(推荐)

在 cmd/mcp/test/ 下编写一个 Go 程序 / go test,用 mcp-go 的 client 以 stdio 方式拉起编译好的 fwh-mcp-server,然后:

  1. initialize → 断言 L0-1
  2. ListTools(翻页聚合)→ 断言 L0-2~L0-4,导出完整工具清单
  3. 逐个 CallTool 执行 L1/L2/L3 用例
  4. 执行 L4 敏感数据脱敏用例(含调用前先跑 go test ./cmd/mcp/tools/helper/... 作为 L4-0)
  5. 汇总生成报告(PASS/FAIL/SKIP + 原因)

优点:类型安全、断言强、可重复运行、结果可机读。 关键点:

  • server 与驱动的 cwd 必须为 /var/www/fwh-toolbox/server(否则读不到 config.env)。
  • 驱动启动 server 时 command="./fwh-mcp-server"、workingDir="/var/www/fwh-toolbox/server"。
  • 给每个 call 设置超时(远程 API 建议 15~30s)。
  • 握手时容忍 server 启动阶段可能出现的 stdout 噪声(见 §2 stdout 污染)。

方式 B:裸 JSON-RPC 脚本(轻量补充)

用 shell/python 向 server stdin 逐条喂报文:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"util_parse_sn","arguments":{"sn":"10060006A03X25310091"}}}

适合快速手工验证 L0/L1,断言能力弱,不适合全量回归。

方式 C:通过 Kiro CLI 加载 @fwh-tools 实测

最贴近真实使用(含 Agent 权限确认、落盘规则),但难以批量断言,适合最终验收抽查。


5. 执行步骤(方式 A)

运行路径固定为 /var/www/fwh-toolbox/server(配置文件 config.env 所在目录)。

# 方案 1:复用运行路径下已有的二进制(推荐,最贴近部署态)
cd /var/www/fwh-toolbox/server
# 二进制 ./fwh-mcp-server 已存在

# 方案 2:如需重建,在源码目录构建后覆盖到运行路径
#   cd <源码>/service
#   go build -o fwh-mcp-server ./cmd/mcp/
#   mv fwh-mcp-server /var/www/fwh-toolbox/server/

# 运行测试驱动(驱动程序位于源码 cmd/mcp/test/ 下)
#   驱动内部以 cwd=/var/www/fwh-toolbox/server 拉起 ./fwh-mcp-server
cd <源码>/service
go run ./cmd/mcp/test/runner        # 或 go test ./cmd/mcp/test/...

# 查看生成的报告
cat /var/www/fwh-toolbox/server/report.md   # 或驱动指定的输出路径

配置由 server 自身通过 config.LoadConfig() 从 cwd 的 config.env 读取; 测试驱动如需断言依赖地址,应读取同一份 /var/www/fwh-toolbox/server/config.env, 保证「驱动断言的依赖」与「server 实际连接的依赖」一致。无需手工导出环境变量。


6. 通过标准

  • L0 全部 PASS(协议与注册无问题)——硬性门禁
  • L1 全部 PASS(纯函数与参数校验正确)——硬性门禁
  • L2:依赖可用的工具 PASS;依赖不可用/凭据失效的记为 SKIP 并在报告说明;不允许出现 FAIL(panic/非法结构/超时)
  • L3:参数校验用例全部 PASS;真实写入用例仅在确认测试环境后执行
  • L4 敏感数据脱敏(硬性门禁,安全相关):
    • L4-0 单元测试 go test ./cmd/mcp/tools/helper/... 全部 PASS
    • L4-1~L4-7 端到端用例中,任何返回给 Agent 的内容出现明文 PII 即判定 FAIL(安全缺陷,最高优先级)
    • 保留字段(sn/iccid/giccid/city 等)被误脱敏也判定 FAIL(可用性缺陷)
    • 落盘文件(L4-7)内容必须同样已脱敏

7. 交付产物

  1. 测试驱动代码:cmd/mcp/test/(方式 A 的 runner 或 _test.go)
  2. 完整工具清单快照:cmd/mcp/test/tools_snapshot.json(tools/list 导出)
  3. 测试报告:cmd/mcp/test/report.md,每个工具一行 PASS/FAIL/SKIP + 原因 + 耗时
  4. 敏感数据脱敏专项报告:cmd/mcp/test/pii_report.md,逐条列出 L4 用例的负向/正向断言结果;对每个端到端用例附「脱敏后返回样本(已确保无明文 PII)」以便复核

8. 风险与注意事项

  • stdio 污染(两处):
    • server 运行日志已重定向到 /var/log/fwh-mcp-server/mcp.log(该目录需写权限,否则 fallback 到 nop logger)。
    • 但 config.LoadConfig() 会向 stdout 打印 ✅ Using config file: ./config.env,这会混入 JSON-RPC 通道。驱动握手需容忍启动阶段的非 JSON 行;若握手失败优先排查此处。
  • 大结果落盘:返回含 "spilled": true 时,完整内容在 localPath(/tmp/fwh-toolbox-agent/),preview 仅片段——断言时需识别此情况。
  • 白名单限制:data_mongo_* 受 collection 白名单、storage_minio_* 受 bucket 白名单约束,用例需使用白名单内对象。
  • 凭据/数据风险:config.env 含真实 FWH(FMU006)、Plane 凭据;Mongo/MySQL 指向生产库。只读查询会读到真实数据,写操作默认一律不真实执行。FWH 远程 API 与远程 MySQL 的网络连通性仍需实测,失败归 SKIP。
  • 写操作:默认不真实写入。若需真实写入验证,务必确认为隔离环境并准备数据清理。
  • 超时:远程 API 调用给足超时(15~30s),避免误判为 FAIL。

9. 待你确认的点

  1. 测试范围:只做 L0+L1(协议+纯函数),还是 L0~L2 全量只读冒烟?L3 是否需要真实写入?(L4 敏感数据脱敏建议无论如何都做,属安全门禁)
  2. 执行方式:确认用方式 A(Go 驱动)?
  3. 数据环境:Mongo/MySQL 指向生产库,只读查询可接受吗?还是需要切到隔离库再测?
  4. 写操作:是否允许对 Plane / 本地 DB 做真实写入(会产生测试数据)?默认不写。
  5. L4 脱敏样本:能否提供 1 个真实站点 SN + 1 个真实 GICCID(用于端到端脱敏验证)?若不便,退化为「单测 L4-0 + 直查一条已知含 PII 的 Mongo 文档」。

确认后我再实现测试驱动并执行。