usgs-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usgs_mcp-0.1.0/.gitignore +45 -0
- usgs_mcp-0.1.0/LICENSE +21 -0
- usgs_mcp-0.1.0/PKG-INFO +125 -0
- usgs_mcp-0.1.0/README.md +95 -0
- usgs_mcp-0.1.0/pyproject.toml +72 -0
- usgs_mcp-0.1.0/server.json +22 -0
- usgs_mcp-0.1.0/smithery.yaml +9 -0
- usgs_mcp-0.1.0/src/usgs_mcp/__init__.py +0 -0
- usgs_mcp-0.1.0/src/usgs_mcp/__main__.py +3 -0
- usgs_mcp-0.1.0/src/usgs_mcp/client.py +78 -0
- usgs_mcp-0.1.0/src/usgs_mcp/models.py +141 -0
- usgs_mcp-0.1.0/src/usgs_mcp/server.py +32 -0
- usgs_mcp-0.1.0/src/usgs_mcp/tools/__init__.py +0 -0
- usgs_mcp-0.1.0/src/usgs_mcp/tools/flood.py +291 -0
- usgs_mcp-0.1.0/src/usgs_mcp/tools/sites.py +294 -0
- usgs_mcp-0.1.0/src/usgs_mcp/tools/statistics.py +228 -0
- usgs_mcp-0.1.0/src/usgs_mcp/tools/streamflow.py +397 -0
- usgs_mcp-0.1.0/tests/__init__.py +0 -0
- usgs_mcp-0.1.0/tests/conftest.py +43 -0
- usgs_mcp-0.1.0/tests/fixtures/daily_stats.rdb +80 -0
- usgs_mcp-0.1.0/tests/fixtures/dv_response.json +1 -0
- usgs_mcp-0.1.0/tests/fixtures/iv_response.json +1 -0
- usgs_mcp-0.1.0/tests/fixtures/monthly_stats.rdb +1182 -0
- usgs_mcp-0.1.0/tests/fixtures/peak_streamflow.rdb +80 -0
- usgs_mcp-0.1.0/tests/fixtures/site_info.rdb +62 -0
- usgs_mcp-0.1.0/tests/fixtures/site_search.rdb +100 -0
- usgs_mcp-0.1.0/tests/test_live.py +77 -0
- usgs_mcp-0.1.0/tests/test_mcp_protocol.py +32 -0
- usgs_mcp-0.1.0/tests/test_tools.py +344 -0
- usgs_mcp-0.1.0/tests/test_validation.py +160 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.whl
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# Testing
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
.tox/
|
|
21
|
+
|
|
22
|
+
# IDE
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
*.swp
|
|
26
|
+
*.swo
|
|
27
|
+
*~
|
|
28
|
+
|
|
29
|
+
# OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
Thumbs.db
|
|
32
|
+
|
|
33
|
+
# Linting
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
|
|
37
|
+
# Lock files (each server manages its own)
|
|
38
|
+
uv.lock
|
|
39
|
+
|
|
40
|
+
# Environment
|
|
41
|
+
.env
|
|
42
|
+
.env.local
|
|
43
|
+
|
|
44
|
+
# Registry tokens
|
|
45
|
+
.mcpregistry_*
|
usgs_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Mansur Ali Jisan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
usgs_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: usgs-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server providing access to USGS Water Services for streamflow, flood stages, and peak events
|
|
5
|
+
Project-URL: Homepage, https://github.com/mansurjisan/ocean-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/mansurjisan/ocean-mcp
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/mansurjisan/ocean-mcp/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/mansurjisan/ocean-mcp/tree/main/servers/usgs-mcp
|
|
9
|
+
Project-URL: Changelog, https://github.com/mansurjisan/ocean-mcp/releases
|
|
10
|
+
Author-email: Mansur Ali Jisan <mansur.jisan@noaa.gov>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: discharge,flood,gage-height,hydrology,mcp,model-context-protocol,river,streamflow,usgs,water-services
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: httpx>=0.27.0
|
|
27
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# usgs-mcp
|
|
32
|
+
|
|
33
|
+
<!-- mcp-name: io.github.mansurjisan/usgs-mcp -->
|
|
34
|
+
|
|
35
|
+
MCP server providing access to **USGS Water Services** for real-time streamflow, flood stages, peak events, and historical statistics.
|
|
36
|
+
|
|
37
|
+
This server fills a critical gap in the ocean-mcp ecosystem by providing **inland flooding and river data** — often the deadliest aspect of hurricanes and storms.
|
|
38
|
+
|
|
39
|
+
## Data Sources
|
|
40
|
+
|
|
41
|
+
- **USGS Water Services** (`waterservices.usgs.gov`) — real-time and historical streamflow data
|
|
42
|
+
- **USGS NWIS Peak** (`nwis.waterdata.usgs.gov`) — annual peak streamflow records
|
|
43
|
+
|
|
44
|
+
No API key required. All data is public domain.
|
|
45
|
+
|
|
46
|
+
## Tools (10)
|
|
47
|
+
|
|
48
|
+
### Site Discovery
|
|
49
|
+
| Tool | Description |
|
|
50
|
+
|------|-------------|
|
|
51
|
+
| `usgs_find_sites` | Find gauge stations by state or bounding box |
|
|
52
|
+
| `usgs_get_site_info` | Get detailed metadata for a specific site |
|
|
53
|
+
| `usgs_find_nearest_sites` | Find sites near a lat/lon point |
|
|
54
|
+
|
|
55
|
+
### Streamflow Data
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `usgs_get_instantaneous_values` | Real-time ~15-minute interval data (up to 120 days) |
|
|
59
|
+
| `usgs_get_daily_values` | Daily mean/min/max values (decades of history) |
|
|
60
|
+
| `usgs_get_hydrograph` | Summary with trend and historical median comparison |
|
|
61
|
+
|
|
62
|
+
### Flood Analysis
|
|
63
|
+
| Tool | Description |
|
|
64
|
+
|------|-------------|
|
|
65
|
+
| `usgs_get_peak_streamflow` | Annual peak flow records (50+ years at many sites) |
|
|
66
|
+
| `usgs_get_flood_status` | Current conditions vs. historical context |
|
|
67
|
+
|
|
68
|
+
### Statistics
|
|
69
|
+
| Tool | Description |
|
|
70
|
+
|------|-------------|
|
|
71
|
+
| `usgs_get_monthly_stats` | Monthly mean/min/max/percentiles |
|
|
72
|
+
| `usgs_get_daily_stats` | Daily percentiles (flow duration) |
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
### Install from PyPI
|
|
77
|
+
```bash
|
|
78
|
+
uvx usgs-mcp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Install from source
|
|
82
|
+
```bash
|
|
83
|
+
cd servers/usgs-mcp
|
|
84
|
+
uv sync
|
|
85
|
+
uv run usgs-mcp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Claude Desktop Configuration
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"usgs": {
|
|
93
|
+
"command": "uvx",
|
|
94
|
+
"args": ["usgs-mcp"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Example Queries
|
|
101
|
+
|
|
102
|
+
- "What is the current streamflow on the Potomac River?" → `usgs_get_instantaneous_values(site_number="01646500")`
|
|
103
|
+
- "Find USGS gauges in Texas" → `usgs_find_sites(state_code="TX")`
|
|
104
|
+
- "Is the Mississippi at St. Louis flooding?" → `usgs_get_flood_status(site_number="07010000")`
|
|
105
|
+
- "What were the biggest floods on the Potomac?" → `usgs_get_peak_streamflow(site_number="01646500")`
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
cd servers/usgs-mcp
|
|
111
|
+
uv sync --group dev
|
|
112
|
+
|
|
113
|
+
# Unit tests (no network)
|
|
114
|
+
uv run pytest tests/ --ignore=tests/test_live.py --ignore=tests/test_mcp_protocol.py -v
|
|
115
|
+
|
|
116
|
+
# Integration tests (needs network)
|
|
117
|
+
uv run pytest tests/test_live.py -v
|
|
118
|
+
|
|
119
|
+
# MCP protocol test
|
|
120
|
+
uv run pytest tests/test_mcp_protocol.py -v
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
usgs_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# usgs-mcp
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.mansurjisan/usgs-mcp -->
|
|
4
|
+
|
|
5
|
+
MCP server providing access to **USGS Water Services** for real-time streamflow, flood stages, peak events, and historical statistics.
|
|
6
|
+
|
|
7
|
+
This server fills a critical gap in the ocean-mcp ecosystem by providing **inland flooding and river data** — often the deadliest aspect of hurricanes and storms.
|
|
8
|
+
|
|
9
|
+
## Data Sources
|
|
10
|
+
|
|
11
|
+
- **USGS Water Services** (`waterservices.usgs.gov`) — real-time and historical streamflow data
|
|
12
|
+
- **USGS NWIS Peak** (`nwis.waterdata.usgs.gov`) — annual peak streamflow records
|
|
13
|
+
|
|
14
|
+
No API key required. All data is public domain.
|
|
15
|
+
|
|
16
|
+
## Tools (10)
|
|
17
|
+
|
|
18
|
+
### Site Discovery
|
|
19
|
+
| Tool | Description |
|
|
20
|
+
|------|-------------|
|
|
21
|
+
| `usgs_find_sites` | Find gauge stations by state or bounding box |
|
|
22
|
+
| `usgs_get_site_info` | Get detailed metadata for a specific site |
|
|
23
|
+
| `usgs_find_nearest_sites` | Find sites near a lat/lon point |
|
|
24
|
+
|
|
25
|
+
### Streamflow Data
|
|
26
|
+
| Tool | Description |
|
|
27
|
+
|------|-------------|
|
|
28
|
+
| `usgs_get_instantaneous_values` | Real-time ~15-minute interval data (up to 120 days) |
|
|
29
|
+
| `usgs_get_daily_values` | Daily mean/min/max values (decades of history) |
|
|
30
|
+
| `usgs_get_hydrograph` | Summary with trend and historical median comparison |
|
|
31
|
+
|
|
32
|
+
### Flood Analysis
|
|
33
|
+
| Tool | Description |
|
|
34
|
+
|------|-------------|
|
|
35
|
+
| `usgs_get_peak_streamflow` | Annual peak flow records (50+ years at many sites) |
|
|
36
|
+
| `usgs_get_flood_status` | Current conditions vs. historical context |
|
|
37
|
+
|
|
38
|
+
### Statistics
|
|
39
|
+
| Tool | Description |
|
|
40
|
+
|------|-------------|
|
|
41
|
+
| `usgs_get_monthly_stats` | Monthly mean/min/max/percentiles |
|
|
42
|
+
| `usgs_get_daily_stats` | Daily percentiles (flow duration) |
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
### Install from PyPI
|
|
47
|
+
```bash
|
|
48
|
+
uvx usgs-mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Install from source
|
|
52
|
+
```bash
|
|
53
|
+
cd servers/usgs-mcp
|
|
54
|
+
uv sync
|
|
55
|
+
uv run usgs-mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Claude Desktop Configuration
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"usgs": {
|
|
63
|
+
"command": "uvx",
|
|
64
|
+
"args": ["usgs-mcp"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Example Queries
|
|
71
|
+
|
|
72
|
+
- "What is the current streamflow on the Potomac River?" → `usgs_get_instantaneous_values(site_number="01646500")`
|
|
73
|
+
- "Find USGS gauges in Texas" → `usgs_find_sites(state_code="TX")`
|
|
74
|
+
- "Is the Mississippi at St. Louis flooding?" → `usgs_get_flood_status(site_number="07010000")`
|
|
75
|
+
- "What were the biggest floods on the Potomac?" → `usgs_get_peak_streamflow(site_number="01646500")`
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cd servers/usgs-mcp
|
|
81
|
+
uv sync --group dev
|
|
82
|
+
|
|
83
|
+
# Unit tests (no network)
|
|
84
|
+
uv run pytest tests/ --ignore=tests/test_live.py --ignore=tests/test_mcp_protocol.py -v
|
|
85
|
+
|
|
86
|
+
# Integration tests (needs network)
|
|
87
|
+
uv run pytest tests/test_live.py -v
|
|
88
|
+
|
|
89
|
+
# MCP protocol test
|
|
90
|
+
uv run pytest tests/test_mcp_protocol.py -v
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "usgs-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server providing access to USGS Water Services for streamflow, flood stages, and peak events"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Mansur Ali Jisan", email = "mansur.jisan@noaa.gov"},
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"usgs",
|
|
20
|
+
"streamflow",
|
|
21
|
+
"flood",
|
|
22
|
+
"water-services",
|
|
23
|
+
"hydrology",
|
|
24
|
+
"river",
|
|
25
|
+
"discharge",
|
|
26
|
+
"gage-height",
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Science/Research",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"License :: OSI Approved :: MIT License",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
38
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
39
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
40
|
+
]
|
|
41
|
+
dependencies = [
|
|
42
|
+
"mcp[cli]>=1.0.0",
|
|
43
|
+
"httpx>=0.27.0",
|
|
44
|
+
"pydantic>=2.0.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/mansurjisan/ocean-mcp"
|
|
49
|
+
Repository = "https://github.com/mansurjisan/ocean-mcp"
|
|
50
|
+
"Bug Tracker" = "https://github.com/mansurjisan/ocean-mcp/issues"
|
|
51
|
+
Documentation = "https://github.com/mansurjisan/ocean-mcp/tree/main/servers/usgs-mcp"
|
|
52
|
+
Changelog = "https://github.com/mansurjisan/ocean-mcp/releases"
|
|
53
|
+
|
|
54
|
+
[project.scripts]
|
|
55
|
+
usgs-mcp = "usgs_mcp.server:main"
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel]
|
|
58
|
+
packages = ["src/usgs_mcp"]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
testpaths = ["tests"]
|
|
62
|
+
asyncio_mode = "auto"
|
|
63
|
+
markers = [
|
|
64
|
+
"integration: tests that hit live USGS APIs (deselect with '-m \"not integration\"')",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[dependency-groups]
|
|
68
|
+
dev = [
|
|
69
|
+
"pytest>=8.0.0",
|
|
70
|
+
"pytest-asyncio>=0.24.0",
|
|
71
|
+
"respx>=0.22.0",
|
|
72
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.mansurjisan/usgs-mcp",
|
|
4
|
+
"title": "USGS Water Services MCP Server",
|
|
5
|
+
"description": "USGS Water Services streamflow, flood stages, and peak events via waterservices.usgs.gov",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/mansurjisan/ocean-mcp",
|
|
9
|
+
"source": "github"
|
|
10
|
+
},
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "pypi",
|
|
14
|
+
"identifier": "usgs-mcp",
|
|
15
|
+
"version": "0.1.0",
|
|
16
|
+
"runtimeHint": "uvx",
|
|
17
|
+
"transport": {
|
|
18
|
+
"type": "stdio"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Async HTTP client for USGS Water Services API."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .models import USGS_BASE_URL, USGS_PEAK_URL, USER_AGENT
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class USGSAPIError(Exception):
|
|
11
|
+
"""Custom exception for USGS API errors."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_rdb(text: str) -> list[dict[str, str]]:
|
|
17
|
+
"""Parse USGS RDB tab-delimited format to list of dicts.
|
|
18
|
+
|
|
19
|
+
RDB format: # comment lines, then header row, then data type row
|
|
20
|
+
(5s, 15n, etc.), then data rows.
|
|
21
|
+
"""
|
|
22
|
+
lines = [line for line in text.strip().split("\n") if not line.startswith("#")]
|
|
23
|
+
if len(lines) < 2:
|
|
24
|
+
return []
|
|
25
|
+
headers = lines[0].split("\t")
|
|
26
|
+
# Skip lines[1] — data type definitions (e.g., "5s\t15s\t20d")
|
|
27
|
+
rows = []
|
|
28
|
+
for line in lines[2:]:
|
|
29
|
+
if not line.strip():
|
|
30
|
+
continue
|
|
31
|
+
vals = line.split("\t")
|
|
32
|
+
rows.append(dict(zip(headers, vals)))
|
|
33
|
+
return rows
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class USGSClient:
|
|
37
|
+
"""Async client for USGS Water Services API."""
|
|
38
|
+
|
|
39
|
+
def __init__(self) -> None:
|
|
40
|
+
self._client: httpx.AsyncClient | None = None
|
|
41
|
+
|
|
42
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
43
|
+
if self._client is None or self._client.is_closed:
|
|
44
|
+
self._client = httpx.AsyncClient(
|
|
45
|
+
timeout=30.0,
|
|
46
|
+
headers={"User-Agent": USER_AGENT},
|
|
47
|
+
)
|
|
48
|
+
return self._client
|
|
49
|
+
|
|
50
|
+
async def get_json(self, endpoint: str, params: dict[str, Any]) -> dict:
|
|
51
|
+
"""Fetch JSON from IV/DV endpoints."""
|
|
52
|
+
params["format"] = "json"
|
|
53
|
+
client = await self._get_client()
|
|
54
|
+
response = await client.get(f"{USGS_BASE_URL}/{endpoint}/", params=params)
|
|
55
|
+
response.raise_for_status()
|
|
56
|
+
return response.json()
|
|
57
|
+
|
|
58
|
+
async def get_rdb(
|
|
59
|
+
self, endpoint: str, params: dict[str, Any]
|
|
60
|
+
) -> list[dict[str, str]]:
|
|
61
|
+
"""Fetch RDB (tab-delimited) from site/stat endpoints."""
|
|
62
|
+
params["format"] = "rdb"
|
|
63
|
+
client = await self._get_client()
|
|
64
|
+
response = await client.get(f"{USGS_BASE_URL}/{endpoint}/", params=params)
|
|
65
|
+
response.raise_for_status()
|
|
66
|
+
return parse_rdb(response.text)
|
|
67
|
+
|
|
68
|
+
async def get_peak(self, params: dict[str, Any]) -> list[dict[str, str]]:
|
|
69
|
+
"""Fetch peak streamflow RDB from nwis.waterdata.usgs.gov."""
|
|
70
|
+
params["format"] = "rdb"
|
|
71
|
+
client = await self._get_client()
|
|
72
|
+
response = await client.get(USGS_PEAK_URL, params=params)
|
|
73
|
+
response.raise_for_status()
|
|
74
|
+
return parse_rdb(response.text)
|
|
75
|
+
|
|
76
|
+
async def close(self) -> None:
|
|
77
|
+
if self._client and not self._client.is_closed:
|
|
78
|
+
await self._client.aclose()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Constants and helpers for usgs-mcp."""
|
|
2
|
+
|
|
3
|
+
USGS_BASE_URL = "https://waterservices.usgs.gov/nwis"
|
|
4
|
+
USGS_PEAK_URL = "https://nwis.waterdata.usgs.gov/nwis/peak"
|
|
5
|
+
|
|
6
|
+
USER_AGENT = "usgs-mcp/0.1.0 (https://github.com/mansurjisan/ocean-mcp)"
|
|
7
|
+
|
|
8
|
+
# Well-known USGS parameter codes
|
|
9
|
+
PARAMETER_CODES: dict[str, dict] = {
|
|
10
|
+
"00060": {
|
|
11
|
+
"name": "Discharge",
|
|
12
|
+
"units": "ft³/s (cfs)",
|
|
13
|
+
"description": "Streamflow discharge",
|
|
14
|
+
},
|
|
15
|
+
"00065": {
|
|
16
|
+
"name": "Gage height",
|
|
17
|
+
"units": "ft",
|
|
18
|
+
"description": "Water surface elevation above datum",
|
|
19
|
+
},
|
|
20
|
+
"00010": {"name": "Temperature", "units": "°C", "description": "Water temperature"},
|
|
21
|
+
"00045": {
|
|
22
|
+
"name": "Precipitation",
|
|
23
|
+
"units": "in",
|
|
24
|
+
"description": "Accumulated precipitation",
|
|
25
|
+
},
|
|
26
|
+
"00095": {
|
|
27
|
+
"name": "Specific conductance",
|
|
28
|
+
"units": "µS/cm",
|
|
29
|
+
"description": "Specific conductance at 25°C",
|
|
30
|
+
},
|
|
31
|
+
"00300": {
|
|
32
|
+
"name": "Dissolved oxygen",
|
|
33
|
+
"units": "mg/L",
|
|
34
|
+
"description": "Dissolved oxygen",
|
|
35
|
+
},
|
|
36
|
+
"00400": {"name": "pH", "units": "standard units", "description": "pH of water"},
|
|
37
|
+
"63680": {"name": "Turbidity", "units": "FNU", "description": "Turbidity"},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Well-known reference sites for testing and examples
|
|
41
|
+
REFERENCE_SITES: dict[str, dict] = {
|
|
42
|
+
"01646500": {"name": "Potomac River at Little Falls, MD", "state": "MD"},
|
|
43
|
+
"02037500": {"name": "James River at Richmond, VA", "state": "VA"},
|
|
44
|
+
"07010000": {"name": "Mississippi River at St. Louis, MO", "state": "MO"},
|
|
45
|
+
"08066500": {"name": "Trinity River at Romayor, TX", "state": "TX"},
|
|
46
|
+
"02146000": {"name": "Catawba River near Pleasant Garden, NC", "state": "NC"},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# US state codes for site queries
|
|
50
|
+
US_STATE_CODES: dict[str, str] = {
|
|
51
|
+
"AL": "Alabama",
|
|
52
|
+
"AK": "Alaska",
|
|
53
|
+
"AZ": "Arizona",
|
|
54
|
+
"AR": "Arkansas",
|
|
55
|
+
"CA": "California",
|
|
56
|
+
"CO": "Colorado",
|
|
57
|
+
"CT": "Connecticut",
|
|
58
|
+
"DE": "Delaware",
|
|
59
|
+
"FL": "Florida",
|
|
60
|
+
"GA": "Georgia",
|
|
61
|
+
"HI": "Hawaii",
|
|
62
|
+
"ID": "Idaho",
|
|
63
|
+
"IL": "Illinois",
|
|
64
|
+
"IN": "Indiana",
|
|
65
|
+
"IA": "Iowa",
|
|
66
|
+
"KS": "Kansas",
|
|
67
|
+
"KY": "Kentucky",
|
|
68
|
+
"LA": "Louisiana",
|
|
69
|
+
"ME": "Maine",
|
|
70
|
+
"MD": "Maryland",
|
|
71
|
+
"MA": "Massachusetts",
|
|
72
|
+
"MI": "Michigan",
|
|
73
|
+
"MN": "Minnesota",
|
|
74
|
+
"MS": "Mississippi",
|
|
75
|
+
"MO": "Missouri",
|
|
76
|
+
"MT": "Montana",
|
|
77
|
+
"NE": "Nebraska",
|
|
78
|
+
"NV": "Nevada",
|
|
79
|
+
"NH": "New Hampshire",
|
|
80
|
+
"NJ": "New Jersey",
|
|
81
|
+
"NM": "New Mexico",
|
|
82
|
+
"NY": "New York",
|
|
83
|
+
"NC": "North Carolina",
|
|
84
|
+
"ND": "North Dakota",
|
|
85
|
+
"OH": "Ohio",
|
|
86
|
+
"OK": "Oklahoma",
|
|
87
|
+
"OR": "Oregon",
|
|
88
|
+
"PA": "Pennsylvania",
|
|
89
|
+
"RI": "Rhode Island",
|
|
90
|
+
"SC": "South Carolina",
|
|
91
|
+
"SD": "South Dakota",
|
|
92
|
+
"TN": "Tennessee",
|
|
93
|
+
"TX": "Texas",
|
|
94
|
+
"UT": "Utah",
|
|
95
|
+
"VT": "Vermont",
|
|
96
|
+
"VA": "Virginia",
|
|
97
|
+
"WA": "Washington",
|
|
98
|
+
"WV": "West Virginia",
|
|
99
|
+
"WI": "Wisconsin",
|
|
100
|
+
"WY": "Wyoming",
|
|
101
|
+
"DC": "District of Columbia",
|
|
102
|
+
"PR": "Puerto Rico",
|
|
103
|
+
"VI": "Virgin Islands",
|
|
104
|
+
"GU": "Guam",
|
|
105
|
+
"AS": "American Samoa",
|
|
106
|
+
"MP": "Northern Mariana Islands",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# NWS flood stage categories
|
|
110
|
+
FLOOD_CATEGORIES: dict[str, str] = {
|
|
111
|
+
"action": "Water is approaching bankfull; begin monitoring",
|
|
112
|
+
"flood": "Minor flooding is occurring or imminent",
|
|
113
|
+
"moderate": "Moderate flooding; some inundation of structures/roads",
|
|
114
|
+
"major": "Major flooding; extensive inundation, significant threat to life/property",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Stat report types
|
|
118
|
+
STAT_REPORT_TYPES: dict[str, str] = {
|
|
119
|
+
"daily": "Daily statistics",
|
|
120
|
+
"monthly": "Monthly statistics",
|
|
121
|
+
"annual": "Annual statistics",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# USGS data qualification codes
|
|
125
|
+
QUALIFICATION_CODES: dict[str, str] = {
|
|
126
|
+
"A": "Approved for publication",
|
|
127
|
+
"P": "Provisional, subject to revision",
|
|
128
|
+
"e": "Estimated",
|
|
129
|
+
"Eqp": "Equipment malfunction",
|
|
130
|
+
"Mnt": "Maintenance",
|
|
131
|
+
"Dry": "Dry conditions",
|
|
132
|
+
"Ssn": "Parameter monitored seasonally",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def format_parameter(code: str) -> str:
|
|
137
|
+
"""Return a human-readable name for a USGS parameter code."""
|
|
138
|
+
info = PARAMETER_CODES.get(code)
|
|
139
|
+
if info:
|
|
140
|
+
return f"{info['name']} ({info['units']})"
|
|
141
|
+
return f"Parameter {code}"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""FastMCP server entry point for USGS Water Services."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
from .client import USGSClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@asynccontextmanager
|
|
12
|
+
async def app_lifespan(server: FastMCP) -> AsyncIterator[dict]:
|
|
13
|
+
"""Manage the shared USGSClient lifecycle."""
|
|
14
|
+
client = USGSClient()
|
|
15
|
+
try:
|
|
16
|
+
yield {"usgs_client": client}
|
|
17
|
+
finally:
|
|
18
|
+
await client.close()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
mcp = FastMCP("usgs_mcp", lifespan=app_lifespan)
|
|
22
|
+
|
|
23
|
+
# Import tool modules to register them with the server
|
|
24
|
+
from .tools import flood, sites, statistics, streamflow # noqa: E402, F401
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> None:
|
|
28
|
+
mcp.run()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
main()
|
|
File without changes
|