mcnoaa-tides/src/noaa_tides/resources.py
Ryan Malloy 6c244b3a63 Initial implementation: FastMCP 3.0 server wrapping NOAA CO-OPS API
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.
2026-02-21 21:04:03 -07:00

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,
)