Metadata-Version: 2.4
Name: au-weather-mcp
Version: 0.4.8
Summary: MCP server for Australian weather + air quality. 45 curated locations + postcode/place-name lookup. Current observations, 16-day forecasts, 80+ years of historical data, air quality (PM2.5 / AQI), and multi-location comparison. Sourced via Open-Meteo (aggregates Bureau of Meteorology data under licence).
Project-URL: Homepage, https://github.com/Bigred97/au-weather-mcp
Project-URL: Documentation, https://github.com/Bigred97/au-weather-mcp#readme
Project-URL: Repository, https://github.com/Bigred97/au-weather-mcp
Project-URL: Issues, https://github.com/Bigred97/au-weather-mcp/issues
Project-URL: Changelog, https://github.com/Bigred97/au-weather-mcp/blob/main/CHANGELOG.md
Project-URL: PyPI, https://pypi.org/project/au-weather-mcp/
Author: Harry Vass
License: MIT
License-File: LICENSE
Keywords: air-quality,australia,australian,bom,bureau-of-meteorology,claude,climate,fastmcp,forecast,government-data,mcp,model-context-protocol,open-meteo,public-data,weather
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: aiosqlite>=0.20
Requires-Dist: fastmcp<4,>=2.0
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: rapidfuzz>=3.9
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# au-weather-mcp

