Files
fire-safety-ymd/docs/project/integrations/superagent-mcp-spatial.md
2026-09-06 17:57:58 +08:00

16 KiB
Raw Permalink Blame History

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-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 墓地坟区、林区工矿企业 周边风险区域与距离 实时危险程度

工具不会返回负责人、书记、队长、值班人员、电话或图片字段。

统一分页契约

七个工具都支持可选的 limit 和 offset,由 MCP 服务端统一校验和分页:

工具类别 默认 limit offset 默认值
地名候选、水源、指挥部候选、防火通道、风险区域 10 0
防火网格、责任中队 20 0

limit 只能是 1..20,offset 只能是 0..10000;offset 是稳定排序结果中的零基偏移量,不是页码。服务端先完成授权范围、空间条件、有效几何排除和其他业务过滤,再计算总量并执行分页。所有工具保持稳定排序:业务主排序相同的记录使用资源类型、源记录 ID 等确定性字段决胜。

每个成功工具结果的 data 都是数组,metadata 至少包含:

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

其中 result_count 是当前页数量,total_count 是全部有效过滤结果的数量;has_more 等价于 offset + result_count < total_count,有下一页时 next_offset 为 offset + result_count,末页为 null。只有 total_count=0 时 status 才是 no_results;当请求超出末页但总量大于零时,仍返回 status=ok 和空数组,不能误报为无结果。

SuperAgent Profile 应遵循以下分页规则:

  1. 首次查询不传分页参数时使用工具默认值;需要更多结果时使用上一次响应的 next_offset,不要自行计算或把它当页码。
  2. has_more=true 时,回答中说明“当前展示 N 条,共 M 条”,并在用户明确要求继续或结果确实影响当前问题时继续查询。
  3. 综合演练方案默认使用当前页的主要候选,不自动循环拉取全部数据;需要完整清单时先告知用户总量,再按 next_offset 分页展示。
  4. data=[] 且 total_count>0 表示请求偏移已超过末页,不得说“数据库没有记录”;只有 status=no_results 才表示有效过滤范围内总量为零。

地名输入的最小流程

fire_safety_search_place_candidates 接受 place_name 和可选的 limit/offset,名称长度为 2 至 100 字符,默认 limit=10、offset=0、最多 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 时,必须说明结果可能不完整;无结果只能表示在有效记录中没有找到。

消防专属 Profile 不应只包含地名搜索的最小片段。完整、可直接粘贴的系统提示词见 superagent-fire-safety-profile-prompt.md。该提示词将对外身份统一为“山东省烟台市牟平区森林防火平台 AI 助手小牟”,并同时承担日常咨询、授权数据查询和演练方案辅助:本地事实必须来自用户确认或 MCP,方案组织可以使用通用专业知识;所有相关 location/nearest_point 默认显示 WGS84 经度、纬度;没有坐标、路线或实时队伍位置时不得补造。

配置顺序

  1. 为数据库创建只授予所需表 SELECT 的独立角色。
  2. 在本地未提交的 .env 中填写 .env.example 新增的 FIRE_SAFETY_POSTGIS_* 配置,先保持 FIRE_SAFETY_MCP_ENABLED=false。
  3. 加载环境并执行只读 readiness probe:
set -a
source .env
set +a
go run ./cmd/postgis-probe

probe 只输出 PostGIS 版本、表行数、空/无效/越界几何计数、几何类型、SRID 和 GiST 索引状态,不输出业务记录、联系人、DSN 或 SQL。无效/空几何作为明确 warning 并从工具查询中排除,不阻塞 MCP;非 4326 SRID、异常类型或越界坐标仍阻塞启动。

  1. 只有数据所有者确认所有几何确为 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。

  1. 配置至少 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 工具参数均不能改变服务端选择的范围。

  1. 启动服务。启用 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 已执行并通过,分页版真实 PostGIS 查询与公网 SuperAgent 调用尚未复验,因此当前仍不能宣称分页或公网 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、工具名、结果类别和耗时,未记录坐标、请求参数或结果正文。