Every change Wellknown observed on this MCP server, newest first, with what it was before and what it became. Tool-surface changes carry the definition diff. Nothing here is edited after the fact.
Authorization required, Bearer, issuer https://cozy-steam-24.authkit.app
Authorization required, Bearer, issuer https://cozy-steam-24.authkit.app
Authorization not required → required, Bearer, issuer https://cozy-steam-24.authkit.app
Changed the definition of "attach_input_layer", "build_scenario", "cancel_run" and 8 more
⟨25 unchanged words⟩ upload the GeoJSON yourself, with the samecredentialagent key, to GeoNode's upload endpoint at the site origin: curl -sS-u-H<user>'Authorization:<password>Bearer hk_...' -F "base_file=@/path/rainfall.geojson" https://<site>/api/v2/uploads/upload/ → JSON with ⟨369 unchanged words⟩ if the dataset is not owned by you. Every layer attached here is marked as written by your agent key and is UNREVIEWED until the user reviews it in their browser (the build refuses UNREVIEWED_INPUTS until then). INFLOWS: never invent an inflow location — no coordinates you made up, guessed or "placed plausibly". Import the location from the study's own files, or give the user the ui_url to draw it in the map; say in say_to_user that the inflow is unreviewed and where they check or draw it. Refused, with no request made: kind `breakline` ⟨68 unchanged words⟩
Added "add_observation_photo_link", "add_observations", "create_observation_layer" and 6 more (19 tools before, 28 now)
Attach a photo link to a flood observation — Attach a photo to a flood observation as a reference LINK (never a file). MCP never uploads photo bytes. A local path, file:// URL or data: URI is refused: to upload a photo file, open the observation in the map and use the photo strip on its form (JPEG, PNG or HEIC, 25 MB, 10 per observation). The link counts toward the 10.
Add flood observations to a layer — Add flood observations (Flood mark, Timed level, Gauge record, Dry point) to a layer. POSTs each observation to /projects/<id>/flood-observations/<layer>/observations/ in order. The API validates every field: a row it refuses (400) is listed in `rejected` with its `problems` and the rest still land; a refusal that is not about the row (403 not an Editor, 404, 409) stops the batch and is reported in `stopped`, with `not_sent_count`. Stages are on the LAYER's vertical datum. Photos are not part of an observation: attach a link with add_observation_photo_link.
Create a flood observation layer — Create a flood observation layer in a project and wait for its map layer. A flood observation layer holds real flood evidence (Flood marks, Timed levels, Gauge records, Dry points) that every historic run is compared with (Compare with observations). It is project-level: it is never a scenario input. POSTs /projects/<id>/flood-observations/, then polls the layer until its GeoServer layer exists (`ready`), usually a few seconds; until then add_observations refuses. On `timed_out`, call list_observations later.
Changed the definition of "attach_input_layer"
⟨267 unchanged words⟩ — the user fixes those in the map. `leftover_datasets` lists the datasets the attach left unreferenced (the raw upload when it was re-uploaded; the empty factory layer of a 'Culvert 01' created here), each {dataset_pk, name, reason}, with a `leftover_hint`; the tool deletes nothing — the user may delete them. The culvert engine conveys flow through each culvert ⟨180 unchanged words⟩
Changed the definition of "attach_input_layer", "create_scenario", "get_scenario" and 1 more
⟨9 unchanged words⟩ project's boundary, friction, inflow, rainfall, building, mesh_region ormesh_regionculvert layer. The agent moves the bytes: first ⟨96 unchanged words⟩ run finalize_terrain_upload / wait for get_terrain first). kind `culvert` (Hydraulic Structures; one two-point line per culvert, Inlet first, then Outlet): no culvert row is seeded, so the tool creates 'Culvert 01' when the project has none and waits for its layer. It reads your culverts back and maps your attribute names onto the culvert fields — name, shape (box|circular), width_m, height_m, diameter_m, upstream_invert_m, downstream_invert_m, barrels (1-20), wall_thickness_m, entrance_type, material (concrete|corrugated_metal), manning_n, blockage_pct (Reduced-area blockage, percent), length_override_m, description — accepting common alternatives (width, diameter, label, us_invert, ds_invert, num_barrels, manning, blockage, length, shape 'pipe' = circular, …) and filling the defaults (barrels 1, wall 0.20 m, square_edge_headwall, concrete, manning_n from the material, blockage 0). Other attributes are kept. If anything changed it re-uploads the mapped culverts and attaches THAT layer (`mapped`: renamed, filled, source_dataset_pk). `culvert_problems` lists what the build will still refuse (typically missing inverts) — the user fixes those in the map. The culvert engine conveys flow through each culvert in a run (GPU lanes and localhost; the multi-core CPU lanes refuse a culvert scenario).
Changed the definition of "attach_input_layer", "create_scenario", "finalize_terrain_upload" and 3 more
⟨8 unchanged words⟩ the project's boundary, friction, inflow, rainfall,structurebuilding or mesh_region layer. The agent moves the bytes ⟨267 unchanged words⟩
⟨22 unchanged words⟩ is: boundary, friction, inflow, rainfall,structurebuilding ormesh_region.mesh_region ('structure' is a deprecated alias of building). breakline and culvert are refused (see below) ⟨68 unchanged words⟩
Added "list_inputs" and "list_time_series"; changed the definition of "attach_input_layer", "build_scenario", "cancel_run" and 14 more (17 tools before, 19 now)
AttachInputanLayeruploaded layer as a project input
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
—
Certificate changed, valid to 2026-12-25
Added "attach_input_layer", "build_scenario", "create_project" and 5 more; changed the definition of "cancel_run", "get_project", "get_run" and 6 more (9 tools before, 17 now)
before
—after
Cancel Runbefore
—after
Get ProjectCertificate recorded, valid to 2026-10-26
Authorization not required
First tool surface recorded: 9 tools (server version 3.0.2)
Showing the latest 14 events. The API returns up to 500 and filters by kind: ?kind=tool_surface_changed
⟨10 unchanged words⟩ it will cost, and poll until built. Needs a launch-level agent key (a build can start AWS Batch); a read or draft key gets 403 AGENT_LEVEL_INSUFFICIENT — the user creates a launch key on the site's Agent keys page (/agents/). Order of operations, so the number is shown ⟨204 unchanged words⟩ no run was created), 409 COMPUTE_TARGET_UNAVAILABLE, 400/403/404. 409 UNREVIEWED_INPUTS (inputs you wrote that the user has not reviewed) answers `outcome` `unreviewed_inputs` with the `unreviewed_inputs` list and a `ui_url` to the scenario's review screen: give the user the link, poll get_scenario until its `unreviewed_inputs` is empty, then build again. A 409 WITHOUT an error_code is the dedup ⟨223 unchanged words⟩
Cancel an in-flight simulation run. Needs a launch-level agent key; a read or draft key gets 403 AGENT_LEVEL_INSUFFICIENT. Works on runs in built, queued, or ⟨31 unchanged words⟩
⟨53 unchanged words⟩ opens it — give it to the user. A project you create is unpublished (`is_published: false`): only its owner and members can see it until the user presses Publish in its Sharing panel in the browser — you cannot publish it. Next step for a new project:presign_terrain_upload.
⟨100 unchanged words⟩ the exact names the rainfall GeoJSON's features carry. The units must match the series_type (see `units`), and a hydrograph or hyetograph must END at 0 unless `hold_last_value` is true. The result carries Hydrata's own `summary` of the series (peak, volume or depth, first and last stamp, trailing value): repeat those numbers to the user (`say_to_user` does) rather than computing your own.
⟨45 unchanged words⟩ (optional)","type":"string"},"hold_last_value":{"default":false,"description":"Set true ONLY when the last value is meant to be held after the series ends. A hydrograph or hyetograph whose last value is not 0 is refused (TRAILING_VALUE_HELD) unless this is true: end the series with a 0 row instead, unless holding is really intended.","type":"boolean"},"location_name":{"default":"","description":" ⟨115 unchanged words⟩ units":{"default":"","description":"DisplayUnitslabelof the values. REQUIRED forthehyetographvalues('mm/hr' or 'mm/h'),e.g.hydrograph'mm/hr'('m3/s', 'm^3/s','m'or 'cumec') and stage (optional'm'): the engine reads those units whatever the label says, so a mismatch is refused before anything is sent. Free for generic.","type":"string"}},"required": ⟨5 unchanged words⟩
⟨9 unchanged words⟩ GeoTIFF as a Terrain and start the import. Needs a launch-level agent key (the import can start AWS Batch); a read or draft key gets 403 AGENT_LEVEL_INSUFFICIENT — the user creates a launch key on the site's Agent keys page (/agents/). Call this only after the presigned PUT returned ⟨77 unchanged words⟩
⟨81 unchanged words⟩ , whose first value is then held backwards), and `hydrology_check` (when Hydrata sends it: for each inflow and rainfall feature, the series that drives it, its peak and volume, and structured warnings — repeat those numbers to the user, never your own), and `unbound_series` with a `say_to_user` warning when the project holds a rainfall or flow series that no inflow or rainfall feature uses (the model ignores it: tell the user and ask which input it belongs to). The `computed_status` field (there is NO `status` ⟨45 unchanged words⟩ false after any edit since the last build. `unreviewed_inputs` lists the inputs written by an agent key that the user has not reviewed at their current content (kind, layer_id, title, feature_count, the grant that wrote it, `reopened` when an edit undid an earlier input review): while it is not empty, build and run are refused UNREVIEWED_INPUTS. Only the user reviews, in the browser — poll this tool every 30-60 s after giving them the review link. A non-member reading a public project's scenario gets ⟨72 unchanged words⟩
⟨39 unchanged words⟩ `workspace:name` WFS typename) and `has_features`. `has_features`comes fromis theAPI for boundary, rainfall,API'smesh_regionownandanswerculvert;forfriction, inflow and building (whose records do not carryeveryit)kind.itIt isa WFS feature count, at most 24 lookups per callnull—onlypastwhen athat,row'sorrecordwhendoesa lookupnotfails,carry it:istreat null(as unknown, notfalse).as empty. A rowwith no `gn_layer`has no layer yet (`has_features` false). Use ⟨16 unchanged words⟩
⟨9 unchanged words⟩ S3 PUT URL for a GeoTIFF DEM. The import needs a launch-level agent key: a draft key can presign, but finalize_terrain_upload (and the multipart fallback below) answer a read or draft key 403 AGENT_LEVEL_INSUFFICIENT — check the key before moving bytes. The agent moves the bytes itself — no tool accepts ⟨139 unchanged words⟩ impossible: multipart-POST the file with the samecredentialagent key to /api/v2/anuga/projects/<id>/terrain/upload/ (form field `file`, optional `title` ⟨10 unchanged words⟩
Retry a failed simulation run.ResetsNeedsanaerroredlaunch-levelrunagentbackkeyto(a'created'retrystatusrebuilds the package andtriggerscan start AWS Batch); anewread or draft key gets 403 AGENT_LEVEL_INSUFFICIENT. Starts a fresh packagebuild.build of the run's scenario as a NEW run, which becomes the scenario's latest run. Thesameerrored run keeps its status and error as history, and the response describes that old run; the new run's ID isreused.get_scenario's `run_id`. Only valid whenstatus is 'error'. Returns409 for any other state.
Start a flood simulation run for a built scenario. Needs a launch-level agent key (a run spends compute); a read or draft key gets 403 AGENT_LEVEL_INSUFFICIENT — the user creates a launch key on the site's Agent keys page (/agents/). The scenario must be in 'built' status. Returns ⟨41 unchanged words⟩ its presigned `s3_*_url` links elided (6-hour capabilities). INPUT REVIEW (Hydrata enforces it): while an input you wrote is not reviewed by the user, the run is not started - `outcome` `unreviewed_inputs`, `error_code` UNREVIEWED_INPUTS, a `ui_url` to the scenario's review screen. A run built from inputs other than the ones reviewed now answers `outcome` `run_invalidated` (RUN_INVALIDATED): build_scenario with rebuild=true, then start it again. SPEND CONSENT (Hydrata enforces it; you cannot): a run whose quote does not fit your agent key's monthly spend limit (default $0), or that Hydrata cannot price, is NOT started. The result is then a normal (non-error) answer with `outcome` `consent_required`, `error_code` CONSENT_REQUIRED, `quoted_cost_usd` and `consent_url`: give the user the link and the price (`say_to_user`). Only they can approve, in their browser. Then do NOT call start_simulation again for this run: poll get_run_status(run_id) — the run leaves `built` once they press the button. (With consent_timeout_seconds > 0 this call polls the run itself and answers `outcome` `consented`, with the run, if they approve within that time.)
⟨21 unchanged words⟩ Default: 'local'","type":"string"},"consent_timeout_seconds":{"default":0,"description":"When the run needs the person's consent (CONSENT_REQUIRED), how long this call keeps polling the run for their click before it returns (ceiling 280). Default 0: return the consent link at once, so you can give it to them — they cannot press a button they have not been shown.","type":"integer"},"poll_interval_seconds":{"default":5,"description":"Seconds between consent polls (default 5)","type":"number"},"scenario_id":{"description":"The scenario ID to ⟨7 unchanged words⟩
Delete one flood observation — Delete one flood observation and its photos (cannot be undone). DELETEs /projects/<id>/flood-observations/<layer>/observations/<fid>/; a missing fid is an error (404), not a silent success.
Compare a run with flood observations — Compare a run with the project's flood observations: how well does it match them? Returns `headline` (the figures to tell the user, e.g. "47 observations · 31 within ±0.30 m (66%) · bias -0.12 m · RMSE 0.35 m · 1 excluded"), `headline_numbers` (the same as numbers), `tolerance_m` (used) and `default_tolerance_m` (the project's saved one), `statistics` (the API's summary: n, within, within_pct, bias_m, rmse_m, weighted_rmse_m, mae_m, max_abs_m, excluded, not_scored by reason, gauges), `gauges` (Gauge records: peak error, timing error, NSE), `datum_checks` (warn the user only when `warning` is…
List flood observation layers or observations — List a project's flood observation layers, or one layer's observations. Without layer_id: every layer (layer_id, title, dataset_alternate, vertical_datum, datum_offset_m, has_features). With layer_id: {layer, count, results} where each observation carries its `fid` (the key for update_observation / delete_observation), subtype + subtype_label, stage/depth, sigma, bound, the observed window in UTC, `datum` status (an `unknown` datum is not scored), `photo_count` and `problems` (fields drawn in the map that fail validation, reported, never fixed).
Save a project's default comparison tolerance — Save a project's default tolerance for comparing runs with flood observations. Needs Editor (or above). Every later get_observation_comparison (and the map's Compare with observations tab) uses it unless a tolerance is passed. Only save it when the user asks to change the project's default; to show the figures at another tolerance, pass `tolerance` to get_observation_comparison.
Exclude or include a flood observation in a run's comparison — Exclude a flood observation from ONE run's comparison with observations, or include it again. Needs Editor (or above) on the project. The exclusion is logged with the user and reason (nothing is overwritten) and keyed on (run, layer, fid), so it survives a recompute. The figures change at once: `headline_after` is the run's headline with the change applied. To exclude, the user must give the reason: if they have not, ASK them; do not make one up.
Edit one flood observation — Edit one flood observation (PATCH; only the fields given change). The API checks the stored observation merged with `changes` and refuses (400, every bad field named) rather than store a half-valid one. Photos are not fields: attach a link with add_observation_photo_link.
⟨22 unchanged words⟩ is: boundary, friction, inflow, rainfall, building, mesh_region ormesh_regionculvert ('structure' is a deprecated alias of building). breaklineand culvert areis refused (see below).","type":" ⟨66 unchanged words⟩
{"destructiveHint":false,"idempotentHint":truefalse,"openWorldHint":false,"readOnlyHint":false,"title" ⟨8 unchanged words⟩
⟨28 unchanged words⟩ ":"Building row id (optional)"},"culvert":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Culvert row id (optional; attach_input_layer kind=culvert → row_id). The build refuses a culvert missing its shape, size or invert levels."},"description":{"default":"","description":" ⟨205 unchanged words⟩
⟨59 unchanged words⟩ `boundary`, `friction`, `inflow`, `rainfall`, `building`, `mesh_region`, `culvert`), `compute_cost_estimate` (USD), `vcpu_hours_estimate`, and `inflow_anchor_mismatch` ⟨158 unchanged words⟩
⟨17 unchanged words⟩ of boundary, friction, inflow, rainfall, building, mesh_region andmesh_regionculvert: the rows (`row_id`, `title`, `gn_layer` — ⟨13 unchanged words⟩ `has_features` comes from the API for boundary, rainfall, mesh_region andmesh_region;culvert; for friction, inflow and building (whose records ⟨60 unchanged words⟩
⟨14 unchanged words⟩ id (attach_input_layer kind=boundary → row_id)"},"building":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Building row id (optional)"},"description":{"default":"","description":"Free-text description ⟨161 unchanged words⟩ null"}],"default":null,"description":"StructureDeprecatedrowaliasidof `building` (optionalTASK-3586); ignored when `building` is given."},"terrain":{"anyOf":[{"type ⟨24 unchanged words⟩
⟨53 unchanged words⟩ six default boundary, friction, inflow, rainfall,structurebuilding and mesh-region rows. Returns 202 with the terrain ⟨34 unchanged words⟩
⟨58 unchanged words⟩ (`terrain`, `boundary`, `friction`, `inflow`, `rainfall`,`structure``building`, `mesh_region`), `compute_cost_estimate` (USD), `vcpu_hours_estimate`, ⟨160 unchanged words⟩
⟨169 unchanged words⟩ Friction 01, Inflow 01, Rainfall 01,StructureBuilding 01, MeshRegion 01, one GeoNode layer each ⟨33 unchanged words⟩
⟨16 unchanged words⟩ each of boundary, friction, inflow, rainfall,structurebuilding and mesh_region: the rows (`row_id`, `title`, ⟨22 unchanged words⟩ rainfall and mesh_region; for friction, inflow andstructurebuilding (whose records do not carry it) it ⟨55 unchanged words⟩
after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Attach an uploaded layer as a project input"}BuildScenarioa scenario's mesh and package
⟨381 unchanged words⟩ latest run is rebuilt). Units: `resolution` — on the SCENARIOisand on every MeshRegion FEATURE — is a LENGTH inmetres —metres; ANUGAmaximum_triangle_area = resolution²/2 (run_utils.py:227/:290;FloatField default 100, not nullable; 0 makes the estimate None), and thesame valueestimatebecomesprices both therastersamecellwaysize;(TASK-3186).aTheMeshRegionsmallestFEATURE'svalue`resolution`becomesisthe rasterconsumedcellbysize.theAmesherMeshRegionasfeatureanmustAREAcarry(max_triangle_area,`resolution_unitsm²)=whilem`the(featuresestimatedrawnpricesinittheasmapaarelength,markedsoautomatically);attachedanregionsunmarkedmeshone~15xmakesmorethethanbuildestimated (Towradgi 100/36/8 m² regions:answerestimate422~18kMESH_REGION_UNITS_UNMARKED,mesh ~283k);naming thesmallestMeshRegion value becomes theconversionrasterancelloperatorsize.runs.
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Build a scenario's mesh and package"}CancelRuna run
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":true,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false,"title":"Cancel a run"}CreateProjecta project
Create a new ANUGA project. Returns the project record including itsid.id, base_map and ui_url. The caller's own account becomes the owner. `projection` ⟨14 unchanged words⟩ — pick the UTM zone covering the site. The project's map is created alongside it; `ui_url` opens it — give it to the user. Next step for a new project:presign_terrain_upload.
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Create a project"}CreateScenarioa draft scenario
⟨34 unchanged words⟩ (+ `_breakdown`) does. Returns a compact record (the same fields get_scenario answers with): id, name, the FK idsas stored, resolution, duration,`computed_status` (`created` = no runyet), the estimate and itsbreakdown,andthe price (`compute_cost_estimate` in USD, `vcpu_hours_estimate`), `inflow_anchor_mismatch`, and http_status. Next step: build_scenario (which asks for ⟨7 unchanged words⟩ is meshed or queued here. Units: `resolution` — on the SCENARIOisand on every MeshRegion FEATURE — is a LENGTH inmetres —metres; ANUGAmaximum_triangle_area = resolution²/2 (run_utils.py:227/:290;FloatField default 100, not nullable; 0 makes the estimate None), and thesame valueestimatebecomesprices both therastersamecellwaysize;(TASK-3186).aTheMeshRegionsmallestFEATURE'svalue`resolution`becomesisthe rasterconsumedcellbysize.theAmesherMeshRegionasfeatureanmustAREAcarry(max_triangle_area,`resolution_unitsm²)=whilem`the(featuresestimatedrawnpricesinittheasmapaarelength,markedsoautomatically);attachedanregionsunmarkedmeshone~15xmakesmorethethanbuildestimated (Towradgi 100/36/8 m² regions:answerestimate422~18kMESH_REGION_UNITS_UNMARKED,mesh ~283k);naming thesmallestMeshRegion value becomes theconversionrasterancelloperatorsize.runs. The server does NOT validate that the FK ⟨86 unchanged words⟩
⟨81 unchanged words⟩ null,"description":"MeshRegion row id (optional).:LeavefinerUNSETmeshingunlessinsideyouitswantpolygons,theeachregions'feature'sfiner`resolution`meshinga—LENGTH in metres (see the units note: they mesh ~15x more than estimated.)."},"name":{"description":"Scenario name ⟨112 unchanged words⟩
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Create a draft scenario"}CreateTimeaSeriestime series
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Create a time series"}FinalizeRegisterTerrainanUploaduploaded terrain
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Register an uploaded terrain"}GetProjecta project and its map link
GetdetailsoneofANUGAaproject:specificid,ANUGAname,projectprojection,includingvisibility, your role, itsscenarios.base map — and the link to it. Returns the projectnamerecord (`id`,projection`name`, `projection` (EPSG code), `simple_view_config`,base`visibility`,map`owner_username`,ID`my_role`,and`base_map`)configuration.plus `ui_url`: the link that opens the project's map, for the user (`<site>/catalogue/#/map/<base_map>`; null while a new project's map is still being created). Scenarios are NOT listed here — use get_scenario with a scenario id.
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get a project and its map link"}GetRuna run's full record
⟨37 unchanged words⟩ for lightweight polling; use this for final results. The presigned `s3_*_url` download links are ELIDED — each is a 6-hour capability and no tool relays one. The results stay viewable through the `gn_layer_*` entries (published WMS layers on the project's map).
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get a run's full record"}GetPollRunaStatusrun's status
⟨15 unchanged words⟩ of get_run. Returns only: id, status, `phase`, progress_pct (0-100), eta_seconds, error_message, andcompute_backend. `phase` is never empty: it is `status`, except `preparing_inputs` while a `computing` run has no progress yet (it is downloading its input package and building the domain — minutes on a big model; this is NOT a stall). Poll every 5-10 seconds. Terminal states: complete, error, cancelled.
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Poll a run's status"}GetScenarioa scenario's state, inputs and price
Getscenarioadetailsscenario'sincludingCOMPACT state: its `computed_status`, inputs, estimate and price. Returns a compact record, never the raw ~50 KB detail: `scenario_id`, `computed_status`, `mesh_triangle_count_estimate` and its `_breakdown`, `latest_run_is_valid`, the latestrun.run's `run_id`, `run_status`, `error_message`, `user_message`, `status_detail` and `mesh_triangle_count` — plus what the scenario IS and what it will cost: `name`, `description`, `project`, `resolution`, `duration`, the input row ids (`terrain`, `boundary`, `friction`, `inflow`, `rainfall`, `structure`, `mesh_region`), `compute_cost_estimate` (USD), `vcpu_hours_estimate`, and `inflow_anchor_mismatch` (null unless an inflow series starts after the model does, whose first value is then held backwards). The `computed_status` field (there is NO `status` ⟨35 unchanged words⟩ must be `built` before it can be run.The detail also carries `mesh_triangle_count_estimate` (+ its `_breakdown`) and`latest_run_is_valid`(is false after any edit since thelastbuild).last build. A non-member reading a public project's scenario gets areduced record: thewithfields the server withholds (the input ids, the price, `inflow_anchor_mismatch`) are ABSENT here rather than reported as null — "withheld" is not "no`computed_status`.terrain, no cost" — while the run-state keys, `computed_status` among them, read null. No presigned S3 URL is returned by any read tool (each is a 6-hour capability): results stay viewable through a run's `gn_layer_*` WMS entries — use get_run for the full run record.
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Get a scenario's state, inputs and price"}GetWaitTerrainfor a terrain import
⟨44 unchanged words⟩ (creating → styling → ready | error) and `phase` (that status, or `not_found`; never empty), `polls`, `elapsed_seconds` and the full `terrain` record ⟨150 unchanged words⟩
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"Wait for a terrain import"}ListProjectsprojects
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"List projects"}ListRunsa project's runs
⟨20 unchanged words⟩ to find active, completed, or failed runs. Each row's presigned `s3_*_url` download links are elided (6-hour capabilities).
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,"title":"List a project's runs"}PresignGetTerrainaUploadterrain upload URL
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Get a terrain upload URL"}RetryRuna failed run
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Retry a failed run"}StartSimulationa simulation run (spends compute)
⟨45 unchanged words⟩ the scenario is not in the correct state. The returned run record has its presigned `s3_*_url` links elided (6-hour capabilities).
{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true}
before
—after
{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Start a simulation run (spends compute)"}List a project's input layers — List every input layer row of a project, per kind, with whether it has features. For each of boundary, friction, inflow, rainfall, structure and mesh_region: the rows (`row_id`, `title`, `gn_layer` — the dataset pk — and `dataset_alternate`, the `workspace:name` WFS typename) and `has_features`. `has_features` comes from the API for boundary, rainfall and mesh_region; for friction, inflow and structure (whose records do not carry it) it is a WFS feature count, at most 24 lookups per call — past that, or when a lookup fails, it is null (unknown, not false). A row with no `gn_layer` has no lay…
List a project's time series — List a project's time series (rain gauges, hydrographs, tides) — never their rows. Each series: `id`, `name` (a rainfall polygon binds to its gauge by this exact name), `series_type`, `units`, `timezone`, `row_count`, `first_timestamp` and `last_timestamp`. The data rows themselves are dropped (a project's list is megabytes); fetch one series with the REST API (GET /projects/<id>/time-series/<id>/) if you need its values.
before
—after
Get Runbefore
—after
Get Run Statusbefore
—after
Get ScenarioGet scenario details including itscurrent status`computed_status` and latest run. Thestatus`computed_status` field (there iscomputedNO `status` key on a scenario) is derived from the latest run and will be one of:created, building, built,queued, computing, processing, complete,error, orcancelled.cancelled — `created` also means no run exists yet. A scenario must bein 'built' status`built` beforeit can berun. The detail also carries `mesh_triangle_count_estimate` (+ its `_breakdown`) and `latest_run_is_valid` (false after any edit since the last build). A non-member reading a public project's scenario gets a reduced record with no `computed_status`.
before
—after
List Projectsbefore
—after
List Runsbefore
—after
Retry Runbefore
—after
Start SimulationAttach Input Layer — Attach a GeoJSON you uploaded to GeoNode as the project's boundary, friction, inflow, rainfall, structure or mesh_region layer. The agent moves the bytes: first upload the GeoJSON yourself, with the same credential, to GeoNode's upload endpoint at the site origin: curl -sS -u <user>:<password> -F "base_file=@/path/rainfall.geojson" https://<site>/api/v2/uploads/upload/ → JSON with `execution_id`. Then call this tool with it. The tool polls GET /api/v2/resource-service/execution-status/<execution_id> (bounded by timeout_seconds; statuses ready → running → finished | failed), reads the n…
Build Scenario — Build a scenario's package (mesh + inputs) after showing what it will cost, and poll until built. Order of operations, so the number is shown before anything is spent: 1. GET the scenario detail. A record with no `boundary`/`computed_status` keys is a non-member's read of a public project → refused: a build needs the EDITOR role. 2. Re-call checks on `latest_run` (a re-POST is NOT deduplicated after `built`/`complete` — it would dispatch a duplicate build): no run → proceed; run `created`/`building` → resume polling, no POST; run `built`/`queued`/`computing`/`processing`/`com…
Create Project — Create a new ANUGA project. Returns the project record including its id. The caller's own account becomes the owner. `projection` is the projected CRS every scenario in the project is meshed and run in — pick the UTM zone covering the site. Next step for a new project: presign_terrain_upload.
Create Scenario — Create a DRAFT scenario (no build, no run) and report its mesh-triangle estimate. POSTs /projects/<id>/scenarios/ with the write fields, then GETs the scenario detail — the create response carries NO estimate; the detail's `mesh_triangle_count_estimate` (+ `_breakdown`) does. Returns a compact record: id, name, the FK ids as stored, resolution, duration, `computed_status` (`created` = no run yet), the estimate and its breakdown, and http_status. Next step: build_scenario (which asks for confirm=true above 100,000 triangles). Nothing is meshed or queued here. Units: `resolution` on the SCENA…
Create Time Series — Create a time series (rain gauge, hydrograph, tide/stage) in a project. POSTs /projects/<id>/time-series/ with `series_type` and `units` as top-level fields. `series_type` is checked against the four choices and `data` against the {"rowData": [{timestamp, value}, ...]} shape before any request is made — the API answers 500 (not 400) to a malformed row. Returns a compact record — id, name, series_type, units, timezone, row_count, http_status — not the echoed rows; fetch the full row with the REST API (GET /projects/<id>/time-series/<id>/) if you need to round-trip it. A rainfall polygon refer…
Finalize Terrain Upload — Step 2 of a terrain import: register the PUT GeoTIFF as a Terrain and start the import. Call this only after the presigned PUT returned 200. Creates the Terrain row (status `creating`) and queues the import chain — reproject to UTM, publish the layer + hillshade, style — which also seeds the project's six default boundary, friction, inflow, rainfall, structure and mesh-region rows. Returns 202 with the terrain record; keep its `id` for get_terrain. A 400 UPLOAD_NOT_FOUND means no object is at `staging_key`: the PUT did not land (check its status code and Content-Type) — do not retry finalize…
Get Terrain — Poll a terrain's import until it is `ready` (or `error`), bounded by timeout_seconds. Returns `outcome` — exactly one of `ready`, `error`, `timed_out`, `not_found` (the project has no terrain yet; an unknown terrain_id is an API 404 error instead) — plus the last `status` seen (creating → styling → ready | error), `polls`, `elapsed_seconds` and the full `terrain` record (its `gn_layer` is the published elevation dataset pk once ready). An import takes minutes to tens of minutes (the SAME 32 MB 1 m DEM measured 4.5 min once and 26 min once — the worker's S3 download speed dominates), so `time…
Presign Terrain Upload — Step 1 of a terrain import: get a presigned S3 PUT URL for a GeoTIFF DEM. The agent moves the bytes itself — no tool accepts file contents. After this call, PUT the file straight to `upload_url`, sending the SAME Content-Type you passed here (it is part of the signature; a mismatch is a 403 SignatureDoesNotMatch before it is anything else): curl -sS -X PUT -H "Content-Type: image/tiff" --upload-file /path/dem.tif "$UPLOAD_URL" then call finalize_terrain_upload with the returned staging_key and process_id. Nothing exists in Hydrata until finalize; the URL expires after `expires_in` seco…