Skip to main content
Glama
Lystopad6763

Weather MCP Server

by Lystopad6763
README.md
# Weather MCP Server 🌦️

A from-scratch **Model Context Protocol (MCP)** server that gives Claude Desktop
(or any MCP host) **live weather data**. It wraps the free
[Open-Meteo](https://open-meteo.com) API (no API key required), keeps a small
SQLite-backed list of favorite cities, and can optionally push weather to a
Telegram bot.

- **Language / SDK:** Python 3.10+ with the official [`mcp`](https://pypi.org/project/mcp/) package (FastMCP high-level API)
- **Transport:** `stdio`
- **Weather API:** Open-Meteo (geocoding + forecast) — **keyless, free**
- **Storage:** local SQLite file (`favorites.db`)

---

## Capabilities

### 🛠 Tools

| Tool | Arguments | Kind | Description |
|------|-----------|------|-------------|
| `get_current_weather` | `city` *(required)* | live API | Current conditions for a city: temperature, feels-like, humidity, precipitation, wind, sky condition. |
| `get_forecast` | `city` *(required)*, `days` *(optional, default `3`, 1–16)* | live API | Multi-day forecast: per-day min/max temperature, precipitation sum, max wind, sky condition. |
| `add_favorite_city` | `city` *(required)* | write → SQLite | Geocodes a city and saves it to the favorites list (deduplicated). |
| `remove_favorite_city` | `city` *(required)* | write → SQLite | Removes a city from favorites (case-insensitive, with a geocoding fallback). |
| `send_telegram_message` | `text` *(required)* | outbound API | Sends an arbitrary text message to a Telegram chat. *(optional — needs `.env`)* |
| `send_weather_to_telegram` | `city` *(required)* | live + outbound | Fetches current weather and pushes it to a Telegram chat in one step. *(optional — needs `.env`)* |

### 📄 Resource

| Resource URI | Returns | Logic |
|--------------|---------|-------|
| `weather://favorites` | Markdown list of saved cities (name, country, coordinates, when added) | Reads **live** from the SQLite database on every read — not a stub. |

All weather is fetched **live** on every call (no hard-coded responses), and the
resource always reflects the real, current contents of the database.

> **Note on resources vs. tools:** an MCP host calls *tools* automatically, but a
> *resource* is content you attach to the conversation. In Claude Desktop, open
> `+ → <weather> → Add from weather → Favorites resource` to load the current
> favorites into context.

---

## Requirements

- Python 3.10+
- The dependencies in [`requirements.txt`](requirements.txt) (`mcp[cli]`, `httpx`, `python-dotenv`)
- An MCP host such as Claude Desktop (optional — you can also test with the MCP Inspector)

---

## Setup

### 1. Clone & create a virtual environment

```powershell
git clone https://github.com/<your-user>/<repo>.git
cd <repo>
python -m venv .venv
.\.venv\Scripts\Activate.ps1          # Windows PowerShell
# source .venv/bin/activate           # macOS / Linux
```

### 2. Install dependencies

```powershell
pip install -r requirements.txt
```

### 3. (Optional) Try it locally with the MCP Inspector

The Inspector speaks the same JSON-RPC/stdio protocol and gives you a UI to list
tools, call them, and read resources — the fastest way to verify the server
before wiring it into a host.

```powershell
mcp dev server.py
```

Open the printed `http://localhost:...` URL, click **List Tools**, call
`get_current_weather` with `{"city": "Kyiv"}`, and read the
`weather://favorites` resource.

---

## Connect to Claude Desktop

1. Open (or create) the Claude Desktop config file:

   - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
     *(for the Microsoft Store build it lives under
     `%LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude\`)*
   - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`

2. Add a `weather` server entry with **absolute paths** to the venv's Python and
   to `server.py` (see [`claude_desktop_config.json`](claude_desktop_config.json)
   for a ready example):

   ```json
   {
     "mcpServers": {
       "weather": {
         "command": "C:\\path\\to\\mcp\\.venv\\Scripts\\python.exe",
         "args": ["C:\\path\\to\\mcp\\server.py"]
       }
     }
   }
   ```

   > On Windows, escape backslashes (`\\`) in JSON. The venv Python is used so
   > that `mcp` and `httpx` are on the path; no `cwd` is needed because the
   > database path is resolved relative to `server.py`.

3. **Fully quit and restart Claude Desktop.** The `weather` server should appear
   in the tools menu (🔌 / hammer icon), and you can ask Claude about the weather.

> ℹ️ The [`claude_desktop_config.json`](claude_desktop_config.json) in this
> repository is a **template/example only**. Claude Desktop reads the file in the
> **system location** from step 1 — *not* the copy in the project folder. Copy the
> `mcpServers` block into that system file (or merge it if the file already exists).

---

## Telegram integration (optional)

The `send_telegram_message` and `send_weather_to_telegram` tools forward weather
to a Telegram bot. They stay **disabled** until credentials are provided, so the
server runs fine without this step.

1. **Create a bot:** message [@BotFather](https://t.me/BotFather), send `/newbot`,
   and copy the **bot token**.
2. **Find your chat id:** message [@userinfobot](https://t.me/userinfobot) (it
   replies with your numeric id), or open
   `https://api.telegram.org/bot<token>/getUpdates` after messaging your bot.
3. **Create `.env`** next to `server.py` (copy from [`.env.example`](.env.example)):

   ```env
   TELEGRAM_BOT_TOKEN=123456789:AA-your-real-token
   TELEGRAM_CHAT_ID=123456789
   ```

4. Restart Claude Desktop, then ask: *"Send the current weather in Kyiv to my
   Telegram."* The message is delivered in Ukrainian.

> 🔒 `.env` is listed in [`.gitignore`](.gitignore) and is never committed — the
> token stays only on your machine.

---

## Example dialogs

> The assistant replies in the user's language; the prompts below match the
> screenshots in [`examples/`](examples/).

### 1. Current weather — *required argument*

```
You:    Яка зараз погода в Києві?
Claude: → get_current_weather(city="Kyiv")
        Зараз у Києві ☁️ хмарно, +22.7 °C (відчувається як +23.4 °C),
        вологість 58 %, вітер слабкий — 3.6 км/год, без опадів.
```

### 2. Forecast — *optional `days` argument*

```
You:    Який прогноз погоди у Львові на найближчі 5 днів?
Claude: → get_forecast(city="Lviv", days=5)
        • 21.06 — ☁️ хмарно, +20…+31 °C
        • 22.06 — 🌧️ помірний дощ, +21…+30 °C, опади 4.9 мм
        • ...
```

### 3. Favorites + resource

```
You:    Додай Київ і Львів у мої улюблені, потім прибери Львів.
Claude: → add_favorite_city("Kyiv") → add_favorite_city("Lviv")
        → remove_favorite_city("Lviv")
        Готово. (Attach "Favorites resource" to see the live list: Київ.)
```

### 4. Weather → Telegram *(optional)*

```
You:    Скинь мені поточну погоду в Києві в Telegram.
Claude: → send_weather_to_telegram(city="Kyiv")
        Готово — повідомлення відправлено у твій Telegram-чат. ✅
```

Screenshots of these conversations are in [`examples/`](examples/).

---

## Project structure

```
mcp/
├── server.py                   # the MCP server (tools + resource)
├── requirements.txt            # pinned dependencies
├── claude_desktop_config.json  # example host config
├── .env.example                # template for Telegram secrets
├── README.md
├── examples/                   # screenshots of Claude Desktop dialogs
├── .env                        # your Telegram secrets (git-ignored, optional)
└── favorites.db                # created at runtime (git-ignored)
```

---

## Known limitations

- **City disambiguation is naive.** The geocoder takes the *top* match for a
  name. Ambiguous names (e.g. "Springfield") may resolve to the wrong place; the
  tools surface the resolved `City, Region, Country` so it is visible.
- **Forecast horizon.** `days` is clamped to Open-Meteo's supported range (1–16);
  out-of-range values are clamped, not rejected.
- **Network-dependent.** Every weather call hits Open-Meteo live; offline or
  rate-limited requests fail with an error surfaced back to the host.
- **No caching.** Repeated identical questions re-fetch from the API.
- **`favorites.db` is local and unencrypted.** It only stores public city
  coordinates and is not synced or backed up.
- **Telegram is optional and single-chat.** The `send_*` tools target one
  pre-configured `TELEGRAM_CHAT_ID`; no per-message recipient, no retry, no rate
  limiting. Without `.env` they fail with a clear "not configured" message.
- **stdio only.** No HTTP/SSE transport and no authentication — intended for a
  local, single-user host, not a shared/remote deployment.

### Security notes

- The server writes **only** JSON-RPC to `stdout`; all diagnostics go to
  `stderr`, as the stdio transport requires.
- No shell execution and no string interpolation into commands → no command
  injection surface.
- API arguments are passed as typed query parameters via `httpx`, never
  concatenated into URLs by hand.
- The only secret (the Telegram token) lives in `.env`, which is git-ignored; the
  weather API itself is keyless.

---

## Credits

- Weather & geocoding data: [Open-Meteo](https://open-meteo.com) (CC BY 4.0).
- Protocol: [Model Context Protocol](https://modelcontextprotocol.io).