7 tools: station search/nearest/info, tide predictions/observations, meteorological data (Literal selector for 8 products), and parallel marine conditions snapshot. 3 resources, 2 prompts, full test suite.
44 lines
1.8 KiB
Python
44 lines
1.8 KiB
Python
"""MCP resources: station catalog, station detail, nearby stations."""
|
|
|
|
import json
|
|
|
|
from fastmcp import Context, FastMCP
|
|
|
|
from noaa_tides.client import NOAAClient
|
|
|
|
|
|
def register(mcp: FastMCP) -> None:
|
|
@mcp.resource("noaa://stations")
|
|
async def station_catalog(ctx: Context) -> str:
|
|
"""Full NOAA tide station catalog. ~301 stations with id, name, state, coordinates."""
|
|
noaa: NOAAClient = ctx.lifespan_context["noaa_client"]
|
|
stations = await noaa.get_stations()
|
|
return json.dumps(
|
|
[s.model_dump() for s in stations],
|
|
indent=2,
|
|
)
|
|
|
|
@mcp.resource("noaa://stations/{station_id}")
|
|
async def station_detail(station_id: str, ctx: Context) -> str:
|
|
"""Expanded metadata for a single station including sensors, datums, and products."""
|
|
noaa: NOAAClient = ctx.lifespan_context["noaa_client"]
|
|
metadata = await noaa.get_station_metadata(station_id)
|
|
return json.dumps(metadata, indent=2)
|
|
|
|
@mcp.resource("noaa://stations/{station_id}/nearby")
|
|
async def nearby_stations(station_id: str, ctx: Context) -> str:
|
|
"""Stations within 50 nm of the given station, sorted by distance."""
|
|
noaa: NOAAClient = ctx.lifespan_context["noaa_client"]
|
|
stations = await noaa.get_stations()
|
|
target = next((s for s in stations if s.id == station_id), None)
|
|
if not target:
|
|
return json.dumps({"error": f"Station {station_id} not found"})
|
|
|
|
nearby = noaa.find_nearest(target.lat, target.lng, limit=10, max_distance=50)
|
|
# Exclude the station itself
|
|
nearby = [(s, d) for s, d in nearby if s.id != station_id]
|
|
return json.dumps(
|
|
[{**s.model_dump(), "distance_nm": round(d, 1)} for s, d in nearby],
|
|
indent=2,
|
|
)
|