Files
fire-safety-ymd/docs/project/integrations/superagent-mcp-spatial.md
2026-09-05 19:33:45 +08:00

190 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SuperAgent 空间只读 MCP 接入指南
| 项 | 内容 |
| --- | --- |
| 状态 | 代码已实现;实库严格 readiness 与本地 7 工具冒烟已通过;同一 SuperAgent 中 `th-hotel-simple-superagent` 已稳定启用/调用并作为兼容档案参照;公网 `/mcp` 已到达 Go,但旧版本门禁曾返回错误,消防服务的兼容档案仍需部署后按完整调用链重测 |
| Endpoint | `POST /mcp` |
| MCP 响应版本 | 固定返回 `2025-06-18` |
| 传输 | 单 JSON 请求/响应;不提供服务端 SSE |
| 数据源 | PostgreSQL/PostGIS,固定只读查询 |
## 调用边界
```mermaid
flowchart LR
SA["SuperAgent"] -->|"独立 MCP Bearer Token"| MCP["fire-safety-ymd /mcp"]
MCP -->|"schema 校验、超时、可信数据范围"| SVC["SpatialService"]
SVC -->|"固定参数化 SQL"| PG["PostgreSQL/PostGIS 只读角色"]
PG --> SVC --> MCP --> SA
```
SuperAgent Open API Key 用于本服务调用 SuperAgent;MCP Token 用于 SuperAgent 回调本服务。两者方向、权限和生命周期不同,必须使用不同 Secret。
## SuperAgent 兼容档案
SuperAgent 当前不能在 MCP 服务配置中填写或固定 `protocolVersion`;配置只需要 URL、HTTP 方法和独立 Bearer。已稳定接通的 `th-hotel-simple-superagent` 不读取 `initialize.params.protocolVersion`,也不读取或校验 `MCP-Protocol-Version` Header,而是固定返回 `2025-06-18`,并正常执行 `notifications/initialized`、`tools/list` 和 `tools/call`;工具结果同时提供 `structuredContent`。消防 MCP 按同一已验证档案接入,目标是打通工具调用,不是追求最新协议。
本服务端固定返回 `2025-06-18`:
- 可解析的 JSON-RPC `initialize` 中,`params.protocolVersion` 的缺失、空值、非字符串或其他值,都不单独触发版本拒绝,响应中的 `result.protocolVersion` 始终为 `2025-06-18`。
- `MCP-Protocol-Version` Header 的缺失或值同样不作为版本拒绝门禁;SuperAgent 配置中无需增加该 Header 或任何版本字段。
- 固定返回 `2025-06-18` 不表示服务实现或声明支持客户端填写的任意版本,也不把 `2025-03-26`、`2025-11-25` 列为实现目标;这是为了兼容已稳定接通的客户端。
上述兼容只放宽版本元数据,不放宽 HTTP 方法、JSON-RPC 结构、独立 Bearer、`Content-Type`、非空 `Origin`、工具 schema、只读 SQL、服务端数据范围或字段脱敏边界。日志仅记录 `direct_success`(客户端值恰为 `2025-06-18`)或 `compatibility_success`(版本字段缺失或其他值被兼容处理),不记录客户端原始版本值。
## 首版工具
| 工具 | 数据表 | 能回答 | 不能回答 |
| --- | --- | --- | --- |
| `fire_safety_search_place_candidates` | 现有 8 张空间表 | 按名称、镇街或村庄搜索有界地点候选 | 任意地址地理编码、自动确定演练点 |
| `fire_safety_resolve_incident_context` | 防火网格 | 点位所在网格、镇街、区域标签 | 实时火情、负责人电话 |
| `fire_safety_find_nearby_water_sources` | 水源地、蓄水池 | 候选水源、距离、容量/源状态(有值时) | 当前可用、取水道路可达 |
| `fire_safety_find_command_post_candidates` | 检查站、瞭望哨 | 附近候选设施 | 自动确定指挥部、安全性/容量结论 |
| `fire_safety_list_nearby_access_lines` | 防火通道 | 附近已绘制通道、最近接入点 | 路线规划、车辆可通行、实时封路 |
| `fire_safety_get_responsible_units` | 防火网格 | 责任中队名称 | 队伍实时位置、战备状态、集结点 |
| `fire_safety_find_nearby_risk_areas` | 墓地坟区、林区工矿企业 | 周边风险区域与距离 | 实时危险程度 |
工具不会返回负责人、书记、队长、值班人员、电话或图片字段。
## 地名输入的最小流程
`fire_safety_search_place_candidates` 接受 `place_name` 和可选 `limit`,名称长度为 2 至 100 字符,默认返回 10 条、最多 20 条。它对现有业务记录的名称、镇街和村庄字段做不区分大小写的包含匹配,不是完整地名库,也不调用外部地图服务。
返回值包含:
- `matched_field`:命中了名称、镇街还是村庄。
- `match_kind`:`exact` 或 `partial`。
- `location_kind=recorded_point`:源记录本身是点。
- `location_kind=representative_point`:源记录是线或面,只返回只读计算的代表点。
SuperAgent Profile 应遵循:
1. 用户只给地名时,先调用该工具,不直接猜测坐标。
2. 无结果时,请用户补充“镇街 + 村庄 + 具体地标”或在地图上选点。
3. 多个候选时按名称、类型、镇街和村庄列出,让用户选择;不能静默使用第一条。
4. 即使只有一个候选,也先回显给用户确认,再把确认后的 WGS84 坐标交给其他空间工具。
5. `representative_point` 不能描述为地点中心、入口或真实演练点。
6. 工具返回 `source_records_with_invalid_geometries_are_excluded` 时,必须说明结果可能不完整;无结果只能表示在有效记录中没有找到。
可加入 SuperAgent 系统提示词的最小片段:
```text
当用户没有经纬度但提供了地名时,先调用 fire_safety_search_place_candidates。
搜索结果只是地点候选:无结果时请用户补充地名或地图选点;多结果时列出候选并请用户选择;不得静默选择第一条。
任何候选都要先回显名称、类型、镇街、村庄和坐标供用户确认。location_kind=representative_point 时必须说明它只是线面记录的代表点,不能当作真实演练点。
只有用户确认坐标后,才调用网格、水源、指挥部候选、防火通道、责任中队和风险区域工具。
所有空间工具都会排除无效几何。看到 source_records_with_invalid_geometries_are_excluded 时,要说明结果可能不完整;不得把无结果解释为原始数据库确认不存在。
```
## 配置顺序
1. 为数据库创建只授予所需表 `SELECT` 的独立角色。
2. 在本地未提交的 `.env` 中填写 `.env.example` 新增的 `FIRE_SAFETY_POSTGIS_*` 配置,先保持 `FIRE_SAFETY_MCP_ENABLED=false`。
3. 加载环境并执行只读 readiness probe:
```bash
set -a
source .env
set +a
go run ./cmd/postgis-probe
```
probe 只输出 PostGIS 版本、表行数、空/无效/越界几何计数、几何类型、SRID 和 GiST 索引状态,不输出业务记录、联系人、DSN 或 SQL。无效/空几何作为明确 warning 并从工具查询中排除,不阻塞 MCP;非 4326 SRID、异常类型或越界坐标仍阻塞启动。
4. 只有数据所有者确认所有几何确为 WGS84,且数据库通过单独、经审查的数据变更把非空几何元数据补充为 `SRID=4326` 后,才设置:
```text
FIRE_SAFETY_POSTGIS_EXPECTED_SRID=4326
```
如果 probe 显示 `SRID=0`,不要让应用在查询时静默 `ST_SetSRID`。本项目的数据提供方已确认源数据为 EPSG:4326 且无偏移;2026-09-05 已按 [`../operations/postgis-srid-4326.md`](../operations/postgis-srid-4326.md) 完成受控元数据迁移,迁移未改变坐标数值或丢弃 Z。重新导入原始 SQL 后仍需重新执行该迁移,不得假设导入文件自带 SRID。
5. 配置至少 32 个可打印 ASCII 字符的独立 MCP Token,并选择一种可信数据范围,最后启用 MCP。
若该 MCP 凭证获准读取当前数据库中 MCP 固定查询表的全部记录,使用显式全范围模式:
```text
FIRE_SAFETY_MCP_AUTH_TOKEN=<独立高熵 Secret>
FIRE_SAFETY_MCP_SCOPE_MODE=all
FIRE_SAFETY_MCP_ALLOWED_TOWNS=
FIRE_SAFETY_MCP_ENABLED=true
```
若只获准读取部分镇街,保留默认白名单模式:
```text
FIRE_SAFETY_MCP_AUTH_TOKEN=<独立高熵 Secret>
FIRE_SAFETY_MCP_SCOPE_MODE=town_allowlist
FIRE_SAFETY_MCP_ALLOWED_TOWNS=莒格庄镇,高陵镇
FIRE_SAFETY_MCP_ENABLED=true
```
`all` 表示当前配置数据库中 MCP 固定查询表的所有记录,包括镇街字段为空的记录;它不表示最终用户已获得动态授权。`all` 模式不能同时保留镇街列表,模型和 MCP 工具参数均不能改变服务端选择的范围。
6. 启动服务。启用 MCP 时,服务会在监听 HTTP 前执行 readiness:SRID、类型或坐标范围不符合要求时拒绝启动;无效/空几何会输出 warning 并由查询排除。
## 协议探测
真实 Token 只放环境变量,不写命令历史或文档:
```bash
curl -sS http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer ${FIRE_SAFETY_MCP_AUTH_TOKEN}" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual-probe","version":"1"}}}'
```
初始化响应中的 `result.protocolVersion` 应固定为 `2025-06-18`。后续请求不需要携带协议版本 Header;如果客户端自行携带,服务端也不以其值作为版本拒绝条件:
```bash
curl -sS http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer ${FIRE_SAFETY_MCP_AUTH_TOKEN}" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -sS http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer ${FIRE_SAFETY_MCP_AUTH_TOKEN}" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
```
再发送受控测试地名/坐标的 `tools/call`,同样无需配置或携带版本 Header。SuperAgent 的真正验收顺序是 `initialize` → `notifications/initialized`(HTTP 202)→ `tools/list`(7 个固定工具)→ 至少一个受控只读 `tools/call`;只看到 initialize 成功不能宣称 MCP 已接通。联调不得使用真实火情或未授权精确坐标。
SuperAgent 侧配置模板见 [`superagent-mcp-client.example.json`](superagent-mcp-client.example.json)。
## 运行与安全说明
- `/mcp` 默认不注册;`GET /mcp` 返回 405,不提供 SSE stream。
- 浏览器携带 `Origin` 的请求被拒绝,首版只支持服务到服务调用。
- 数据范围由服务端配置注入;默认镇街白名单,数据库全范围必须显式选择,工具参数不能提供或扩大权限范围。
- 数据库连接设置只读事务默认值和 statement/lock timeout;工具另有总超时、半径与数量限制。
- 普通日志只记录 request ID、方法/工具名、耗时和结果类别,不记录坐标、参数或结果正文。
- 所有工具结果包含 `source_records_with_invalid_geometries_are_excluded`;SuperAgent 必须把无结果表述为“有效记录中未找到”,不能据此断言原始数据库不存在相关记录。
- HTTP 服务本身不终止 TLS;部署时必须通过受控网关或反向代理提供 HTTPS、网络白名单、限流和 Secret 轮换。
## 当前联调门禁
- 样例 SQL 不可执行,也不可提交;其中包含破坏性 DDL 和受限联系人数据。
- 2026-09-05 已完成受控 SRID 元数据迁移和严格 audit:8 表共 4,055 条记录,4,048 条非空几何均为 SRID 4326,SRID/类型/范围硬门禁通过。7 条空几何、35 条无效面几何以及 7 张缺 GiST 索引表仍按预期告警;本地实库 7 个 `tools/call` 已执行并通过,但公网 SuperAgent `tools/call` 尚未执行,因此当前仍不能宣称公网 MCP 业务联调完成。
- 用户提供的 SuperAgent 现场截图证明公网 `/mcp` 请求已经到达 Go 服务,Bearer、`Content-Type` 和 JSON-RPC 前置校验通过;随后旧版本门禁返回错误。该历史截图没有捕获客户端版本字段是否存在、类型和值。同一 SuperAgent 中 `th-hotel-simple-superagent` 已稳定调用是兼容档案的参照,但不是消防 MCP 的成功证据;部署后仍需观察 `direct_success` 或 `compatibility_success`,并完成 initialize → initialized → tools/list → 至少一个 tools/call。
- 缺少适用于距离表达式的 GiST 索引时可做小数据开发联调,但生产前必须补齐并验证查询计划。
- 地名包含匹配当前没有专用名称索引;真实数据量下先验证查询耗时,后续再决定标准地名表、别名词典或 `pg_trgm` 索引。
- 用户身份、动态区域授权和持久审计仍是后续 checkpoint;当前一个 MCP Token 只对应一个静态数据库全范围或镇街白名单。
## 2026-09-05 本地实库冒烟结果
服务仅监听 `127.0.0.1:18080`,使用运行时只读 PostGIS 账号和显式 `all` 数据范围完成测试;未输出联系人、完整业务记录或测试坐标。
- `GET /health` 返回 200;无 Token 的 `POST /mcp` 返回 401。
- `initialize` 固定返回 MCP `2025-06-18`;版本字段/Header 不作为拒绝门禁,initialized notification 返回 202;`tools/list` 返回全部 7 个工具,并至少完成一个受控 `tools/call`。该记录仍需目标机和真实消防 Profile 提供公网证据。
- 使用“观水镇”得到 10 个地点候选,并选取一个精确匹配的 `recorded_point` 蓄水池记录,仅作为获授权测试坐标。
- 网格上下文返回 1 条;水源、指挥部候选和通道各返回 10 条;责任中队返回 1 条;风险区域返回 9 条。
- 7 个响应的 `structuredContent` 与文本 JSON 投影一致,空间参考均为 EPSG:4326,计数与数据数组一致,不包含已禁止的联系人类字段键。
- 所有工具均保留 `source_records_with_invalid_geometries_are_excluded` 以及各自能力限制 warning。
- 第二轮 7 个数据库工具调用分别约耗时 0.35 至 0.99 秒,均低于 5 秒工具超时;这只是当前小数据本地结果,不替代生产索引和并发验证。
- 不存在的地名稳定返回 `no_results`;越界经度返回 `INVALID_ARGUMENT`;不可信 Origin 返回 403;`GET /mcp` 返回 405。
- 运行日志只记录 request ID、工具名、结果类别和耗时,未记录坐标、请求参数或结果正文。