mcp-tenki
by acxelerator
- mcp-tenki
- mcp_tenki
from pydantic import BaseModel, Field
import httpx
from typing import Any
from mcp.server.fastmcp import FastMCP
USER_AGENT = "cmp-tenki-app/1.0"
# Initialize FastMCP server
mcp = FastMCP("weather")
location_data = [
{"pref": "Hokkaido", "city": "稚内", "id": "011000"},
{"pref": "Hokkaido", "city": "旭川", "id": "012010"},
{"pref": "Hokkaido", "city": "留萌", "id": "012020"},
{"pref": "Hokkaido", "city": "網走", "id": "013010"},
{"pref": "Hokkaido", "city": "北見", "id": "013020"},
{"pref": "Hokkaido", "city": "紋別", "id": "013030"},
{"pref": "Hokkaido", "city": "根室", "id": "014010"},
{"pref": "Hokkaido", "city": "釧路", "id": "014020"},
{"pref": "Hokkaido", "city": "帯広", "id": "014030"},
{"pref": "Hokkaido", "city": "室蘭", "id": "015010"},
{"pref": "Hokkaido", "city": "浦河", "id": "015020"},
{"pref": "Hokkaido", "city": "札幌", "id": "016010"},
{"pref": "Hokkaido", "city": "岩見沢", "id": "016020"},
{"pref": "Hokkaido", "city": "倶知安", "id": "016030"},
{"pref": "Hokkaido", "city": "函館", "id": "017010"},
{"pref": "Hokkaido", "city": "江差", "id": "017020"},
{"pref": "Aomori", "city": "青森", "id": "020010"},
{"pref": "Aomori", "city": "むつ", "id": "020020"},
{"pref": "Aomori", "city": "八戸", "id": "020030"},
{"pref": "Iwate", "city": "盛岡", "id": "030010"},
{"pref": "Iwate", "city": "宮古", "id": "030020"},
{"pref": "Iwate", "city": "大船渡", "id": "030030"},
{"pref": "Miyagi", "city": "仙台", "id": "040010"},
{"pref": "Miyagi", "city": "白石", "id": "040020"},
{"pref": "Akita", "city": "秋田", "id": "050010"},
{"pref": "Akita", "city": "横手", "id": "050020"},
{"pref": "Yamagata", "city": "山形", "id": "060010"},
{"pref": "Yamagata", "city": "米沢", "id": "060020"},
{"pref": "Yamagata", "city": "酒田", "id": "060030"},
{"pref": "Yamagata", "city": "新庄", "id": "060040"},
{"pref": "Fukushima", "city": "福島", "id": "070010"},
{"pref": "Fukushima", "city": "小名浜", "id": "070020"},
{"pref": "Fukushima", "city": "若松", "id": "070030"},
{"pref": "Ibaraki", "city": "水戸", "id": "080010"},
{"pref": "Ibaraki", "city": "土浦", "id": "080020"},
{"pref": "Tochigi", "city": "宇都宮", "id": "090010"},
{"pref": "Tochigi", "city": "大田原", "id": "090020"},
{"pref": "Gunma", "city": "前橋", "id": "100010"},
{"pref": "Gunma", "city": "みなかみ", "id": "100020"},
{"pref": "Saitama", "city": "さいたま", "id": "110010"},
{"pref": "Saitama", "city": "熊谷", "id": "110020"},
{"pref": "Saitama", "city": "秩父", "id": "110030"},
{"pref": "Chiba", "city": "千葉", "id": "120010"},
{"pref": "Chiba", "city": "銚子", "id": "120020"},
{"pref": "Chiba", "city": "館山", "id": "120030"},
{"pref": "Tokyo", "city": "東京", "id": "130010"},
{"pref": "Tokyo", "city": "大島", "id": "130020"},
{"pref": "Tokyo", "city": "八丈島", "id": "130030"},
{"pref": "Tokyo", "city": "父島", "id": "130040"},
{"pref": "Kanagawa", "city": "横浜", "id": "140010"},
{"pref": "Kanagawa", "city": "小田原", "id": "140020"},
{"pref": "Niigata", "city": "新潟", "id": "150010"},
{"pref": "Niigata", "city": "長岡", "id": "150020"},
{"pref": "Niigata", "city": "高田", "id": "150030"},
{"pref": "Niigata", "city": "相川", "id": "150040"},
{"pref": "Toyama", "city": "富山", "id": "160010"},
{"pref": "Toyama", "city": "伏木", "id": "160020"},
{"pref": "Ishikawa", "city": "金沢", "id": "170010"},
{"pref": "Ishikawa", "city": "輪島", "id": "170020"},
{"pref": "Fukui", "city": "福井", "id": "180010"},
{"pref": "Fukui", "city": "敦賀", "id": "180020"},
{"pref": "Yamanashi", "city": "甲府", "id": "190010"},
{"pref": "Yamanashi", "city": "河口湖", "id": "190020"},
{"pref": "Nagano", "city": "長野", "id": "200010"},
{"pref": "Nagano", "city": "松本", "id": "200020"},
{"pref": "Nagano", "city": "飯田", "id": "200030"},
{"pref": "Gifu", "city": "岐阜", "id": "210010"},
{"pref": "Gifu", "city": "高山", "id": "210020"},
{"pref": "Shizuoka", "city": "静岡", "id": "220010"},
{"pref": "Shizuoka", "city": "網代", "id": "220020"},
{"pref": "Shizuoka", "city": "三島", "id": "220030"},
{"pref": "Shizuoka", "city": "浜松", "id": "220040"},
{"pref": "Aichi", "city": "名古屋", "id": "230010"},
{"pref": "Aichi", "city": "豊橋", "id": "230020"},
{"pref": "Mie", "city": "津", "id": "240010"},
{"pref": "Mie", "city": "尾鷲", "id": "240020"},
{"pref": "Shiga", "city": "大津", "id": "250010"},
{"pref": "Shiga", "city": "彦根", "id": "250020"},
{"pref": "Kyoto", "city": "京都", "id": "260010"},
{"pref": "Kyoto", "city": "舞鶴", "id": "260020"},
{"pref": "Osaka", "city": "大阪", "id": "270000"},
{"pref": "Hyogo", "city": "神戸", "id": "280010"},
{"pref": "Hyogo", "city": "豊岡", "id": "280020"},
{"pref": "Nara", "city": "奈良", "id": "290010"},
{"pref": "Nara", "city": "風屋", "id": "290020"},
{"pref": "Wakayama", "city": "和歌山", "id": "300010"},
{"pref": "Wakayama", "city": "潮岬", "id": "300020"},
{"pref": "Tottori", "city": "鳥取", "id": "310010"},
{"pref": "Tottori", "city": "米子", "id": "310020"},
{"pref": "Shimane", "city": "松江", "id": "320010"},
{"pref": "Shimane", "city": "浜田", "id": "320020"},
{"pref": "Shimane", "city": "西郷", "id": "320030"},
{"pref": "Okayama", "city": "岡山", "id": "330010"},
{"pref": "Okayama", "city": "津山", "id": "330020"},
{"pref": "Hiroshima", "city": "広島", "id": "340010"},
{"pref": "Hiroshima", "city": "庄原", "id": "340020"},
{"pref": "Yamaguchi", "city": "下関", "id": "350010"},
{"pref": "Yamaguchi", "city": "山口", "id": "350020"},
{"pref": "Yamaguchi", "city": "柳井", "id": "350030"},
{"pref": "Yamaguchi", "city": "萩", "id": "350040"},
{"pref": "Tokushima", "city": "徳島", "id": "360010"},
{"pref": "Tokushima", "city": "日和佐", "id": "360020"},
{"pref": "Kagawa", "city": "高松", "id": "370000"},
{"pref": "Ehime", "city": "松山", "id": "380010"},
{"pref": "Ehime", "city": "新居浜", "id": "380020"},
{"pref": "Ehime", "city": "宇和島", "id": "380030"},
{"pref": "Kochi", "city": "高知", "id": "390010"},
{"pref": "Kochi", "city": "室戸岬", "id": "390020"},
{"pref": "Kochi", "city": "清水", "id": "390030"},
{"pref": "Fukuoka", "city": "福岡", "id": "400010"},
{"pref": "Fukuoka", "city": "八幡", "id": "400020"},
{"pref": "Fukuoka", "city": "飯塚", "id": "400030"},
{"pref": "Fukuoka", "city": "久留米", "id": "400040"},
{"pref": "Saga", "city": "佐賀", "id": "410010"},
{"pref": "Saga", "city": "伊万里", "id": "410020"},
{"pref": "Nagasaki", "city": "長崎", "id": "420010"},
{"pref": "Nagasaki", "city": "佐世保", "id": "420020"},
{"pref": "Nagasaki", "city": "厳原", "id": "420030"},
{"pref": "Nagasaki", "city": "福江", "id": "420040"},
{"pref": "Kumamoto", "city": "熊本", "id": "430010"},
{"pref": "Kumamoto", "city": "阿蘇乙姫", "id": "430020"},
{"pref": "Kumamoto", "city": "牛深", "id": "430030"},
{"pref": "Kumamoto", "city": "人吉", "id": "430040"},
{"pref": "Oita", "city": "大分", "id": "440010"},
{"pref": "Oita", "city": "中津", "id": "440020"},
{"pref": "Oita", "city": "日田", "id": "440030"},
{"pref": "Oita", "city": "佐伯", "id": "440040"},
{"pref": "Miyazaki", "city": "宮崎", "id": "450010"},
{"pref": "Miyazaki", "city": "延岡", "id": "450020"},
{"pref": "Miyazaki", "city": "都城", "id": "450030"},
{"pref": "Miyazaki", "city": "高千穂", "id": "450040"},
{"pref": "Kagoshima", "city": "鹿児島", "id": "460010"},
{"pref": "Kagoshima", "city": "鹿屋", "id": "460020"},
{"pref": "Kagoshima", "city": "種子島", "id": "460030"},
{"pref": "Kagoshima", "city": "名瀬", "id": "460040"},
{"pref": "Okinawa", "city": "那覇", "id": "471010"},
{"pref": "Okinawa", "city": "名護", "id": "471020"},
{"pref": "Okinawa", "city": "久米島", "id": "471030"},
{"pref": "Okinawa", "city": "南大東", "id": "472000"},
{"pref": "Okinawa", "city": "宮古島", "id": "473000"},
{"pref": "Okinawa", "city": "石垣島", "id": "474010"},
{"pref": "Okinawa", "city": "与那国島", "id": "474020"},
]
class Location(BaseModel):
prefecture: str = Field(alias="pref")
city: str
id_: str = Field(alias="id")
class Temperature(BaseModel):
celsius: str | None
fahrenheit: str | None
class TemperatureRange(BaseModel):
min: Temperature
max: Temperature
class WeatherDetail(BaseModel):
weather: str
wind: str
wave: str
class WeatherImage(BaseModel):
title: str
url: str
width: int
height: int
class ChanceOfRain(BaseModel):
T00_06: str
T06_12: str
T12_18: str
T18_24: str
class WeatherForecast(BaseModel):
date: str
date_label: str = Field(alias="dateLabel")
telop: str
detail: WeatherDetail = Field(alias="detail")
temperature: TemperatureRange
chance_of_rain: ChanceOfRain = Field(alias="chanceOfRain")
def format_forecast(self) -> str:
return f"""
Daet: {self.date}
TemperatureMin: {self.temperature.min.celsius}度
TemperatureMax: {self.temperature.max.celsius}度
ChanceOfRain(00_06): {self.chance_of_rain.T00_06}
ChanceOfRain(06_12): {self.chance_of_rain.T06_12}
ChanceOfRain(12_18): {self.chance_of_rain.T12_18}
ChanceOfRain(18_24): {self.chance_of_rain.T18_24}
"""
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {"User-Agent": USER_AGENT}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def convert_pref_to_id(prefecture: str) -> Location | None:
locations = [Location(**d) for d in location_data]
location_in_prefecture = [l for l in locations if l.prefecture == prefecture]
if len(location_in_prefecture) == 0:
return None
else:
return location_in_prefecture[0]
@mcp.tool()
async def get_forecast(prefecture: str) -> str:
"""
Get forecast for a location in Japan
Args:
prefecture: Name of the prefecture of Japan (in English alphabets, e.g.) "Hokkaido", "Tokyo", "Osaka", etc.)
"""
location = convert_pref_to_id(prefecture=prefecture)
if location is None:
return "Unable to locate"
url = f"https://weather.tsukumijima.net/api/forecast?city={location.id_}"
res = await make_nws_request(url=url)
if not res:
return "Unable to fetch forecast data for this location."
forecasts = [WeatherForecast(**f) for f in res["forecasts"]]
return "\n------\n".join([f.format_forecast() for f in forecasts])
def main():
mcp.run(transport="stdio")
if __name__ == "__main__":
main()