agent-mcp.md 26 KB

FWH Agent MCP:设计

状态:设计初稿,2026-10-24。基于 ToolBox master a10cbda、AgentServer 969a993(ADR-0021 第三方 MCP 服务)。本文描述为新 AI 助手新建的 MCP Server(下称 Agent MCP),与旧助手的 service/cmd/mcp 无关:旧 MCP 保持现状继续服务 Kiro,新 MCP 不复用其工具代码。

1. 定位

Agent MCP 是 FWH 业务数据的无用户状态的工具服务:

  • 只回答“数据是什么”,不知道、也不关心是谁在问。没有 userId、sessionId 参数,不按用户隔离数据,不记录调用人。
  • 由 AgentServer 后台“MCP 服务”登记为远程 HTTP 服务(ADR-0021),经 MCP Gateway 转接给 Agent。Gateway 用固定请求头连接、多个会话共用连接,本来就不传会话信息,与“无用户”的定位一致。
  • 谁能用、能用到什么程度,由 ToolBox 和 AgentServer 在上层控制:ToolBox 决定用户能否使用 AI 助手、创建会话时选用哪个 Profile;Profile 决定勾选哪些 MCP 服务;AgentServer 按工具策略决定是否审批。调用追溯走“ToolBox 会话归属 → AgentServer 审计(gateway_call 含参数)”,见 technical-design.md。

1.1 为什么不沿用旧 MCP

旧 MCP 的做法 在新框架下的问题
工具参数里带 userId/sessionId,由模型填写(storage_*、反馈、批量下载 owner 等) 身份不可信;新框架不传用户信息,参数失去意义
大结果写到 /tmp/fwh-toolbox-agent/results 并返回本地路径 AgentServer 的 Agent 在沙箱里,看不到这个目录
文件经 MinIO upload/、download/ 前缀与 Agent 交换 新框架的上传和产出都在 AgentServer 会话工作区
222 个工具都没有声明 readOnlyHint ADR-0021 默认策略下全部变成“跟随权限模式”,每次查询都弹审批;逐个手工设置也难以维护
大量一对一包装接口和原始存储访问(data_mongo_*、data_redis_*、data_pgsql_*、Plane 全量接口) 工具多、描述短,模型选错工具;原始访问的结果大、难控制
只有 stdio 只能作为本地进程接入;本地进程在 AgentServer 的 bwrap 沙箱中运行,要开放网络和读取 .env,不合适
依赖 Kiro steering 中的路由表决定用哪个工具 新框架没有 steering,工具描述要能自解释

2. 设计原则

  1. 无用户:不接收、不推断调用人;写操作(若有)以固定的服务账号身份执行。
  2. 任务粒度:一个工具对应一个排查/分析动作(“查站点概况”“查一段时间的告警事件”),在服务端完成过滤、分页、聚合,而不是暴露底层接口或存储。
  3. 结果小而明确:默认返回紧凑结果并给出总数与是否截断;需要大量数据时用专门的导出工具,以文件形式交给 Agent(见第 5 节),不把明细塞进上下文。
  4. 自解释:工具描述写清用途、适用场景、参数格式、单位和返回字段;不依赖外部路由表。
  5. 全部显式声明读写:每个工具声明 readOnlyHint,由测试保证。
  6. 读写分离部署:只读工具与写工具放在两个 MCP 服务中(第 7 节),Profile 按需勾选,权限边界清晰。

3. 框架

3.1 位置与目录

放在 ToolBox 仓库,与主服务同一个 Go module,直接复用 repositories、services、dto、config。

service/
  cmd/agent-mcp/main.go          入口:读取配置、初始化基础设施、启动 HTTP 服务
  agentmcp/
    bootstrap.go                 初始化 DB / Redis / MinIO / repositories / FWH API / Plane(与主服务相同的依赖,只取需要的部分)
    server.go                    NewServer(set):创建 MCPServer、挂中间件、按工具集注册
    http.go                      Streamable HTTP(无状态)+ Bearer 鉴权 + /healthz
    kit/
      tool.go                    工具定义 Def[T]、注册函数
      result.go                  结果构造:Object / List / Table / Export,统一的截断与大小上限
      errors.go                  UserError:给模型看的错误信息(参数不对、无数据、上游不可用)
      middleware.go              日志、panic 恢复、单次超时、并发上限
    tools/
      site.go  alarm.go  shutdown.go  logs.go  faultrecord.go
      tou.go   sim.go    emspara.go   plane.go  util.go
      readonly.go                只读工具集
      ops.go                     写工具集(第 7 节,二期)
    agentmcp_test.go             约束测试(第 3.5 节)
  utils/pii/                     从 cmd/mcp/tools/helper 的 pii_*.go 拷贝为独立包(旧 MCP 不改)

