Files
fire-safety-ymd/docs/specs/fire-safety-ymd-superagent-mcp-spatial-readonly-v1.md
T
2026-09-06 17:57:58 +08:00

20 KiB
Raw Blame History

森林防火空间只读 MCP v1 Spec

项 内容
状态 Implemented;live readiness 与分页改造前的本地 7 tools/call 已通过;分页代码和自动化已通过,分页版真实 PostGIS 查询待复验;同一 SuperAgent 中 th-hotel-simple-superagent 已稳定启用/调用并作为兼容档案参照,消防服务仍需完成公网真实工具回调
日期 2026-09-05
Checkpoint fire-safety-ymd-superagent-mcp-proven-profile-compatibility(基础空间工具契约沿用 v1)
需求来源 用户提供 8 张 PostgreSQL/PostGIS 表结构与每表 2 条样例,要求先基于现有数据建设 MCP

1. 背景与已核对输入

docs/import/db-samples/ 中包含以下表的结构和样例:

  • st_2_mpslfh_t_slfh_syd:水源地,点。
  • st_2_xianyouxushuichiguan:蓄水池,点。
  • st_2_xianyoufanghuotongdao:防火通道,线。
  • st_2_fanghuojianchazhan:防火检查站,点。
  • st_2_fanghuoliaowangshao:防火瞭望哨,点。
  • st_2_fanghuowangge:防火网格,面。
  • st_2_linqugongkuangqiye:林区工矿企业,面。
  • st_2_mudifenqu_mian:墓地坟区,面。

SQL 文件仅作为结构和数据契约参考。文件包含 DROP TABLE 等导出语句,本项目不会执行这些文件。

当前样例足以确定首版字段映射,但不能证明全库的数据完整性、坐标系、几何类型一致性、索引、时效或资源当前可用性。尤其是:

  • 所有 geom 列均声明为宽泛的 geometry(GEOMETRY),没有 typmod SRID。
  • 现场全量导入进一步确认:st_2_xianyoufanghuotongdao.geom 的源记录混合二维与 Z 维度,原 geometry(GEOMETRY) 二维 typmod 会拒绝 Z;用户改用裸 geometry 保留原始维度后报告重导成功。该导入反馈仍需只读 probe 核验最终数量和分布。
  • 样例几何十六进制是未携带 SRID 的 WKB;坐标数值及水源表独立经纬度字段看起来符合 WGS84,但这只能作为待验证线索。
  • 样例 DDL 只有防火网格明确包含 GiST 几何索引。
  • 多张表含姓名、电话等受限字段;首版 MCP 不返回这些字段。
  • 用户提供的 SuperAgent 现场截图显示公网 /mcp 请求已经到达 Go 服务,Bearer、Content-Type 和 JSON-RPC 前置校验通过,随后旧版本门禁返回错误;截图没有捕获客户端版本字段是否存在、类型和值。同一 SuperAgent 中 th-hotel-simple-superagent 已稳定启用/调用,这是兼容档案的现场参照,但消防 MCP 尚未完成公网数据库工具调用。

2. 目标

  • 在现有 Go HTTP 服务内提供默认关闭的 POST /mcp。
  • 与不能配置协议版本的 SuperAgent 客户端保持兼容:服务端固定返回 MCP 2025-06-18,对齐已稳定接通的 th-hotel-simple-superagent compatibility profile;initialize 中的版本字段和后续协议 Header 都不作为拒绝门禁。使用 JSON-RPC 2.0 和单请求 JSON 响应,目标是打通工具调用而不是追求最新协议。
  • 通过独立 Bearer Token 鉴权;不得复用 SuperAgent Open API Key。
  • 只提供固定、参数化、只读、按服务端可信数据范围执行的空间查询工具;默认使用镇街白名单,数据库全范围必须显式启用。
  • 支持在现有业务记录中按地名搜索有界候选,让没有坐标的用户先确认一个候选,再进入距离和包含关系查询。
  • 使用 PostgreSQL/PostGIS 作为事实来源,并在启用 MCP 前校验 PostGIS、表、SRID、几何类型和有效性。
  • 工具结果明确表达数据来源、生成时间、限制与需要现场确认的事项。
  • 提供一个只输出 schema/几何统计、不输出业务记录或联系人数据的 PostGIS readiness probe。

