Skip to main content

hydrotrek-chat-agent-implementation-review

Documentation status: CURRENT Architecture review of the DWD chat agent stack (CopilotKit → MAF → MCP → FastAPI). Use alongside chat-agent-mcp-development-guide.md for implementation workflow.

DWD chat agent — implementation review

Audience: Developers and reviewers evaluating chat agent design, API-to-tool mapping, and roadmap for hydraulics/SCADA-style prompts.

Captured: 2026-10-15

Related:


1. End-to-end architecture​

dwd-frontend-react (CopilotKit + CopilotAppContext)
│
▼
dwd-copilot-server (HttpAgent proxy — no system prompt)
│
▼
dwd-api-fastapi POST /agent (MAF + OpenAI + composed playbooks)
│
▼
dwd-mcp-fastmcp (FastMCP tools → httpx → FastAPI)
│
▼
dwd-api-fastapi REST + specialty routes → Azure (tables, blobs, Parquet, SCADA DB)
LayerRepo pathRoleIntelligence here?
CopilotKit UIdwd-frontend-reactChat, JWT, live twin/scenario/asset contextNo — passes context to /agent
Runtime proxydwd-copilot-serverCORS + HttpAgent → AGUI_AGENT_URLNo system prompt
MAF agentdwd-api-fastapi/app/agent/Agent Framework, instructions/*.md, middlewareYes — routing guidance via playbooks
MCP serverdwd-mcp-fastmcp/tools.pyTool definitions, HTTP proxy, formattersExecution + response shaping
Product APIdwd-api-fastapi/app/Auth, caps, persistence, DuckDB/Parquet, SCADAYes — business rules and data access

Skills vs production agent​

Cursor Agent Skills (.cursor/skills) are for IDE workflows only. They do not load into the production MAF agent.

Production behavior comes from:

  • dwd-api-fastapi/app/agent/instructions/*.md (composed at startup)
  • MCP tool docstrings and Annotated parameter descriptions

Design order (MAF-first)​

When adding or changing behavior, the suite standard is:

  1. API / MCP enforcement — reject invalid actions; cap resources; omit dangerous tools.
  2. Tool shape & dedicated tools — resolvers, composite reads (get_scenario_details), actionable error strings.
  3. MAF middleware / CopilotKit context — session facts (selected twin), MCP connected/disconnected hints.
  4. Composed instructions — thin 00_core.md + domain playbooks.
  5. Avoid duplicating tool routing tables in prose when tool docstrings + manifest already define behavior.

2. How APIs become MCP tools (“pythonify”)​

The MCP server is a REST adapter, not a second domain layer.

  1. Handler in dwd-mcp-fastmcp/tools.py — @mcp.tool, clear docstring, Annotated[..., "…"] param descriptions (critical for the model).
  2. HTTP — dwd_api for /api/v2/..., fastapi_client for FastAPI-only paths (e.g. /twins/.../network).
  3. Auth — forward Authorization when present; otherwise X-DWD-MCP-Secret (and agent-origin headers for trusted mutations).
  4. Errors as strings — _call() returns HTTP/error text instead of raising so the model can explain failures in chat.
  5. Optional formatters — reshape API JSON for operators (e.g. get_scenario_details → agent-summary with displaySections).

Two tool tiers in use​

TierExamplesUser questions
Thin proxyget_scenario, get_node_timeseries“Return this API payload for known parameters.”
Intent-shaped compositeget_scenario_details, get_twin_details, network formatters“What are this scenario’s settings?” (aligned with Edit scenario UI)

Recommendation: Copy the composite pattern for hydraulics and SCADA Q&A — not more raw 1:1 OpenAPI mirrors.

Current MCP surface (summary)​

Registered in dwd-mcp-fastmcp/tools.py (see README tool table — table may lag code):

  • Twins / scenarios: list, get, details, create, update, run, unqueue, definition
  • Results (entity-centric): get_node_timeseries, get_link_timeseries, get_model_summary, get_model_status_report
  • Specialty: fireflow, leakage
  • Network (INP): summary, section, search

get_hydraulics_metrics was removed; metric codes come from the EPANET SSOT (same as UI).


3. Industry patterns (short glossary)​

PatternIdeaDWD fit
Tool-first on product APILLM chooses tools; backend owns truthCurrent default — good for audit, UI parity, auth
Fat tools / workflowsOne tool = one user intentUnderused — best next step for hydraulics Q&A
OpenAPI / MCP auto-exposureGenerate hundreds of tools from specUsually bad at scale (context bloat, wrong granularity)
Text-to-SQL / ad hoc queryModel writes queries against a schemaPossible for SCADA DB; needs strict guardrails
RAG on docsAnswer “how does PDA work?”Complements tools; does not replace result reads
Separate compute planeIn-memory engine session (SSC openswmm.mcp)DWD uses persisted runs + Parquet — no in-proc EPANET session for chat

DWD should not copy SSC engine tools literally (e.g. session get_flooding_summary). Port the user intent, backed by completed scenario results and SCADA APIs.


4. API first, or tool-only?​

Default: API first for anything that touches persisted data, auth, or product semantics.

SituationWhere to implement
New persisted behavior, UI might use it, caps/validationFastAPI route/service first, then MCP tool
Chat-only phrasing / which tool to callPlaybook (app/agent/instructions/NN_*.md)
Chat-only response shape (no new server capability)MCP formatter or agent-summary / display registry
Purely conversational (definitions, EPANET concepts)Instructions only — no API

Tool-only without API is appropriate for:

  • Thin orchestration in MCP (call two existing endpoints and merge — still backed by APIs), or
  • Ephemeral session-local helpers (SSC engine pattern — not DWD’s data plane).

Anti-pattern: mutations (create/run scenario) implemented only in MCP without FastAPI enforcement.


5. Example future prompts vs current stack​

“Which nodes are flooded?” / “Which nodes are under pressure?”​

Gap: Partially implemented at the API layer; not exposed to the agent via MCP.

FastAPI already supports bulk result reads, for example:

  • GET .../results/metric_slice?time=&objectSubType=Junction&metric=EN_PRESSURE (and other EN_* metrics from SSOT)
  • Timestep snapshots: time_node / time_link

These routes live in dwd-api-fastapi/app/results/routes.py. They are not registered in dwd-mcp-fastmcp/tools.py.

What is exposed: get_node_timeseries, which requires 1-based node_index, SSOT node type, and metric enum — suitable for a known asset, not for “which nodes?” across the network.

The model cannot reliably answer those prompts without either hundreds of per-node calls or intent tools / analytics primitives (see section 6).

Domain note: In EPANET/DWD, “flooded” may mean different things than in SWMM — e.g. low pressure / PDA deficiency, tank overflow (EN_CANOVERFLOW), etc. Product language should be explicit in tool names and docstrings.

“Generate pump curves for the available SCADA data”​

Larger gap: SCADA is served from dwd-api-fastapi/app/scada/routes.py (Postgres per twin). Network and scenario result tools do not replace SCADA tag time series + pump linkage.

There is no MCP tool surface for SCADA reads today (only scenario fields like scadaAvailable in details/update).

Needs: API for pump/tag series (and optionally curve-fit) → MCP intent tool → playbook (units, SCADA DB configured, time window limits).


6. Generic support without coding every API call​

Fully generic (“any question → auto tool”) — no, not safely at production quality. Models need bounded, named capabilities with clear inputs/outputs.

Partially generic — yes, in layers:

ApproachGenericityEffortDWD status
A. Analytics primitivesMedium — model picks metric + time + filterLow–mediummetric_slice, time_node, entity_at_time in API; not in MCP
B. SSOT-driven metric catalog in tool docsMediumLowapp/core/ssot_metrics.py; use SSOT labels in docstrings
C. Intent tools with parametersLow genericity, high UXPer intentget_scenario_details is the template
D. Server-side “views” / agent summariesHigh for fixed reportsMediumExtend model-hydr-summary, registries, new /agent-summary-style routes
E. OpenAPI → MCP codegenHigh tool count, low qualityOne-timeNot recommended as primary strategy
F. NL → structured query (validated)High if schema stableHighFuture for SCADA; allowlisted tables/metrics

Recommended generic core: (A + C + D)

  • Primitives: a small set of MCP tools on existing result routes (metric_slice, list hydraulic times, bridge to search_network_element for ID ↔ index).
  • Intents: composites where operators repeat the same question (“junctions below X psi at T”, “worst 10 pressures”).
  • Summaries: when the UI would show a report, add agent-oriented endpoints so MCP stays thin.

Avoid N tools for N HTTP paths and avoid exposing the full OpenAPI surface to the model.

Output size: Raw metric_slice for all junctions can be huge. Formatters should return counts, thresholds, top-K, EPANET IDs — not full arrays in chat.


7. Strengths of the current implementation​

  1. Clear separation — proxy vs MAF vs MCP vs API; single place for prompts (instructions/).
  2. Security model — JWT + MCP secret, origin=agent, caps — not prompt-dependent.
  3. UI parity — scenario/twin agent-summary + display registries mirror Edit dialogs.
  4. Operational hardening — stateless OpenAI, AG-UI tool order, live selection injection (see development guide troubleshooting).
  5. Contributor workflow — chat-agent-mcp-development-guide.md is a usable internal standard.

8. Gaps and risks​

AreaObservation
Results / hydraulics Q&ARich Parquet API; MCP still entity-centric timeseries. Bulk routes not exposed to the agent.
SCADAAPI exists; no MCP tools; no results/SCADA playbooks.
Tool catalog vs codedwd-mcp-fastmcp/README.md table may lag tools.py.
PlaybooksCore + scenario/twin details + lifecycle only — no hydraulics/SCADA playbook.
Index vs IDNetwork search returns EPANET IDs; timeseries wants engine index — easy for the model to get wrong without a bridge tool.
SSC comparisonSWMM MCP has in-memory analysis tools; DWD should port intent, not the session model.

9. Suggested roadmap​

Phase 1 — Analytics primitives (high leverage)​

  • MCP: wrap metric_slice, time_node (or POST variants); document how to obtain time (Copilot simulationStartUtc / simulationEndUtc or list hydraulic times).
  • Playbook: 20_hydraulics_results.md — completed run required, SSOT metric names, no raw JSON dumps.
  • Formatter: optional threshold / top-K in API or MCP so the model does not filter thousands of rows in prose.

Phase 2 — Intent tools for operator prompts​

  • e.g. list_junctions_by_pressure(...) — internally read_metric_slice + index/ID map.
  • Disambiguate “flooded” vs “low pressure” vs tank overflow in naming and docs.

Phase 3 — SCADA and pump curves​

  • API for pump SCADA series and/or curve generation.
  • MCP intent tool + playbook; reuse twin/scenario SCADA flags from agent-summary.

Phase 4 — Governance​

  • Keep checklist: API enforcement → agent-summary or formatter → MCP → playbook → tests.
  • Tool budget: prefer composites over new thin proxies (rough guide: stay near ~25 MCP tools unless primitives are shared).

10. FAQ (direct answers)​

How do we translate APIs into meaningful tools?

Name and document user intents, not routes. Use composite reads where the UI already defines meaning (get_scenario_details). For raw data APIs, add primitives with guardrails (time, metric, top-K) and formatters that return counts, extremes, and IDs.

API first or straight to agent tool?

API first for persistence, auth, and shared UI behavior. MCP may orchestrate multiple API calls without a new route when logic is chat-only and non-reusable; heavy domain logic belongs in FastAPI/services.

Generic support without every API call?

Use a small set of query primitives plus SSOT-backed metric vocabulary, and add named intent tools where multi-step composition is too error-prone for the model. Do not rely on one MCP tool per OpenAPI operation.


11. Code touchpoints (quick reference)​

ConcernPath
MAF agent builddwd-api-fastapi/app/agent/copilot.py
Playbooksdwd-api-fastapi/app/agent/instructions/*.md
MCP registrationdwd-mcp-fastmcp/tools.py
MCP HTTP clientdwd-mcp-fastmcp/api_client.py
Bulk results APIdwd-api-fastapi/app/results/routes.py
SCADA APIdwd-api-fastapi/app/scada/routes.py
SSOT metricsdwd-api-fastapi/app/core/ssot_metrics.py
Agent summaries.../agent-summary on twins/scenarios; app/agent/scenario_summary.py, twin_summary.py
Copilot proxydwd-copilot-server/lib/copilotRuntime.js

Implementation workflow for changes: chat-agent-mcp-development-guide.md.