一个进程、一个端口加载多个工具集,按路径区分(-set 逗号分隔,省略时加载全部):

fwh-agent-mcp -set=readonly,ops -listen=127.0.0.1:8091
路径 工具集 AgentServer 服务名
/mcp/readonly(以及兼容旧地址的 /mcp) 只读 fwh
/mcp/ops 写 fwhops

每个工具集有自己的 MCPServer、并发名额与令牌(AGENT_MCP_TOKEN_<工具集大写>,未配置时用 AGENT_MCP_TOKEN),只共用监听端口。哪些 Agent 能用哪个工具集由 AgentServer 的 Profile 勾选决定,不靠端口隔离。以后新增工具集只是多一条路径,不再占用端口。

3.2 工具定义

使用 mcp-go v1.4(go.mod 当前版本)的结构体生成参数 schema(mcp.WithInputSchema[T])和类型化处理函数(mcp.NewTypedToolHandler),参数校验交给 server.WithInputSchemaValidation():

// kit/tool.go
type Def[T any] struct {
    Name        string // <领域>_<动作>,小写下划线,≤ 40 字符
    Title       string // 中文短标题,审批卡片与后台显示
    Description string // 用途、适用场景、返回字段与单位
    ReadOnly    bool
    Handler     func(ctx context.Context, args T) (*mcp.CallToolResult, error)
}

func (d Def[T]) Register(s *server.MCPServer) {
    s.AddTool(
        mcp.NewTool(d.Name,
            mcp.WithDescription(d.Description),
            mcp.WithInputSchema[T](),
            mcp.WithTitleAnnotation(d.Title),
            mcp.WithReadOnlyHintAnnotation(d.ReadOnly),
            mcp.WithDestructiveHintAnnotation(d.Destructive),   // 覆盖、删除类写操作为 true:自动模式下也每次审批
            mcp.WithOpenWorldHintAnnotation(false),
        ),
        mcp.NewTypedToolHandler(func(ctx context.Context, _ mcp.CallToolRequest, args T) (*mcp.CallToolResult, error) {
            return d.Handler(ctx, args)
        }),
    )
}

一个工具的写法:

// tools/alarm.go
type alarmEventsArgs struct {
    SN    string `json:"sn" jsonschema:"站点 SN(aGate 序列号)"`
    From  string `json:"from" jsonschema:"开始时间,yyyy-MM-dd HH:mm:ss,站点当地时间"`
    To    string `json:"to" jsonschema:"结束时间,同上;与开始时间相差不超过 31 天"`
    Kind  string `json:"kind,omitempty" jsonschema:"alarm / event / all,默认 all"`
    Limit int    `json:"limit,omitempty" jsonschema:"最多返回条数,1~500,默认 100"`
}

var AlarmEvents = kit.Def[alarmEventsArgs]{
    Name:  "alarm_events",
    Title: "查询站点告警/事件记录",
    Description: "查询单个站点在一段时间内的告警和事件记录,按时间倒序。用于排查某次故障前后发生了什么。" +
        "返回字段:time(站点当地时间)、code、name、level、device、status。超过 limit 时返回 truncated=true,请缩短时间范围。",
    ReadOnly: true,
    Handler: func(ctx context.Context, a alarmEventsArgs) (*mcp.CallToolResult, error) {
        from, to, err := kit.ParseRange(a.From, a.To, 31*24*time.Hour)
        if err != nil {
            return kit.Fail(err), nil
        }
        rows, err := repositories.QueryEventOrAlarmListOnTimeRange(a.SN, from, to, kindOf(a.Kind))
        if err != nil {
            return kit.Upstream("FWH 平台", err), nil
        }
        return kit.List(mapAlarmRows(rows), kit.Limit(a.Limit, 100)), nil
    },
}

处理函数只做三件事:校验与换算参数、调用 repositories/services、把上游数据映射成精简的行结构。字段映射集中写,丢弃对分析无用的字段,统一时间格式与单位。