3. 非目标

  • 不开放任意 SQL、任意表查询、文件读取或通用 HTTP 代理。
  • 不写数据库,不调度人员或资源,不生成/下发指挥命令。
  • 不声称防火通道是可通行路线,不实现路网拓扑、最短路、坡度或实时封路计算。
  • 不声称网格队伍的实时位置或集结点;现有表只提供责任队伍和区域信息。
  • 不依据资源记录存在就宣称水源、检查站或瞭望哨当前可用。
  • 不返回负责人、队长、值班人员、书记、联系电话或图片地址。
  • 不提供任意地址、山名或道路的外部地理编码,不实现完整地名库、拼音/别名纠错,也不自动选定地名候选。
  • 不在本 checkpoint 内建立最终用户身份、动态角色或多租户模型;首版使用每环境 MCP 凭证和服务端静态数据库全范围/镇街白名单。

4. MCP 传输与安全契约

4.1 支持的方法

  • initialize
  • notifications/initialized
  • tools/list
  • tools/call

服务端固定返回协议版本标识 2025-06-18 和 tools capability。首版是无会话、非流式的 Streamable HTTP 子集:POST 返回 application/json;GET /mcp 返回 405 Method Not Allowed。

SuperAgent compatibility profile

服务端固定返回 result.protocolVersion=2025-06-18,但不把客户端版本字段当作版本协商或拒绝依据:可解析的 JSON-RPC initialize 中,params.protocolVersion 可以缺失、为空、为非字符串或为其他值,均不单独触发版本错误。MCP-Protocol-Version Header 可以缺失或携带任意值,也不作为版本拒绝门禁;SuperAgent 配置中不需要填写版本字段或 Header。

固定返回 2025-06-18 不表示服务实现或声明支持客户端填写的任意版本,也不把 2025-03-26、2025-11-25 等值列为实现目标。该 profile 只为兼容已稳定接通的 SuperAgent 客户端,版本字段/Header 之外的 HTTP 方法、JSON-RPC 结构、鉴权、Origin、Content-Type、工具 schema、只读数据范围和敏感字段边界仍严格执行。

initialize 日志只记录 direct_success(客户端值为 2025-06-18)或 compatibility_success(版本字段缺失或其他值被兼容处理),不记录客户端原始版本值。

4.2 入站控制

  • MCP 默认关闭,关闭时不注册 /mcp。
  • 只接受 Content-Type: application/json。
  • 使用 Authorization: Bearer <FIRE_SAFETY_MCP_AUTH_TOKEN>,常量时间比较。
  • Token 至少 32 个可打印 ASCII 字符,且不能等于 SuperAgent Open API Key。
  • 初始化响应固定返回 protocolVersion: 2025-06-18;请求 body 中的 protocolVersion 以及 MCP-Protocol-Version Header 均为兼容元数据,不作为拒绝门禁。SuperAgent 配置不需要增加版本字段或 Header。
  • 请求体默认最大 256 KiB,最大可配置 1 MiB。
  • 首版只接受服务到服务请求;带非空 Origin 的请求拒绝,避免浏览器和 DNS rebinding 风险。
  • 不接受模型提供的用户、角色、租户或授权范围。范围模式和可选镇街列表只来自服务端配置。
  • 每次工具调用有硬超时;日志只记录 request ID、方法/工具名、耗时和结果类别,不记录参数、坐标、Token、SQL 或结果正文。

4.3 服务端数据范围

  • FIRE_SAFETY_MCP_SCOPE_MODE=town_allowlist 是默认值;启用 MCP 时要求 FIRE_SAFETY_MCP_ALLOWED_TOWNS 非空,并按数据库镇街字段精确过滤。
  • FIRE_SAFETY_MCP_SCOPE_MODE=all 必须显式配置;它授权查询当前配置数据库中 MCP 固定查询表的全部镇街以及镇街字段为空的记录,此时 FIRE_SAFETY_MCP_ALLOWED_TOWNS 必须为空。
  • all 不会放宽只读、固定 SQL、空间半径、结果数量、字段脱敏、SRID 或 readiness 限制。
  • 业务表中的镇街值不能自动决定授权范围;范围只能由可信服务端配置建立。

5. MCP 工具

