# 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、工具名、结果类别和耗时,未记录坐标、请求参数或结果正文。