schema 由 mcp-go 依赖的 google/jsonschema-go 生成:jsonschema 标签的内容就是字段描述(不支持 key=value 选项),没有 omitempty 的字段为必填。枚举、取值范围写进描述,并在处理函数中校验(kit.Enum、kit.Limit 等辅助函数);需要在 schema 中声明枚举、取值范围或数组长度时,用 Def.Properties 对生成的字段 schema 做补充(合并进对应字段,服务端按补充后的 schema 校验);Go 的切片字段默认生成可空类型(null | array),必填数组要用它改为 array。AgentServer 网关只接受 type: object 的 inputSchema,测试中要检查。

3.3 结果构造

kit/result.go 提供统一的返回形式,所有工具都通过它返回:

函数 用途 形式
Object(v) 单个对象(站点概况、模版详情) 紧凑 JSON(不缩进、省略空值)
List(rows, limit) 列表 {"total":N,"returned":n,"truncated":bool,"items":[...]}
Table(cols, rows, limit) 行多、字段固定的数据 首行列名的 TSV 文本,比 JSON 省一半以上字符
Export(name, csv) 导出工具(第 5 节) EmbeddedResource(text/csv)
Fail(err) / Upstream(name, err) 错误 isError + 一句中文说明 + 处理建议。Upstream 只返回数据源、错误类别与错误编号,原始错误(可能含内部地址、SQL)只写日志

统一限制:单次结果(非导出)不超过 30,000 字符;超过时按行截断并标明 truncated,附一句“请缩小范围或使用 xxx_export”。这样不会触发 AgentServer 的 10 万字截断(那条路径会把前 10 万字仍放进上下文)。

所有对象和行在序列化前经过 utils/pii 脱敏(规则沿用旧 MCP:用户姓名、电话、邮箱、详细地址等)。

3.4 中间件与运行约束

  • 日志:工具名、参数摘要(截断到 2KB)、耗时、结果大小、是否截断、错误;写入独立日志文件,不含调用人。
  • 超时:单次调用默认 60 秒(导出工具 300 秒),超时返回 Upstream 错误。AgentServer 侧第三方工具超时为 10 分钟,不会先于 Agent MCP 超时。
  • 并发:全局信号量(默认 8),保护 FWH 云平台与数据库;排队超过 30 秒返回“服务繁忙”。
  • panic 恢复:返回“内部错误”,日志记录堆栈。

3.5 约束测试

agentmcp_test.go 注册全部工具集后检查:

  • 名称匹配 ^[a-z][a-z0-9_]{2,39}$,且全局唯一。AgentServer 中引擎侧完整名称为 mcp__gateway__<服务名>__<工具名>,服务名用 3~6 个字符(fwh、fwhops)时总长不超过 64;
  • 每个工具都有 title、非空中文描述,并显式声明 readOnlyHint;readonly 工具集中不得有 readOnlyHint=false 的工具,ops 工具集中不得有只读工具;
  • inputSchema 的 type 为 object;
  • kit.List、kit.Table 的截断与 30,000 字符上限。

各领域的映射函数(上游结构 → 精简行)配单测,用固定的上游 JSON 样本。

4. 工具清单(一期,只读)

按领域组织,只读服务 45 个、写服务 7 个(工具数量超过接入指南建议的 20 个,全部完成后再统一考虑分组拆分)。“来源”列是复用的现有数据访问函数,实现时以代码为准。

