Skip to main content
Glama
README.md
# 台北市中山區 天氣回報 MCP Server

> A simple **MCP (Model Context Protocol) server** that reports the weather for
> Zhongshan District, Taipei, Taiwan — adapted from the official MCP tutorial,
> but using the free, key-less, global **Open-Meteo API** so it works outside the US.

這是一個 **MCP 天氣伺服器**:裝到 Claude 桌面版(或其他 MCP 客戶端)後,你用中文問
「中山區現在天氣如何?」、「未來三天會下雨嗎?」,AI 就會自動呼叫這個伺服器回報。

本專案改寫自 MCP 官方教學 [Build an MCP server](https://modelcontextprotocol.io/docs/develop/build-server)。
官方範例用美國國家氣象局 API(只能查美國),本專案改用 **Open-Meteo**(免費、免申請
金鑰、支援全球),因此可以查到台灣的天氣。

---

## 什麼是 MCP?

MCP(Model Context Protocol)是一套讓 AI 助理能「呼叫外部工具」的標準。
你可以把 MCP server 想成幫 AI 裝的**外掛**:AI 本身不會查天氣,但透過這個外掛,
它就能去 Open-Meteo 拿即時天氣資料回來給你。

一個 MCP server 可提供三種能力,本專案聚焦在 **Tools(工具)**:

| 能力 | 說明 |
|------|------|
| Tools | 可被 AI 呼叫的函式(需使用者同意)← 本專案使用 |
| Resources | 可被讀取的類檔案資料 |
| Prompts | 預先寫好的提示樣板 |

---

## 架構示意圖

整體運作流程:你用自然語言問 Claude,Claude 透過 MCP 協定呼叫本伺服器的工具,
伺服器再去 Open-Meteo 拿即時資料,格式化後一路回傳給你。

```mermaid
flowchart LR
    U(["👤 使用者"]) -->|"問:中山區會下雨嗎?"| C["Claude 桌面版<br/>(MCP Host)"]
    C -->|"回答"| U
    C <-->|"MCP 協定 / stdio"| S

    subgraph S ["weather-taipei MCP Server"]
        direction TB
        T1["get_zhongshan_weather<br/>中山區天氣"]
        T2["get_weather_by_city<br/>各縣市天氣"]
        T3["get_weather_by_coords<br/>經緯度天氣"]
        T4["get_hourly_rain<br/>逐小時降雨"]
    end

    S -->|"HTTPS 查詢"| A[("🌤️ Open-Meteo API")]
    A -->|"天氣 JSON"| S
```

> GitHub 會自動把上面的 `mermaid` 區塊渲染成圖。若在不支援 Mermaid 的檢視器,
> 它會以文字方式呈現,內容一樣看得懂。

---

## 提供的工具(Tools)

| 工具名稱 | 參數 | 功能 |
|----------|------|------|
| `get_zhongshan_weather` | 無 | 回報台北市中山區「目前天氣」+「未來 3 天預報」 |
| `get_weather_by_city` | `city` | 用**台灣縣市名稱**查天氣(台北、台中、高雄、宜蘭…,支援關鍵字) |
| `get_weather_by_coords` | `latitude`, `longitude` | 依經緯度查任一地點的天氣+未來 3 天預報 |
| `get_hourly_rain` | `city`, `hours` | **逐小時降雨**:未來數小時(1~48)的雨量與降雨機率 |

回報內容包含:天氣狀況(中文)、溫度、體感溫度、相對濕度、降雨量、風速,
以及未來三天的高低溫與降雨機率;逐小時降雨則列出每小時的雨量與降雨機率。

支援的縣市:台北市中山區、台北市、新北市、基隆市、桃園市、新竹市/縣、苗栗縣、
台中市、彰化縣、南投縣、雲林縣、嘉義市/縣、台南市、高雄市、屏東縣、宜蘭縣、
花蓮縣、台東縣、澎湖縣、金門縣、連江縣。

**輸出範例:**

```
📍 台北市中山區 目前天氣(2026-07-05T16:00)
天氣狀況:毛毛雨(中)
溫度:31.8°C(體感 37.5°C)
相對濕度:63%
降雨量:0.2 mm
風速:1.7 km/h

未來 3 天預報:
  2026-07-05:毛毛雨(中),26.7~33.3°C,降雨機率 99%
  2026-07-06:毛毛雨(中),25.9~34.1°C,降雨機率 69%
  2026-07-07:雷雨,25.1~32.8°C,降雨機率 97%
```

**逐小時降雨範例(`get_hourly_rain`):**

```
📍 台北市中山區 未來 6 小時逐小時降雨
  17:00 0.1 mm 降雨機率 98%  ☔
  18:00 0.1 mm 降雨機率 90%  ☔
  19:00 0.0 mm 降雨機率 77%
  20:00 0.1 mm 降雨機率 63%  ☔
  21:00 0.0 mm 降雨機率 48%
  22:00 0.0 mm 降雨機率 33%

這段期間預估總雨量約 0.3 mm。
```

---

## 程式碼解說(`weather_taipei.py`)

程式很短,分成五個部分。以下逐段說明。

### 1. 初始化與常數

```python
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather-taipei")          # 建立一個名為 weather-taipei 的伺服器

OPEN_METEO_BASE = "https://api.open-meteo.com/v1/forecast"
ZHONGSHAN_LAT = 25.0637                   # 中山區大約緯度
ZHONGSHAN_LON = 121.5265                  # 中山區大約經度
```

`FastMCP` 是官方提供的框架,會**自動**根據函式的型別註記與 docstring 產生工具定義,
不必手寫繁瑣的設定。

### 2. 天氣代碼轉中文

Open-Meteo 用「WMO 天氣代碼」(數字)表示天氣,例如 `0`=晴天、`61`=小雨、`95`=雷雨。
我們用一個字典把數字翻成中文:

```python
WEATHER_CODES = {0: "晴天", 1: "大致晴朗", 2: "局部多雲", 3: "陰天",
                 61: "小雨", 63: "中雨", 65: "大雨", 95: "雷雨", ...}

def describe_code(code: int) -> str:
    return WEATHER_CODES.get(code, f"未知天氣(代碼 {code})")
```

### 3. 向 Open-Meteo 發出請求(含錯誤處理)

```python
async def make_request(params: dict) -> dict | None:
    async with httpx.AsyncClient() as client:
        try:
            resp = await client.get(OPEN_METEO_BASE, params=params, timeout=30.0)
            resp.raise_for_status()       # 若 HTTP 狀態非 2xx 會丟出例外
            return resp.json()
        except Exception:
            return None                   # 出錯就回 None,交給上層處理
```

用 `httpx` 的非同步(`async`)方式連網,速度快且不會卡住整個伺服器。

### 4. 查詢並排版天氣(核心邏輯)

`fetch_weather()` 組好查詢參數(座標、時區、要哪些欄位、預報幾天),呼叫 API,
再把結果排成好讀的文字。關鍵在於同時要了「目前(current)」與「每日(daily)」兩組資料:

```python
params = {
    "latitude": lat, "longitude": lon, "timezone": "Asia/Taipei",
    "current": "temperature_2m,relative_humidity_2m,apparent_temperature,"
               "precipitation,weather_code,wind_speed_10m",
    "daily": "weather_code,temperature_2m_max,temperature_2m_min,"
             "precipitation_probability_max",
    "forecast_days": 3,
}
```

### 5. 縣市名稱查座標 & 逐小時降雨

為了支援「用縣市名稱查天氣」,程式內建一份台灣各縣市的座標表 `TAIWAN_LOCATIONS`,
再用 `resolve_location()` 把名稱對應到座標(支援關鍵字,例如「高雄」→「高雄市」):

```python
def resolve_location(name: str) -> tuple[float, float, str] | None:
    if name in TAIWAN_LOCATIONS:
        lat, lon = TAIWAN_LOCATIONS[name]
        return lat, lon, name
    for key, (lat, lon) in TAIWAN_LOCATIONS.items():
        if name and name in key:          # 部分關鍵字也能對應
            return lat, lon, key
    return None
```

逐小時降雨 `fetch_hourly_rain()` 則向 API 要 `hourly=precipitation,precipitation_probability`,
再從「現在」這個時間點往後取 N 小時:

```python
now = data.get("current", {}).get("time", "")
start = next((i for i, t in enumerate(times) if t >= now), 0)  # 只看未來時段
for i in range(start, start + hours):
    ...  # 印出每小時的雨量與降雨機率
```

### 6. 用 `@mcp.tool()` 把函式變成 AI 可呼叫的工具

只要在函式上加 `@mcp.tool()`,它就會被登錄為工具;函式的 docstring 會成為
AI 看到的「工具說明」,所以描述要寫清楚。本專案共登錄四個工具:

```python
@mcp.tool()
async def get_zhongshan_weather() -> str:
    """取得台灣台北市中山區目前的天氣與未來 3 天預報。"""
    return await fetch_weather(ZHONGSHAN_LAT, ZHONGSHAN_LON, "台北市中山區")

@mcp.tool()
async def get_weather_by_city(city: str) -> str:
    """用台灣縣市名稱查詢天氣與未來 3 天預報。"""
    ...

@mcp.tool()
async def get_weather_by_coords(latitude: float, longitude: float) -> str:
    """依經緯度取得任一地點的天氣與未來 3 天預報。"""
    ...

@mcp.tool()
async def get_hourly_rain(city: str = "台北市中山區", hours: int = 12) -> str:
    """取得台灣某地未來數小時的逐小時降雨(雨量與降雨機率)。"""
    ...
```

最後用 stdio 方式啟動,讓 Claude 桌面版能連上:

```python
if __name__ == "__main__":
    mcp.run(transport="stdio")
```

---

## 安裝與使用

### 1. 先決條件

- Python 3.10 以上
- 安裝套件:`pip install "mcp[cli]" httpx`

### 2. 設定 Claude 桌面版

打開(或新建)設定檔:
`%APPDATA%\Claude\claude_desktop_config.json`(Windows)

把下列內容加入(路徑請改成你電腦上的實際路徑):

```json
{
  "mcpServers": {
    "weather-taipei": {
      "command": "python",
      "args": ["C:/路徑/weather-taipei/weather_taipei.py"]
    }
  }
}
```

存檔後**完全關閉並重開** Claude 桌面版,再於對話中詢問天氣即可。
可參考本專案的 `claude_desktop_config.sample.json`。

設定完成後,可以這樣問 Claude:

- 「中山區現在天氣如何?」→ `get_zhongshan_weather`
- 「台中市未來三天會下雨嗎?」→ `get_weather_by_city`
- 「中山區未來 6 小時會下雨嗎?」→ `get_hourly_rain`
- 「幫我查緯度 24.15、經度 120.67 的天氣」→ `get_weather_by_coords`

### 3. 不透過 Claude、直接用指令測試

```bash
python -c "import asyncio, weather_taipei as w; print(asyncio.run(w.fetch_weather(w.ZHONGSHAN_LAT, w.ZHONGSHAN_LON, '台北市中山區')))"
```

---

## 資料來源與致謝

- 天氣資料:[Open-Meteo](https://open-meteo.com)(免費、免金鑰、CC BY 4.0)
- 架構參考:[MCP 官方教學](https://modelcontextprotocol.io/docs/develop/build-server)

## 授權

MIT License,詳見 [LICENSE](LICENSE)。