spaceweather-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.
- spaceweather_mcp-0.1.0/.gitignore +29 -0
- spaceweather_mcp-0.1.0/LICENSE +21 -0
- spaceweather_mcp-0.1.0/PKG-INFO +90 -0
- spaceweather_mcp-0.1.0/README.md +62 -0
- spaceweather_mcp-0.1.0/pyproject.toml +64 -0
- spaceweather_mcp-0.1.0/server.json +20 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/__init__.py +3 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/cache.py +51 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/schemas.py +210 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/server.py +235 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/sources/__init__.py +2 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/sources/donki.py +109 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/sources/kyoto_dst.py +107 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/sources/swpc.py +482 -0
- spaceweather_mcp-0.1.0/src/spaceweather_mcp/sources/usgs_geomag.py +160 -0
- spaceweather_mcp-0.1.0/tests/test_extra_parse.py +64 -0
- spaceweather_mcp-0.1.0/tests/test_geomag_dst_parse.py +71 -0
- spaceweather_mcp-0.1.0/tests/test_swpc_parse.py +78 -0
- spaceweather_mcp-0.1.0/uv.lock +1551 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
|
|
7
|
+
# Build artifacts
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
|
|
11
|
+
# Virtual envs
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
|
|
15
|
+
# Tooling caches
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
|
|
20
|
+
# Local Claude Code settings
|
|
21
|
+
.claude/settings.local.json
|
|
22
|
+
|
|
23
|
+
# MCP registry publisher binary
|
|
24
|
+
mcp-publisher.exe
|
|
25
|
+
mcp-publisher.tar.gz
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 matt
|
|
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,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spaceweather-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Space-weather and geomagnetic conditions (NOAA SWPC planetary Kp, solar wind, storm scales, alerts, 27-day outlook) as a Model Context Protocol server.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hoon1983/spaceweather-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/hoon1983/spaceweather-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/hoon1983/spaceweather-mcp/issues
|
|
8
|
+
Author: matt
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: aurora,geomagnetic,heliophysics,kp-index,mcp,model-context-protocol,noaa,solar-wind,space-weather,swpc
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: fastmcp>=3.0
|
|
25
|
+
Requires-Dist: httpx[http2]>=0.27
|
|
26
|
+
Requires-Dist: pydantic>=2.7
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# spaceweather-mcp
|
|
30
|
+
|
|
31
|
+
<!-- mcp-name: io.github.hoon1983/spaceweather-mcp -->
|
|
32
|
+
|
|
33
|
+
A Model Context Protocol server for **space weather and geomagnetic conditions** — solar flares, the solar wind, geomagnetic storms (Kp/Dst), radiation storms, sunspot regions, aurora forecasts, and a catalogued event history — so an AI agent can answer "what's the sun/space weather doing right now, and will I see aurora?" from authoritative data.
|
|
34
|
+
|
|
35
|
+
Sibling to [seismic-mcp](https://github.com/hoon1983/seismic-mcp): that one reconciles earthquakes across agencies; this one reports solar/geomagnetic conditions. They install side by side.
|
|
36
|
+
|
|
37
|
+
## Status
|
|
38
|
+
|
|
39
|
+
**15 tools across 4 data sources — all live and verified.** Everything is U.S./NASA public domain except the Kyoto Dst index, which is non-commercial and clearly labeled (and kept segregated from the public-domain feeds).
|
|
40
|
+
|
|
41
|
+
## Tools
|
|
42
|
+
|
|
43
|
+
**NOAA SWPC** (public domain)
|
|
44
|
+
| Tool | Returns |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `get_conditions_now` | One-call snapshot: Kp, Bz/Bt, wind speed/density, G/S/R, storm flag, summary |
|
|
47
|
+
| `get_kp_index` | Observed planetary Kp series (~1 week) |
|
|
48
|
+
| `get_kp_forecast` | NOAA 3-day Kp forecast |
|
|
49
|
+
| `get_solar_wind` | L1 magnetic field + plasma (`window`: `1-day`/`7-day`) |
|
|
50
|
+
| `get_alerts` | Recent SWPC alerts/watches/warnings |
|
|
51
|
+
| `get_noaa_scales` | G/S/R scales for today + 3 days |
|
|
52
|
+
| `get_27day_outlook` | Daily F10.7 flux, Ap, largest Kp for 27 days |
|
|
53
|
+
| `get_solar_flares` | Recent GOES X-ray flares (C/M/X class events) |
|
|
54
|
+
| `get_radiation_storm` | Current proton flux + NOAA S-scale (radiation storm) |
|
|
55
|
+
| `get_solar_regions` | Today's sunspot regions + C/M/X flare probabilities |
|
|
56
|
+
| `get_aurora_forecast` | OVATION aurora probability (pass lat/lon for a local %) |
|
|
57
|
+
|
|
58
|
+
**NASA DONKI** (public domain) — `get_space_weather_events` (CME / GST / FLR / SEP / IPS / HSS event catalog)
|
|
59
|
+
|
|
60
|
+
**USGS Geomagnetism** (public domain) — `list_observatories`, `get_observatory` (magnetometer X/Y/Z/F)
|
|
61
|
+
|
|
62
|
+
**Kyoto WDC** (non-commercial) — `get_dst` (hourly disturbance storm-time index)
|
|
63
|
+
|
|
64
|
+
## Run
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv run spaceweather-mcp # stdio MCP server
|
|
68
|
+
uv run pytest # tests
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Add to `claude_desktop_config.json` (after publishing to PyPI):
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{ "mcpServers": { "spaceweather": { "command": "uvx", "args": ["spaceweather-mcp"] } } }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Data sources, attribution & licensing
|
|
78
|
+
|
|
79
|
+
- **NOAA SWPC** (`services.swpc.noaa.gov`) — U.S. public domain. Credit NOAA SWPC.
|
|
80
|
+
- **NASA DONKI** via CCMC (`kauai.ccmc.gsfc.nasa.gov/DONKI`) — U.S. public domain (no API key needed). Credit NASA/CCMC DONKI.
|
|
81
|
+
- **USGS Geomagnetism** (`geomag.usgs.gov`) — U.S. public domain. Credit the U.S. Geological Survey. (Sourced directly from USGS, not via INTERMAGNET, to keep public-domain status.)
|
|
82
|
+
- **Kyoto WDC for Geomagnetism** — Dst index, **non-commercial use only**: acknowledge "WDC for Geomagnetism, Kyoto", cite the Dst DOI; real-time values are provisional. The non-commercial restriction is documented in the `get_dst` tool description and the `DstReading` schema (both surfaced to MCP clients), and this feed is kept separate from the public-domain ones.
|
|
83
|
+
|
|
84
|
+
> ⚠️ Only the Kyoto Dst feed is non-commercial. It is segregated, and its non-commercial terms are stated in the tool/schema docs the client sees, so a downstream commercial user is not silently bound by those terms.
|
|
85
|
+
|
|
86
|
+
**Deliberately not included:** INTERMAGNET and SILSO sunspot number (both CC BY-**NC** — would taint the otherwise commercially-usable server); JPL Horizons ephemeris (different domain). F10.7 is available via `get_27day_outlook`; GFZ Hp30/Hp60 high-cadence indices (CC BY 4.0) are a possible future add.
|
|
87
|
+
|
|
88
|
+
## Safety
|
|
89
|
+
|
|
90
|
+
Informational only. **Not** for operational decisions affecting power grids, aviation, satellites, or human safety — consult official NOAA SWPC products for those. Real-time values are preliminary and routinely revised.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# spaceweather-mcp
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.hoon1983/spaceweather-mcp -->
|
|
4
|
+
|
|
5
|
+
A Model Context Protocol server for **space weather and geomagnetic conditions** — solar flares, the solar wind, geomagnetic storms (Kp/Dst), radiation storms, sunspot regions, aurora forecasts, and a catalogued event history — so an AI agent can answer "what's the sun/space weather doing right now, and will I see aurora?" from authoritative data.
|
|
6
|
+
|
|
7
|
+
Sibling to [seismic-mcp](https://github.com/hoon1983/seismic-mcp): that one reconciles earthquakes across agencies; this one reports solar/geomagnetic conditions. They install side by side.
|
|
8
|
+
|
|
9
|
+
## Status
|
|
10
|
+
|
|
11
|
+
**15 tools across 4 data sources — all live and verified.** Everything is U.S./NASA public domain except the Kyoto Dst index, which is non-commercial and clearly labeled (and kept segregated from the public-domain feeds).
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
**NOAA SWPC** (public domain)
|
|
16
|
+
| Tool | Returns |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `get_conditions_now` | One-call snapshot: Kp, Bz/Bt, wind speed/density, G/S/R, storm flag, summary |
|
|
19
|
+
| `get_kp_index` | Observed planetary Kp series (~1 week) |
|
|
20
|
+
| `get_kp_forecast` | NOAA 3-day Kp forecast |
|
|
21
|
+
| `get_solar_wind` | L1 magnetic field + plasma (`window`: `1-day`/`7-day`) |
|
|
22
|
+
| `get_alerts` | Recent SWPC alerts/watches/warnings |
|
|
23
|
+
| `get_noaa_scales` | G/S/R scales for today + 3 days |
|
|
24
|
+
| `get_27day_outlook` | Daily F10.7 flux, Ap, largest Kp for 27 days |
|
|
25
|
+
| `get_solar_flares` | Recent GOES X-ray flares (C/M/X class events) |
|
|
26
|
+
| `get_radiation_storm` | Current proton flux + NOAA S-scale (radiation storm) |
|
|
27
|
+
| `get_solar_regions` | Today's sunspot regions + C/M/X flare probabilities |
|
|
28
|
+
| `get_aurora_forecast` | OVATION aurora probability (pass lat/lon for a local %) |
|
|
29
|
+
|
|
30
|
+
**NASA DONKI** (public domain) — `get_space_weather_events` (CME / GST / FLR / SEP / IPS / HSS event catalog)
|
|
31
|
+
|
|
32
|
+
**USGS Geomagnetism** (public domain) — `list_observatories`, `get_observatory` (magnetometer X/Y/Z/F)
|
|
33
|
+
|
|
34
|
+
**Kyoto WDC** (non-commercial) — `get_dst` (hourly disturbance storm-time index)
|
|
35
|
+
|
|
36
|
+
## Run
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv run spaceweather-mcp # stdio MCP server
|
|
40
|
+
uv run pytest # tests
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Add to `claude_desktop_config.json` (after publishing to PyPI):
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{ "mcpServers": { "spaceweather": { "command": "uvx", "args": ["spaceweather-mcp"] } } }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Data sources, attribution & licensing
|
|
50
|
+
|
|
51
|
+
- **NOAA SWPC** (`services.swpc.noaa.gov`) — U.S. public domain. Credit NOAA SWPC.
|
|
52
|
+
- **NASA DONKI** via CCMC (`kauai.ccmc.gsfc.nasa.gov/DONKI`) — U.S. public domain (no API key needed). Credit NASA/CCMC DONKI.
|
|
53
|
+
- **USGS Geomagnetism** (`geomag.usgs.gov`) — U.S. public domain. Credit the U.S. Geological Survey. (Sourced directly from USGS, not via INTERMAGNET, to keep public-domain status.)
|
|
54
|
+
- **Kyoto WDC for Geomagnetism** — Dst index, **non-commercial use only**: acknowledge "WDC for Geomagnetism, Kyoto", cite the Dst DOI; real-time values are provisional. The non-commercial restriction is documented in the `get_dst` tool description and the `DstReading` schema (both surfaced to MCP clients), and this feed is kept separate from the public-domain ones.
|
|
55
|
+
|
|
56
|
+
> ⚠️ Only the Kyoto Dst feed is non-commercial. It is segregated, and its non-commercial terms are stated in the tool/schema docs the client sees, so a downstream commercial user is not silently bound by those terms.
|
|
57
|
+
|
|
58
|
+
**Deliberately not included:** INTERMAGNET and SILSO sunspot number (both CC BY-**NC** — would taint the otherwise commercially-usable server); JPL Horizons ephemeris (different domain). F10.7 is available via `get_27day_outlook`; GFZ Hp30/Hp60 high-cadence indices (CC BY 4.0) are a possible future add.
|
|
59
|
+
|
|
60
|
+
## Safety
|
|
61
|
+
|
|
62
|
+
Informational only. **Not** for operational decisions affecting power grids, aviation, satellites, or human safety — consult official NOAA SWPC products for those. Real-time values are preliminary and routinely revised.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spaceweather-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Space-weather and geomagnetic conditions (NOAA SWPC planetary Kp, solar wind, storm scales, alerts, 27-day outlook) as a Model Context Protocol server."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
authors = [{ name = "matt" }]
|
|
8
|
+
license = { text = "MIT" }
|
|
9
|
+
keywords = [
|
|
10
|
+
"mcp",
|
|
11
|
+
"model-context-protocol",
|
|
12
|
+
"space-weather",
|
|
13
|
+
"geomagnetic",
|
|
14
|
+
"kp-index",
|
|
15
|
+
"solar-wind",
|
|
16
|
+
"aurora",
|
|
17
|
+
"noaa",
|
|
18
|
+
"swpc",
|
|
19
|
+
"heliophysics",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Environment :: Console",
|
|
24
|
+
"Intended Audience :: Science/Research",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Scientific/Engineering",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
32
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"fastmcp>=3.0",
|
|
36
|
+
"httpx[http2]>=0.27",
|
|
37
|
+
"pydantic>=2.7",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
spaceweather-mcp = "spaceweather_mcp.server:main"
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/hoon1983/spaceweather-mcp"
|
|
45
|
+
Repository = "https://github.com/hoon1983/spaceweather-mcp"
|
|
46
|
+
Issues = "https://github.com/hoon1983/spaceweather-mcp/issues"
|
|
47
|
+
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
dev = [
|
|
50
|
+
"pytest>=8",
|
|
51
|
+
"pytest-asyncio>=0.23",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["hatchling"]
|
|
56
|
+
build-backend = "hatchling.build"
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/spaceweather_mcp"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
asyncio_mode = "auto"
|
|
64
|
+
filterwarnings = ["ignore::DeprecationWarning"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.hoon1983/spaceweather-mcp",
|
|
4
|
+
"description": "Space weather and geomagnetic conditions (NOAA SWPC, NASA DONKI, USGS Geomag, Kyoto Dst) as a Model Context Protocol server.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/hoon1983/spaceweather-mcp",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "pypi",
|
|
13
|
+
"identifier": "spaceweather-mcp",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Tiny async TTL cache with in-flight de-duplication.
|
|
2
|
+
|
|
3
|
+
SWPC products update every 1-5 minutes; a short TTL turns a burst of tool
|
|
4
|
+
calls (and a UI poll loop) into a single upstream fetch. The per-key lock
|
|
5
|
+
collapses concurrent misses so we never fire the same request twice at once.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import time
|
|
12
|
+
from typing import Awaitable, Callable, TypeVar
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TTLCache:
|
|
18
|
+
def __init__(self, ttl_seconds: float = 60.0) -> None:
|
|
19
|
+
self.ttl = ttl_seconds
|
|
20
|
+
self._store: dict[str, tuple[float, object]] = {}
|
|
21
|
+
self._locks: dict[str, asyncio.Lock] = {}
|
|
22
|
+
|
|
23
|
+
async def get_or_fetch(self, key: str, fetch: Callable[[], Awaitable[T]]) -> T:
|
|
24
|
+
hit = self._store.get(key)
|
|
25
|
+
if hit is not None and (time.monotonic() - hit[0]) < self.ttl:
|
|
26
|
+
return hit[1] # type: ignore[return-value]
|
|
27
|
+
|
|
28
|
+
lock = self._locks.setdefault(key, asyncio.Lock())
|
|
29
|
+
async with lock:
|
|
30
|
+
# Re-check: another coroutine may have filled it while we waited.
|
|
31
|
+
hit = self._store.get(key)
|
|
32
|
+
if hit is not None and (time.monotonic() - hit[0]) < self.ttl:
|
|
33
|
+
return hit[1] # type: ignore[return-value]
|
|
34
|
+
value = await fetch()
|
|
35
|
+
self._store[key] = (time.monotonic(), value)
|
|
36
|
+
self._prune()
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
def _prune(self) -> None:
|
|
40
|
+
"""Drop expired entries (and their now-idle locks) so the cache stays
|
|
41
|
+
bounded even when callers key on ever-changing URLs (time windows)."""
|
|
42
|
+
now = time.monotonic()
|
|
43
|
+
for k in [k for k, (ts, _) in self._store.items() if (now - ts) >= self.ttl]:
|
|
44
|
+
self._store.pop(k, None)
|
|
45
|
+
lk = self._locks.get(k)
|
|
46
|
+
if lk is not None and not lk.locked():
|
|
47
|
+
self._locks.pop(k, None)
|
|
48
|
+
|
|
49
|
+
def clear(self) -> None:
|
|
50
|
+
self._store.clear()
|
|
51
|
+
self._locks.clear()
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Canonical schemas for space-weather data returned by the MCP tools.
|
|
2
|
+
|
|
3
|
+
All times are timezone-aware UTC. Missing numeric values are ``None`` (never
|
|
4
|
+
NaN) so the output is always valid JSON.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class KpReading(BaseModel):
|
|
16
|
+
"""One observed planetary K-index sample (3-hour cadence, NOAA SWPC)."""
|
|
17
|
+
|
|
18
|
+
time: datetime
|
|
19
|
+
kp: float = Field(..., description="Planetary K-index, 0-9. >=5 is geomagnetic storm level.")
|
|
20
|
+
a_running: Optional[float] = Field(None, description="Running estimate of the daily Ap index.")
|
|
21
|
+
station_count: Optional[int] = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class KpForecastPoint(BaseModel):
|
|
25
|
+
time: datetime
|
|
26
|
+
kp: float
|
|
27
|
+
observed: str = Field("predicted", description="'observed', 'estimated', or 'predicted'.")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SolarWindMag(BaseModel):
|
|
31
|
+
"""Interplanetary magnetic field, GSM coordinates (nT)."""
|
|
32
|
+
|
|
33
|
+
time: datetime
|
|
34
|
+
bx: Optional[float] = None
|
|
35
|
+
by: Optional[float] = None
|
|
36
|
+
bz: Optional[float] = Field(None, description="Southward (negative) Bz couples energy into the magnetosphere.")
|
|
37
|
+
bt: Optional[float] = Field(None, description="Total field magnitude.")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SolarWindPlasma(BaseModel):
|
|
41
|
+
time: datetime
|
|
42
|
+
density: Optional[float] = Field(None, description="Proton density, particles/cm^3.")
|
|
43
|
+
speed: Optional[float] = Field(None, description="Bulk solar wind speed, km/s.")
|
|
44
|
+
temperature: Optional[float] = Field(None, description="Kelvin.")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SolarWind(BaseModel):
|
|
48
|
+
window: str
|
|
49
|
+
mag: list[SolarWindMag]
|
|
50
|
+
plasma: list[SolarWindPlasma]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Alert(BaseModel):
|
|
54
|
+
"""A NOAA SWPC alert/watch/warning bulletin."""
|
|
55
|
+
|
|
56
|
+
product_id: str
|
|
57
|
+
issued: datetime
|
|
58
|
+
message: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ScaleValue(BaseModel):
|
|
62
|
+
scale: Optional[int] = Field(None, description="0-5 on the NOAA scale (0 = none).")
|
|
63
|
+
text: Optional[str] = Field(None, description="e.g. 'Minor', 'Moderate', 'Strong'.")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ScaleDay(BaseModel):
|
|
67
|
+
index: int = Field(..., description="0 = current/today, 1-3 = following days.")
|
|
68
|
+
date_stamp: Optional[str] = None
|
|
69
|
+
g: Optional[ScaleValue] = Field(None, description="G - geomagnetic storm.")
|
|
70
|
+
s: Optional[ScaleValue] = Field(None, description="S - solar radiation storm.")
|
|
71
|
+
r: Optional[ScaleValue] = Field(None, description="R - radio blackout.")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class NoaaScales(BaseModel):
|
|
75
|
+
days: list[ScaleDay]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class OutlookRow(BaseModel):
|
|
79
|
+
date: str
|
|
80
|
+
radio_flux_107: int = Field(..., description="F10.7 cm solar radio flux (sfu).")
|
|
81
|
+
planetary_a: int = Field(..., description="Predicted planetary Ap index.")
|
|
82
|
+
largest_kp: int = Field(..., description="Largest predicted Kp for the day.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Outlook(BaseModel):
|
|
86
|
+
issued: str
|
|
87
|
+
rows: list[OutlookRow]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ConditionsNow(BaseModel):
|
|
91
|
+
"""A single-call snapshot of current space-weather conditions."""
|
|
92
|
+
|
|
93
|
+
kp: Optional[float] = None
|
|
94
|
+
kp_time: Optional[datetime] = None
|
|
95
|
+
bz_nt: Optional[float] = None
|
|
96
|
+
bt_nt: Optional[float] = None
|
|
97
|
+
solar_wind_speed_kms: Optional[float] = None
|
|
98
|
+
density_per_cc: Optional[float] = None
|
|
99
|
+
scale_g: Optional[ScaleValue] = None
|
|
100
|
+
scale_s: Optional[ScaleValue] = None
|
|
101
|
+
scale_r: Optional[ScaleValue] = None
|
|
102
|
+
geomagnetic_storm: bool = Field(False, description="True when current Kp >= 5.")
|
|
103
|
+
summary: str = Field("", description="Human-readable one-line status.")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Observatory(BaseModel):
|
|
107
|
+
"""A magnetic observatory whose data is queryable via get_observatory."""
|
|
108
|
+
|
|
109
|
+
id: str = Field(..., description="3-letter IAGA code, e.g. 'BOU', 'HON'.")
|
|
110
|
+
name: str
|
|
111
|
+
latitude: float
|
|
112
|
+
longitude: float
|
|
113
|
+
network: str = "USGS"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ObservatorySeries(BaseModel):
|
|
117
|
+
"""Ground magnetometer time series for one observatory (USGS Geomagnetism)."""
|
|
118
|
+
|
|
119
|
+
station: str
|
|
120
|
+
name: Optional[str] = None
|
|
121
|
+
latitude: Optional[float] = None
|
|
122
|
+
longitude: Optional[float] = None
|
|
123
|
+
sampling_period_s: int
|
|
124
|
+
data_type: str = Field(..., description="variation | adjusted | quasi-definitive | definitive")
|
|
125
|
+
times: list[datetime]
|
|
126
|
+
values: dict[str, list[Optional[float]]] = Field(
|
|
127
|
+
default_factory=dict, description="Per-element series (nT), e.g. {'X': [...], 'Y': [...]}."
|
|
128
|
+
)
|
|
129
|
+
latest: dict[str, Optional[float]] = Field(
|
|
130
|
+
default_factory=dict, description="Most recent finite value per element (nT)."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class DstReading(BaseModel):
|
|
135
|
+
"""One hourly Dst (disturbance storm-time) index value, in nT (Kyoto WDC).
|
|
136
|
+
|
|
137
|
+
Dst measures the ring-current depression during geomagnetic storms: roughly
|
|
138
|
+
> -30 quiet, -30..-50 unsettled, -50..-100 moderate storm, -100..-250 intense,
|
|
139
|
+
< -250 great storm.
|
|
140
|
+
|
|
141
|
+
NON-COMMERCIAL data: cite "WDC for Geomagnetism, Kyoto" and the Dst DOI;
|
|
142
|
+
real-time values are provisional ("quicklook") and subject to revision.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
time: datetime
|
|
146
|
+
dst_nt: int
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class SolarFlare(BaseModel):
|
|
150
|
+
"""One GOES X-ray flare event (NOAA SWPC)."""
|
|
151
|
+
|
|
152
|
+
begin_time: Optional[datetime] = None
|
|
153
|
+
begin_class: Optional[str] = None
|
|
154
|
+
max_time: Optional[datetime] = None
|
|
155
|
+
max_class: Optional[str] = Field(None, description="Peak GOES class, e.g. 'C3.2', 'M2.1', 'X1.0'.")
|
|
156
|
+
max_flux: Optional[float] = Field(None, description="Peak long-channel flux, W/m^2.")
|
|
157
|
+
end_time: Optional[datetime] = None
|
|
158
|
+
satellite: Optional[int] = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class RadiationStatus(BaseModel):
|
|
162
|
+
"""Current solar radiation (energetic proton) environment, NOAA SWPC GOES."""
|
|
163
|
+
|
|
164
|
+
time: Optional[datetime] = None
|
|
165
|
+
flux_by_energy: dict[str, Optional[float]] = Field(
|
|
166
|
+
default_factory=dict, description="Latest integral proton flux (pfu) per energy threshold, e.g. {'>=10 MeV': 0.3}."
|
|
167
|
+
)
|
|
168
|
+
s_scale: int = Field(0, description="NOAA S (radiation storm) scale 0-5, from the >=10 MeV flux.")
|
|
169
|
+
s_text: str = ""
|
|
170
|
+
radiation_storm: bool = Field(False, description="True when S >= 1 (>=10 MeV flux >= 10 pfu).")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class SolarRegion(BaseModel):
|
|
174
|
+
"""An active sunspot region with flare probabilities (NOAA SWPC)."""
|
|
175
|
+
|
|
176
|
+
region: Optional[int] = None
|
|
177
|
+
observed_date: Optional[str] = None
|
|
178
|
+
location: Optional[str] = None
|
|
179
|
+
latitude: Optional[float] = None
|
|
180
|
+
longitude: Optional[float] = None
|
|
181
|
+
area: Optional[int] = None
|
|
182
|
+
number_spots: Optional[int] = None
|
|
183
|
+
spot_class: Optional[str] = None
|
|
184
|
+
mag_class: Optional[str] = None
|
|
185
|
+
c_flare_probability: Optional[int] = None
|
|
186
|
+
m_flare_probability: Optional[int] = None
|
|
187
|
+
x_flare_probability: Optional[int] = None
|
|
188
|
+
proton_probability: Optional[int] = None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class AuroraForecast(BaseModel):
|
|
192
|
+
"""OVATION short-term auroral-oval forecast (NOAA SWPC)."""
|
|
193
|
+
|
|
194
|
+
observation_time: Optional[datetime] = None
|
|
195
|
+
forecast_time: Optional[datetime] = None
|
|
196
|
+
latitude: Optional[float] = None
|
|
197
|
+
longitude: Optional[float] = None
|
|
198
|
+
aurora_probability: Optional[int] = Field(None, description="Auroral activity probability (%) at the requested point.")
|
|
199
|
+
global_max_probability: Optional[int] = Field(None, description="Peak probability anywhere on the oval.")
|
|
200
|
+
note: str = ""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class SpaceWeatherEvent(BaseModel):
|
|
204
|
+
"""A catalogued space-weather event from NASA DONKI (CME/GST/FLR/SEP/IPS/HSS)."""
|
|
205
|
+
|
|
206
|
+
event_type: str
|
|
207
|
+
event_id: str
|
|
208
|
+
time: Optional[datetime] = None
|
|
209
|
+
summary: str = Field("", description="Type-specific highlight, e.g. 'max Kp 6.33' or 'class M2.1'.")
|
|
210
|
+
link: Optional[str] = Field(None, description="DONKI detail page for the full record.")
|