除地名候选工具外,所有工具的坐标参数使用 WGS84:longitude 范围 [-180, 180],latitude 范围 [-90, 90]。需要距离的工具接收 radius_meters;七个工具均可选 limit 和 offset,并统一执行本节的分页契约。

5.0 统一分页契约

七个工具均接受可选的 limit 和 offset:

  • limit 范围为 1..20,默认值按工具类别确定:地名候选、水源、指挥部候选、防火通道和风险区域默认为 10;防火网格和责任中队默认为 20。
  • offset 范围为 0..10000,默认值为 0。它表示在稳定排序结果中的零基偏移量,不是页码。
  • 未传入参数时仍保持各工具既有默认返回数量;传入超出范围或非整数值时返回 INVALID_ARGUMENT。
  • data 始终是数组,即使网格或责任中队通常只返回一条记录;无结果页也返回空数组,不使用 null 或对象替代。
  • 所有工具必须先应用服务端授权范围、空间条件、有效几何排除和其他业务过滤,再在完整有效结果上计算 total_count,最后按稳定排序执行 offset/limit。总量不得泄露授权范围外的记录。
  • 稳定排序沿用各工具的业务主排序(例如距离、匹配优先级);主排序相同或距离相同时,必须使用资源类型和源记录 ID 等确定性字段作并列决胜,保证同一数据快照的分页不会重复或漏项。

统一成功响应的 metadata 为:

{
  "result_count": 10,
  "total_count": 27,
  "limit": 10,
  "offset": 0,
  "has_more": true,
  "next_offset": 10
}

字段语义如下:

  • result_count 是当前页 data 数组的元素数量,不是全部匹配数。
  • total_count 是授权范围和全部有效过滤完成后、分页前的总数量。
  • has_more 在仍有下一页时为 true;等价于 offset + result_count < total_count。
  • next_offset 在 has_more=true 时为下一次请求应使用的偏移量(offset + result_count),末页为 null。
  • 只有 total_count=0 时 status 才能为 no_results。当 total_count>0 但 offset 超过末页时,响应仍为 status=ok、data=[]、result_count=0、has_more=false、next_offset=null。

5.1 fire_safety_search_place_candidates

输入 place_name(去除首尾空白后 2 至 100 字符)以及可选分页参数,在 8 张现有业务表的名称、镇街和村庄字段中做不区分大小写的文字包含匹配。该工具默认 limit=10、offset=0,百分号和下划线按普通文字处理,不作为 SQL 通配符。

返回稳定资源类型、记录 ID、名称、镇街、村庄、命中字段、exact | partial、WGS84 坐标和 location_kind:

  • 点记录返回 recorded_point。
  • 线记录返回首个组成线的起点,面记录返回 ST_PointOnSurface 代表点,两者标记为 representative_point。

候选不等于用户位置。无结果时 Agent 必须请用户补充名称或地图选点;多结果时必须请用户选择;即使只有一个候选,也要先回显确认,不能直接调用后续距离工具。该工具不返回联系人字段,不调用外部地图服务。

5.2 fire_safety_resolve_incident_context

输入演练点坐标,查询覆盖该点的防火网格,返回网格 ID、镇街、区域标签。用于建立后续查询上下文,不返回个人信息。

5.3 fire_safety_find_nearby_water_sources

合并水源地和蓄水池,按球面距离返回候选水源。返回名称/标签、类型、镇街、村、坐标、距离、容量(数据存在时)、源表报告状态和未经解释的源时间原值(数据存在时)。结果固定标记“当前可用性未验证”;时间原值的单位和时区待数据所有者确认。

5.4 fire_safety_find_command_post_candidates

合并防火检查站和防火瞭望哨,返回附近候选设施、坐标、距离及源表报告状态。结果只代表空间候选,必须由现场核验安全、通信、容量、可达性和火势上风向等条件。

5.5 fire_safety_list_nearby_access_lines

返回附近防火通道名称、按 WGS84 geometry 计算的米制长度、最近接入点和未经解释的源更新时间原值。该工具不称为 route planner;它不返回“推荐路线”或“可通行”结论。

5.6 fire_safety_get_responsible_units

根据坐标查询覆盖网格及其防火中队名称。明确返回 live_location_available=false 和 assembly_site_available=false,因为当前表没有队伍实时位置、战备状态或正式集结点字段。

5.7 fire_safety_find_nearby_risk_areas

