Add MCP prompts, README, LICENSE, and PyPI metadata
Five new prompts guide LLMs through multi-tool workflows: satellite_snapshot, climate_monitor, layer_deep_dive, multi_layer_story, polar_watch. README documents all 11 tools, 5 resources, and 7 prompts with example conversations. MIT license, project URLs, and updated classifiers for PyPI.
This commit is contained in:
parent
4a5035ca52
commit
110b85c829
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Ryan Malloy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
274
README.md
Normal file
274
README.md
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
# mcgibs
|
||||||
|
|
||||||
|
NASA Earth science visualizations for LLMs.
|
||||||
|
|
||||||
|
An [MCP](https://modelcontextprotocol.io/) server that connects language models to [NASA GIBS](https://www.earthdata.nasa.gov/engage/open-data-services-software/earthdata-developer-portal/gibs-api) (Global Imagery Browse Services) — 1000+ visualization layers covering satellite imagery, scientific data products, and derived Earth observations, updated daily.
|
||||||
|
|
||||||
|
**Three pillars:**
|
||||||
|
|
||||||
|
- **Discovery** — search layers by keyword, browse measurement categories, check date availability
|
||||||
|
- **Visualization** — fetch imagery and data products by place name and date, compare dates side-by-side, composite multiple layers
|
||||||
|
- **Interpretation** — natural-language colormap explanations, legend graphics, scientific context
|
||||||
|
|
||||||
|
No API key required. All data is freely available from NASA.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### From PyPI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uvx mcgibs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add to Claude Code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude mcp add mcgibs -- uvx mcgibs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.supported.systems/rpm/mcgibs.git
|
||||||
|
cd mcgibs
|
||||||
|
uv sync --all-extras
|
||||||
|
uv run mcgibs
|
||||||
|
```
|
||||||
|
|
||||||
|
Or add a local dev server to Claude Code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude mcp add mcgibs-local -- uv run --directory /path/to/mcgibs mcgibs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `search_gibs_layers` | Search 1000+ layers by keyword, measurement, period, or status |
|
||||||
|
| `get_layer_info` | Full metadata for a layer — instrument, platform, resolution, dates |
|
||||||
|
| `list_measurements` | All measurement categories with layer counts |
|
||||||
|
| `check_layer_dates` | Available date range for a layer (capabilities + live DescribeDomains) |
|
||||||
|
| `get_imagery` | Fetch a visualization by layer, date, and place name or bbox |
|
||||||
|
| `compare_dates` | Side-by-side comparison of two dates for change detection |
|
||||||
|
| `get_imagery_composite` | Overlay up to 5 layers into a single composite image |
|
||||||
|
| `explain_layer_colormap` | Natural-language explanation of what colors represent |
|
||||||
|
| `get_legend` | Pre-rendered legend graphic for a layer |
|
||||||
|
| `resolve_place` | Geocode a place name to coordinates and bounding box |
|
||||||
|
| `build_tile_url` | Construct a direct WMTS tile URL for embedding |
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
| URI | Description |
|
||||||
|
|-----|-------------|
|
||||||
|
| `gibs://catalog` | Full layer catalog grouped by measurement category |
|
||||||
|
| `gibs://layer/{layer_id}` | Individual layer metadata as JSON |
|
||||||
|
| `gibs://colormap/{layer_id}` | Colormap explanation for a layer |
|
||||||
|
| `gibs://dates/{layer_id}` | Available date range for a layer |
|
||||||
|
| `gibs://projections` | Supported GIBS projections and endpoints |
|
||||||
|
|
||||||
|
## Prompts
|
||||||
|
|
||||||
|
| Prompt | Parameters | Description |
|
||||||
|
|--------|------------|-------------|
|
||||||
|
| `earth_overview` | *(none)* | Introduction to GIBS with suggested explorations |
|
||||||
|
| `investigate_event` | `event_type`, `location`, `date` | Guided workflow for investigating natural events |
|
||||||
|
| `satellite_snapshot` | `place`, `date` | Quick satellite view of any location |
|
||||||
|
| `climate_monitor` | `indicator`, `location`, `start_date`, `end_date` | Track climate changes over time |
|
||||||
|
| `layer_deep_dive` | `layer_id`, `location`, `date` | Full scientific analysis of a single layer |
|
||||||
|
| `multi_layer_story` | `topic`, `location`, `date` | Data journalism — composite layers to tell a story |
|
||||||
|
| `polar_watch` | `pole`, `date`, `compare_date` | Arctic/Antarctic ice and snow monitoring |
|
||||||
|
|
||||||
|
## Conversational Examples
|
||||||
|
|
||||||
|
These examples show what interacting with mcgibs looks like from any MCP client. Tool calls and responses are abbreviated for readability.
|
||||||
|
|
||||||
|
### "What's happening in the Amazon right now?"
|
||||||
|
|
||||||
|
*Try this with the `satellite_snapshot` prompt: `place="Amazon Rainforest"`*
|
||||||
|
|
||||||
|
> **You:** Show me what the Amazon rainforest looks like from space today.
|
||||||
|
|
||||||
|
The assistant resolves the location, fetches true color imagery, and searches for relevant science layers:
|
||||||
|
|
||||||
|
```
|
||||||
|
→ resolve_place("Amazon Rainforest")
|
||||||
|
{"display_name": "Amazon Rainforest", "lat": -3.46, "lon": -62.21,
|
||||||
|
"bbox": {"west": -73.98, "south": -16.52, "east": -43.75, "north": 5.27}}
|
||||||
|
|
||||||
|
→ get_imagery("MODIS_Terra_CorrectedReflectance_TrueColor", "2025-06-01",
|
||||||
|
place="Amazon Rainforest")
|
||||||
|
[image: true color view of the Amazon basin]
|
||||||
|
|
||||||
|
→ search_gibs_layers("vegetation")
|
||||||
|
Found 12 layer(s) matching 'vegetation':
|
||||||
|
MODIS_Terra_NDVI_8Day
|
||||||
|
MODIS/Terra Vegetation Index (NDVI), 8-Day — from 2000-02-18 (8-Day)
|
||||||
|
[has colormap]
|
||||||
|
MODIS_Terra_EVI_8Day
|
||||||
|
MODIS/Terra Enhanced Vegetation Index (EVI), 8-Day — from 2000-02-18 (8-Day)
|
||||||
|
[has colormap]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Assistant:** Here's the Amazon from MODIS Terra today — you can see the river system threading through dense canopy, with some cloud cover over the western basin. I also found vegetation index layers. Want me to overlay NDVI to show vegetation density, or check for fire/deforestation activity?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Understanding what the colors mean
|
||||||
|
|
||||||
|
*Try this with the `layer_deep_dive` prompt: `layer_id="AIRS_L3_Surface_Air_Temperature_Daily_Day"`*
|
||||||
|
|
||||||
|
> **You:** What does the AIRS surface temperature layer actually show?
|
||||||
|
|
||||||
|
The assistant fetches metadata and the colormap interpretation:
|
||||||
|
|
||||||
|
```
|
||||||
|
→ get_layer_info("AIRS_L3_Surface_Air_Temperature_Daily_Day")
|
||||||
|
{"identifier": "AIRS_L3_Surface_Air_Temperature_Daily_Day",
|
||||||
|
"title": "Surface Air Temperature (Day, Daily)",
|
||||||
|
"instrument": "AIRS", "platform": "Aqua",
|
||||||
|
"period": "Daily", "ongoing": true,
|
||||||
|
"time": {"start": "2002-08-30", "end": null}}
|
||||||
|
|
||||||
|
→ explain_layer_colormap("AIRS_L3_Surface_Air_Temperature_Daily_Day")
|
||||||
|
Surface Air Temperature (K):
|
||||||
|
- dark blue = ~213 K (-60 C) -- extreme cold
|
||||||
|
- cyan = ~243 K (-30 C) -- severe cold
|
||||||
|
- green = ~268 K (-5 C) -- near freezing
|
||||||
|
- yellow = ~293 K (20 C) -- mild
|
||||||
|
- dark red = ~323 K (50 C) -- extreme heat
|
||||||
|
Color gradient runs from dark blues (low values) through greens to dark reds (high values).
|
||||||
|
(254 color stops total, 5 key values shown)
|
||||||
|
Transparent / no-data: No Data
|
||||||
|
|
||||||
|
→ get_legend("AIRS_L3_Surface_Air_Temperature_Daily_Day")
|
||||||
|
[image: horizontal color bar from blue to red, labeled 213K to 323K]
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Assistant:** The AIRS instrument on NASA's Aqua satellite measures surface air temperature globally every day, and has been doing so since August 2002. The colormap runs from dark blue (around -60°C, polar regions) through greens (near freezing) to dark red (above 50°C, desert extremes). The 254 color stops give smooth gradation — areas with no valid retrieval show as transparent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tracking change over time
|
||||||
|
|
||||||
|
*Try this with the `climate_monitor` prompt: `indicator="sea ice", location="Arctic", start_date="2025-03-01", end_date="2025-09-01"`*
|
||||||
|
|
||||||
|
> **You:** How has Arctic sea ice changed between winter and summer this year?
|
||||||
|
|
||||||
|
The assistant uses the `climate_monitor` workflow — search, verify dates, compare:
|
||||||
|
|
||||||
|
```
|
||||||
|
→ search_gibs_layers("sea ice concentration")
|
||||||
|
Found 6 layer(s) matching 'sea ice concentration':
|
||||||
|
AMSR2_Sea_Ice_Concentration_12km
|
||||||
|
AMSR2 Sea Ice Concentration, 12 km — from 2012-07-02 (Daily)
|
||||||
|
[has colormap]
|
||||||
|
...
|
||||||
|
|
||||||
|
→ explain_layer_colormap("AMSR2_Sea_Ice_Concentration_12km")
|
||||||
|
Sea Ice Concentration (%):
|
||||||
|
- dark blue = ~15%
|
||||||
|
- cyan = ~40%
|
||||||
|
- light grey = ~70%
|
||||||
|
- white = ~100%
|
||||||
|
Color gradient runs from dark blues (low values) through cyans to whites (high values).
|
||||||
|
Transparent / no-data: No Data, Land
|
||||||
|
|
||||||
|
→ compare_dates("AMSR2_Sea_Ice_Concentration_12km",
|
||||||
|
"2025-03-01", "2025-09-01",
|
||||||
|
bbox=[-180, 60, 180, 90])
|
||||||
|
[image: side-by-side comparison — March (left) vs September (right)]
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Assistant:** The side-by-side view is striking. In March, the Arctic Ocean is almost entirely white (90-100% ice concentration), with the ice edge extending well into the Bering Sea and Barents Sea. By September, the ice has retreated dramatically — the white core is confined to a smaller region around the pole, with large stretches of dark blue (15-40%) and open water visible along the Northeast Passage and in Hudson Bay. The colormap tells us white = solid pack ice and dark blue = scattered ice at 15% concentration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Compositing layers to tell a story
|
||||||
|
|
||||||
|
*Try this with the `investigate_event` prompt: `event_type="wildfire", location="Los Angeles", date="2025-01-08"`*
|
||||||
|
|
||||||
|
> **You:** Can you show me how wildfire smoke from the 2025 LA fires affected air quality?
|
||||||
|
|
||||||
|
The assistant searches for complementary layers, checks dates, and builds a composite:
|
||||||
|
|
||||||
|
```
|
||||||
|
→ search_gibs_layers("aerosol")
|
||||||
|
Found 8 layer(s) matching 'aerosol':
|
||||||
|
MODIS_Terra_Aerosol_Optical_Depth
|
||||||
|
MODIS/Terra Aerosol Optical Depth — from 2000-02-24 (Daily)
|
||||||
|
[has colormap]
|
||||||
|
...
|
||||||
|
|
||||||
|
→ search_gibs_layers("fire thermal")
|
||||||
|
Found 4 layer(s) matching 'fire thermal':
|
||||||
|
MODIS_Fires_Terra
|
||||||
|
MODIS/Terra Thermal Anomalies and Fire — from 2000-02-24 (Daily)
|
||||||
|
[has colormap]
|
||||||
|
...
|
||||||
|
|
||||||
|
→ get_imagery_composite(
|
||||||
|
["MODIS_Terra_CorrectedReflectance_TrueColor",
|
||||||
|
"MODIS_Terra_Aerosol_Optical_Depth"],
|
||||||
|
"2025-01-08", place="Los Angeles")
|
||||||
|
[image: true color with aerosol optical depth overlay]
|
||||||
|
|
||||||
|
→ explain_layer_colormap("MODIS_Terra_Aerosol_Optical_Depth")
|
||||||
|
Aerosol Optical Depth:
|
||||||
|
- yellow-green = ~0.15 (clean air)
|
||||||
|
- orange = ~0.55 (moderate haze)
|
||||||
|
- dark red = ~1.0 (thick smoke/dust)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Assistant:** The composite overlay tells the story clearly. Over the LA basin, the true color base shows a visible smoke plume extending southwest over the Pacific. The aerosol optical depth overlay confirms it — the orange-to-red values (0.5-1.0) directly over the fire zone indicate thick particulate loading, fading to yellow-green (0.15, clean air) further out over the ocean. The fire thermal anomaly layer pinpoints the active burn areas as bright red hotspots in the foothills.
|
||||||
|
|
||||||
|
## Projections
|
||||||
|
|
||||||
|
| EPSG | Description | Use case |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| 4326 | Geographic (WGS84) | Default — global coverage, most layers |
|
||||||
|
| 3857 | Web Mercator | Web map tiles, Leaflet/Mapbox integration |
|
||||||
|
| 3413 | Arctic Polar Stereographic | Arctic-focused imagery |
|
||||||
|
| 3031 | Antarctic Polar Stereographic | Antarctic-focused imagery |
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync --all-extras
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
uv run ruff check src/ tests/
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
uv run pytest
|
||||||
|
|
||||||
|
# Build
|
||||||
|
uv build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
src/mcgibs/
|
||||||
|
server.py MCP server — tools, resources, prompts, middleware
|
||||||
|
client.py GIBS HTTP client — WMS, WMTS, colormaps, geocoding
|
||||||
|
capabilities.py WMTS GetCapabilities parser and layer search
|
||||||
|
colormaps.py Colormap XML parser and natural-language interpreter
|
||||||
|
models.py Pydantic models — Layer, BBox, GeoResult, ColormapEntry
|
||||||
|
constants.py API endpoints, projections, tile matrix definitions
|
||||||
|
geo.py Bounding box math and geocoding helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [NASA GIBS](https://www.earthdata.nasa.gov/engage/open-data-services-software/earthdata-developer-portal/gibs-api)
|
||||||
|
- [GIBS API Documentation](https://nasa-gibs.github.io/gibs-api-docs/)
|
||||||
|
- [Worldview](https://worldview.earthdata.nasa.gov/) — NASA's browser-based GIBS viewer
|
||||||
|
- [FastMCP](https://gofastmcp.com/) — the MCP framework powering this server
|
||||||
|
- [Source](https://git.supported.systems/rpm/mcgibs)
|
||||||
@ -1,15 +1,17 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcgibs"
|
name = "mcgibs"
|
||||||
version = "2026.02.18"
|
version = "2026.02.18.2"
|
||||||
description = "FastMCP server for NASA Global Imagery Browse Services (GIBS)"
|
description = "FastMCP server for NASA Global Imagery Browse Services (GIBS)"
|
||||||
readme = "CLAUDE.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
license = {text = "MIT"}
|
||||||
authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}]
|
authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}]
|
||||||
keywords = ["nasa", "gibs", "mcp", "satellite", "imagery", "earth-science"]
|
keywords = ["nasa", "gibs", "mcp", "satellite", "imagery", "earth-science"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Intended Audience :: Science/Research",
|
"Intended Audience :: Science/Research",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
"Programming Language :: Python :: 3.14",
|
"Programming Language :: Python :: 3.14",
|
||||||
@ -21,6 +23,11 @@ dependencies = [
|
|||||||
"defusedxml>=0.7.1",
|
"defusedxml>=0.7.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://git.supported.systems/rpm/mcgibs"
|
||||||
|
Documentation = "https://nasa-gibs.github.io/gibs-api-docs/"
|
||||||
|
"Bug Tracker" = "https://git.supported.systems/rpm/mcgibs/issues"
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
mcgibs = "mcgibs.server:main"
|
mcgibs = "mcgibs.server:main"
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
__version__ = "2026.02.18"
|
__version__ = "2026.02.18.2"
|
||||||
|
|||||||
@ -746,6 +746,251 @@ def earth_overview() -> str:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.prompt
|
||||||
|
def satellite_snapshot(
|
||||||
|
place: str,
|
||||||
|
date: str = "today",
|
||||||
|
) -> str:
|
||||||
|
"""Quick satellite view of any place on Earth.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
place: Location to view (e.g. "Tokyo", "Amazon Rainforest", "Sahara Desert").
|
||||||
|
date: Date to view (YYYY-MM-DD), or "today" for the most recent imagery.
|
||||||
|
"""
|
||||||
|
lines = [
|
||||||
|
f"Show me a satellite view of {place} on {date}.",
|
||||||
|
"",
|
||||||
|
"Follow this workflow:",
|
||||||
|
"",
|
||||||
|
f'1. **Geocode**: Use resolve_place to get coordinates for "{place}".',
|
||||||
|
"",
|
||||||
|
f"2. **True color first**: Fetch imagery for "
|
||||||
|
f'"MODIS_Terra_CorrectedReflectance_TrueColor" '
|
||||||
|
f"on {date} at that location. This gives a natural-looking view.",
|
||||||
|
"",
|
||||||
|
f"3. **Find science layers**: Search for layers relevant to "
|
||||||
|
f'"{place}" — consider what would be scientifically interesting '
|
||||||
|
f"for this region (vegetation for forests, sea surface temperature "
|
||||||
|
f"for coastal areas, snow cover for mountains, etc.).",
|
||||||
|
"",
|
||||||
|
"4. **Check dates**: Verify the science layers have data for "
|
||||||
|
f"{date} using check_layer_dates.",
|
||||||
|
"",
|
||||||
|
"5. **Overlay**: If a relevant science layer is available, "
|
||||||
|
"use get_imagery_composite to overlay it on the true color base.",
|
||||||
|
"",
|
||||||
|
"6. **Interpret**: If the overlaid layer has a colormap, "
|
||||||
|
"use explain_layer_colormap to describe what the colors mean.",
|
||||||
|
"",
|
||||||
|
f"7. **Describe**: Summarize what's visible in the satellite view "
|
||||||
|
f"of {place} — notable features, weather patterns, land use, "
|
||||||
|
f"or any phenomena visible from orbit.",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.prompt
|
||||||
|
def climate_monitor(
|
||||||
|
indicator: str,
|
||||||
|
location: str,
|
||||||
|
start_date: str,
|
||||||
|
end_date: str,
|
||||||
|
) -> str:
|
||||||
|
"""Track a climate indicator over time at a specific location.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
indicator: What to monitor (e.g. "sea ice", "vegetation", "temperature", "snow cover").
|
||||||
|
location: Region to monitor (e.g. "Arctic", "Amazon Basin", "Greenland").
|
||||||
|
start_date: Beginning of monitoring period (YYYY-MM-DD).
|
||||||
|
end_date: End of monitoring period (YYYY-MM-DD).
|
||||||
|
"""
|
||||||
|
lines = [
|
||||||
|
f"Monitor {indicator} changes near {location} "
|
||||||
|
f"from {start_date} to {end_date}.",
|
||||||
|
"",
|
||||||
|
"Follow this workflow:",
|
||||||
|
"",
|
||||||
|
f'1. **Find layers**: Search for GIBS layers related to "{indicator}". '
|
||||||
|
f"Look for layers with colormaps — these encode quantitative data.",
|
||||||
|
"",
|
||||||
|
"2. **Check availability**: Use check_layer_dates on the best "
|
||||||
|
f"matching layer to confirm data exists for both {start_date} "
|
||||||
|
f"and {end_date}.",
|
||||||
|
"",
|
||||||
|
"3. **Understand the scale**: Use explain_layer_colormap to learn "
|
||||||
|
"what the color values represent (units, range). "
|
||||||
|
"Also fetch the legend with get_legend for visual reference.",
|
||||||
|
"",
|
||||||
|
f"4. **Compare endpoints**: Use compare_dates to create a "
|
||||||
|
f"side-by-side view of {start_date} vs {end_date} "
|
||||||
|
f'for "{location}". This immediately shows the magnitude of change.',
|
||||||
|
"",
|
||||||
|
"5. **Intermediate snapshots**: Fetch 1-2 additional dates between "
|
||||||
|
"start and end using get_imagery to show the progression "
|
||||||
|
"(e.g. mid-point of the range).",
|
||||||
|
"",
|
||||||
|
"6. **Synthesize**: Describe the observed trend, referencing "
|
||||||
|
"specific colormap values where possible. Is the indicator "
|
||||||
|
"increasing, decreasing, or showing seasonal variation? "
|
||||||
|
"What does the spatial pattern reveal?",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.prompt
|
||||||
|
def layer_deep_dive(
|
||||||
|
layer_id: str,
|
||||||
|
location: str = "global",
|
||||||
|
date: str = "",
|
||||||
|
) -> str:
|
||||||
|
"""Full scientific analysis of a single GIBS layer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
layer_id: The GIBS layer identifier (e.g. "AIRS_L3_Surface_Air_Temperature_Daily_Day").
|
||||||
|
location: Region to focus on, or "global" for worldwide view.
|
||||||
|
date: Specific date (YYYY-MM-DD), or empty for the most recent available.
|
||||||
|
"""
|
||||||
|
location_desc = f'for "{location}"' if location != "global" else "at global scale"
|
||||||
|
date_desc = f"on {date}" if date else "using the most recent available date"
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"Do a deep scientific analysis of the {layer_id} layer "
|
||||||
|
f"{location_desc} {date_desc}.",
|
||||||
|
"",
|
||||||
|
"Follow this workflow:",
|
||||||
|
"",
|
||||||
|
"1. **Layer metadata**: Use get_layer_info to retrieve full metadata "
|
||||||
|
f'for "{layer_id}" — instrument, platform, resolution, date range, '
|
||||||
|
"and scientific description.",
|
||||||
|
"",
|
||||||
|
"2. **Color interpretation**: Use explain_layer_colormap to get a "
|
||||||
|
"detailed description of what each color represents, including "
|
||||||
|
"physical units and value ranges. Fetch the legend with get_legend.",
|
||||||
|
"",
|
||||||
|
f"3. **Sample imagery**: Use get_imagery to fetch the layer "
|
||||||
|
f"{location_desc} {date_desc}. If no date was specified, "
|
||||||
|
"use check_layer_dates to find the latest available date first.",
|
||||||
|
"",
|
||||||
|
"4. **Image interpretation**: Walk through what's visible in the "
|
||||||
|
"image, referencing specific colormap values. Identify notable "
|
||||||
|
"spatial patterns, anomalies, or features.",
|
||||||
|
"",
|
||||||
|
"5. **Scientific context**: Explain what this layer measures, "
|
||||||
|
"why it matters, what instrument collects the data, and how "
|
||||||
|
"scientists use it. Connect the visualization to the underlying "
|
||||||
|
"Earth science.",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.prompt
|
||||||
|
def multi_layer_story(
|
||||||
|
topic: str,
|
||||||
|
location: str,
|
||||||
|
date: str,
|
||||||
|
) -> str:
|
||||||
|
"""Compose a data-driven narrative by combining multiple GIBS layers.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
topic: Story theme (e.g. "wildfire smoke", "ocean productivity", "dust storm").
|
||||||
|
location: Region of interest.
|
||||||
|
date: Date for the story (YYYY-MM-DD).
|
||||||
|
"""
|
||||||
|
lines = [
|
||||||
|
f'Tell the story of "{topic}" near {location} on {date} '
|
||||||
|
f"by compositing multiple satellite layers.",
|
||||||
|
"",
|
||||||
|
"Follow this workflow:",
|
||||||
|
"",
|
||||||
|
f'1. **Find related layers**: Search for 2-4 layers related to "{topic}". '
|
||||||
|
f"Choose layers that complement each other — for example, "
|
||||||
|
f"fire detections + aerosol optical depth + true color for wildfires, "
|
||||||
|
f"or SST + chlorophyll + true color for ocean events.",
|
||||||
|
"",
|
||||||
|
"2. **Verify dates**: Use check_layer_dates on each candidate "
|
||||||
|
f"to confirm data is available for {date}.",
|
||||||
|
"",
|
||||||
|
"3. **Understand each layer**: For each layer with a colormap, "
|
||||||
|
"use explain_layer_colormap to understand the value encoding.",
|
||||||
|
"",
|
||||||
|
"4. **Composite view**: Use get_imagery_composite to overlay "
|
||||||
|
"the layers into a single image. Put the base layer "
|
||||||
|
"(true color or reference) first in the list.",
|
||||||
|
"",
|
||||||
|
"5. **Individual layers**: Also fetch get_imagery for each layer "
|
||||||
|
"separately so you can describe what each one contributes.",
|
||||||
|
"",
|
||||||
|
"6. **Narrative**: Write a data-journalism style narrative "
|
||||||
|
"describing how the layers interact and what story they tell "
|
||||||
|
"together. Reference specific colormap values and spatial "
|
||||||
|
"patterns. Explain cause and effect where the data supports it.",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.prompt
|
||||||
|
def polar_watch(
|
||||||
|
pole: str,
|
||||||
|
date: str,
|
||||||
|
compare_date: str = "",
|
||||||
|
) -> str:
|
||||||
|
"""Monitor Arctic or Antarctic ice and snow conditions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pole: Which pole — "arctic" or "antarctic".
|
||||||
|
date: Primary observation date (YYYY-MM-DD).
|
||||||
|
compare_date: Optional second date for before/after comparison (YYYY-MM-DD).
|
||||||
|
"""
|
||||||
|
if pole.lower().startswith("ant"):
|
||||||
|
region = "Antarctic"
|
||||||
|
bbox = "[-180, -90, 180, -60]"
|
||||||
|
else:
|
||||||
|
region = "Arctic"
|
||||||
|
bbox = "[-180, 60, 180, 90]"
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"Monitor {region} ice and snow conditions on {date}.",
|
||||||
|
"",
|
||||||
|
"Follow this workflow:",
|
||||||
|
"",
|
||||||
|
f'1. **Find polar layers**: Search for "sea ice", "snow", '
|
||||||
|
f'and "ice sheet" layers. Focus on layers that cover the '
|
||||||
|
f"{region} region.",
|
||||||
|
"",
|
||||||
|
f"2. **Check availability**: Use check_layer_dates to verify "
|
||||||
|
f"data exists for {date} on the selected layers.",
|
||||||
|
"",
|
||||||
|
f"3. **Polar imagery**: Fetch imagery using the {region} "
|
||||||
|
f"bounding box {bbox}. Get true color first, then ice/snow "
|
||||||
|
f"concentration layers.",
|
||||||
|
"",
|
||||||
|
"4. **Color interpretation**: Use explain_layer_colormap on each "
|
||||||
|
"science layer and fetch legends with get_legend to understand "
|
||||||
|
"concentration values and classifications.",
|
||||||
|
"",
|
||||||
|
"5. **Embeddable tiles**: Use build_tile_url to generate a "
|
||||||
|
"WMTS URL for the primary ice layer — useful for embedding "
|
||||||
|
"in reports or dashboards.",
|
||||||
|
]
|
||||||
|
|
||||||
|
if compare_date:
|
||||||
|
lines.extend([
|
||||||
|
"",
|
||||||
|
f"6. **Temporal comparison**: Use compare_dates to show "
|
||||||
|
f"{compare_date} vs {date} side-by-side. Describe the "
|
||||||
|
f"change in ice extent and concentration between these dates.",
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
lines.extend([
|
||||||
|
"",
|
||||||
|
f"6. **Summarize**: Describe the current {region} ice/snow "
|
||||||
|
f"state visible in the imagery, referencing colormap values "
|
||||||
|
f"for concentration percentages.",
|
||||||
|
])
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
# Entry point
|
# Entry point
|
||||||
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|||||||
@ -277,6 +277,65 @@ async def test_list_resources(capabilities_xml):
|
|||||||
server_module._client = None
|
server_module._client = None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Prompt tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_list_prompts(capabilities_xml):
|
||||||
|
"""All expected prompts are registered on the MCP server."""
|
||||||
|
respx.get(url__regex=r".*WMTSCapabilities\.xml").mock(
|
||||||
|
return_value=httpx.Response(200, text=capabilities_xml)
|
||||||
|
)
|
||||||
|
|
||||||
|
server_module._client = await _init_mock_client(capabilities_xml)
|
||||||
|
try:
|
||||||
|
async with Client(mcp) as client:
|
||||||
|
prompts = await client.list_prompts()
|
||||||
|
prompt_names = {p.name for p in prompts}
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
"investigate_event",
|
||||||
|
"earth_overview",
|
||||||
|
"satellite_snapshot",
|
||||||
|
"climate_monitor",
|
||||||
|
"layer_deep_dive",
|
||||||
|
"multi_layer_story",
|
||||||
|
"polar_watch",
|
||||||
|
}
|
||||||
|
|
||||||
|
for name in expected:
|
||||||
|
assert name in prompt_names, f"Missing prompt: {name}"
|
||||||
|
finally:
|
||||||
|
await server_module._client.close()
|
||||||
|
server_module._client = None
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_satellite_snapshot_prompt(capabilities_xml):
|
||||||
|
"""satellite_snapshot prompt includes the place name and expected tool references."""
|
||||||
|
respx.get(url__regex=r".*WMTSCapabilities\.xml").mock(
|
||||||
|
return_value=httpx.Response(200, text=capabilities_xml)
|
||||||
|
)
|
||||||
|
|
||||||
|
server_module._client = await _init_mock_client(capabilities_xml)
|
||||||
|
try:
|
||||||
|
async with Client(mcp) as client:
|
||||||
|
result = await client.get_prompt(
|
||||||
|
"satellite_snapshot", {"place": "Tokyo", "date": "2025-06-01"}
|
||||||
|
)
|
||||||
|
text = result.messages[0].content.text
|
||||||
|
|
||||||
|
assert "Tokyo" in text
|
||||||
|
assert "resolve_place" in text
|
||||||
|
assert "get_imagery" in text or "get_imagery_composite" in text
|
||||||
|
assert "MODIS_Terra_CorrectedReflectance_TrueColor" in text
|
||||||
|
finally:
|
||||||
|
await server_module._client.close()
|
||||||
|
server_module._client = None
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_colormap_resource(capabilities_xml, colormap_xml):
|
async def test_colormap_resource(capabilities_xml, colormap_xml):
|
||||||
"""gibs://colormap/{layer_id} returns colormap explanation text."""
|
"""gibs://colormap/{layer_id} returns colormap explanation text."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user