ofs-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.
- ofs_mcp-0.1.0/.gitignore +42 -0
- ofs_mcp-0.1.0/LICENSE +21 -0
- ofs_mcp-0.1.0/PKG-INFO +131 -0
- ofs_mcp-0.1.0/README.md +99 -0
- ofs_mcp-0.1.0/pyproject.toml +72 -0
- ofs_mcp-0.1.0/server.json +22 -0
- ofs_mcp-0.1.0/smithery.yaml +9 -0
- ofs_mcp-0.1.0/src/ofs_mcp/__init__.py +1 -0
- ofs_mcp-0.1.0/src/ofs_mcp/__main__.py +5 -0
- ofs_mcp-0.1.0/src/ofs_mcp/client.py +212 -0
- ofs_mcp-0.1.0/src/ofs_mcp/models.py +255 -0
- ofs_mcp-0.1.0/src/ofs_mcp/server.py +32 -0
- ofs_mcp-0.1.0/src/ofs_mcp/tools/__init__.py +1 -0
- ofs_mcp-0.1.0/src/ofs_mcp/tools/discovery.py +362 -0
- ofs_mcp-0.1.0/src/ofs_mcp/tools/forecast.py +439 -0
- ofs_mcp-0.1.0/src/ofs_mcp/utils.py +559 -0
- ofs_mcp-0.1.0/tests/__init__.py +1 -0
- ofs_mcp-0.1.0/tests/test_utils.py +273 -0
ofs_mcp-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
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
|
ofs_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.
|
ofs_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ofs-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for NOAA Operational Forecast System (OFS) regional ocean model access
|
|
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/ofs-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: fvcom,mcp,model-context-protocol,noaa,ocean-forecast,oceanography,ofs,roms,salinity,temperature,water-level
|
|
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: netcdf4>=1.7.0
|
|
29
|
+
Requires-Dist: numpy>=1.26.0
|
|
30
|
+
Requires-Dist: pydantic>=2.0.0
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# OFS MCP Server
|
|
34
|
+
|
|
35
|
+
<!-- mcp-name: io.github.mansurjisan/ofs-mcp -->
|
|
36
|
+
|
|
37
|
+
MCP server for NOAA's Operational Forecast System (OFS) — regional hydrodynamic ocean models covering US coastal waters.
|
|
38
|
+
|
|
39
|
+
**Status**: Ready
|
|
40
|
+
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
OFS comprises ~15 regional ocean models providing 48–72 hour forecasts of water levels, temperature, and salinity for US coastal bays, estuaries, and offshore waters. This server provides AI assistants with access to model metadata, cycle availability, and forecast extraction at geographic points.
|
|
44
|
+
|
|
45
|
+
## Supported Models
|
|
46
|
+
|
|
47
|
+
| Model | Name | Grid | Region |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| `cbofs` | Chesapeake Bay OFS | ROMS | Chesapeake Bay, MD/VA |
|
|
50
|
+
| `dbofs` | Delaware Bay OFS | ROMS | Delaware Bay, DE/NJ |
|
|
51
|
+
| `gomofs` | Gulf of Maine OFS | ROMS | Gulf of Maine, ME/MA |
|
|
52
|
+
| `ngofs2` | N. Gulf of Mexico OFS v2 | FVCOM | Gulf Coast, LA/TX |
|
|
53
|
+
| `nyofs` | New York/NJ Harbor OFS | FVCOM | NY Harbor, NY/NJ |
|
|
54
|
+
| `sfbofs` | San Francisco Bay OFS | FVCOM | San Francisco Bay, CA |
|
|
55
|
+
| `tbofs` | Tampa Bay OFS | FVCOM | Tampa Bay, FL |
|
|
56
|
+
| `wcofs` | West Coast OFS | ROMS | US West Coast, CA–WA |
|
|
57
|
+
| `ciofs` | Cook Inlet OFS | FVCOM | Cook Inlet, AK |
|
|
58
|
+
|
|
59
|
+
All models run 4× daily (2× for WCOFS) at 00, 06, 12, 18 UTC with 6-minute output resolution.
|
|
60
|
+
|
|
61
|
+
## Tools
|
|
62
|
+
|
|
63
|
+
| Tool | Description |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `ofs_list_models` | List all supported models with metadata |
|
|
66
|
+
| `ofs_get_model_info` | Detailed specs for a specific model |
|
|
67
|
+
| `ofs_list_cycles` | Check S3 for available forecast cycles |
|
|
68
|
+
| `ofs_find_models_for_location` | Which models cover a lat/lon point |
|
|
69
|
+
| `ofs_get_forecast_at_point` | Forecast time series at lat/lon |
|
|
70
|
+
| `ofs_compare_with_coops` | Compare model vs CO-OPS observations |
|
|
71
|
+
|
|
72
|
+
## Data Access
|
|
73
|
+
|
|
74
|
+
Model data is accessed via two strategies:
|
|
75
|
+
- **NOAA THREDDS OPeNDAP** (`https://opendap.co-ops.nos.noaa.gov/thredds/`): lazy remote access to BEST aggregations (most efficient, only loads requested variables/points)
|
|
76
|
+
- **AWS S3** (`noaa-nos-ofs-pds`): direct download for single forecast timesteps
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd servers/ofs-mcp
|
|
82
|
+
uv sync
|
|
83
|
+
uv run ofs-mcp
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### MCP Client Config
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"ofs": {
|
|
92
|
+
"command": "uv",
|
|
93
|
+
"args": ["--directory", "/path/to/ocean-mcp/servers/ofs-mcp", "run", "ofs-mcp"]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Example Queries
|
|
100
|
+
|
|
101
|
+
- "What OFS models cover the Chesapeake Bay?"
|
|
102
|
+
- "List available CBOFS forecast cycles for today"
|
|
103
|
+
- "Get the water level forecast at lat 38.98, lon -76.48 from CBOFS"
|
|
104
|
+
- "Compare CBOFS water level with CO-OPS observations at station 8571892"
|
|
105
|
+
- "What OFS models are available for San Francisco Bay?"
|
|
106
|
+
- "Get temperature forecast at lat 37.8, lon -122.4 from SFBOFS"
|
|
107
|
+
|
|
108
|
+
## Variables
|
|
109
|
+
|
|
110
|
+
All models provide surface-layer output:
|
|
111
|
+
|
|
112
|
+
| Variable | Units | Description |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `water_level` | m | Surface elevation relative to model datum |
|
|
115
|
+
| `temperature` | °C | Water temperature at surface sigma layer |
|
|
116
|
+
| `salinity` | PSU | Salinity at surface sigma layer |
|
|
117
|
+
|
|
118
|
+
## Datum Notes
|
|
119
|
+
|
|
120
|
+
Most OFS models use **NAVD88** as their vertical datum. CO-OPS observations use either NAVD or MSL depending on the station. Small systematic offsets (1–5 cm) are expected when comparing model output to observations due to datum differences and distance between CO-OPS stations and model grid points.
|
|
121
|
+
|
|
122
|
+
## Data Sources
|
|
123
|
+
|
|
124
|
+
- [NOAA OFS Overview](https://tidesandcurrents.noaa.gov/models.html)
|
|
125
|
+
- [AWS S3: noaa-nos-ofs-pds](https://registry.opendata.aws/noaa-nos-ofs/)
|
|
126
|
+
- [NOAA THREDDS](https://opendap.co-ops.nos.noaa.gov/thredds/)
|
|
127
|
+
- [CO-OPS API](https://api.tidesandcurrents.noaa.gov/api/prod/)
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
ofs_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# OFS MCP Server
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.mansurjisan/ofs-mcp -->
|
|
4
|
+
|
|
5
|
+
MCP server for NOAA's Operational Forecast System (OFS) — regional hydrodynamic ocean models covering US coastal waters.
|
|
6
|
+
|
|
7
|
+
**Status**: Ready
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
OFS comprises ~15 regional ocean models providing 48–72 hour forecasts of water levels, temperature, and salinity for US coastal bays, estuaries, and offshore waters. This server provides AI assistants with access to model metadata, cycle availability, and forecast extraction at geographic points.
|
|
12
|
+
|
|
13
|
+
## Supported Models
|
|
14
|
+
|
|
15
|
+
| Model | Name | Grid | Region |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| `cbofs` | Chesapeake Bay OFS | ROMS | Chesapeake Bay, MD/VA |
|
|
18
|
+
| `dbofs` | Delaware Bay OFS | ROMS | Delaware Bay, DE/NJ |
|
|
19
|
+
| `gomofs` | Gulf of Maine OFS | ROMS | Gulf of Maine, ME/MA |
|
|
20
|
+
| `ngofs2` | N. Gulf of Mexico OFS v2 | FVCOM | Gulf Coast, LA/TX |
|
|
21
|
+
| `nyofs` | New York/NJ Harbor OFS | FVCOM | NY Harbor, NY/NJ |
|
|
22
|
+
| `sfbofs` | San Francisco Bay OFS | FVCOM | San Francisco Bay, CA |
|
|
23
|
+
| `tbofs` | Tampa Bay OFS | FVCOM | Tampa Bay, FL |
|
|
24
|
+
| `wcofs` | West Coast OFS | ROMS | US West Coast, CA–WA |
|
|
25
|
+
| `ciofs` | Cook Inlet OFS | FVCOM | Cook Inlet, AK |
|
|
26
|
+
|
|
27
|
+
All models run 4× daily (2× for WCOFS) at 00, 06, 12, 18 UTC with 6-minute output resolution.
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
| Tool | Description |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `ofs_list_models` | List all supported models with metadata |
|
|
34
|
+
| `ofs_get_model_info` | Detailed specs for a specific model |
|
|
35
|
+
| `ofs_list_cycles` | Check S3 for available forecast cycles |
|
|
36
|
+
| `ofs_find_models_for_location` | Which models cover a lat/lon point |
|
|
37
|
+
| `ofs_get_forecast_at_point` | Forecast time series at lat/lon |
|
|
38
|
+
| `ofs_compare_with_coops` | Compare model vs CO-OPS observations |
|
|
39
|
+
|
|
40
|
+
## Data Access
|
|
41
|
+
|
|
42
|
+
Model data is accessed via two strategies:
|
|
43
|
+
- **NOAA THREDDS OPeNDAP** (`https://opendap.co-ops.nos.noaa.gov/thredds/`): lazy remote access to BEST aggregations (most efficient, only loads requested variables/points)
|
|
44
|
+
- **AWS S3** (`noaa-nos-ofs-pds`): direct download for single forecast timesteps
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd servers/ofs-mcp
|
|
50
|
+
uv sync
|
|
51
|
+
uv run ofs-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### MCP Client Config
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"mcpServers": {
|
|
59
|
+
"ofs": {
|
|
60
|
+
"command": "uv",
|
|
61
|
+
"args": ["--directory", "/path/to/ocean-mcp/servers/ofs-mcp", "run", "ofs-mcp"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Example Queries
|
|
68
|
+
|
|
69
|
+
- "What OFS models cover the Chesapeake Bay?"
|
|
70
|
+
- "List available CBOFS forecast cycles for today"
|
|
71
|
+
- "Get the water level forecast at lat 38.98, lon -76.48 from CBOFS"
|
|
72
|
+
- "Compare CBOFS water level with CO-OPS observations at station 8571892"
|
|
73
|
+
- "What OFS models are available for San Francisco Bay?"
|
|
74
|
+
- "Get temperature forecast at lat 37.8, lon -122.4 from SFBOFS"
|
|
75
|
+
|
|
76
|
+
## Variables
|
|
77
|
+
|
|
78
|
+
All models provide surface-layer output:
|
|
79
|
+
|
|
80
|
+
| Variable | Units | Description |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `water_level` | m | Surface elevation relative to model datum |
|
|
83
|
+
| `temperature` | °C | Water temperature at surface sigma layer |
|
|
84
|
+
| `salinity` | PSU | Salinity at surface sigma layer |
|
|
85
|
+
|
|
86
|
+
## Datum Notes
|
|
87
|
+
|
|
88
|
+
Most OFS models use **NAVD88** as their vertical datum. CO-OPS observations use either NAVD or MSL depending on the station. Small systematic offsets (1–5 cm) are expected when comparing model output to observations due to datum differences and distance between CO-OPS stations and model grid points.
|
|
89
|
+
|
|
90
|
+
## Data Sources
|
|
91
|
+
|
|
92
|
+
- [NOAA OFS Overview](https://tidesandcurrents.noaa.gov/models.html)
|
|
93
|
+
- [AWS S3: noaa-nos-ofs-pds](https://registry.opendata.aws/noaa-nos-ofs/)
|
|
94
|
+
- [NOAA THREDDS](https://opendap.co-ops.nos.noaa.gov/thredds/)
|
|
95
|
+
- [CO-OPS API](https://api.tidesandcurrents.noaa.gov/api/prod/)
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ofs-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for NOAA Operational Forecast System (OFS) regional ocean model access"
|
|
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
|
+
"noaa",
|
|
20
|
+
"ocean-forecast",
|
|
21
|
+
"ofs",
|
|
22
|
+
"roms",
|
|
23
|
+
"fvcom",
|
|
24
|
+
"water-level",
|
|
25
|
+
"temperature",
|
|
26
|
+
"salinity",
|
|
27
|
+
"oceanography",
|
|
28
|
+
]
|
|
29
|
+
classifiers = [
|
|
30
|
+
"Development Status :: 4 - Beta",
|
|
31
|
+
"Intended Audience :: Science/Research",
|
|
32
|
+
"Intended Audience :: Developers",
|
|
33
|
+
"License :: OSI Approved :: MIT License",
|
|
34
|
+
"Programming Language :: Python :: 3",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
38
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
39
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
40
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
41
|
+
]
|
|
42
|
+
dependencies = [
|
|
43
|
+
"mcp[cli]>=1.0.0",
|
|
44
|
+
"httpx>=0.27.0",
|
|
45
|
+
"pydantic>=2.0.0",
|
|
46
|
+
"netCDF4>=1.7.0",
|
|
47
|
+
"numpy>=1.26.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/mansurjisan/ocean-mcp"
|
|
52
|
+
Repository = "https://github.com/mansurjisan/ocean-mcp"
|
|
53
|
+
"Bug Tracker" = "https://github.com/mansurjisan/ocean-mcp/issues"
|
|
54
|
+
Documentation = "https://github.com/mansurjisan/ocean-mcp/tree/main/servers/ofs-mcp"
|
|
55
|
+
Changelog = "https://github.com/mansurjisan/ocean-mcp/releases"
|
|
56
|
+
|
|
57
|
+
[project.scripts]
|
|
58
|
+
ofs-mcp = "ofs_mcp.server:main"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["src/ofs_mcp"]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests"]
|
|
65
|
+
asyncio_mode = "auto"
|
|
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/ofs-mcp",
|
|
4
|
+
"title": "NOAA OFS MCP Server",
|
|
5
|
+
"description": "MCP server for NOAA Operational Forecast System regional ocean models (CBOFS, NGOFS2, WCOFS, etc.) — water level, temperature, salinity. No API key required.",
|
|
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": "ofs-mcp",
|
|
15
|
+
"version": "0.1.0",
|
|
16
|
+
"runtimeHint": "uvx",
|
|
17
|
+
"transport": {
|
|
18
|
+
"type": "stdio"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""OFS MCP — NOAA Operational Forecast System MCP server."""
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Async HTTP client for OFS data on AWS S3, THREDDS/OPeNDAP, and CO-OPS API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .models import COOPS_API_BASE, OFS_MODELS, S3_BASE, THREDDS_BASE
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OFSAPIError(Exception):
|
|
15
|
+
"""Custom exception for OFS API errors."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OFSClient:
|
|
20
|
+
"""Async client for OFS data access and CO-OPS observations."""
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
self._client: httpx.AsyncClient | None = None
|
|
24
|
+
|
|
25
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
26
|
+
if self._client is None or self._client.is_closed:
|
|
27
|
+
self._client = httpx.AsyncClient(
|
|
28
|
+
timeout=120.0,
|
|
29
|
+
follow_redirects=True,
|
|
30
|
+
)
|
|
31
|
+
return self._client
|
|
32
|
+
|
|
33
|
+
async def check_file_exists(self, url: str) -> bool:
|
|
34
|
+
"""Check if a file exists using HTTP HEAD."""
|
|
35
|
+
client = await self._get_client()
|
|
36
|
+
try:
|
|
37
|
+
response = await client.head(url)
|
|
38
|
+
return response.status_code == 200
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
def build_s3_url(
|
|
43
|
+
self,
|
|
44
|
+
model: str,
|
|
45
|
+
date: str,
|
|
46
|
+
cycle: str,
|
|
47
|
+
ftype: str = "f",
|
|
48
|
+
fhour: int = 1,
|
|
49
|
+
) -> str:
|
|
50
|
+
"""Build the S3 URL for an OFS fields NetCDF file.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
model: OFS model key (e.g., 'cbofs').
|
|
54
|
+
date: Date in YYYYMMDD format.
|
|
55
|
+
cycle: Cycle hour (e.g., '00', '06', '12', '18').
|
|
56
|
+
ftype: 'f' for forecast, 'n' for nowcast.
|
|
57
|
+
fhour: Forecast/nowcast hour number (1-indexed).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Full HTTPS URL to the NetCDF file on S3.
|
|
61
|
+
"""
|
|
62
|
+
y, m, d = date[:4], date[4:6], date[6:8]
|
|
63
|
+
fname = f"{model}.t{cycle}z.fields.{ftype}{fhour:03d}.nc"
|
|
64
|
+
return f"{S3_BASE}/{model}/netcdf/{y}/{m}/{d}/{fname}"
|
|
65
|
+
|
|
66
|
+
def build_thredds_url(self, model: str) -> str:
|
|
67
|
+
"""Build the THREDDS OPeNDAP URL for the BEST aggregation of an OFS model.
|
|
68
|
+
|
|
69
|
+
The BEST aggregation combines the most recent nowcast and forecast data
|
|
70
|
+
into a continuous time series accessible via OPeNDAP for lazy loading.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
model: OFS model key (e.g., 'cbofs').
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
OPeNDAP URL for the BEST aggregation dataset.
|
|
77
|
+
"""
|
|
78
|
+
model_info = OFS_MODELS.get(model, {})
|
|
79
|
+
thredds_id = model_info.get("thredds_id", model.upper())
|
|
80
|
+
return f"{THREDDS_BASE}/{thredds_id}/{thredds_id}_BEST.nc"
|
|
81
|
+
|
|
82
|
+
async def download_netcdf(self, url: str) -> Path:
|
|
83
|
+
"""Download a NetCDF file to a temporary location.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
url: Full HTTPS URL to the NetCDF file.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Path to the temporary file. Caller is responsible for deletion.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
httpx.HTTPStatusError: If the request fails.
|
|
93
|
+
"""
|
|
94
|
+
client = await self._get_client()
|
|
95
|
+
response = await client.get(url)
|
|
96
|
+
response.raise_for_status()
|
|
97
|
+
|
|
98
|
+
tmp = tempfile.NamedTemporaryFile(suffix=".nc", delete=False)
|
|
99
|
+
tmp.write(response.content)
|
|
100
|
+
tmp.close()
|
|
101
|
+
return Path(tmp.name)
|
|
102
|
+
|
|
103
|
+
def open_opendap(self, model: str):
|
|
104
|
+
"""Open a THREDDS OPeNDAP dataset for lazy remote access.
|
|
105
|
+
|
|
106
|
+
Uses netCDF4.Dataset with OPeNDAP — only loads data when variables
|
|
107
|
+
are actually indexed, enabling efficient point extraction.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
model: OFS model key (e.g., 'cbofs').
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
netCDF4.Dataset opened via OPeNDAP.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
RuntimeError: If OPeNDAP is not available or connection fails.
|
|
117
|
+
"""
|
|
118
|
+
import netCDF4
|
|
119
|
+
|
|
120
|
+
url = self.build_thredds_url(model)
|
|
121
|
+
try:
|
|
122
|
+
nc = netCDF4.Dataset(url)
|
|
123
|
+
return nc
|
|
124
|
+
except Exception as e:
|
|
125
|
+
raise RuntimeError(
|
|
126
|
+
f"Failed to open OPeNDAP dataset for {model.upper()} at {url}. "
|
|
127
|
+
f"Error: {e}\n\n"
|
|
128
|
+
"Possible causes:\n"
|
|
129
|
+
"- THREDDS server temporarily unavailable\n"
|
|
130
|
+
"- netCDF4 library not compiled with DAP support\n"
|
|
131
|
+
"- Network connectivity issue\n\n"
|
|
132
|
+
"Try using a different model or check cycle availability with ofs_list_cycles."
|
|
133
|
+
) from e
|
|
134
|
+
|
|
135
|
+
async def resolve_latest_cycle(
|
|
136
|
+
self,
|
|
137
|
+
model: str,
|
|
138
|
+
num_days: int = 2,
|
|
139
|
+
) -> tuple[str, str] | None:
|
|
140
|
+
"""Find the latest available OFS cycle on AWS S3.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
model: OFS model key (e.g., 'cbofs').
|
|
144
|
+
num_days: Number of past days to check (default: 2).
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
(date_str, cycle_str) tuple (YYYYMMDD, CC), or None if not found.
|
|
148
|
+
"""
|
|
149
|
+
from datetime import datetime, timedelta, timezone
|
|
150
|
+
|
|
151
|
+
model_info = OFS_MODELS.get(model, {})
|
|
152
|
+
cycles = model_info.get("cycles", ["00", "06", "12", "18"])
|
|
153
|
+
# Check newest first
|
|
154
|
+
cycles_desc = sorted(cycles, reverse=True)
|
|
155
|
+
|
|
156
|
+
today = datetime.now(timezone.utc)
|
|
157
|
+
for day_offset in range(num_days):
|
|
158
|
+
date = today - timedelta(days=day_offset)
|
|
159
|
+
date_str = date.strftime("%Y%m%d")
|
|
160
|
+
for cycle in cycles_desc:
|
|
161
|
+
url = self.build_s3_url(model, date_str, cycle, "f", 1)
|
|
162
|
+
if await self.check_file_exists(url):
|
|
163
|
+
return date_str, cycle
|
|
164
|
+
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
async def fetch_coops_observations(
|
|
168
|
+
self,
|
|
169
|
+
station_id: str,
|
|
170
|
+
begin_date: str,
|
|
171
|
+
end_date: str,
|
|
172
|
+
datum: str = "NAVD",
|
|
173
|
+
) -> dict[str, Any]:
|
|
174
|
+
"""Fetch CO-OPS observed water levels.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
station_id: CO-OPS station ID (e.g., '8571892').
|
|
178
|
+
begin_date: Start date (YYYYMMDD or 'YYYYMMDD HH:MM').
|
|
179
|
+
end_date: End date (YYYYMMDD or 'YYYYMMDD HH:MM').
|
|
180
|
+
datum: Vertical datum — 'NAVD' for NAVD88, 'MSL', 'MLLW', etc.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
CO-OPS API JSON response with 'data' list.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ValueError: If the CO-OPS API returns an error.
|
|
187
|
+
"""
|
|
188
|
+
client = await self._get_client()
|
|
189
|
+
params = {
|
|
190
|
+
"station": station_id,
|
|
191
|
+
"product": "water_level",
|
|
192
|
+
"datum": datum,
|
|
193
|
+
"units": "metric",
|
|
194
|
+
"time_zone": "gmt",
|
|
195
|
+
"format": "json",
|
|
196
|
+
"begin_date": begin_date,
|
|
197
|
+
"end_date": end_date,
|
|
198
|
+
"application": "ofs_mcp",
|
|
199
|
+
}
|
|
200
|
+
response = await client.get(COOPS_API_BASE, params=params)
|
|
201
|
+
response.raise_for_status()
|
|
202
|
+
data = response.json()
|
|
203
|
+
if "error" in data:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f"CO-OPS API error: {data['error'].get('message', 'Unknown error')}"
|
|
206
|
+
)
|
|
207
|
+
return data
|
|
208
|
+
|
|
209
|
+
async def close(self) -> None:
|
|
210
|
+
"""Close the HTTP client."""
|
|
211
|
+
if self._client and not self._client.is_closed:
|
|
212
|
+
await self._client.aclose()
|