Files
next-tjwater-server/app/mcp/catalog.py
T
jiang be6317eb48 feat(server): integrate MCP queries and investigation messaging
Add project-bound OAuth MCP queries and controlled analysis with scoped evidence,
limits, timeouts and a nearby-model spatial query using EPSG:4326 inputs.
Default nearby searches to 200 meters and five results.

Integrate investigation messaging/presentation, health/readiness and typed history;
remove duplicate web search and fix online simulation and compressed baseline writes.
Include project migrations 023-025; keep local credentials and demo seeding excluded.

Validation: 319 focused pytest checks; real PostGIS temporary-table regression;
read-only Jinqian bridge radius comparison; cross-language MCP discovery/calls.
2026-10-19 18:46:58 +08:00

222 lines
9.4 KiB
Python
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.
"""One finite query catalog; names, schemas and permissions come from this registry."""
from dataclasses import dataclass
from pydantic import BaseModel
from mcp import types
from app.mcp import models as m
from app.mcp.analysis_models import BurstDetectionInput, BurstLocationInput, ValveIsolationInput, LeakageIdentificationInput
from app.mcp.analysis_models import ScenarioInput, ContaminantSimulationInput, ValveClosureInput, FlushingInput, PressureRegulationInput, SensorPlacementInput
@dataclass(frozen=True)
class QueryTool:
name: str
description: str
model: type[BaseModel]
permission: str
semantics: str
task_only: bool = False
computation: bool = False
def protocol_tool(self) -> types.Tool:
if self.name == "run_simulation":
suffix = "。任务调用复用证据留存。"
elif self.computation:
suffix = "。仅计算建议,不保存方案、不控制设备;任务调用复用证据留存。"
elif self.name == "query_nearby_elements":
suffix = "。仅查询当前已授权项目的模型管网,按距离和元素ID稳定排序;按需分页,不自动遍历全量;空间邻近不代表管网连通或供水关系。"
else:
suffix = (
"。先限定对象和时间范围,再读取明细;集合默认20条,按需逐页扩大,"
"不要自动遍历全量;截断或非零offset不能证明完整范围无异常。"
)
return types.Tool(
name=self.name,
description=self.description + suffix,
inputSchema=self.model.model_json_schema(),
outputSchema=m.QueryResult.model_json_schema(),
annotations=types.ToolAnnotations(
readOnlyHint=False, destructiveHint=self.name == "run_simulation", openWorldHint=False
),
)
_specs = [
("run_simulation", "按水力步长在在线业务模型上执行指定时间段的实时模拟,不复制数据库;更新该时段的模型条件及实时基线,返回指定节点和管段结果;duration单位为秒,不创建方案或正式分析运行,结果不是实测", ScenarioInput, "simulation.run", "analysis", False, True),
("run_contaminant_simulation", "在隔离模型中模拟假设污染源的浓度传播;返回指定节点/管段结果,不反演污染源,不代表实测污染范围", ContaminantSimulationInput, "simulation.run", "analysis", False, True),
("analyze_valve_closure", "在隔离模型中模拟关阀的水力影响,返回指定节点/管段结果;不操作真实阀门", ValveClosureInput, "simulation.run", "analysis", False, True),
("analyze_flushing", "在隔离模型中评估指定排放节点、冲洗流量和关阀组合的水力结果;不是现场冲洗或消毒合格证明", FlushingInput, "simulation.run", "analysis", False, True),
("analyze_pressure_regulation", "在隔离模型中评估给定调泵序列和水箱初始水位的压力影响;不自动优化调度,不实际控制泵", PressureRegulationInput, "simulation.run", "analysis", False, True),
("optimize_sensor_placement", "使用灵敏度或K-means算法推荐压力监测点;仅返回候选节点,不保存正式方案,不保证污染溯源可观测性", SensorPlacementInput, "optimization.run", "analysis", False, True),
("run_leakage_identification", "基于SCADA压力与模型进行DMA漏损分配识别,需有依据的总漏损流量和时间对齐;返回区域估计,不是已确认漏点。内部执行遗传算法与水力计算", LeakageIdentificationInput, "burst.run", "analysis", False, True),
("run_burst_detection", "爆管异常侦测:使用至少5个完整压力传感器此前15天数据;只提示压力异常,不定位、不证明爆管", BurstDetectionInput, "burst.run", "analysis", False, True),
("run_burst_location", "爆管候选定位:对比异常与正常压力,可辅助流量;需有依据的漏水量假设。压力节点可省略,自动读取项目SCADA压力测点;monitoring正常窗可成对省略,默认前一天同时间段,须说明该对照假设。simulation须有已有分析运行UUID并读取同窗模型基线,不是实测;定位内部会执行水力计算,不创建新的事件方案", BurstLocationInput, "burst.run", "analysis", False, True),
("analyze_valve_isolation", "按事故候选要素分析拓扑隔离边界、建议关闭阀门和受影响节点;不是污染扩散或水力影响范围,不执行关阀,须现场核实", ValveIsolationInput, "simulation.run", "analysis", False, True),
(
"get_element_properties",
"读取当前项目的单个管网元素属性",
m.ElementInput,
"webgis.view",
"asset",
),
(
"list_elements",
"分页读取管网元素,不自动读取全网",
m.ElementPageInput,
"webgis.view",
"asset",
),
(
"query_nearby_elements",
"查询坐标附近的模型元素,默认pipe;center为EPSG:4326 [经度,纬度],可直接使用geocode.center;radius_m默认200米、最大10000米,limit默认5、最大100,支持offset。按点到完整几何的WGS84椭球最短距离筛选并排序,返回element_id、distance_m和属性;不查询原始GIS管线、不自动扩大半径、不解析道路交叉点",
m.NearbyElementsInput,
"webgis.view",
"asset",
),
(
"get_network_options",
"读取模型时间、能源或网络选项",
m.OptionsInput,
"webgis.view",
"asset",
),
(
"get_network_schema",
"读取当前支持的元素、SCADA和选项结构",
m.SchemaInput,
"webgis.view",
"asset",
),
(
"list_scada_devices",
"分页读取SCADA设备和物理单位",
m.PageInput,
"scada.view",
"asset",
),
("get_scada_device", "读取一个SCADA设备", m.DeviceInput, "scada.view", "asset"),
(
"query_scada_readings",
"查询带时区且最多24小时的原始或清洗监测数据",
m.ScadaInput,
"scada.view",
"observed",
),
(
"query_model_baseline_series",
"读取最多20个元素的模型基线,不能称为实测",
m.BaselineSeriesInput,
"simulation.view",
"model_baseline",
),
(
"get_model_baseline_at_time",
"读取单个元素指定时刻的模型基线",
m.BaselineAtTimeInput,
"simulation.view",
"model_baseline",
),
(
"query_model_baseline_field",
"分页读取指定时刻的模型字段",
m.BaselineFieldInput,
"simulation.view",
"model_baseline",
),
(
"query_analysis_series",
"读取指定分析运行和元素字段时序",
m.AnalysisSeriesInput,
"simulation.view",
"analysis",
),
(
"query_analysis_values",
"分页读取指定分析运行某时刻的元素字段",
m.AnalysisValuesInput,
"simulation.view",
"analysis",
),
(
"list_analysis_runs",
"分页读取持久分析运行",
m.RunsInput,
"webgis.view",
"analysis",
),
("get_analysis_run", "读取单个持久分析运行", m.RunInput, "webgis.view", "analysis"),
(
"list_analysis_results",
"分页读取分析成果,可筛选result_type",
m.ResultsInput,
"webgis.view",
"analysis",
),
(
"list_sensor_placement_runs",
"分页读取已有监测点选址运行",
m.PageInput,
"optimization.run",
"analysis",
),
(
"get_sensor_placement_run",
"读取已有监测点选址成果",
m.RunInput,
"optimization.run",
"analysis",
),
(
"query_element_history",
"比较实测与模型基线或分析结果;analysis_comparison要求run_id;limit/offset按序列、时间排序的数据点分页",
m.HistoryInput,
"scada.view",
"observed",
),
(
"get_element_gis_properties",
"读取元素关联GIS台账",
m.ElementInput,
"webgis.view",
"asset",
),
(
"get_source_gis_properties",
"读取指定图层源要素台账",
m.SourceGisInput,
"webgis.view",
"asset",
),
(
"get_workbench_runtime",
"读取工作台已有运行态",
m.StrictInput,
"simulation.view",
"task",
),
(
"get_task_context",
"读取绑定任务的有界摘要及handling_skill元数据;section=skill读取Server按任务workflow分发的业务处置Skill正文与版本,属于流程指引而非事件证据;其他section选择task/report/evidence/actions/progress/access_requests/plan/run,data_path按返回路径读取嵌套字段,limit/offset分页;report_revision保留选定报告版本",
m.TaskContextInput,
"operational_task.view",
"task",
True,
),
(
"query_peripheral_data",
"查询有限外围数据源;synthetic是合成证据",
m.PeripheralInput,
"simulation.view",
"peripheral",
),
(
"get_task_evidence",
"读取当前绑定任务已保存的证据快照",
m.EvidenceInput,
"operational_task.view",
"task",
True,
),
]
TOOLS = {row[0]: QueryTool(*row) for row in _specs}