gagelink
gagelink
Гидрологические данные для ИИ-агентов. Уровень реки, расход воды, прогнозы наводнений, качество воды, водосборные бассейны и спутниковая высота водной поверхности — из USGS, NOAA и SWOT. Каждое значение несёт свою единицу измерения, датум, от которого оно отсчитано, часовой пояс и пометку о том, является ли запись предварительной или утверждённой.
mcp-name: io.github.Adeniyikayodee/gagelink
Пре-альфа. API будет меняться.
Запуск в качестве MCP-сервера
{
"mcpServers": {
"gagelink": {
"command": "uvx",
"args": ["--from", "gagelink", "gagelink-mcp"]
}
}
}Для запуска аккаунт не нужен. Бесплатный ключ с api.waterdata.usgs.gov/signup повышает лимит с 50 запросов в час до 1000; задайте его как GAGELINK_API_KEY.
Или как библиотека:
pip install gagelinkRelated MCP server: Environment Agency Flood Monitoring MCP Server
На какие вопросы он отвечает
Насколько высок уровень реки на гидропосту и как это соотносится с уровнем затопления?
Каков запас высоты между водой и обследованным гребнем дамбы?
Каков расход сейчас и какую долю от рекордного пика он составляет?
Каков прогноз на ближайшие дни и пересекает ли он категорию наводнения?
Что находится выше или ниже по течению от этой точки вдоль реки?
Насколько велик бассейн, стекающий к этой точке?
Что записала эта станция за диапазон дат и была ли эта запись пересмотрена с тех пор?
Какова высота водной поверхности реки, на которой нет гидропоста?
Является ли показание предварительным или утверждённым и насколько оно свежее?
Что он отказывается делать и почему в этом суть
Высота гидропоста измеряется от собственного датума станции, а не от уровня моря. Вычитание одной из обследованной высоты даёт число, которое выглядит как запас высоты, но ошибочно на десятки футов, причём в сторону признания дамбы безопасной. Обе величины — длины в футах, поэтому ничто размерное их не разделяет, и ни одна библиотека единиц не поймает эту ошибку.
Этот пакет отказывается от такого вычитания, а не отвечает на него, и describe_location возвращает смещение, которое делает операцию корректной. То же самое относится к спутниковым высотам, которые отсчитываются от геоида, и к модельным расходам, за которыми может не стоять никаких измерений.
Почему
Водные сервисы уже публикуют всё необходимое для корректного использования своих данных. Расход указывает свою единицу, уровень — датум, от которого он измерен, показание — предварительное оно или утверждённое, а временная метка — своё смещение. Клиенты обычно извлекают число и отбрасывают остальное, и ошибки следуют из этого.
Сбой измерим. В бенчмарке из 4 288 прогонов на одиннадцати моделях quantity-guard обнаружил, что каждая модель, дошедшая до вычислительного инструмента, отправляла расход, опубликованный в кубических футах в секунду, в параметр, объявленный в кубических метрах в секунду, без пересчёта, почти в каждом прогоне, давая ответ в 35,3 раза больше, и ничто в выводе не указывало на это. Семь из одиннадцати вычитали уровень на локальном датуме гидропоста из высоты на NAVD88 и сообщали результат как запас высоты.
gagelink извлекает данные с сохранёнными метаданными и использует quantity-guard для их соблюдения при вызовах инструментов агента.
Текущая поверхность
from gagelink import Service
service = Service(api_key="...") # free key, see below
page, retrieval = service.items(
"latest-continuous",
monitoring_location_id="USGS-07374000",
parameter_code="00060",
)
retrieval.record() # what a replay needs: url, params, time, status, sha256
retrieval.quota # Quota(limit=1000, remaining=999)items возвращает разобранную страницу и запись о её получении вместе, а не только страницу, потому что число, дошедшее до ответа без запроса, который его породил, невозможно воспроизвести, а связывание их в единственной точке входа дешевле, чем помнить о записи.
Полезные нагрузки становятся величинами, несущими собственные системы отсчёта:
from gagelink import location_from, readings_from
page, _ = service.items("monitoring-locations", id="USGS-06730500")
station = location_from(page["features"][0])
station.register() # its datum, and the offset where one is published
observations, _ = service.items("latest-continuous", monitoring_location_id=station.id)
readings = {r.parameter_code: r for r in readings_from(observations, station)}
readings["00060"].value # Q(1.35 ft³/s (provisional))
readings["00065"].value # Q(9.11 ft (GAGE:06730500, provisional))
readings["00065"].value.to_datum("NGVD29") # Q(4869.11 ft (NGVD29, provisional))
readings["00065"].value.to_datum("NAVD88") # DatumConversionUnavailableПоследняя строка и есть суть. Boulder Creek публикует свою высоту на NGVD29, поэтому уровень там разрешается на NGVD29 и отказывается от NAVD88, поскольку смещение между ними меняется в зависимости от местоположения и здесь не публикуется. Предполагать современный датум только потому, что он современный, — это ошибка запаса высоты на один шаг раньше той, которую все ищут.
Что не публикуется и что с этим делается
altitude и drainage_area возвращаются как голые числа, и схема коллекции не указывает единицу ни для одного из них, поэтому соглашения USGS о футах и квадратных милях применяются в normalise.py, где они видны, а не предполагаются дальше по конвейеру.
Единица без сопоставления отвергается, а не угадывается. Единица, которую pint может разобрать, но для которой в этом пакете нет записи, пропускается с предупреждением, потому что разбираемость — не то же самое, что понимание: ppt для pint читается как частей на триллион, а для USGS означает частей на тысячу, что даёт коэффициент 10^9 между двумя размерно идентичными показаниями.
Отсутствующее значение здесь — null, а не -999999, который публикует WaterServices, и оно остаётся отсутствующим. Квалификатор объясняет причину: ["EQUIP"] — для сбоя оборудования.
Утверждение приходит как Provisional или Approved, а не как P или A, и коды состояния оцениваются ниже их статуса проверки, поэтому утверждённая запись измерения, подверженного льду, оценивается как непроверенная, а не как утверждённая.
Часовой пояс станции определяется из аббревиатуры вместе с флагом перехода на летнее время, поскольку MST без летнего времени — это Аризона, а MST с ним — Колорадо, и они различаются на час в течение восьми месяцев в году.
Инструменты
Сессия хранит состояние для одного вопроса и запись о том, что на него ответило. Инструменты возвращают результат, а не вызывают исключение, потому что сбой, несущий исправление, удерживает модель в разговоре, где она может себя поправить, а выброшенное исключение завершает ход.
from gagelink import Session, Toolkit
with Session(question="How high is the Potomac at Little Falls?") as work:
kit = Toolkit(work)
kit.describe_location("USGS-01646500")
kit.get_latest("USGS-01646500", parameters=["00060", "00065"], max_age_hours=6)
work.audit("The gage height is 3.02 ft and the discharge is 2960 ft3/s.")
work.manifest()Каждое значение уходит со своей привязкой, и каждое заносится в журнал, так что ответ можно проверить по тому, что было фактически получено:
[ok] 3.02 ft from get_latest.00065
[ok] 2960 ft3/s from get_latest.00060
[UNSOURCED] 116000 ft3/s no tool output produced this valueТретья строка — это проверка, оправдывающая своё существование. Цифра — правдоподобный расход для этой реки, она неверна, и ничто в предложении, содержащем её, не указывает на это.
Ряд возвращается как дескриптор с сводкой и выборкой из двадцати точек, а не сами точки, поскольку год 15-минутных записей — это 35 000 значений. Дескриптор выводится из запроса, который его породил, так что повторное воспроизведение той же сессии даёт тот же дескриптор. Результаты ограничены бюджетом, и всё, что отброшено для соблюдения бюджета, указывается в результате, поскольку молчаливое усечение читается как полнота покрытия.
инструмент | назначение |
| поиск по штату, округу, гидрологическому участку, типу объекта или ограничивающей рамке |
| метаданные, датум, часовой пояс и смещение, необходимое для уровня |
| самое свежее значение по параметру, с возрастом и качеством |
| диапазон дат, как дескриптор плюс сводка |
| сузить сохранённый ряд без повторного запроса |
| ежегодный рекорд пиковых расходов |
| наблюдаемый и прогнозируемый уровень, с порогами затопления |
| пункты мониторинга выше или ниже по течению вдоль реки |
| площадь, стекающая к точке |
| разрешить код параметра, поскольку показания не несут названия |
Как MCP-сервер
export GAGELINK_API_KEY=... # free, see below
gagelink-mcp{"mcpServers": {"gagelink": {"command": "gagelink-mcp"}}}Одиннадцать инструментов, не больше. Модель деградирует по мере роста списка инструментов, поэтому поверхность организована по глаголам, а выбор того, какой сервис отвечает, делает сервер, а не возлагается на вызывающего.
Описания инструментов — часть продукта, а не документация к нему. В оценке quantity-guard объявление физических метаданных в схеме без их соблюдения всё равно восстановило треть прогонов, которые провалились на исходном уровне, так что то, что описание говорит о датумах, единицах и предварительных записях, работает до запуска любой проверки.
Сбой инструмента возвращается как контент, помеченный как ошибка, а не как протокольный сбой, что удерживает исправление перед моделью вместо завершения хода. Сессия сбрасывается при initialize, поэтому величины одного разговора не могут появиться в манифесте другого.
Запас высоты, где опасности встречаются
python demo/freeboard.py запускает всё это офлайн на записанных ответах:
stage 3.02 ft (GAGE:01646500)
crest 41 ft (NAVD88)
The two are both lengths, so nothing dimensional separates them:
refused: cannot difference an elevation on NAVD88 against one on GAGE:01646500
The gage's zero is at 37.04 ft NAVD88, so the stage is 40.06 ft (NAVD88).
freeboard = 0.94 ft
Ignoring the datum gives 37.98 ft of margin where 0.94 ft is correct,
overstating it by a factor of 40.Уровень и обследованная высота — обе длины в футах, и вычитание одной из другой даёт число, которое выглядит как запас высоты. Ошибка молчалива, она действует в сторону сообщения о дамбе как безопасной, и никакая библиотека единиц её не предотвращает, потому что с единицами всё в порядке.
Прогнозы
Пороги затопления берутся из NOAA National Water Prediction Service, поскольку уровень ничего не значит, пока его не сопоставят с уровнем, при котором река выходит из берегов. В этой полезной нагрузке есть три вещи, требующие обработки, и ни одна не обозначена:
Расход появляется как cfs в категориях затопления и как kcfs в блоке статуса того же ответа, поэтому вызывающий, читающий оба и относящийся к ним одинаково, ошибается в тысячу раз.
Пороги, которые никогда не были установлены, публикуются как -9999, а не опускаются. Это размерно допустимо, правдоподобно по знаку и проходит все проверки ниже по конвейеру, поэтому оно читается как sentinel, которым и является.
Уровни отсчитываются от собственного датума гидропоста, а не от национального. Наблюдаемый уровень, опубликованный там, точно совпадает с параметром USGS 00065 на той же станции и в то же время, что является доказательством этого чтения, и именно поэтому уровень затопления вычитается из высоты гидропоста, но не из обследованной высоты.
Речная сеть
Навигация идёт вдоль реки, а не в пределах радиуса, и это различие делает ответ полезным: гидропост в двух милях на соседнем водосборе не является выше по течению ни для чего здесь. Направления — слова, а не двухбуквенные коды индекса, поэтому upstream включает притоки, а upstream_main следует только по главному руслу.
Бассейн приходит как полигон из пары тысяч пар координат. Это ответ на картографический вопрос и неправильный ответ на любой вопрос, который задаёт агент, поэтому полигон сохраняется, а его площадь, протяжённость и количество вершин сообщаются. Площадь вычисляется из полигона по линейно-интегральной форме сферической площади, которая не требует проекции и поэтому не имеет зоны, которую нужно выбирать или в которой можно ошибиться. Она согласуется с площадью водосбора, публикуемой USGS для единственной станции, где обе цифры существуют, с точностью до 0,06%, и результат говорит, что она вычислена, а не опубликована, поэтому её не цитируют против обследованной цифры, как будто они одинаковы.
Воспроизведение
Гидрология воспроизводится на 1,6% в протестированной литературе. Обычное объяснение — что данные и код не были опубликованы, и оно скрывает более интересный сбой: опубликованный конвейер против живого сервиса тоже не воспроизводится, потому что сервис пересмотрел запись под ним. Предварительный расход становится утверждённым, и число меняется.
Сессия сохраняет пакет из своего манифеста и тел ответов, которые она видела. Его воспроизведение работает в трёх режимах, и различие между ними и есть суть.
режим | процедура | изолирует |
| пересчёт из архивных тел | изменения кода и библиотек |
| повторный запрос, требование идентичных ответов | любой дрейф вообще |
| повторный запрос, сравнение, запрос к сервису объяснить каждое различие | пересмотр данных |
with Session(question="what was the discharge in mid May 2021?") as work:
Toolkit(work).get_series("USGS-02344872", "00060", "2021-05-16", "2021-05-20")
work.save("bundle.json")gagelink-replay bundle.json --mode strict
gagelink-replay bundle.json --mode revision_awareТот же пакет, тот же повторный запрос и два разных вердикта:
strict replay: changed
changed daily
[changed] USGS-02344872 00060 at 2021-05-16: 702.1 -> 826.0 ft^3/s
revision_aware replay: reproduced
changed daily
[revised] USGS-02344872 00060 at 2021-05-16: 702.1 -> 826.0 ft^3/s,
Revisions: Discharge for the period May 16, 2021 to Oct. 27, 2021,
was revised on Aug. 16, 2024, based on changes to the estimated discharge.Результат, который изменился, потому что агентство пересмотрело 400 предварительных значений, — это иной факт о науке, чем тот, который изменился, потому что изменился код, и в остальном они неразличимы. Запись о пересмотре берётся из собственной коллекции time-series-revisions сервиса и соединяется по идентификатору временного ряда, который уже несёт в себе чтение, так что атрибуция — это поиск, а не догадка. Различие без опубликованной записи о пересмотре остаётся в отчёте как необъяснённое — именно это не даёт проверке стать пустой.
Тела проверяются по их хэшам, прежде чем что-либо сравнивается. Пакет, чей архив не совпадает, отклоняется, а не воспроизводится, поскольку каждый вердикт опирается на то, что архив — это именно то, что видел сеанс.
waterbench
bench/ — это бенчмарк, измеряющий, чего стоит инструментарий для модели, на девяти задачах на одной станции, охватывающих девять опасностей, каждая из которых наблюдалась в live-нагрузке сервиса, пока пакет собирался.
Три условия сравниваются на идентичных данных, различаясь только интерфейсом между моделью и байтами:
условие | что получает модель |
| один fetch-инструмент, возвращающий собственный JSON сервиса — то, что есть у разработчика сегодня |
| одиннадцать инструментов с результатами, урезанными до голых величин, и без примечаний |
| инструменты как есть, с единицами, датумами, качеством, устареванием и примечаниями |
Среднее условие — это то, что делает измерение значимым. Без него различие между первым и последним показало бы лишь, что структурированное извлечение бьёт сырой JSON, в чём никто не сомневается. Разница между последними двумя — это то, чего стоит сама метаданные.
python -m bench --dry-run # no provider, no spend
python -m bench --model anthropic/claude-opus-5 --replicates 4Каждый ожидаемый ответ выводится из тех же записанных ответов, которые обслуживает сервис, а tests/test_bench.py решает каждую задачу на основе этих ответов и сверяет результат с заявленным ответом. Задача, к ответу которой так не подобраться, — это сломанная задача, и это видно сразу. Каждая задача также фиксирует basis — указание, откуда взята её цифра, чтобы читатель мог проверить её, не полагаясь на слово проекта.
Задача о запасе над водой проверяется по собственной арифметике агентства: USGS публикует отметку водной поверхности как параметр 63160, на высоте 40.07 фута над NAVD88, что равно высоте водомерного поста 3.03 фута плюс датумная поправка станции 37.04 фута.
Оценка записывается до любого прогона и хранится в системе контроля версий, так что правило нельзя подогнать после того, как увидел результат, который ему не угоден.
Первые результаты
gpt-oss-120b, девять задач, три условия, восемь повторов, 216 прогонов, $0.09.
условие | верно |
| 61/72 |
| 63/72 |
| 70/72 |
Правильность учитывает каждый прогон, включая те восемь, что завершились без ответа вовсе. Семь из них относятся к http_only на двух задачах, чья сырая запись достигает 42 000 и 50 000 токенов в запросе, где модель вырождается в повторение числа вместо ответа. Эти сбои вызваны условием, так что, исключив их, мы зачли бы сырому JSON то, что разрушил его собственный размер нагрузки.
Набор сидит на потолке в шести задачах из девяти — и это вывод о самом наборе. Где он разделяет:
задача |
|
|
|
| 3/8 | 8/8 | 8/8 |
| 6/8 | 8/8 | 8/8 |
| 8/8 | 1/8 | 7/8 |
На двух задачах с длинной записью медианный запрос составлял 49 864 и 42 006 токенов через сырой JSON против 5 384 и 2 384 через инструментарий. На задаче с непрозрачной единицей, сняв опорные кадры, модель отправила семь из восьми прогонов в записанную ловушку, ответив расходом USGS 3010 ft³/s вместо прогнозных 2.95 kcfs.
Инструментарий не бьёт сырой JSON по точности в целом. Разрыв почти полностью обусловлен двумя задачами, где сырая нагрузка не помещается. Восемь из 27 ячеек разделены по повторам, так что различия менее чем в два прогона из восьми не разделяются этой схемой, и одна модель — это одна модель.
Ключи API и лимиты запросов
Сервис разрешает 50 запросов на IP в час без аутентификации и 1000 в час с ключом, который бесплатен на api.waterdata.usgs.gov/signup. Один вопрос агента, сравнивающий условия на пяти станциях, обходится примерно в 15–25 запросов, так что кэширование — это несущая нагрузка, а не оптимизация, и ответы кэшируются на время жизни процесса по умолчанию.
Оставшийся лимит читается из заголовка X-RateLimit-Remaining в каждом ответе и переносится в запрос, так что агенту видно, сколько у него осталось, а не приходится обнаруживать предел, провалившись.
Ключ передаётся в заголовке X-Api-Key и никогда не появляется в записанном URL, поскольку манифест должен оставаться публикуемым.
На что это нацелено
USGS выводит из эксплуатации семейство API WaterServices, с плановым отключением в первом квартале 2027 года и возможной деградацией со второй половины 2026-го. gagelink нацелен только на замену — api.waterdata.usgs.gov/ogcapi/v0. Одно из следствий, которое стоит назвать, — коллекция time-series-revisions, публикующая изменения и удаления в утверждённой записи; именно она позволит воспроизвести, изменился ли ответ из-за того, что агентство пересмотрело измерение, или из-за того, что изменился код.
Разработка
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytestСетевой доступ идёт через заменяемый fetch, так что набор запускается на записанных ответах, и ни одному тесту не нужна сеть.
Лицензия
MIT
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceProvides access to real-time water data from the USGS Water Services API, allowing users to fetch instantaneous measurements like stream flow, gage height, temperature, and water quality parameters from thousands of monitoring stations across the US.3
- AlicenseBqualityCmaintenanceProvides access to UK Environment Agency's real-time flood monitoring data, enabling users to check flood warnings, monitor water levels and flow rates, and access historical measurements from monitoring stations across the UK.1111MIT
- FlicenseNot gradedqualityDmaintenanceProvides real-time hydrological data from Korea's Flood Control Office via MCP protocol, optimized for AI assistants with features to prevent infinite loop calls and standardize data structures.
- AlicenseAqualityBmaintenanceEnables querying USGS water data including real-time and historical streamflow, gage height, and water temperature from USGS gauges across the United States.3MIT
Related MCP Connectors
US weather, alerts, earthquakes and elevation for AI agents, from NWS/NOAA and USGS. No API keys.
US weather & geo for AI agents: forecasts, alerts, earthquakes, elevation, geocoding. No keys.
Query real-time and historical USGS water data from ~8,000 stream gages and groundwater wells.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Adeniyikayodee/gagelink'
If you have feedback or need assistance with the MCP directory API, please join our Discord server