cwms-tools 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.
- cwms_tools-0.1.0/LICENSE +21 -0
- cwms_tools-0.1.0/PKG-INFO +266 -0
- cwms_tools-0.1.0/README.md +231 -0
- cwms_tools-0.1.0/pyproject.toml +146 -0
- cwms_tools-0.1.0/src/cwms_tools/__init__.py +12 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/__init__.py +1 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/app.py +127 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/__init__.py +1 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/config.py +73 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/env.py +62 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/fingerprint.py +35 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/mcp.py +129 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/place.py +232 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/publisher.py +52 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/region.py +100 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/schema.py +157 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/value.py +228 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/commands/whoami.py +30 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/exit_codes.py +45 -0
- cwms_tools-0.1.0/src/cwms_tools/cli/render.py +97 -0
- cwms_tools-0.1.0/src/cwms_tools/core/__init__.py +1 -0
- cwms_tools-0.1.0/src/cwms_tools/core/_workarounds.py +29 -0
- cwms_tools-0.1.0/src/cwms_tools/core/cache.py +188 -0
- cwms_tools-0.1.0/src/cwms_tools/core/catalog.py +448 -0
- cwms_tools-0.1.0/src/cwms_tools/core/concurrency.py +85 -0
- cwms_tools-0.1.0/src/cwms_tools/core/errors.py +193 -0
- cwms_tools-0.1.0/src/cwms_tools/core/fingerprint.py +84 -0
- cwms_tools-0.1.0/src/cwms_tools/core/geo.py +80 -0
- cwms_tools-0.1.0/src/cwms_tools/core/levels.py +338 -0
- cwms_tools-0.1.0/src/cwms_tools/core/locations.py +108 -0
- cwms_tools-0.1.0/src/cwms_tools/core/models.py +414 -0
- cwms_tools-0.1.0/src/cwms_tools/core/offices.py +119 -0
- cwms_tools-0.1.0/src/cwms_tools/core/overview.py +178 -0
- cwms_tools-0.1.0/src/cwms_tools/core/places.py +526 -0
- cwms_tools-0.1.0/src/cwms_tools/core/projects.py +182 -0
- cwms_tools-0.1.0/src/cwms_tools/core/publishers.py +190 -0
- cwms_tools-0.1.0/src/cwms_tools/core/publishers_index.py +198 -0
- cwms_tools-0.1.0/src/cwms_tools/core/session.py +195 -0
- cwms_tools-0.1.0/src/cwms_tools/core/timeseries.py +212 -0
- cwms_tools-0.1.0/src/cwms_tools/core/values.py +243 -0
- cwms_tools-0.1.0/src/cwms_tools/data/__init__.py +1 -0
- cwms_tools-0.1.0/src/cwms_tools/data/cwms-overview.md +925 -0
- cwms_tools-0.1.0/src/cwms_tools/mcp/__init__.py +1 -0
- cwms_tools-0.1.0/src/cwms_tools/mcp/fastmcp_capabilities.py +60 -0
- cwms_tools-0.1.0/src/cwms_tools/mcp/resources.py +200 -0
- cwms_tools-0.1.0/src/cwms_tools/mcp/server.py +249 -0
- cwms_tools-0.1.0/src/cwms_tools/mcp/tools.py +489 -0
- cwms_tools-0.1.0/src/cwms_tools/py.typed +0 -0
cwms_tools-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brian Connelly
|
|
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.
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cwms-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent-friendly tools (MCP server and CLI) for the USACE Corps Water Management System (CWMS) Data API.
|
|
5
|
+
Keywords: cwms,usace,mcp,cli,water,hydrology,agent
|
|
6
|
+
Author: Brian Connelly
|
|
7
|
+
Author-email: Brian Connelly <bdc@bconnelly.net>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Hydrology
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Dist: cwms-python>=1.0.7
|
|
22
|
+
Requires-Dist: fastmcp>=3
|
|
23
|
+
Requires-Dist: typer>=0.15
|
|
24
|
+
Requires-Dist: pydantic>=2
|
|
25
|
+
Requires-Dist: platformdirs>=4
|
|
26
|
+
Requires-Dist: diskcache>=5
|
|
27
|
+
Requires-Dist: anyio>=4
|
|
28
|
+
Requires-Dist: python-dateutil>=2.9
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Project-URL: Homepage, https://github.com/briandconnelly/cwms-tools
|
|
31
|
+
Project-URL: Repository, https://github.com/briandconnelly/cwms-tools
|
|
32
|
+
Project-URL: Issues, https://github.com/briandconnelly/cwms-tools/issues
|
|
33
|
+
Project-URL: Changelog, https://github.com/briandconnelly/cwms-tools/blob/main/CHANGELOG.md
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# cwms-tools
|
|
37
|
+
|
|
38
|
+
[](https://github.com/briandconnelly/cwms-tools/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/cwms-tools/)
|
|
40
|
+
[](https://pypi.org/project/cwms-tools/)
|
|
41
|
+
|
|
42
|
+
Read-only, agent-friendly tools for the U.S. Army Corps of Engineers'
|
|
43
|
+
[CWMS Data API](https://cwms-data.usace.army.mil/cwms-data/). `cwms-tools` wraps the official
|
|
44
|
+
[`cwms-python`](https://github.com/HydrologicEngineeringCenter/cwms-python) client with two surfaces that share one behavioral
|
|
45
|
+
core:
|
|
46
|
+
|
|
47
|
+
- an [MCP](https://modelcontextprotocol.io/) server for agent runtimes such as Claude Code, Codex, and custom
|
|
48
|
+
MCP clients
|
|
49
|
+
- a non-interactive CLI with compact JSON output, stable exit codes, and a
|
|
50
|
+
machine-readable schema
|
|
51
|
+
|
|
52
|
+
The goal is simple: let agents answer common hydrologic questions with one task
|
|
53
|
+
call instead of a brittle chain of raw API lookups.
|
|
54
|
+
|
|
55
|
+
## What It Does
|
|
56
|
+
|
|
57
|
+
- Resolves natural place names to canonical CWMS locations, with ghost-location
|
|
58
|
+
filtering and co-located sensor hints.
|
|
59
|
+
- Describes a place in one call: location record, project metadata when
|
|
60
|
+
available, published parameters, publishers, and latest data timestamp.
|
|
61
|
+
- Reads the latest value or bounded history for CWMS time series parameters.
|
|
62
|
+
- Optionally classifies the latest value against CWMS Location Levels when
|
|
63
|
+
callers opt in with `--with-status` or `with_status=true`.
|
|
64
|
+
- Browses one office's catalog by state or bounding box.
|
|
65
|
+
- Finds publishers for a parameter across cached or explicitly requested
|
|
66
|
+
offices.
|
|
67
|
+
- Serves a bundled CWMS orientation document as MCP resources so agents can load
|
|
68
|
+
background material selectively.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv add cwms-tools
|
|
74
|
+
# or: pipx install cwms-tools
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
To run from a checkout instead:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git clone https://github.com/briandconnelly/cwms-tools.git
|
|
81
|
+
cd cwms-tools
|
|
82
|
+
uv sync
|
|
83
|
+
uv run cwms-tools --help
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## CLI Quick Start
|
|
87
|
+
|
|
88
|
+
The CLI is designed for non-interactive callers. When stdout is not a TTY,
|
|
89
|
+
machine mode is enabled automatically: compact JSON on stdout, diagnostics on
|
|
90
|
+
stderr, no color, no prompts, and no progress UI. You can force that profile
|
|
91
|
+
with `--machine` or `--json`.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Inspect the resolved session and upstream API root.
|
|
95
|
+
uv run cwms-tools whoami
|
|
96
|
+
|
|
97
|
+
# Print the command tree, output classes, exit codes, environment inputs,
|
|
98
|
+
# MCP tools, and MCP resources as a stable machine-readable contract.
|
|
99
|
+
uv run cwms-tools schema
|
|
100
|
+
|
|
101
|
+
# Print a SHA-256 fingerprint over the agent-visible capability surface.
|
|
102
|
+
uv run cwms-tools fingerprint
|
|
103
|
+
|
|
104
|
+
# Resolve a place name to ranked CWMS locations.
|
|
105
|
+
uv run cwms-tools place search "Fort Peck" --office NWDM
|
|
106
|
+
|
|
107
|
+
# Describe one place: location, project metadata, parameters, publishers,
|
|
108
|
+
# freshness, and partial-result flags.
|
|
109
|
+
uv run cwms-tools place describe NWDM/FTPK
|
|
110
|
+
|
|
111
|
+
# List parameters published at a place.
|
|
112
|
+
uv run cwms-tools place parameters NWDM/FTPK
|
|
113
|
+
|
|
114
|
+
# Read the latest elevation value. Status lookup is skipped by default because
|
|
115
|
+
# CWMS Location Levels calls can be slow.
|
|
116
|
+
uv run cwms-tools value get NWDM/FTPK/Elev
|
|
117
|
+
|
|
118
|
+
# Opt in to threshold classification when status context matters.
|
|
119
|
+
uv run cwms-tools value get NWDM/FTPK/Elev --with-status
|
|
120
|
+
|
|
121
|
+
# Read a bounded history window.
|
|
122
|
+
uv run cwms-tools value history NWDM/FTPK/Elev \
|
|
123
|
+
--begin 2026-05-16T00:00:00Z \
|
|
124
|
+
--end 2026-05-17T00:00:00Z
|
|
125
|
+
|
|
126
|
+
# Browse an office catalog by state.
|
|
127
|
+
uv run cwms-tools region browse --office SWT --state OK
|
|
128
|
+
|
|
129
|
+
# Find publishers reporting a parameter in selected offices.
|
|
130
|
+
uv run cwms-tools publisher for-parameter Elev --office NWDM --office SWT
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Useful global flags:
|
|
134
|
+
|
|
135
|
+
- `--machine` / `--json`: compact structured output for agents and scripts.
|
|
136
|
+
- `--no-cache`: bypass the on-disk catalog cache for one invocation.
|
|
137
|
+
- `--isolated`: bypass cache and ignore `CWMS_TOOLS_*` environment variables.
|
|
138
|
+
- `--version`: print the installed `cwms-tools` version.
|
|
139
|
+
|
|
140
|
+
Exit codes are part of the CLI contract:
|
|
141
|
+
|
|
142
|
+
| Exit | Meaning |
|
|
143
|
+
| ---: | --- |
|
|
144
|
+
| `0` | success |
|
|
145
|
+
| `2` | usage or invalid field |
|
|
146
|
+
| `3` | not found or publisher unavailable |
|
|
147
|
+
| `4` | session unconfigured |
|
|
148
|
+
| `6` | rate limited |
|
|
149
|
+
| `7` | timeout |
|
|
150
|
+
| `9` | upstream error or catalog cursor invalidation |
|
|
151
|
+
| `11` | wrapper bug |
|
|
152
|
+
| `12` | ghost location or ghost office |
|
|
153
|
+
|
|
154
|
+
## MCP Quick Start
|
|
155
|
+
|
|
156
|
+
Use stdio for local agent runtimes:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
uv run cwms-tools mcp serve --transport stdio
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Use streamable HTTP for shared or remote deployment:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
uv run cwms-tools mcp serve --transport streamable-http --host 127.0.0.1 --port 8765
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Claude Code config example:
|
|
169
|
+
|
|
170
|
+
```jsonc
|
|
171
|
+
{
|
|
172
|
+
"mcpServers": {
|
|
173
|
+
"cwms-tools": {
|
|
174
|
+
"command": "uv",
|
|
175
|
+
"args": ["run", "cwms-tools", "mcp", "serve", "--transport", "stdio"]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The MCP server exposes task-level tools rather than raw endpoint mirrors:
|
|
182
|
+
|
|
183
|
+
| Tool | Purpose |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `cwms_search_places` | Resolve an ambiguous place name to ranked locations. |
|
|
186
|
+
| `cwms_describe_place` | Read location, project, parameter, publisher, and freshness data in one call. |
|
|
187
|
+
| `cwms_list_parameters` | List parameters published at a location, grouped by publisher. |
|
|
188
|
+
| `cwms_get_value` | Read the latest observation, optionally with threshold status. |
|
|
189
|
+
| `cwms_get_history` | Read raw observations across a bounded time window. |
|
|
190
|
+
| `cwms_browse_region` | Browse one office's locations, optionally by state or bounding box. |
|
|
191
|
+
| `cwms_publishers_for_parameter` | List publishers reporting a parameter across selected offices. |
|
|
192
|
+
| `cwms_get_overview_section` | Read bundled CWMS orientation content. |
|
|
193
|
+
|
|
194
|
+
Resources:
|
|
195
|
+
|
|
196
|
+
- `cwms://capabilities`: server version, fingerprint, tools, and resources.
|
|
197
|
+
- `cwms://overview`: index of bundled CWMS overview sections.
|
|
198
|
+
- `cwms://overview/{section_id}{?detail}`: summary or full section body.
|
|
199
|
+
- `cwms://overview/{section_id}/chunk/{chunk_id}`: one large-section chunk.
|
|
200
|
+
|
|
201
|
+
## Configuration
|
|
202
|
+
|
|
203
|
+
`cwms-tools` works anonymously by default. Environment variables are optional:
|
|
204
|
+
|
|
205
|
+
| Variable | Purpose |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| `CWMS_TOOLS_API_ROOT` | Override the CWMS Data API root. |
|
|
208
|
+
| `CWMS_TOOLS_CACHE_DIR` | Override the disk cache location. |
|
|
209
|
+
| `CWMS_TOOLS_WORKERS` | Set bounded worker concurrency. |
|
|
210
|
+
| `CWMS_TOOLS_REPO_URL` | Override the repository URL advertised in the user agent. |
|
|
211
|
+
| `CWMS_TOOLS_USER_AGENT_EXTRA` | Append extra text to the user agent. |
|
|
212
|
+
| `CWMS_TOOLS_OPERATOR_EMAIL` | Send a contact email via the `From` header. |
|
|
213
|
+
| `CWMS_TOOLS_MAX_RPS` | Declared for rate-limit policy; not enforced in v0.1.0. |
|
|
214
|
+
| `CWMS_API_KEY` | Reserved secret input for authenticated CDA deployments. |
|
|
215
|
+
| `CWMS_TOKEN` | Reserved secret input for authenticated CDA deployments. |
|
|
216
|
+
|
|
217
|
+
Inspect the resolved configuration without making a data call:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
uv run cwms-tools env
|
|
221
|
+
uv run cwms-tools config show --resolved
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## CWMS Notes
|
|
225
|
+
|
|
226
|
+
CWMS is USACE's Corps Water Management System: the operational data platform for
|
|
227
|
+
federal reservoirs, flood-control dams, navigation locks, hydropower projects,
|
|
228
|
+
and environmental monitoring stations.
|
|
229
|
+
|
|
230
|
+
Two upstream data-shape issues come up often:
|
|
231
|
+
|
|
232
|
+
- **Ghost records.** Some catalog locations do not publish time-series data.
|
|
233
|
+
Search and browse responses expose `parameter_count`, `parameters`, and
|
|
234
|
+
`data_at` repair hints so agents can move to the data-bearing sibling.
|
|
235
|
+
- **Northwestern Division stubs.** `NWO`, `NWK`, `NWS`, `NWP`, and `NWW` are
|
|
236
|
+
near-empty CDA stubs. Use `NWDM` for Missouri River data and `NWDP` for
|
|
237
|
+
Pacific Northwest data. Error envelopes include repair hints when a stub is
|
|
238
|
+
targeted.
|
|
239
|
+
|
|
240
|
+
## Upstream Etiquette
|
|
241
|
+
|
|
242
|
+
The CWMS Data API is a shared public service. `cwms-tools` identifies itself
|
|
243
|
+
with a descriptive `User-Agent`, caps concurrent requests, honors
|
|
244
|
+
`Retry-After`, avoids background scans, and does not cache live time-series
|
|
245
|
+
values. If you operate CDA and see problematic traffic from this client, please
|
|
246
|
+
open an issue at <https://github.com/briandconnelly/cwms-tools/issues>.
|
|
247
|
+
|
|
248
|
+
## Development
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
uv sync
|
|
252
|
+
uv run prek run --all-files
|
|
253
|
+
uv run pytest --cov=cwms_tools
|
|
254
|
+
uv run ty check
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The test suite uses unit tests and mocked CDA responses. Live CDA integration
|
|
258
|
+
tests are marked `integration` and are skipped unless explicitly selected.
|
|
259
|
+
|
|
260
|
+
Before opening a substantial PR, please open an issue to discuss the intended
|
|
261
|
+
change. The package is still pre-release, and the CLI/MCP schema contract is
|
|
262
|
+
the main compatibility surface.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# cwms-tools
|
|
2
|
+
|
|
3
|
+
[](https://github.com/briandconnelly/cwms-tools/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/cwms-tools/)
|
|
5
|
+
[](https://pypi.org/project/cwms-tools/)
|
|
6
|
+
|
|
7
|
+
Read-only, agent-friendly tools for the U.S. Army Corps of Engineers'
|
|
8
|
+
[CWMS Data API](https://cwms-data.usace.army.mil/cwms-data/). `cwms-tools` wraps the official
|
|
9
|
+
[`cwms-python`](https://github.com/HydrologicEngineeringCenter/cwms-python) client with two surfaces that share one behavioral
|
|
10
|
+
core:
|
|
11
|
+
|
|
12
|
+
- an [MCP](https://modelcontextprotocol.io/) server for agent runtimes such as Claude Code, Codex, and custom
|
|
13
|
+
MCP clients
|
|
14
|
+
- a non-interactive CLI with compact JSON output, stable exit codes, and a
|
|
15
|
+
machine-readable schema
|
|
16
|
+
|
|
17
|
+
The goal is simple: let agents answer common hydrologic questions with one task
|
|
18
|
+
call instead of a brittle chain of raw API lookups.
|
|
19
|
+
|
|
20
|
+
## What It Does
|
|
21
|
+
|
|
22
|
+
- Resolves natural place names to canonical CWMS locations, with ghost-location
|
|
23
|
+
filtering and co-located sensor hints.
|
|
24
|
+
- Describes a place in one call: location record, project metadata when
|
|
25
|
+
available, published parameters, publishers, and latest data timestamp.
|
|
26
|
+
- Reads the latest value or bounded history for CWMS time series parameters.
|
|
27
|
+
- Optionally classifies the latest value against CWMS Location Levels when
|
|
28
|
+
callers opt in with `--with-status` or `with_status=true`.
|
|
29
|
+
- Browses one office's catalog by state or bounding box.
|
|
30
|
+
- Finds publishers for a parameter across cached or explicitly requested
|
|
31
|
+
offices.
|
|
32
|
+
- Serves a bundled CWMS orientation document as MCP resources so agents can load
|
|
33
|
+
background material selectively.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add cwms-tools
|
|
39
|
+
# or: pipx install cwms-tools
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To run from a checkout instead:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/briandconnelly/cwms-tools.git
|
|
46
|
+
cd cwms-tools
|
|
47
|
+
uv sync
|
|
48
|
+
uv run cwms-tools --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## CLI Quick Start
|
|
52
|
+
|
|
53
|
+
The CLI is designed for non-interactive callers. When stdout is not a TTY,
|
|
54
|
+
machine mode is enabled automatically: compact JSON on stdout, diagnostics on
|
|
55
|
+
stderr, no color, no prompts, and no progress UI. You can force that profile
|
|
56
|
+
with `--machine` or `--json`.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Inspect the resolved session and upstream API root.
|
|
60
|
+
uv run cwms-tools whoami
|
|
61
|
+
|
|
62
|
+
# Print the command tree, output classes, exit codes, environment inputs,
|
|
63
|
+
# MCP tools, and MCP resources as a stable machine-readable contract.
|
|
64
|
+
uv run cwms-tools schema
|
|
65
|
+
|
|
66
|
+
# Print a SHA-256 fingerprint over the agent-visible capability surface.
|
|
67
|
+
uv run cwms-tools fingerprint
|
|
68
|
+
|
|
69
|
+
# Resolve a place name to ranked CWMS locations.
|
|
70
|
+
uv run cwms-tools place search "Fort Peck" --office NWDM
|
|
71
|
+
|
|
72
|
+
# Describe one place: location, project metadata, parameters, publishers,
|
|
73
|
+
# freshness, and partial-result flags.
|
|
74
|
+
uv run cwms-tools place describe NWDM/FTPK
|
|
75
|
+
|
|
76
|
+
# List parameters published at a place.
|
|
77
|
+
uv run cwms-tools place parameters NWDM/FTPK
|
|
78
|
+
|
|
79
|
+
# Read the latest elevation value. Status lookup is skipped by default because
|
|
80
|
+
# CWMS Location Levels calls can be slow.
|
|
81
|
+
uv run cwms-tools value get NWDM/FTPK/Elev
|
|
82
|
+
|
|
83
|
+
# Opt in to threshold classification when status context matters.
|
|
84
|
+
uv run cwms-tools value get NWDM/FTPK/Elev --with-status
|
|
85
|
+
|
|
86
|
+
# Read a bounded history window.
|
|
87
|
+
uv run cwms-tools value history NWDM/FTPK/Elev \
|
|
88
|
+
--begin 2026-05-16T00:00:00Z \
|
|
89
|
+
--end 2026-05-17T00:00:00Z
|
|
90
|
+
|
|
91
|
+
# Browse an office catalog by state.
|
|
92
|
+
uv run cwms-tools region browse --office SWT --state OK
|
|
93
|
+
|
|
94
|
+
# Find publishers reporting a parameter in selected offices.
|
|
95
|
+
uv run cwms-tools publisher for-parameter Elev --office NWDM --office SWT
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Useful global flags:
|
|
99
|
+
|
|
100
|
+
- `--machine` / `--json`: compact structured output for agents and scripts.
|
|
101
|
+
- `--no-cache`: bypass the on-disk catalog cache for one invocation.
|
|
102
|
+
- `--isolated`: bypass cache and ignore `CWMS_TOOLS_*` environment variables.
|
|
103
|
+
- `--version`: print the installed `cwms-tools` version.
|
|
104
|
+
|
|
105
|
+
Exit codes are part of the CLI contract:
|
|
106
|
+
|
|
107
|
+
| Exit | Meaning |
|
|
108
|
+
| ---: | --- |
|
|
109
|
+
| `0` | success |
|
|
110
|
+
| `2` | usage or invalid field |
|
|
111
|
+
| `3` | not found or publisher unavailable |
|
|
112
|
+
| `4` | session unconfigured |
|
|
113
|
+
| `6` | rate limited |
|
|
114
|
+
| `7` | timeout |
|
|
115
|
+
| `9` | upstream error or catalog cursor invalidation |
|
|
116
|
+
| `11` | wrapper bug |
|
|
117
|
+
| `12` | ghost location or ghost office |
|
|
118
|
+
|
|
119
|
+
## MCP Quick Start
|
|
120
|
+
|
|
121
|
+
Use stdio for local agent runtimes:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
uv run cwms-tools mcp serve --transport stdio
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use streamable HTTP for shared or remote deployment:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
uv run cwms-tools mcp serve --transport streamable-http --host 127.0.0.1 --port 8765
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Claude Code config example:
|
|
134
|
+
|
|
135
|
+
```jsonc
|
|
136
|
+
{
|
|
137
|
+
"mcpServers": {
|
|
138
|
+
"cwms-tools": {
|
|
139
|
+
"command": "uv",
|
|
140
|
+
"args": ["run", "cwms-tools", "mcp", "serve", "--transport", "stdio"]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The MCP server exposes task-level tools rather than raw endpoint mirrors:
|
|
147
|
+
|
|
148
|
+
| Tool | Purpose |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `cwms_search_places` | Resolve an ambiguous place name to ranked locations. |
|
|
151
|
+
| `cwms_describe_place` | Read location, project, parameter, publisher, and freshness data in one call. |
|
|
152
|
+
| `cwms_list_parameters` | List parameters published at a location, grouped by publisher. |
|
|
153
|
+
| `cwms_get_value` | Read the latest observation, optionally with threshold status. |
|
|
154
|
+
| `cwms_get_history` | Read raw observations across a bounded time window. |
|
|
155
|
+
| `cwms_browse_region` | Browse one office's locations, optionally by state or bounding box. |
|
|
156
|
+
| `cwms_publishers_for_parameter` | List publishers reporting a parameter across selected offices. |
|
|
157
|
+
| `cwms_get_overview_section` | Read bundled CWMS orientation content. |
|
|
158
|
+
|
|
159
|
+
Resources:
|
|
160
|
+
|
|
161
|
+
- `cwms://capabilities`: server version, fingerprint, tools, and resources.
|
|
162
|
+
- `cwms://overview`: index of bundled CWMS overview sections.
|
|
163
|
+
- `cwms://overview/{section_id}{?detail}`: summary or full section body.
|
|
164
|
+
- `cwms://overview/{section_id}/chunk/{chunk_id}`: one large-section chunk.
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
`cwms-tools` works anonymously by default. Environment variables are optional:
|
|
169
|
+
|
|
170
|
+
| Variable | Purpose |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `CWMS_TOOLS_API_ROOT` | Override the CWMS Data API root. |
|
|
173
|
+
| `CWMS_TOOLS_CACHE_DIR` | Override the disk cache location. |
|
|
174
|
+
| `CWMS_TOOLS_WORKERS` | Set bounded worker concurrency. |
|
|
175
|
+
| `CWMS_TOOLS_REPO_URL` | Override the repository URL advertised in the user agent. |
|
|
176
|
+
| `CWMS_TOOLS_USER_AGENT_EXTRA` | Append extra text to the user agent. |
|
|
177
|
+
| `CWMS_TOOLS_OPERATOR_EMAIL` | Send a contact email via the `From` header. |
|
|
178
|
+
| `CWMS_TOOLS_MAX_RPS` | Declared for rate-limit policy; not enforced in v0.1.0. |
|
|
179
|
+
| `CWMS_API_KEY` | Reserved secret input for authenticated CDA deployments. |
|
|
180
|
+
| `CWMS_TOKEN` | Reserved secret input for authenticated CDA deployments. |
|
|
181
|
+
|
|
182
|
+
Inspect the resolved configuration without making a data call:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
uv run cwms-tools env
|
|
186
|
+
uv run cwms-tools config show --resolved
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## CWMS Notes
|
|
190
|
+
|
|
191
|
+
CWMS is USACE's Corps Water Management System: the operational data platform for
|
|
192
|
+
federal reservoirs, flood-control dams, navigation locks, hydropower projects,
|
|
193
|
+
and environmental monitoring stations.
|
|
194
|
+
|
|
195
|
+
Two upstream data-shape issues come up often:
|
|
196
|
+
|
|
197
|
+
- **Ghost records.** Some catalog locations do not publish time-series data.
|
|
198
|
+
Search and browse responses expose `parameter_count`, `parameters`, and
|
|
199
|
+
`data_at` repair hints so agents can move to the data-bearing sibling.
|
|
200
|
+
- **Northwestern Division stubs.** `NWO`, `NWK`, `NWS`, `NWP`, and `NWW` are
|
|
201
|
+
near-empty CDA stubs. Use `NWDM` for Missouri River data and `NWDP` for
|
|
202
|
+
Pacific Northwest data. Error envelopes include repair hints when a stub is
|
|
203
|
+
targeted.
|
|
204
|
+
|
|
205
|
+
## Upstream Etiquette
|
|
206
|
+
|
|
207
|
+
The CWMS Data API is a shared public service. `cwms-tools` identifies itself
|
|
208
|
+
with a descriptive `User-Agent`, caps concurrent requests, honors
|
|
209
|
+
`Retry-After`, avoids background scans, and does not cache live time-series
|
|
210
|
+
values. If you operate CDA and see problematic traffic from this client, please
|
|
211
|
+
open an issue at <https://github.com/briandconnelly/cwms-tools/issues>.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uv sync
|
|
217
|
+
uv run prek run --all-files
|
|
218
|
+
uv run pytest --cov=cwms_tools
|
|
219
|
+
uv run ty check
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The test suite uses unit tests and mocked CDA responses. Live CDA integration
|
|
223
|
+
tests are marked `integration` and are skipped unless explicitly selected.
|
|
224
|
+
|
|
225
|
+
Before opening a substantial PR, please open an issue to discuss the intended
|
|
226
|
+
change. The package is still pre-release, and the CLI/MCP schema contract is
|
|
227
|
+
the main compatibility surface.
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cwms-tools"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Agent-friendly tools (MCP server and CLI) for the USACE Corps Water Management System (CWMS) Data API."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Brian Connelly", email = "bdc@bconnelly.net" },
|
|
11
|
+
]
|
|
12
|
+
keywords = ["cwms", "usace", "mcp", "cli", "water", "hydrology", "agent"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Hydrology",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"cwms-python>=1.0.7",
|
|
28
|
+
"fastmcp>=3",
|
|
29
|
+
"typer>=0.15",
|
|
30
|
+
"pydantic>=2",
|
|
31
|
+
"platformdirs>=4",
|
|
32
|
+
"diskcache>=5",
|
|
33
|
+
"anyio>=4",
|
|
34
|
+
"python-dateutil>=2.9",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/briandconnelly/cwms-tools"
|
|
39
|
+
Repository = "https://github.com/briandconnelly/cwms-tools"
|
|
40
|
+
Issues = "https://github.com/briandconnelly/cwms-tools/issues"
|
|
41
|
+
Changelog = "https://github.com/briandconnelly/cwms-tools/blob/main/CHANGELOG.md"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
cwms-tools = "cwms_tools.cli.app:app"
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=8",
|
|
49
|
+
"pytest-cov>=5",
|
|
50
|
+
"pytest-asyncio>=0.24",
|
|
51
|
+
"responses>=0.25",
|
|
52
|
+
"vcrpy>=6",
|
|
53
|
+
"ruff>=0.15",
|
|
54
|
+
"ty>=0.0.1a1",
|
|
55
|
+
"prek>=0.2",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[build-system]
|
|
59
|
+
requires = ["uv_build>=0.11.14,<0.12.0"]
|
|
60
|
+
build-backend = "uv_build"
|
|
61
|
+
|
|
62
|
+
[tool.uv]
|
|
63
|
+
package = true
|
|
64
|
+
|
|
65
|
+
# --------------------------------------------------------------------------
|
|
66
|
+
# Ruff (lint + format)
|
|
67
|
+
# --------------------------------------------------------------------------
|
|
68
|
+
[tool.ruff]
|
|
69
|
+
line-length = 100
|
|
70
|
+
target-version = "py310"
|
|
71
|
+
src = ["src", "tests"]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint]
|
|
74
|
+
select = [
|
|
75
|
+
"E", "F", "W", # pycodestyle + pyflakes
|
|
76
|
+
"I", # isort
|
|
77
|
+
"B", # bugbear
|
|
78
|
+
"UP", # pyupgrade
|
|
79
|
+
"SIM", # simplify
|
|
80
|
+
"PIE", # misc
|
|
81
|
+
"RUF", # ruff-specific
|
|
82
|
+
"PL", # pylint subset
|
|
83
|
+
"PT", # pytest style
|
|
84
|
+
"TID", # tidy imports
|
|
85
|
+
"TCH", # type-checking imports
|
|
86
|
+
"ARG", # unused arguments
|
|
87
|
+
"PTH", # use pathlib
|
|
88
|
+
]
|
|
89
|
+
ignore = [
|
|
90
|
+
"PLR0913", # too many arguments — common in API wrappers
|
|
91
|
+
"PLR2004", # magic value used in comparison — noisy in tests
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint.per-file-ignores]
|
|
95
|
+
# Tests get a permissive lint policy: pytest idioms regularly trip checks meant
|
|
96
|
+
# for production code (TID -> inline imports for monkeypatching, PT018 -> compound
|
|
97
|
+
# asserts, PLC0415 -> per-test imports), and ruff's en-dash heuristic fires on
|
|
98
|
+
# normal English in docstrings.
|
|
99
|
+
"tests/**" = ["ARG", "PLR2004", "S101", "TC002", "TC003", "PT018", "PLC0415", "RUF002", "RUF003"]
|
|
100
|
+
|
|
101
|
+
[tool.ruff.format]
|
|
102
|
+
quote-style = "double"
|
|
103
|
+
|
|
104
|
+
# --------------------------------------------------------------------------
|
|
105
|
+
# ty (type checker)
|
|
106
|
+
# --------------------------------------------------------------------------
|
|
107
|
+
[tool.ty.src]
|
|
108
|
+
include = ["src", "tests"]
|
|
109
|
+
|
|
110
|
+
[tool.ty.rules]
|
|
111
|
+
# Strict on our own code; permissive on third-party shapes
|
|
112
|
+
unresolved-import = "warn"
|
|
113
|
+
|
|
114
|
+
# --------------------------------------------------------------------------
|
|
115
|
+
# pytest
|
|
116
|
+
# --------------------------------------------------------------------------
|
|
117
|
+
[tool.pytest.ini_options]
|
|
118
|
+
minversion = "8.0"
|
|
119
|
+
testpaths = ["tests"]
|
|
120
|
+
addopts = [
|
|
121
|
+
"-ra",
|
|
122
|
+
"--strict-markers",
|
|
123
|
+
"--strict-config",
|
|
124
|
+
]
|
|
125
|
+
asyncio_mode = "auto"
|
|
126
|
+
markers = [
|
|
127
|
+
"integration: live-CDA tests (skipped by default)",
|
|
128
|
+
"slow: slow tests",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# --------------------------------------------------------------------------
|
|
132
|
+
# coverage
|
|
133
|
+
# --------------------------------------------------------------------------
|
|
134
|
+
[tool.coverage.run]
|
|
135
|
+
source = ["src/cwms_tools"]
|
|
136
|
+
branch = true
|
|
137
|
+
|
|
138
|
+
[tool.coverage.report]
|
|
139
|
+
exclude_lines = [
|
|
140
|
+
"pragma: no cover",
|
|
141
|
+
"raise NotImplementedError",
|
|
142
|
+
"if TYPE_CHECKING:",
|
|
143
|
+
"if __name__ == .__main__.:",
|
|
144
|
+
]
|
|
145
|
+
fail_under = 80 # v0.1.0 floor; raise toward 95 as wrapper-error paths get hit
|
|
146
|
+
show_missing = true
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""cwms-tools — agent-friendly tools for the USACE CWMS Data API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("cwms-tools")
|
|
9
|
+
except PackageNotFoundError: # pragma: no cover
|
|
10
|
+
__version__ = "0.0.0+unknown"
|
|
11
|
+
|
|
12
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Typer CLI adapter over the core."""
|