{
  "markdown": "# GageLink\n\nHydrology data for AI agents, with the reference frames kept attached.\n\nRiver levels, streamflow, flood forecasts, drainage basins and satellite water levels from\nUSGS, NOAA, Hub'Eau, the UK Environment Agency and SWOT. Every value arrives carrying its\nunit, the datum it was measured from, its timezone, and whether the record is provisional or\napproved.\n\nmcp-name: io.github.Adeniyikayodee/gagelink\n\n**Pre-alpha. The API may change.**\n\n## Install\n\n`pip install gagelink` for the library. From an MCP client, with nothing installed:\n\n```json\n{\n  \"mcpServers\": {\n    \"gagelink\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"gagelink\", \"gagelink-mcp\"]\n    }\n  }\n}\n```\n\nOr open the [`.mcpb` bundle](https://github.com/Adeniyikayodee/gagelink/releases/latest),\nwhich carries the server and its dependencies in one file.\n\nNo account is needed. A [free key](https://api.waterdata.usgs.gov/signup) raises the\nallowance from 50 requests an hour to 1,000; set it as `GAGELINK_API_KEY`.\n\n## What can it answer?\n\n* How high is the river, and how does that compare with flood stage?\n* How much freeboard is there between the water and a surveyed levee crest?\n* What is the flow now, and what fraction of the record peak is that?\n* What is forecast over the next few days, and does it cross a flood category?\n* What lies upstream or downstream along the river network?\n* How large is the basin draining to this point?\n* What did a station record over a date range, and has that record been revised?\n* What is the water surface elevation of a river with no gage on it?\n* Is a reading provisional or approved, and how old is it?\n\n## Why the frames matter\n\nAt Little Falls on the Potomac, a river stage of `3.02 ft` is measured upward from the\ngage's own zero. A surveyed levee crest of `41 ft` is measured upward from a national datum.\nBoth are lengths in feet, so subtracting one from the other produces a number that reads as\nfreeboard, and a units library will pass it.\n\nThe gage zero at this station sits 37.04 ft above NAVD88, so the stage is 40.06 ft on that\ndatum and the freeboard is 0.94 ft. Subtracting without the offset gives 37.98 ft, which\noverstates the margin by a factor of 40 in the direction of calling a levee safe.\n\nGageLink refuses that subtraction and returns the offset that makes it well defined. The\nsame applies to satellite elevations, which sit on a geoid, and to modelled flows, which may\nhave no measurement behind them.\n\n`python demo/freeboard.py` runs the whole example offline from recorded responses.\n\n## Converting a datum\n\nThe offset is available for most stations, so the refusal can become an answer. Pass\n`on_datum` to `describe_location` and the station's offset is converted through NOAA's\nVDatum, with the uncertainty of the conversion returned beside it:\n\n```text\naltitude_of_gage_datum        4860 ft (NGVD29)      Boulder Creek at mouth, CO\naltitude_accuracy             10 ft, interpolated from a topographic map\naltitude_on_requested_datum   4863.061 ft (NAVD88)\nconversion_uncertainty        0.17 ft\noffset_uncertainty            10 ft\n```\n\nTwo things this surfaces are easy to miss.\n\n**The offset has an accuracy of its own.** Across 7,361 USGS stream stations sampled in four\nstates, 3,397 publish an altitude for their gage datum. Of those, 72% are known no better\nthan a foot. The commonest published accuracy is 15 ft, a third were interpolated from a\ntopographic map, and about one in twenty is levelled to a hundredth. A freeboard is bounded\nby that figure whatever precision the stage was read to, so `describe_location` returns it\nalongside the method used to determine it.\n\n**Most stations are on the older datum.** 58% of those altitudes are published on NGVD29\nwhile a modern survey or lidar product is on NAVD88. Across the contiguous states the\ndifference runs to feet.\n\n`on_datum` also takes the tidal datums (`MLLW`, `MLW`, `LMSL`, `MTL`, `DTL`, `MHW`, `MHHW`)\nfor questions about level relative to the tide, and `get_satellite_passes` takes it to move\nSWOT elevations off the EGM2008 geoid they are measured against. Both cover the contiguous\nUnited States. Outside that coverage the conversion is refused and the reason is stated.\n\n## Tools\n\n| Tool                   | What it does                          |\n| ---------------------- | ------------------------------------- |\n| `find_locations`       | Find monitoring stations              |\n| `describe_location`    | Station metadata and reference frames |\n| `get_latest`           | The latest reading for each parameter |\n| `get_series`           | A time series over a date range       |\n| `slice_series`         | Work with part of a retrieved series  |\n| `get_peaks`            | Annual peak flows                     |\n| `get_forecast`         | Forecasts and flood thresholds        |\n| `get_model_forecast`   | Modelled flow for ungaged reaches     |\n| `get_satellite_passes` | Water levels measured from orbit      |\n| `navigate_network`     | Upstream and downstream stations      |\n| `get_basin`            | The contributing drainage basin       |\n| `lookup_parameter`     | Resolve a parameter code              |\n| `export_manifest`      | Everything that answered the question |\n\nAll thirteen are read-only and annotated as such, so a client asks for consent once.\n\nResults come back as structured data against each tool's output schema, so a unit, datum or\ngrade is a field the client can read directly.\n\nA series is returned as a handle with a summary. A year of 15-minute record is 35,000\nvalues, and no answer needs them in a context window.\n\n## Prompts\n\nA tool list says what can be called. It does not say what to call first, and the datum rule\nabove is an order of operations rather than a call. Four prompts state the ones that go\nwrong when a model assembles them itself.\n\n| Prompt                | What it walks through                                    |\n| --------------------- | -------------------------------------------------------- |\n| `freeboard_check`     | Fetch the offset, then difference, then bound the answer |\n| `flood_status`        | Stage against flood category, now and over the forecast  |\n| `find_a_station`      | The filter the relevant agency actually matches on       |\n| `reproducible_answer` | Answer, then export the manifest                         |\n\n## Resources\n\nThe tables that do not change between calls, readable without spending a request against\nthe hourly allowance.\n\n| Resource                 | What is in it                                        |\n| ------------------------ | ---------------------------------------------------- |\n| `gagelink://instructions` | The four rules that decide whether an answer is right |\n| `gagelink://parameters`   | The common parameter codes and what each measures     |\n| `gagelink://datums`       | Every datum named here, and which can be converted onto |\n| `gagelink://coverage`     | Which tools answer for which country, and what each service omits |\n| `gagelink://manifest`     | This conversation's ledger, without a tool call       |\n\n`gagelink://parameter/{code}` and `gagelink://datum/{name}` are templates, and their\nvariables complete: the server answers `completion/complete` for them and for the prompt\narguments with a closed set of values. The specification scopes completion to prompt\narguments and resource template variables, which is why those two vocabularies are exposed\nas templates as well as inside the tool schemas.\n\n## Coverage\n\n| Region | Services | Available |\n| ------ | -------- | --------- |\n| United States | USGS, NOAA NWPS, NOAA National Water Model, NLDI, VDatum | All thirteen tools |\n| France | Hub'Eau | Search, metadata, latest readings, time series |\n| United Kingdom | Environment Agency | Search, metadata, latest readings |\n| Global | SWOT | Satellite water surface elevation |\n\nERA5, GRACE, CAMELS and HydroBASINS are available to library callers.\n\nEach service publishes a different amount, and the tools say which. Hub'Eau states no unit\non any value, so levels in millimetres and flows in litres per second are labelled here from\na recorded table. The Environment Agency publishes no record grade on live data, so age is\nthe only staleness signal for a UK reading.\n\nTo find a UK station, `find_locations` takes `country=GB`. The agency matches river and town\nin full and in its own spelling, so `River Thames` returns stations and `Thames` returns\nnone. Free text matched against the station name is the filter to use when the agency's\nspelling is unknown.\n\n## Protocol support\n\nGageLink serves MCP revision `2026-07-28` and the three handshake revisions before it\n(`2025-06-18`, `2025-03-26`, `2024-11-05`). It declares tools, prompts, resources and\ncompletions, and declares `listChanged` false on all of them: every list is built at\nimport, so a client that subscribed would be waiting on a notification that cannot come.\n\nThe 2026 revision removed the `initialize` handshake. Every request carries its own version\nand capabilities, so a client calls a tool on its first message and learns what the server\nis through `server/discover`. Clients on the earlier revisions continue to open a session\nand keep it.\n\nBecause a connection no longer implies a conversation, a client that wants a ledger of its\nown names one in `_meta`:\n\n```json\n{\"_meta\": {\"io.github.adeniyikayodee.gagelink/conversation\": \"whatever-you-call-it\"}}\n```\n\nEach name gets its own manifest, quantities and checks. A client that sends no name shares\nthe default.\n\nFor clients that cannot start a local process:\n\n```bash\ngagelink-mcp --http          # http://127.0.0.1:8765/mcp\n```\n\nThis binds to loopback and checks the `Origin` header. It has no authentication, so\n`--host` on a reachable interface gives away your hourly allowance.\n\n## Reproducible answers\n\nEvery retrieval is recorded with its URL, the time it was made, and a hash of the response\nbody. `export_manifest` returns that record, and a session can be replayed later in three\nmodes:\n\n* `offline` uses the archived bodies\n* `strict` checks the live service returns identical data\n* `revision_aware` separates a changed answer caused by an official record revision from one\n  caused by changed code\n\nThe third mode exists because hydrology data is revised. A provisional measurement is often\napproved or corrected months later, so an answer can change for reasons that have nothing to\ndo with the code. `revision_aware` tells the two causes apart.\n\nValues are also checked against the ledger, so an answer can be audited:\n\n```text\n[ok]         3.02 ft        from get_latest.00065\n[ok]         2960 ft3/s     from get_latest.00060\n[UNSOURCED]  116000 ft3/s   no tool output produced this value\n```\n\n## Benchmark\n\n`waterbench` measures whether the interface changes what a model gets right. It runs the\nsame nine tasks under three conditions: raw API responses, structured results with the\nmetadata stripped, and the full toolkit.\n\nFirst results, gpt-oss-120b, eight replicates, 216 runs:\n\n| Condition               | Correct |\n| ----------------------- | ------: |\n| Raw API                 |   61/72 |\n| Structured, no metadata |   63/72 |\n| GageLink                |   70/72 |\n\nSix of the nine tasks sit at ceiling, which is a finding about the suite. Where it\nseparates, the causes are legible. Two long-record tasks sent 49,864 and 42,006 prompt\ntokens through raw JSON against 5,462 and 2,384 through the toolkit. On the opaque-unit\ntask, stripping the reference frames sent seven of eight runs into the recorded trap,\nanswering with the USGS\ndischarge of 3010 ft³/s where the forecast service had published 2.95 kcfs.\n\nOne model and a small suite, so these numbers are an early signal about the interface. A\ngeneral claim would need more models and more tasks.\n\n## Development\n\n```bash\npython3 -m venv .venv\n.venv/bin/pip install -e \".[dev]\"\n.venv/bin/pytest\n```\n\nRequires Python 3.10 or later. The suite answers from recorded fixtures and needs no network\naccess. `mypy src/gagelink` is expected to be clean.\n\n## License\n\nMIT\n",
  "bytes": 12044,
  "sha": "36725f6534c67704fc4c67a7461e8d452c63fa70313826845848349f03773899",
  "repo_slug": "adeniyikayodee/gagelink",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_adeniyikayodee_gagelink_89b949b6/readme"
}