224 lines
17 KiB
Markdown
224 lines
17 KiB
Markdown
# 森林防火空间只读 MCP v1 Spec
|
||
|
||
| 项 | 内容 |
|
||
| --- | --- |
|
||
| 状态 | Implemented;live readiness 与本地 7 tools/call 已通过;同一 SuperAgent 中 `th-hotel-simple-superagent` 已稳定启用/调用并作为兼容档案参照;公网 `/mcp` 已到达 Go,但旧版本门禁曾返回错误,消防服务按 proven-profile 兼容方式部署后仍需完成真实工具回调 |
|
||
| 日期 | 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`,同时有工具级默认值和硬上限。
|
||
|
||
### 5.1 `fire_safety_search_place_candidates`
|
||
|
||
输入 `place_name`(去除首尾空白后 2 至 100 字符)和可选 `limit`(默认 10、最大 20),在 8 张现有业务表的名称、镇街和村庄字段中做不区分大小写的文字包含匹配。百分号和下划线按普通文字处理,不作为 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. 统一结果语义
|
||
|
||
工具成功结果使用:
|
||
|
||
```json
|
||
{
|
||
"status": "ok | no_results",
|
||
"data": {},
|
||
"metadata": {
|
||
"generated_at": "RFC3339 UTC",
|
||
"data_sources": [],
|
||
"spatial_reference": "EPSG:4326",
|
||
"result_count": 0
|
||
},
|
||
"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 限制地名长度、经纬度、半径、数量和未知字段;工具不接受授权身份参数。
|
||
- Service 测试证明可信数据库全范围/镇街白名单来自构造时配置,并覆盖互斥校验、无结果、超时和仓储失败。
|
||
- Repository 只使用参数化值,MCP 结果不包含联系人字段。
|
||
- readiness 测试证明无效几何不阻塞启动、会产生排除 warning,且全部固定查询显式包含 `ST_IsValid`;SRID、类型和越界仍为硬失败。
|
||
- 地名 Service/Repository 测试覆盖首尾空白、长度、控制字符、结果上限、服务端范围、8 张固定表和候选确认 warning。
|
||
- 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` 或经审批的外部地理编码服务。
|
||
- 路线规划需要的路网拓扑、路面/宽度/坡度/车辆限制、实时封路、火场和天气数据。
|
||
- 队伍集结需要的正式集结点、实时位置、战备状态、装备和容量数据。
|