合并墓地坟区和林区工矿企业,返回点位周边风险区域类别、名称/标签、镇街、村、方位、是否覆盖演练点和距离。联系人字段不返回。

6. 统一结果语义

工具成功结果使用:

{
  "status": "ok | no_results",
  "data": [],
  "metadata": {
    "generated_at": "RFC3339 UTC",
    "data_sources": [],
    "spatial_reference": "EPSG:4326",
    "result_count": 0,
    "total_count": 0,
    "limit": 10,
    "offset": 0,
    "has_more": false,
    "next_offset": null
  },
  "warnings": []
}

structuredContent 保存上述对象,同时在 content[0].text 返回同一对象的 JSON 文本,兼容只读取文本内容的 MCP 客户端。

data_sources 只使用稳定领域名称(如 water_source、fire_grid),不向模型暴露历史物理表名。

warnings 使用 results_limited_to_server_authorized_towns 表示镇街白名单模式,使用 results_include_all_towns_in_configured_database 表示显式数据库全范围模式,避免 Agent 混淆结果范围。所有工具同时返回 source_records_with_invalid_geometries_are_excluded,防止 Agent 把排除后的无结果解释成数据库确认不存在相关记录。

工具级失败仍使用 JSON-RPC 成功响应中的 isError=true,结构化错误只公开稳定错误码:

  • INVALID_ARGUMENT
  • TOOL_NOT_FOUND
  • DATA_SOURCE_UNAVAILABLE
  • QUERY_TIMEOUT
  • INTERNAL_ERROR

数据库 DSN、SQL、表结构细节和原始驱动错误不得进入 MCP 响应。

7. PostgreSQL/PostGIS 契约

  • 使用 pgx/v5 原生连接池;当前仅面向 PostgreSQL,不引入 ORM。
  • 连接设置 default_transaction_read_only=on、statement_timeout 和应用名。
  • SQL 固定在 Repository,所有值使用 $n 参数;只有代码内表白名单可参与标识符拼接。
  • 查询始终应用服务端范围、半径和返回数量上限;town_allowlist 使用参数化镇街数组,all 使用服务端布尔参数显式跳过镇街条件,模型不能提供该参数。
  • 地名查询使用参数化的文字包含匹配,先按精确名称、精确村庄、精确镇街,再按前缀和普通包含排序;不拼接用户输入。线面候选的二维代表点只在只读查询中计算,不修改原始几何。
  • MCP v1 只支持已实库确认的 EPSG:4326。启用 MCP 时 FIRE_SAFETY_POSTGIS_EXPECTED_SRID 必须显式配置为 4326。
  • readiness 校验要求每张已用表存在、PostGIS 可用、非空几何的 SRID 均为 4326、类型符合点/线/面预期且坐标不越过 WGS84 范围;表/SRID/类型/越界不符合时服务拒绝启用 MCP。
  • 无效几何、空几何、空表或缺少空间索引作为 readiness warning。固定查询使用 ST_IsValid 排除无效几何,不自动调用 ST_MakeValid,不修改原始事实;生产前应评估数据缺口并补齐查询表达式适用的 GiST 索引。
  • 原始防火通道列保持裸 geometry 以容纳混合二维/Z。不得用 ST_Force2D 或重写 WKB 修改原始数据;确需二维投影时仅在只读查询/派生层显式执行并测试其结果语义。
  • 不自动执行样例 SQL、SRID 修复或索引 DDL。

8. 配置

环境变量 默认值 启用时要求
FIRE_SAFETY_MCP_ENABLED false 显式为 true
FIRE_SAFETY_MCP_AUTH_TOKEN 空 必填,独立高熵 Token
FIRE_SAFETY_MCP_SCOPE_MODE town_allowlist town_allowlist 或 all
FIRE_SAFETY_MCP_ALLOWED_TOWNS 空 town_allowlist 时必填;all 时必须为空
FIRE_SAFETY_MCP_MAX_BODY_BYTES 262144 1..1048576
FIRE_SAFETY_MCP_TOOL_TIMEOUT 5s >0 且不超过 30s
FIRE_SAFETY_POSTGIS_ENABLED false MCP 启用时必须为 true
FIRE_SAFETY_POSTGIS_DSN 空 PostGIS 启用时必填;Secret
FIRE_SAFETY_POSTGIS_EXPECTED_SRID 空/0 MCP 启用时必须显式为 4326
FIRE_SAFETY_POSTGIS_CONNECT_TIMEOUT 5s >0 且不超过 30s
FIRE_SAFETY_POSTGIS_QUERY_TIMEOUT 3s >0 且不超过 30s
FIRE_SAFETY_POSTGIS_MAX_CONNS 4 1..20

