14 KiB
SuperAgent 空间只读 MCP 接入指南
| 项 | 内容 |
|---|---|
| 状态 | 代码已实现;实库严格 readiness 与本地 7 工具冒烟已通过;同一 SuperAgent 中 th-hotel-simple-superagent 已稳定启用/调用并作为兼容档案参照;公网 /mcp 已到达 Go,但旧版本门禁曾返回错误,消防服务的兼容档案仍需部署后按完整调用链重测 |
| Endpoint | POST /mcp |
| MCP 响应版本 | 固定返回 2025-06-18 |
| 传输 | 单 JSON 请求/响应;不提供服务端 SSE |
| 数据源 | PostgreSQL/PostGIS,固定只读查询 |
调用边界
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-VersionHeader 的缺失或值同样不作为版本拒绝门禁;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 应遵循:
- 用户只给地名时,先调用该工具,不直接猜测坐标。
- 无结果时,请用户补充“镇街 + 村庄 + 具体地标”或在地图上选点。
- 多个候选时按名称、类型、镇街和村庄列出,让用户选择;不能静默使用第一条。
- 即使只有一个候选,也先回显给用户确认,再把确认后的 WGS84 坐标交给其他空间工具。
representative_point不能描述为地点中心、入口或真实演练点。- 工具返回
source_records_with_invalid_geometries_are_excluded时,必须说明结果可能不完整;无结果只能表示在有效记录中没有找到。
可加入 SuperAgent 系统提示词的最小片段:
当用户没有经纬度但提供了地名时,先调用 fire_safety_search_place_candidates。
搜索结果只是地点候选:无结果时请用户补充地名或地图选点;多结果时列出候选并请用户选择;不得静默选择第一条。
任何候选都要先回显名称、类型、镇街、村庄和坐标供用户确认。location_kind=representative_point 时必须说明它只是线面记录的代表点,不能当作真实演练点。
只有用户确认坐标后,才调用网格、水源、指挥部候选、防火通道、责任中队和风险区域工具。
所有空间工具都会排除无效几何。看到 source_records_with_invalid_geometries_are_excluded 时,要说明结果可能不完整;不得把无结果解释为原始数据库确认不存在。
配置顺序
- 为数据库创建只授予所需表
SELECT的独立角色。 - 在本地未提交的
.env中填写.env.example新增的FIRE_SAFETY_POSTGIS_*配置,先保持FIRE_SAFETY_MCP_ENABLED=false。 - 加载环境并执行只读 readiness probe:
set -a
source .env
set +a
go run ./cmd/postgis-probe
probe 只输出 PostGIS 版本、表行数、空/无效/越界几何计数、几何类型、SRID 和 GiST 索引状态,不输出业务记录、联系人、DSN 或 SQL。无效/空几何作为明确 warning 并从工具查询中排除,不阻塞 MCP;非 4326 SRID、异常类型或越界坐标仍阻塞启动。
- 只有数据所有者确认所有几何确为 WGS84,且数据库通过单独、经审查的数据变更把非空几何元数据补充为
SRID=4326后,才设置:
FIRE_SAFETY_POSTGIS_EXPECTED_SRID=4326
如果 probe 显示 SRID=0,不要让应用在查询时静默 ST_SetSRID。本项目的数据提供方已确认源数据为 EPSG:4326 且无偏移;2026-09-05 已按 ../operations/postgis-srid-4326.md 完成受控元数据迁移,迁移未改变坐标数值或丢弃 Z。重新导入原始 SQL 后仍需重新执行该迁移,不得假设导入文件自带 SRID。
- 配置至少 32 个可打印 ASCII 字符的独立 MCP Token,并选择一种可信数据范围,最后启用 MCP。
若该 MCP 凭证获准读取当前数据库中 MCP 固定查询表的全部记录,使用显式全范围模式:
FIRE_SAFETY_MCP_AUTH_TOKEN=<独立高熵 Secret>
FIRE_SAFETY_MCP_SCOPE_MODE=all
FIRE_SAFETY_MCP_ALLOWED_TOWNS=
FIRE_SAFETY_MCP_ENABLED=true
若只获准读取部分镇街,保留默认白名单模式:
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 工具参数均不能改变服务端选择的范围。
- 启动服务。启用 MCP 时,服务会在监听 HTTP 前执行 readiness:SRID、类型或坐标范围不符合要求时拒绝启动;无效/空几何会输出 warning 并由查询排除。
协议探测
真实 Token 只放环境变量,不写命令历史或文档:
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;如果客户端自行携带,服务端也不以其值作为版本拒绝条件:
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。
运行与安全说明
/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已执行并通过,但公网 SuperAgenttools/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固定返回 MCP2025-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、工具名、结果类别和耗时,未记录坐标、请求参数或结果正文。