领域 工具 说明 来源
站点 site_search 按关键字与条件(国家、州省、在线、激活、运行状态、联网方式、固件/协议版本、aGate 硬件、激活时间,全部为“且”)搜索站点,分页;关键字与包含匹配按字面值(转义正则) QuerySiteInfoDataList、站点信息表
site_get 单站点概况:基本信息、版本、网络、工作模式、当前告警数 QuerySiteBaseInfoList、QuerySiteModeInfo、QuerySiteAlarmList
site_power_flow 单站点单日功率流(5 分钟粒度,Table) QuerySitePowerFlow
site_power_flow_export 单站点多日功率流导出(≤ 31 天,CSV) 同上,按天循环
site_info_sync_status 站点信息表同步状态:最近一次同步的进度、请求/完成时间,最近一次成功同步的时间与站点数,是否需要重新同步的建议 GetSiteInfoUpdateNewRecord、GetSiteInfoUpdateLastComplete
告警 alarm_current 站点当前告警列表 QuerySiteAlarmList
alarm_events 站点一段时间内的告警/事件记录 QueryEventOrAlarmListOnTimeRange
alarm_snapshot 站点某日的告警快照 QueryAlarmSnapshotInfo、PG 快照数据
alarm_stats 全量站点的告警统计(按日期、国家汇总) QueryAlarmStatistics、告警统计服务
alarm_sites_by_code 触发某告警码的站点列表 QueryAlarmStatisticsFromCode
停机 shutdown_stats 某日停机统计汇总(不传日期取最新) 停机统计服务
shutdown_sites 某日停机站点列表 QuerySiteShutdownList
日志 mqtt_log_search 站点 MQTT 日志,按命令类型与时间范围 QueryMqttLogListOnTimeRange
ems_log_records EMS 日志召唤记录 FWH Pro API
update_log_system 系统升级记录;传记录 ID 时返回详情 FWH Pro API
update_log_ahub aHub 升级记录 FWH Pro API
故障录波(直连设备) fault_record_devices 站点可做录波操作的设备(aGate、aPower-PE/MPPT、aHub) GetFaultRecordSiteDevices
fault_record_device_info 读取设备录波信息(故障总体信息、录波功能基本信息、录波总目录);长任务,经主服务 MQTT 收发 SubmitFaultRecordOperation(device_info)
fault_record_param_read 读取设备时间、录波开关;长任务 同上(param_read)
fault_record_operations / fault_record_operation_get 操作记录、操作结果(逐项解析,不含原始报文) FaultRecordOperations 集合
fault_record_data 解析录波文件:故障信息与波形概要(各通道 min、max、触发点值,不含逐点数据) MinIO fault-record/
fault_record_export 录波 CSV(zip,blob 资源) 同上
故障录波(云平台) cloud_fault_record_list 云平台故障录波日志列表 FWH Pro API
cloud_fault_record_detail 录波子记录 FWH Pro API
cloud_fault_record_callable 设备可召唤的录波列表 FWH Pro API
cloud_fault_record_call_history 录波召唤记录 FWH Pro API
MQTT 报文量 mqtt_traffic_trend 月总量与每日量(UTC) iotcore-stat ES
mqtt_traffic_sites / mqtt_traffic_site_cmd 某日站点报文量排行、站点命令类型分布 同上
mqtt_message_search 跨站点按命令类型、时间检索报文元数据 同上
自动升级 auto_update_catalog / auto_update_config_get 升级目录;某 aGate 硬件类型的升级条件与步骤(附升级包标题) 自动升级配置集合
站点分析 site_analysis_list / site_analysis_timeline / site_analysis_export 页面创建的分析任务、时间线、结果 CSV(只读) 站点分析集合
调度 tou_template_list TOU 模版列表,env=prod/test 调度配置服务
tou_template_get TOU 模版详情:按季节 × 日类型整理时段,附调度模式识别结果 同上 + 调度配置文档中的解析规则
SIM sim_get 按 ICCID 查询,自动判断平台(SmartHub / Acceleronix) SIM 卡管理服务
sim_search 按状态、平台、关键字搜索 同上
sim_usage_monthly 单卡某月每日用量 同上
EMS 参数 ems_param_lookup 按 PID / VID 查询宏名与描述;ids 为十进制整数数组(0~65535,1~50 个,与快照 XID 的 uint16 编号一致),schema 中声明约束由服务端校验 emspara
Plane plane_issue_search 按关键字、状态、项目搜索工作项(只读) Plane V1
plane_issue_get 工作项详情(含最近评论) Plane V1
工具 sn_parse 解析设备 SN 的含义(型号、产地、批次等) 旧 util_parse_sn 的规则(拷贝)

一期不提供:

  • 原始存储访问(Mongo / Redis / PG 任意查询)。确实需要时,二期增加限定集合、限定字段、只读账号、带行数上限的查询工具,或针对具体场景补任务级工具;
  • 批量下载、站点分析、站点监控配置等与 ToolBox 页面任务绑定的工具(它们的“任务属于谁”在无用户模型下没有意义);
  • 写工具(第 7 节)。

5. 大数据与文件

5.1 导出工具