[![PyPI](https://img.shields.io/pypi/v/au-weather-mcp.svg)](https://pypi.org/project/au-weather-mcp/)
[![Python](https://img.shields.io/pypi/pyversions/au-weather-mcp.svg)](https://pypi.org/project/au-weather-mcp/)
[![License](https://img.shields.io/pypi/l/au-weather-mcp.svg)](https://github.com/Bigred97/au-weather-mcp/blob/main/LICENSE)
[![Tests](https://github.com/Bigred97/au-weather-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/Bigred97/au-weather-mcp/actions/workflows/test.yml)
[![CodeQL](https://github.com/Bigred97/au-weather-mcp/actions/workflows/codeql.yml/badge.svg)](https://github.com/Bigred97/au-weather-mcp/actions/workflows/codeql.yml)
[![Glama MCP server quality](https://glama.ai/mcp/servers/Bigred97/au-weather-mcp/badges/score.svg)](https://glama.ai/mcp/servers/Bigred97/au-weather-mcp)

**Ask Claude about Australian weather and air quality and get real, current numbers** — not "I don't have access to that data." This MCP server gives Claude (and other MCP clients like Cursor) live access to Australian weather + air-quality data via [Open-Meteo](https://open-meteo.com), which aggregates Bureau of Meteorology observations under licence. 45 curated locations (every state capital + every regional centre over ~25k), postcode and place-name lookup, current observations, 16-day forecasts, 80+ years of historical data, and multi-location comparison.

Companion to [abs-mcp](https://github.com/Bigred97/abs-mcp) (ABS macro stats), [rba-mcp](https://github.com/Bigred97/rba-mcp) (Reserve Bank), and [ato-mcp](https://github.com/Bigred97/ato-mcp) (tax + charity register) — together the four cover Australia's most-asked public data.

## What you can ask

Once installed, your LLM can answer questions like:

| Question | Real response |
|---|---|
| What's the weather in Sydney right now? | Current temperature, humidity, wind, rain, pressure with the time stamped |
| Forecast for Melbourne next week? | 7-day daily forecast with max/min temps and rain |
| How was Sydney summer in January 2020? | Historical daily data from Open-Meteo's archive (1940+) |
| Compare rainfall in Cairns vs Brisbane this year | Multi-location queries with provenance per row |
| Tropical Queensland weather today | Search fuzzy by region/state/description |

Every response carries a CC-BY 4.0 attribution string and a direct Open-Meteo URL the agent can cite back to the user.

## Why Open-Meteo (not BOM directly)

BOM publishes their own JSON/XML endpoints, but they actively 403 non-browser User-Agents and have no documented commercial-use path below their ~$5k/yr Registered User Service. Open-Meteo:

- Aggregates BOM data under their existing licensing arrangements with national meteorological services
- Free tier is explicit and generous; commercial use is $30/mo with public terms
- Returns clean, versioned, schema-stable JSON with units alongside every value
- Covers historical data back to 1940 via their archive endpoint
- No API key, no User-Agent gymnastics

We attribute both Open-Meteo and BOM in every response.

## Install

```bash
# After publish:
uvx au-weather-mcp

# Local dev:
uv pip install -e .
```

### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "weather": {
      "command": "uvx",
      "args": ["--upgrade", "au-weather-mcp"]
    }
  }
}
```

The `--upgrade` flag makes uvx re-check PyPI on each Claude Desktop launch, so bug fixes propagate without manual cache refresh. Costs ~100ms at startup.

For a local checkout (before PyPI publish):

```json
{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/au-weather-mcp", "au-weather-mcp"]
    }
  }
}
```

### Cursor

Add to `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "weather": {
      "command": "uvx",
      "args": ["--upgrade", "au-weather-mcp"]
    }
  }
}
```

## Tools

| Tool | What it does |
|---|---|
| `search_locations(query, limit=10)` | Fuzzy-search the 45 curated AU locations by name, state, or description. |
| `describe_location(location)` | Lat/lng, timezone, elevation, nearest BOM station, and the canonical Open-Meteo URL. |
| `latest(location)` | Current weather observation — temp, humidity, wind, rain, pressure. 15-min cache. |
| `get_weather(location, start_date, end_date, granularity)` | Time-series query. Auto-routes to historical archive (1940+) or forecast (today + 16 days). Daily or hourly granularity. |
| `air_quality(location)` | Current PM2.5, PM10, ozone, NO₂, SO₂, CO + European & US AQI with plain-English labels. *(v0.4.0)* |
| `compare_locations([locs])` | Side-by-side current weather for 2–10 locations in one call. Fans out concurrently. *(v0.4.0)* |
| `list_curated()` | All 45 supported location IDs. |

## Accepts almost any input shape

The `location` parameter on every tool resolves six different input shapes — agents and users don't need to know the curated key format:

| Input shape | Example | Resolves via |
|---|---|---|
| Curated ID | `"sydney"`, `"gold_coast"` | Direct curated lookup (fast) |
| Place name, any case | `"Sydney"`, `"Gold Coast"`, `"GOLD COAST"` | Normalised curated lookup |
| State code or full name | `"NSW"`, `"Queensland"`, `"Western Australia"` | State → capital alias |
| Raw coordinates | `"-33.87,151.21"` | Direct lat/lng (AU bbox enforced) |
| **AU postcode** | `"2026"` (Bondi Beach), `"4217"` (Gold Coast), `"6160"` (Fremantle) | **OpenStreetMap Nominatim** |
| Any AU place name | `"Byron Bay"`, `"Margaret River"`, `"Toowoomba"` | Open-Meteo geocoding (AU-filtered, population-sorted) |
| Typo of a curated name | `"Sydny"`, `"Melbourn"` | High-confidence fuzzy match |

Every response includes a `location_resolution` field with one of `curated`, `state_alias`, `raw_coordinates`, `geocoded`, or `fuzzy_curated` — so the agent (and the user) can see HOW the input was interpreted.

## Curated locations

**45 curated locations** covering all 8 state/territory capitals plus 37 major regional centres (every AU population centre over ~25k). Anything outside the curated set still works via the place-name geocoder or postcode lookup.

| Region | Locations |
|---|---|
| **Capitals (8)** | `sydney` · `melbourne` · `brisbane` · `perth` · `adelaide` · `hobart` · `darwin` · `canberra` |
| **NSW regional (10)** | `newcastle` · `wollongong` · `tamworth` · `wagga_wagga` · `albury` · `orange` · `bathurst` · `dubbo` · `coffs_harbour` · `port_macquarie` |
| **QLD regional (9)** | `gold_coast` · `sunshine_coast` · `cairns` · `townsville` · `mackay` · `toowoomba` · `rockhampton` · `bundaberg` · `hervey_bay` |
| **VIC regional (6)** | `geelong` · `ballarat` · `bendigo` · `mildura` · `shepparton` · `warrnambool` |
| **WA regional (5)** | `broome` · `bunbury` · `geraldton` · `albany` · `kalgoorlie` |
| **SA regional (2)** | `mount_gambier` · `whyalla` |
| **TAS regional (3)** | `launceston` · `devonport` · `burnie` |
| **NT regional (2)** | `alice_springs` · `katherine` |

Coordinates are anchored to the canonical BOM observation point for each city (e.g. Sydney = Observatory Hill, Melbourne = Olympic Park) so cross-checking against BOM's official observations is straightforward. See [src/au_weather_mcp/data/curated/locations.yaml](src/au_weather_mcp/data/curated/locations.yaml) for the full registry.

## Worked examples

**"What's the weather in Sydney right now?"**

```
latest(location="sydney")
```

Returns:

```json
{
  "location_id": "sydney",
  "location_name": "Sydney",
  "state": "NSW",
  "latitude": -33.8607,
  "longitude": 151.205,
  "timezone": "Australia/Sydney",
  "period": {"start": "2026-05-12T11:30", "end": "2026-05-12T11:30"},
  "current": {
    "time": "2026-05-12T11:30",
    "temperature_c": 19.7,
    "apparent_temperature_c": 18.1,
    "relative_humidity_pct": 67,
    "precipitation_mm": 0.0,
    "cloud_cover_pct": 43,
    "pressure_msl_hpa": 1034.5,
    "wind_speed_kmh": 18.4,
    "wind_direction_deg": 149,
    "wind_gusts_kmh": 43.2,
    "weather_code": 1,
    "weather_description": "Mainly clear"
  },
  "source": "Open-Meteo (aggregates Bureau of Meteorology data under licence)",
  "attribution": "Weather data by Open-Meteo.com (https://open-meteo.com), licensed under CC BY 4.0...",
  "source_url": "https://api.open-meteo.com/v1/forecast?latitude=-33.8607&...",
  "server_version": "<package version, e.g. 0.3.3>",
  "location_resolution": "curated",
  "location_input": "sydney"
}
```

**"How was Sydney in January 2020?"**

```
get_weather(
  location="sydney",
  start_date="2020-01-01",
  end_date="2020-01-31",
  granularity="daily"
)
```

Returns 31 `DailyAggregate` rows with `temperature_max_c`, `temperature_min_c`, `precipitation_sum_mm`, and weather descriptions per day.

**"7-day Melbourne forecast, hourly detail"**

```
get_weather(
  location="melbourne",
  start_date="2026-05-12",
  end_date="2026-05-19",
  granularity="hourly"
)
```

Returns 168 hourly rows.

## Date formats

`start_date` and `end_date` must be `YYYY-MM-DD`. The server validates both the regex and the semantic date — `2024-13-40` raises a clean `ValueError` rather than silently failing.

| Date range | Routes to | Coverage |
|---|---|---|
| `end_date >= today - 5 days` | Open-Meteo forecast endpoint | Today + 16 days |
| `end_date < today - 5 days` | Open-Meteo historical archive | 1940-01-01 onwards |

## Trust contract

Every response carries:

- `source_url` — the exact Open-Meteo URL the data came from
- `attribution` — CC-BY 4.0 notice for both Open-Meteo and BOM
- `retrieved_at` — UTC timestamp when we fetched
- `server_version` — which wheel served the call (debugging stale `uvx` caches)
- `stale` — true if we're serving cached data because upstream failed; comes with `stale_reason`

Sanity validators reject upstream values outside the plausible Australian range (temperature outside −30°C to +55°C, humidity outside 0-100%, pressure outside 850-1080 hPa). Rather than silently passing bad data to your agent, we raise an explicit validation error so the agent can degrade gracefully.

## Development

```bash
git clone https://github.com/Bigred97/au-weather-mcp.git
cd au-weather-mcp
uv sync --extra dev
uv pip install -e .

# Unit tests (no network)
uv run pytest

# Live integration tests (hits Open-Meteo)
uv run pytest -m live
```

The SQLite cache lives at `~/.au-weather-mcp/cache.db`. Current observations refresh every 15 minutes (matching Open-Meteo's update cadence), forecasts every 1 hour, historical never (a year-old day in the archive doesn't change). Delete the file to force a refresh.

## Sister MCPs (Australian Public Data portfolio)

The portfolio runs side-by-side in any MCP client; Claude disambiguates via the server prefix (`weather:latest` vs `abs:latest` vs `rba:latest` vs `ato:get_data`).

- [abs-mcp](https://pypi.org/project/abs-mcp/) — Australian Bureau of Statistics (CPI, unemployment, ERP, building approvals)
- [rba-mcp](https://pypi.org/project/rba-mcp/) — Reserve Bank of Australia (cash rate, lending stats, exchange rates)
- [ato-mcp](https://pypi.org/project/ato-mcp/) — Australian Taxation Office (tax stats, ACNC charities)
- [apra-mcp](https://pypi.org/project/apra-mcp/) — Australian Prudential Regulation Authority (banking, insurance, super)
- [aihw-mcp](https://pypi.org/project/aihw-mcp/) — Australian Institute of Health and Welfare
- [asic-mcp](https://pypi.org/project/asic-mcp/) — Australian Securities and Investments Commission (company registers)
- [aemo-mcp](https://pypi.org/project/aemo-mcp/) — Australian Energy Market Operator (NEM dispatch, spot prices, generation)
- **au-weather-mcp** — this one. Open-Meteo (Bureau of Meteorology aggregator).
- [wgea-mcp](https://pypi.org/project/wgea-mcp/) — Workplace Gender Equality Agency
- [aus-identity](https://pypi.org/project/aus-identity/) — Postcode / state / ABN normalisation helper used by all sisters

## Data attribution

Weather data is provided by [Open-Meteo](https://open-meteo.com), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The underlying observations include data from the [Australian Bureau of Meteorology](https://www.bom.gov.au) under Open-Meteo's licensing arrangement.

**Postcode resolutions** (when `location_resolution == "postcode"`) additionally use [OpenStreetMap](https://www.openstreetmap.org/copyright) via the Nominatim service, licensed under the [Open Database Licence (ODbL)](https://opendatacommons.org/licenses/odbl/). The OSM attribution is automatically appended to the response's `attribution` field for any postcode lookup, so redistribution is licence-compliant out of the box.

If you redistribute responses, credit Open-Meteo + BOM (always) and OpenStreetMap (when postcode lookup was used).

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for release history.

## License

MIT — Harry Vass, 2026.
