Skip to main content
Glama
server.py8.37 kB
"""MCP server for JMA data using FastMCP.""" from datetime import datetime from typing import Optional from fastmcp import FastMCP from .stations import ( get_all_stations, get_station, get_stations_by_type, search_stations_by_location, search_stations_by_name, ) from .weather import ( AREA_CODES, JST, fetch_amedas_data, fetch_forecast, fetch_historical_amedas_data, fetch_time_series_data, ) mcp = FastMCP("jma-data-mcp") # Station tools @mcp.tool() async def get_station_info(code: str) -> dict: """Get AMeDAS station information by station code. Args: code: Station code (e.g., '44132' for Tokyo) Returns: Station information including name, location, and type """ station = get_station(code) if station: return station return {"error": f"Station with code '{code}' not found."} @mcp.tool() async def search_stations(name: str) -> dict: """Search AMeDAS stations by name (Japanese, Kana, or English). Args: name: Station name to search Returns: List of matching stations """ stations = search_stations_by_name(name) return {"count": len(stations), "stations": stations} @mcp.tool() async def search_nearby_stations( lat: float, lon: float, radius_km: float = 50.0, ) -> dict: """Search AMeDAS stations within a radius from given coordinates. Args: lat: Latitude in decimal degrees lon: Longitude in decimal degrees radius_km: Search radius in kilometers (default: 50) Returns: List of nearby stations sorted by distance """ stations = search_stations_by_location(lat, lon, radius_km) return { "count": len(stations), "search_center": {"lat": lat, "lon": lon}, "radius_km": radius_km, "stations": stations, } @mcp.tool() async def get_stations_of_type( station_type: str, ) -> dict: """Get all AMeDAS stations of a specific type. Args: station_type: Station type - A (Staffed), B (Special Regional), C (AMeDAS), D (Rain Gauge), E (Snow Depth), F (Regional Rain) Returns: List of stations of the specified type """ if station_type not in ["A", "B", "C", "D", "E", "F"]: return {"error": f"Invalid station type: {station_type}. Must be A, B, C, D, E, or F."} stations = get_stations_by_type(station_type) return {"count": len(stations), "type": station_type, "stations": stations} @mcp.tool() async def list_stations( limit: int = 100, offset: int = 0, ) -> dict: """List all AMeDAS stations (1286 stations) with pagination. Args: limit: Maximum number of stations to return (default: 100) offset: Number of stations to skip (default: 0) Returns: Paginated list of stations """ all_stations = get_all_stations() paginated = all_stations[offset : offset + limit] return { "total": len(all_stations), "offset": offset, "limit": limit, "count": len(paginated), "stations": paginated, } # Weather tools @mcp.tool() async def get_current_weather( station_code: Optional[str] = None, ) -> dict: """Get current weather observation data from AMeDAS stations. Args: station_code: Station code (e.g., '44132' for Tokyo). If not specified, returns data for all stations. Returns: Weather data including temperature, humidity, pressure, wind, precipitation, etc. """ weather_data = await fetch_amedas_data(station_code) if station_code: station_info = get_station(station_code) if station_info and station_code in weather_data["stations"]: weather_data["station_info"] = station_info weather_data["weather"] = weather_data["stations"][station_code] del weather_data["stations"] return weather_data @mcp.tool() async def get_weather_by_location( lat: float, lon: float, ) -> dict: """Get current weather from the nearest AMeDAS station to given coordinates. Args: lat: Latitude in decimal degrees lon: Longitude in decimal degrees Returns: Weather data from the nearest station """ nearby = search_stations_by_location(lat, lon, radius_km=100) if not nearby: return {"error": "No stations found within 100km of the specified location"} nearest = nearby[0] station_code = nearest["code"] weather_data = await fetch_amedas_data(station_code) return { "observation_time": weather_data["observation_time"], "observation_time_jst": weather_data["observation_time_jst"], "station": nearest, "weather": weather_data["stations"].get(station_code, {}), } @mcp.tool() async def get_forecast(prefecture: str) -> dict: """Get weather forecast for a prefecture. Args: prefecture: Prefecture name in English (e.g., 'tokyo', 'osaka', 'hokkaido_sapporo') Returns: Weather forecast data """ area_code = AREA_CODES.get(prefecture) if not area_code: return { "error": f"Unknown prefecture: {prefecture}", "available": list(AREA_CODES.keys()), } forecast_data = await fetch_forecast(area_code) return { "prefecture": prefecture, "area_code": area_code, "forecast": forecast_data, } @mcp.tool() async def list_prefectures() -> dict: """List all available prefecture codes for weather forecast. Returns: Dictionary of prefecture names and their codes """ return {"prefectures": AREA_CODES} # Historical data tools @mcp.tool() async def get_historical_weather( station_code: str, target_datetime: str, ) -> dict: """Get historical weather data for a specific date and time. Data is available for approximately the past 1-2 weeks. Args: station_code: Station code (e.g., '44132' for Tokyo) target_datetime: Target datetime in ISO format (e.g., '2025-12-01T12:00:00') or 'YYYY-MM-DD HH:MM' format. Time is in JST. Returns: Weather data for the specified time """ try: if "T" in target_datetime: target_time = datetime.fromisoformat(target_datetime.replace("Z", "+00:00")) else: for fmt in ["%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M"]: try: target_time = datetime.strptime(target_datetime, fmt) break except ValueError: continue else: raise ValueError(f"Could not parse datetime: {target_datetime}") if target_time.tzinfo is None: target_time = target_time.replace(tzinfo=JST) except Exception as e: return { "error": f"Invalid datetime format: {e}", "hint": "Use ISO format (e.g., '2025-12-01T12:00:00') or 'YYYY-MM-DD HH:MM'", } historical_data = await fetch_historical_amedas_data(station_code, target_time) station_info = get_station(station_code) if station_info: historical_data["station_info"] = station_info return historical_data @mcp.tool() async def get_weather_time_series( station_code: str, hours: int = 24, interval_minutes: int = 60, ) -> dict: """Get time series weather data for a station. Useful for analyzing weather trends over hours or days. Args: station_code: Station code (e.g., '44132' for Tokyo) hours: Number of hours to fetch (default: 24, max: 168 for ~1 week) interval_minutes: Interval between data points in minutes (10, 30, or 60) Returns: Time series weather data """ if interval_minutes not in [10, 30, 60]: return {"error": f"Invalid interval: {interval_minutes}. Must be 10, 30, or 60."} time_series_data = await fetch_time_series_data( station_code, hours=hours, interval_minutes=interval_minutes, ) station_info = get_station(station_code) if station_info: time_series_data["station_info"] = station_info return time_series_data def main() -> None: """Run the MCP server.""" mcp.run() if __name__ == "__main__": main()

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/koizumikento/jma-data-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server