需要明细分析的场景(多日功率流、长时间告警明细、批量 SIM 用量)用 *_export 工具:服务端分页拉齐数据,生成 CSV,以 EmbeddedResource(text/csv,URI 如 fwh://export/site_power_flow/SN123_20261001_20261031.csv)返回,同时附一段文本摘要(行数、列名、时间范围、前 5 行)。导出上限 20MB,超过时要求缩小范围。

5.2 AgentServer 需要的小改动

ADR-0021 的结果处理(mcp-gateway/server.ts 的 postProcess)目前:超过 10 万字的文本保存到 .agent/mcp/ 但前 10 万字仍放进上下文;resource 类型的内容原样交给引擎。为了让导出数据只落盘、不进上下文,建议在 AgentServer 增加一条通用规则:

  • resource 内容一律保存到工作区 .agent/mcp/(文件名取 URI 最后一段,blob 解码为二进制,text 原样写入),在结果中替换为“已保存到 <路径>(大小、MIME)”,不内联内容。

这是通用行为,不含 FWH 逻辑,约 20 行改动加测试。改动之前的过渡办法:导出工具改为返回普通文本并控制在 10 万字以内(约 1,500~3,000 行),超出部分要求分段导出。

5.3 二进制文件

故障录波原始文件、日志包等二进制内容在一期不直接交给 Agent。需要时提供“解析后的结果”工具(如录波通道数据导出为 CSV),由 Agent MCP 在服务端解析;是否需要见 Q-03。上述 5.2 的改动落地后,也可以用 blob 资源交付原始文件。

5.4 长任务(MCP Tasks)

超过 3 分钟的操作以 MCP 长任务执行(AgentServer 接入指南 §5、docs/design/16-mcp-support.md §3):工具立即返回任务,AgentServer 在后台按 pollInterval 查询,结束后把结果作为新消息发到会话、唤醒模型。不做“提交 + 查询”让模型轮询;查询进度的只读工具保留,供用户主动询问。

任务状态不放在 MCP 进程里。 mcp-go 的任务只存在进程内存,fwh-agent-mcp 重启后平台查询得到“任务不存在”而按失败通知用户,而底层作业其实仍在执行。所以长任务工具用 kit.TaskDef 定义,由 kit.TaskRouter 在 mcp-go 之前处理任务协议:

请求 处理
tools/call 带 task Start:启动或挂到已有作业上,立即返回任务(含 pollInterval 与给模型的即时说明 model-immediate-response)
tasks/get Status:读取业务数据(如 Mongo 中的作业记录)翻译为任务状态
tasks/result 任务结束后返回工具结果(失败为 isError),带 related-task
tasks/cancel Cancel:由工具按任务 id 中的标记决定是否真的停止作业
其他请求、不是本服务签发的任务 id 原样交给 mcp-go
  • 任务 id:fwhtask.<工具>.<业务键>.<标记>.<随机数>.<签名>,签名为以访问令牌为密钥的 HMAC,伪造或篡改的任务 id 一律返回“找不到任务”。
  • MCP 进程不保存任何任务状态,重启后照常回答查询;业务数据长期保存,满足平台至少 24 小时的要求。
  • 不带 task 的普通调用照常由 Def.Handler 同步返回。

site_info_refresh 是第一个长任务工具:业务键为同步记录(SiteInfoManageRecord)的 id,标记为本次调用的动作。

动作(标记) 任务
triggered(本次触发) 跟随同步记录:pending / running → working(进度说明“同步中,已进行 N 分钟”);complete → completed(站点数、耗时、完成时间);error → failed;cancelled → cancelled;超过 6 小时未完成 → failed(疑似中断)。取消时停止同步
tracking(已有同步在进行) 同上,但取消只停止跟踪,不停止同步(其他会话可能也在等它)
skipped_fresh / skipped_stale(未触发) 立即完成,结果为不触发的原因

取消同步由主服务配合:同步循环每页检查 ctx;fwhQueue.CancelSiteInfoUpdate 对执行中的任务发出取消(worker 停止后记为 cancelled),对排队中的直接删除。同步任务超时为 4 小时(SiteInfoUpdateTimeout),超时同样会停止同步并记为失败。

6. 部署与接入

6.1 Agent MCP

  • systemd 单元 fwh-agent-mcp.service:fwh-agent-mcp -set=readonly,ops -listen=127.0.0.1:8091,工作目录与主服务相同。
  • 独立配置文件 config.agent-mcp.env(-config 可指定,示例 deploy/config.agent-mcp.env.example):AGENT_MCP_TOKEN(Bearer 令牌)、AGENT_MCP_TOKEN_<工具集大写>(可选,单个工具集的令牌)、AGENT_MCP_MAX_CONCURRENCY(每个工具集各自计数)、AGENT_MCP_LOG_PATH,以及工具用到的数据库、Redis、FWH 生产/测试 API、Plane Web。不读主服务的 config.env 与旧 MCP 的 config.mcp.env,只放自己需要的凭证;文件缺失时拒绝启动。
  • 部署脚本:新增 service/cmd/deploy_agent_mcp.sh(构建、拷贝、重启),不改动旧 MCP 的 deploy_mcp.sh。
  • AgentServer 与 ToolBox 不在同一台机器时,监听内网地址,防火墙只放行 AgentServer 主机(见 requirements Q-01)。

6.2 AgentServer 登记

后台“MCP 服务” › 添加:

项 值
名称 fwh
标题 FWH 业务数据
传输 远程 HTTP,http://127.0.0.1:8091/mcp/readonly(旧地址 /mcp 仍可用)
请求头 Authorization: Bearer <AGENT_MCP_TOKEN>
工具策略 全部只读,默认“直接调用”

Profile fwh-assistant 勾选 fwh。工具使用的业务说明(字段含义、常见排查流程、各工具的配合方式)写在 AgentServer 技能 fwh-assistant 中(technical-design.md 第 6 节);Gateway 不转发上游的 instructions,所以不依赖它。

7. 写工具(二期)

写工具集与只读工具集在同一进程、同一端口,路径 /mcp/ops(3.1),在 AgentServer 中登记为第二个 MCP 服务 fwhops,只勾选进需要它的 Profile(例如只给运维角色用的 Profile)。工具策略保持“跟随权限模式”,Profile 的权限模式为“需审批”,每次调用都弹审批卡片。

候选:

工具 说明 注意
feedback_create 在 Plane 创建 Intake Issue 提交人固定为服务账号“AI 助手”;描述中由 Agent 写明背景。需要真实提交人时由 ToolBox 页面提交,不经过 MCP
tou_template_save 保存测试服 TOU 模版 只允许测试服;写入前按调度配置需求中的规则校验,返回差异
site_info_refresh 触发站点信息表同步,以长任务执行(5.4) 触发前检查同步状态(与 site_info_sync_status 相同的判断):同步中改为跟踪,不重复触发;最近 24 小时内已成功、或超过 6 小时未完成疑似中断时不触发,force=true 可强制。同步结束后平台把结果发给模型。任务投递到主服务的队列执行(MCP 只创建入队客户端与 Inspector,Redis 库号与主服务一致)
fault_record_fetch 召唤设备故障录波,长任务 经主服务执行器收发 MQTT(MCP 不连 MQTT,只提交与读取 Mongo 中的操作);任务跟随操作记录
fault_record_param_write 设置设备录波参数或执行动作,长任务 破坏性(清除波形不可恢复),每次审批
fault_record_cancel / fault_record_retry_download 取消操作;上传完成但下载失败时重试下载

无用户模型下,写工具的“谁做的”只记录在 ToolBox 会话归属和 AgentServer 审计中,业务系统里看到的是服务账号。如果某个写操作在业务上必须归属到具体的人,就不放进 MCP。

8. 待确认

编号 问题 建议
Q-01 一期工具清单(第 4 节)是否覆盖主要使用场景,是否有遗漏的高频查询 按旧助手的调用日志统计高频工具补充
Q-02 是否需要原始存储查询工具(Mongo / PG) 一期不做;收集到具体场景后补任务级工具
Q-03 故障录波、日志包等二进制数据是否需要交给 Agent 分析 先提供解析后的 CSV;原始文件待 5.2 改动后再评估
Q-04 5.2 的 AgentServer 改动是否接受 建议做,导出类工具依赖它
Q-05 写工具(第 7 节)是否需要、给哪些角色 一期不做;二期先上 feedback_create
Q-06 是否需要访问测试服 FWH 平台的工具 只在调度模版上区分 env;其他工具只查生产服