ovf-data-mcp
# ovf-data-mcp
[](assets/ovf-data-hero-v2.png)
[](https://pypi.org/project/ovf-data-mcp/)
[](https://www.python.org/downloads/)
[](LICENSE)
[](https://modelcontextprotocol.io/)
[](TODO.md)
Agent-friendly, read-only access to public Hungarian water-management data from the
Országos Vízügyi Főigazgatóság (OVF).
> [!IMPORTANT]
> `ovf-data-mcp` is an independent, unofficial project. It is not affiliated with or
> endorsed by OVF. OVF and the regional water directorates remain the authoritative
> sources for the data.
> [!WARNING]
> This repository is an experimental proof of concept, not a finished or supported
> production service. Interfaces and upstream integrations may change. See
> [`TODO.md`](TODO.md) for known limitations.
`vizugy` supports the investigation workflow agents actually need:
```text
discover → resolve stations → inspect coverage → explain query → retrieve or aggregate → cite
```
The same application logic is exposed through a composable CLI and a local MCP server.
The CLI is the primary interface; MCP tools are thin adapters over identical operations.
## Installation
Install [`uv`](https://docs.astral.sh/uv/), then choose the CLI or an MCP client.
### CLI
Install both `vizugy` and `ovf-data-mcp` as isolated global tools:
```bash
uv tool install ovf-data-mcp
vizugy --help
```
Run the CLI without installing it:
```bash
uvx --from ovf-data-mcp vizugy --help
```
### MCP clients
Claude Code:
```bash
claude mcp add --scope user ovf-data -- uvx ovf-data-mcp
```
Codex CLI and IDE extension:
```bash
codex mcp add ovf-data -- uvx ovf-data-mcp
```
Editor one-click installs and manual configuration are in
[MCP server](#mcp-server).
## What it can do
- Discover and inspect public OVF ArcGIS datasets.
- Search surface-water stations, shallow-groundwater wells (`--network wells`),
confined/layer-aquifer wells (`--network deep-wells`), and precipitation stations
(`--network precipitation`) by name, municipality, or watercourse.
- Find the nearest stations to WGS84 coordinates.
- List authoritative measurement codes, units, accepted ranges, and data types.
- Inspect temporal coverage before requesting observations.
- Retrieve compact, bounded operational or historical time series, optionally with
upstream quality codes and labels (`--quality`).
- Report per-station context: flood-alert thresholds, record low/high water levels,
and river kilometre where the upstream registry provides them.
- Aggregate observations upstream by day, ten-day period, month, or year.
- Query VRA soil moisture and soil temperature by the upstream `DataExt` dimension;
for verified soil metrics this is exposed as sensor depth in centimetres.
- Find stations with documented coverage for a requested metric and compare aligned,
upstream-aggregated soil series across 10, 20, 30, 45, 60, and 75 cm depths.
- Report officially declared water-shortage (drought) grades per district, with the
declaring action and timestamps — administrative status, not measurements.
- Explain resolved identifiers and query semantics without fetching values.
- Return structured provenance and explicit upstream caveats.
It deliberately does not interpret hydrology, detect anomalies, expose arbitrary SQL,
or provide unrestricted bulk access. The agent remains responsible for analysis.
## Showcase
What an agent can build from a handful of `vizugy` queries — 92 years of Lake Velence
water levels, from the near-dry 1930s to July 2026, which sits below the 2022 crisis
floor at a level last seen in 1938:
[](https://kalcifield.github.io/ovf-data-mcp/examples/lake-velence-90-years.html)
**[View the live dashboard →](https://kalcifield.github.io/ovf-data-mcp/examples/lake-velence-90-years.html)**
([source](docs/examples/lake-velence-90-years.html)) — every number on the page came from
these commands, no scraping or manual downloads:
```bash
vizugy stations search Velence
vizugy observations coverage surface:818 # available from 1934-01-01
vizugy observations aggregate surface:818 \
--start 1934-01-01T00:00:00Z --end 1960-01-01T00:00:00Z \
--interval yearly --operation avg # × avg/min/max × 4 windows, + monthly close-up
```
More examples, each built the same way:
- **[How deep does the drought bite?](https://kalcifield.github.io/ovf-data-mcp/examples/varosfold-soil-drought.html)**
([source](docs/examples/varosfold-soil-drought.html)) — soil moisture at six sensor
depths on the Danube–Tisza ridge, day by day through 2026 against the record 2022
drought: a wetter start, yet by late May the topsoil was drier than 2022's low.
- **[Hungary's hidden groundwater pulse](https://kalcifield.github.io/ovf-data-mcp/examples/hungary-deep-groundwater-pulse.html)**
([source](docs/examples/hungary-deep-groundwater-pulse.html)) — the latest reported
month at 30 spatially distributed deep wells, ranked against decades of observations
from the same calendar month, with 8,679 historical monthly values behind the view.
- **[A year of the Danube, measured in Balatons](https://kalcifield.github.io/ovf-data-mcp/examples/danube-in-balatons.html)**
([source](docs/examples/danube-in-balatons.html)) — 47 km³ of water passed Budapest
this year, about one Lake Balaton every 15 days, integrated from daily discharge.
- **[The sinking water table of the Homokhátság](https://kalcifield.github.io/ovf-data-mcp/examples/homokhatsag-groundwater.html)**
([source](docs/examples/homokhatsag-groundwater.html)) — three shallow wells on the
Danube–Tisza ridge, up to 92 years each: the water table has sunk 1–3 metres.
- **[Danube at Budapest — water level, last 12 months](https://kalcifield.github.io/ovf-data-mcp/examples/danube-water-level.html)**
([source](docs/examples/danube-water-level.html)) — daily mean with min–max range
and the flood-alert threshold at the Budapest gauge.
- **[A flood wave travelling down the Danube](https://kalcifield.github.io/ovf-data-mcp/examples/danube-flood-wave.html)**
([source](docs/examples/danube-flood-wave.html)) — small multiples of five gauges,
Komárom to Mohács, tracking the February 2026 crest across ~320 river-km in 3 days.
- **[How low are Hungary's rivers right now?](https://kalcifield.github.io/ovf-data-mcp/examples/rivers-drought-scoreboard.html)**
([source](docs/examples/rivers-drought-scoreboard.html)) — six gauges across the
Duna, Tisza, Dráva and Balaton, each placed in its own 12-month range.
All examples with previews: **[kalcifield.github.io/ovf-data-mcp](https://kalcifield.github.io/ovf-data-mcp/)**
## Data sources
| Source | Purpose | Status |
|---|---|---|
| [VRAQuery OpenAPI](https://vmservice.vizugy.hu/vraquery/swagger/index.html) | Stations, measurement catalogues, coverage, observations, aggregation | Officially documented |
| [OVF ArcGIS REST](https://geoportal.vizugy.hu/arcgis/rest/services) | Spatial dataset discovery and layer metadata | Public; metadata quality varies |
| [data.vizugy.hu](https://data.vizugy.hu/) | Official public-data frontend and anonymous access flow | Public frontend |
Operational observations may be preliminary or unchecked. For official proceedings or
guaranteed checked data, follow OVF's formal data-request process.
## Quick investigation
### 1. Resolve a station
```bash
vizugy stations search Budapest --watercourse Duna --limit 10
```
Results use stable namespaced IDs such as `surface:1026`.
Find stations by coordinates:
```bash
vizugy stations nearest 47.4979 19.0402 --limit 5
```
### 2. Inspect available measurements and coverage
```bash
vizugy catalog measurements
vizugy observations coverage surface:1026 \
--metric water-level \
--data-type operational
```
Useful metric aliases:
- `water-level`
- `discharge`
- `water-temperature`
- `soil-moisture`
- `soil-temperature`
Useful data-type aliases:
- `raw`
- `observed`
- `checked`
- `processed`
- `hydrological`
- `operational`
An empty result includes documented coverage for other available data types, when the
upstream catalogue provides it. Retry explicitly with the suggested `--data-type`; the
tool never silently substitutes one data type for another.
Numeric VRA codes and exact catalogue names are also accepted.
### 3. Explain before fetching
```bash
vizugy observations get surface:1026 \
--metric water-level \
--data-type operational \
--start 2026-07-16T00:00:00Z \
--end 2026-07-17T00:00:00Z \
--explain
```
The explanation shows the resolved station, metric and data-type codes, UTC bounds,
upstream operation, expected mode, and safety warnings. It performs no value query.
### 4. Retrieve a bounded raw series
```bash
vizugy observations get surface:1026 \
--metric water-level \
--data-type operational \
--start 2026-07-16T00:00:00Z \
--end 2026-07-17T00:00:00Z \
--limit 1000 \
--format jsonl
```
Raw observation queries require explicit bounds and may span at most seven days.
JSONL emits one compact timestamp/value record per line followed by a `_meta` record.
### 5. Aggregate longer periods upstream
```bash
vizugy observations aggregate surface:2046 \
--metric water-level \
--data-type operational \
--start 2026-06-01T00:00:00Z \
--end 2026-07-01T00:00:00Z \
--interval daily \
--operation max
```
Intervals: `daily`, `tenday`, `monthly`, `yearly`.
Operations supported by VRAQuery:
`min`, `max`, `avg`, `sum`, `cnt`, `mean`, `cntday`.
Aggregation buckets follow upstream hydrological/local-day boundaries. Returned bucket
labels remain UTC timestamps and can precede the requested UTC boundary by an offset.
### 6. Compare soil depths
Find precipitation-network stations with documented soil-moisture coverage, then
compare upstream daily averages across the six verified sensor depths:
```bash
vizugy stations nearest 46.91 19.69 \
--network precipitation --metric soil-moisture
vizugy observations coverage precip:6994 \
--metric soil-moisture --data-type operational
vizugy observations compare-depths precip:6994 \
--start 2026-07-01T00:00:00Z \
--end 2026-07-19T00:00:00Z
```
Raw and general aggregate queries also accept `--data-ext`; `--depth-cm` is the
validated semantic alias for soil moisture and soil temperature. `DataExt` remains a
generic upstream dimension because it may mean something else for other metrics.
`DataCatalogMinMax` documents station/metric/data-type coverage, not depth-specific
coverage. The comparison helper therefore reports empty requested depths from its
bounded value query. A live probe on 2026-07-19 found soil-moisture coverage at 24 of
the 428 active VRA precipitation stations; the separate OVF drought-monitoring API has
wider coverage and computed drought indicators, but is not integrated yet.
## Dataset discovery
Search the OVF ArcGIS catalogue without knowing folder or layer identifiers:
```bash
vizugy datasets list --query Vizmercek --limit 20 --format json
```
Inspect one service or layer:
```bash
vizugy datasets describe \
VIR/Vizmercek_vizugyhu_orszagos_adatsoros \
--layer 6
```
Some advertised ArcGIS folders require authentication. Public discovery skips them and
returns explicit warnings rather than failing the entire catalogue request.
### Declared water-shortage grades
The ArcGIS drought folder publishes the water-shortage grade each directorate has
formally declared for its districts. This is the administrative response to drought,
not a measurement, and pairs with the measured series above:
```bash
vizugy datasets water-shortage --grade-code 723 --limit 10
vizugy datasets water-shortage --directorate ATIVIZIG
```
A live probe on 2026-07-20 returned 85 districts: 28 at `III. fok`, 34 at `II. fok`,
9 at `I. fok`, with declarations as recent as 2026-07-19. Each record carries the
previous grade code, so escalations stay visible. Grade codes are 720 (none), 721,
722, and 723 (most severe).
The same layer joins a drought-index block whose values are stale by roughly two
years; those fields are deliberately not read. See `docs/arcgis-drought-layers.md`.
## CLI reference
```text
vizugy datasets list
vizugy datasets describe
vizugy datasets water-shortage
vizugy catalog measurements
vizugy stations search
vizugy stations nearest
vizugy observations coverage
vizugy observations get
vizugy observations aggregate
vizugy observations compare-depths
```
Machine-readable output goes to stdout; diagnostics go to stderr.
| Exit code | Meaning |
|---:|---|
| `0` | Success |
| `2` | Invalid or unsafe query |
| `3` | Upstream unavailable or invalid response |
| `4` | Requested entity not found |
## MCP server
MCP clients launch the local stdio server automatically using the commands in
[Installation](#installation) or the configurations at the end of this section.
To start it directly:
```bash
uvx ovf-data-mcp
```
Available tools:
| Tool | Intent |
|---|---|
| `discover_datasets` | Search public spatial datasets |
| `describe_dataset` | Inspect a service or layer schema |
| `list_measurement_types` | Resolve metrics, units, ranges, and data types |
| `find_stations` | Resolve station names, rivers, and municipalities |
| `nearest_stations` | Resolve coordinates to nearby stations |
| `inspect_coverage` | Check temporal availability before querying |
| `get_observations` | Retrieve a bounded raw series |
| `aggregate_observations` | Aggregate a longer series upstream |
| `compare_soil_depths` | Compare upstream-aggregated soil series by sensor depth |
One-click editor installation:
| Client | Install |
|---|---|
| VS Code | [](https://vscode.dev/redirect/mcp/install?name=ovf-data&config=%7B%22type%22%3A%22stdio%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22ovf-data-mcp%22%5D%7D) |
| Cursor | [](https://cursor.com/en/install-mcp?name=ovf-data&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyJvdmYtZGF0YS1tY3AiXX0=) |
Generic stdio configuration for Cursor and other MCP clients:
```json
{
"mcpServers": {
"ovf-data": {
"command": "uvx",
"args": ["ovf-data-mcp"]
}
}
}
```
<details>
<summary>Manual VS Code configuration</summary>
Add this to your user configuration or `.vscode/mcp.json`:
```json
{
"servers": {
"ovf-data": {
"type": "stdio",
"command": "uvx",
"args": ["ovf-data-mcp"]
}
}
}
```
</details>
<details>
<summary>Manual Codex configuration</summary>
Add this to `~/.codex/config.toml`:
```toml
[mcp_servers.ovf-data]
command = "uvx"
args = ["ovf-data-mcp"]
```
</details>
## Output semantics
Observation results distinguish:
- station identity and location;
- observation or aggregation-bucket timestamp;
- metric and unit;
- VRA data type;
- requested UTC interval;
- retrieval timestamp;
- provider and source operation;
- truncation and upstream warnings.
The coverage endpoint currently omits composed operational type `101`. When related
type `100` coverage exists, `vizugy` returns it with an explicit inference warning; it
does not silently claim equivalence.
## Configuration
| Variable | Default | Purpose |
|---|---|---|
| `VIZUGY_ARCGIS_URL` | `https://geoportal.vizugy.hu/arcgis/rest` | ArcGIS catalogue root |
| `VIZUGY_VRA_URL` | `https://vmservice.vizugy.hu/vraquery` | VRAQuery API root |
| `VIZUGY_TOKEN_URL` | `https://data.vizugy.hu/AuthApi/auth/token` | Public frontend token endpoint |
| `VIZUGY_TIMEOUT_SECONDS` | `15` | Upstream request timeout |
| `VIZUGY_CACHE_TTL_SECONDS` | `300` | ArcGIS metadata cache lifetime |
## Development
```bash
git clone https://github.com/kalcifield/ovf-data-mcp.git
cd ovf-data-mcp
uv sync --extra test
uv run ruff format --check src tests
uv run ruff check src tests
uv run mypy src tests
uv run pytest -q
```
VRAQuery wire models are generated from the pinned OpenAPI document. Regenerate them
after intentionally updating that document:
```bash
scripts/generate-vra-models
```
Design decisions, verified upstream behavior, and unresolved questions are documented
in [`docs/design.md`](docs/design.md) and [`docs/phase-2-review.md`](docs/phase-2-review.md).
Observed upstream limits — aggregation timeouts under load, catalogued-but-dead
stations, and which comparisons the data actually supports — are in
[`docs/ovf-service-behavior.md`](docs/ovf-service-behavior.md).
## Licence and data attribution
The software is available under the [MIT License](LICENSE). This does not establish
unrestricted reuse rights for every upstream dataset. Preserve OVF provenance and
verify the applicable data terms before redistribution or production use.
TDQS
Scored across 10 tools
Each tool targets a distinct purpose: dataset discovery, schema inspection, aggregation, soil-depth comparison, drought declarations, measurement-type metadata, station lookup, proximity search, raw observations, and coverage inspection. The only mild overlap is find_stations vs nearest_stations, but the descriptions (registry/name search vs geographic proximity) make the distinction fairly clear.
The naming is inconsistent, mixing verb-based names (discover_datasets, describe_dataset, aggregate_observations, compare_soil_depths, list_measurement_types, find_stations, get_observations, inspect_coverage) with noun-phrase names (water_shortage_districts, nearest_stations). Some use verb_noun (find_stations, describe_dataset), others drop the leading verb (nearest_stations, water_shortage_districts). The 'describe_dataset' vs 'discover_datasets' singular/plural mismatch also adds inconsistency.
Ten tools is a well-scoped surface for an observational water data domain, covering discovery, metadata, querying, and analysis. Each tool serves a distinct function and none feels like filler; the count is squarely in the ideal 3-15 range.
The surface covers discovery, metadata inspection, raw observation retrieval, aggregation, soil-depth comparison, station lookup, proximity search, and drought declarations. Minor gaps include no direct update/delete (not applicable to a read-oriented data service) and possibly no explicit download/export tool, but the core observational querying workflow appears complete.