9. 验收标准

  • MCP 关闭时 /mcp 不暴露,健康检查保持可用。
  • MCP 启用但 Token、合法范围、PostGIS 或显式 SRID 缺失时配置加载失败;all 与非空镇街列表同时出现时失败,错误不含 Secret。
  • 无 Token、错误 Token、错误 Content-Type、非空 Origin、超大 body 和非法 JSON 均被稳定拒绝。
  • initialize、initialized notification、tools/list 和 7 个 tools/call 契约通过本地测试。
  • initialize 对客户端版本字段缺失、空值、非字符串、2025-06-18 或其他值均固定返回 2025-06-18;版本字段和 MCP-Protocol-Version Header 不作为拒绝门禁。服务端不宣称支持任意其他版本,也不追求最新版本;日志按 direct_success/compatibility_success 分类且不包含原始版本值。
  • 真实 SuperAgent 验收必须在部署后依次出现 initialize → notifications/initialized(HTTP 202)→ tools/list(7 个固定工具)→ 至少一个受控只读 tools/call;只有 initialize 成功不能宣称 MCP 已接通。
  • 七个工具的 schema 都接受可选 limit/offset;默认值、1..20 的 limit、0..10000 的 offset、未知字段和非法分页值均有稳定校验,工具不接受授权身份参数。
  • 七个工具的成功响应都保持 data 数组,并返回 result_count、total_count、limit、offset、has_more 和 next_offset;total_count 在授权范围和全部过滤完成后、分页前计算,排序稳定,no_results 与超尾页语义符合统一契约。
  • Service 测试证明可信数据库全范围/镇街白名单来自构造时配置,并覆盖互斥校验、无结果、超时和仓储失败。
  • Repository 只使用参数化值,MCP 结果不包含联系人字段。
  • readiness 测试证明无效几何不阻塞启动、会产生排除 warning,且全部固定查询显式包含 ST_IsValid;SRID、类型和越界仍为硬失败。
  • 地名 Service 测试覆盖首尾空白、长度、控制字符、结果上限、分页元数据、超尾页、服务端范围和候选确认 warning;Repository 测试覆盖 8 张固定表、窗口总量、稳定排序、参数位置和空后续页辅助语义。其他六个工具覆盖默认分页、边界值、总量与当前页计数;真实连续分页待测试库验证。
  • readiness probe 不输出业务行、电话、负责人、DSN 或 SQL。
  • gofmt、go test -count=1 ./...、go test -race -count=1 ./... 和 go vet ./... 通过。

10. 上线前未确认项

  • 实库源 CRS 已由数据提供方确认为 EPSG:4326 且无偏移;2026-09-05 已通过单独审核、显式确认和单事务迁移为 4,048 条非空几何补齐 SRID 4326,严格 readiness 已通过。重新导入无 SRID 的原始 SQL 时仍必须重新经过该流程,应用查询不得静默赋值。
  • 实库 35 条无效面几何按首版决策排除;需评估由此造成的网格、责任单位和风险区域覆盖缺口是否满足生产要求。
  • 各资源状态字段的枚举、更新时间含义和数据刷新责任人。
  • MCP 回调网络地址、TLS、SuperAgent Token 轮换方式及真实工具权限。
  • SuperAgent 无法配置协议版本;proven-profile 兼容响应、notifications/initialized、tools/list 和真实 tools/call 仍待目标机重建后确认。是否发送 MCP-Protocol-Version 不影响版本兼容验收。
  • 最终用户身份、区域权限、精确位置权限和审计保留策略。
  • 真实数据量下地名重复率、字段质量和无索引包含搜索性能;后续是否引入标准地名表、别名词典、pg_trgm 或经审批的外部地理编码服务。
  • 路线规划需要的路网拓扑、路面/宽度/坡度/车辆限制、实时封路、火场和天气数据。
  • 队伍集结需要的正式集结点、实时位置、战备状态、装备和容量数据。