Skip to main content
Glama
pierre-phu

swiss-outdoor-mcp

by pierre-phu
README.md
# swiss-outdoor-mcp

[![CI](https://github.com/pierre-phu/swiss-outdoor-mcp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/pierre-phu/swiss-outdoor-mcp/actions/workflows/ci.yml)

An [MCP](https://modelcontextprotocol.io) server that gives an LLM client reliable, computed facts
for planning Swiss mountain outings by public transport. The v0.1 use case is paragliding:

> "Saturday, from Lausanne, where can I go paragliding by train?"

The server does not answer that question. It provides the deterministic pieces — launch sites,
ensemble-based flyability probabilities, train connections — and the LLM composes them.
**There are no LLM calls inside the server.**

## Status

Work in progress towards v0.1; the current pre-release is `0.1.0a1`. Working today:
`list_sites`, `get_flyability` and `get_connections`. Still to come: `estimate_trip_co2`, offline
fixture mode and the eval harness. See [`docs/SPEC.md`](docs/SPEC.md).

## Example

A real `get_flyability` call for the Fiesch launch (Valais, faces SE–SW, 2163 m), made on
2026-09-25 for the next day. The `hourly` list is trimmed to its first and last hours; the real
response has all eight, 10:00 to 17:00.

```json
{
  "site_id": "fiesch",
  "date": "2026-09-26",
  "p_flyable": 1.0,
  "hourly": [
    {
      "time": "2026-09-26T10:00:00+02:00",
      "p_wind_ok": 1.0,
      "p_gust_ok": 1.0,
      "p_dry": 1.0,
      "p_direction_ok": 1.0,
      "p_flyable": 1.0,
      "wind_kmh": { "median": 1.8, "p10": 1.6, "p90": 2.5 }
    },
    {
      "time": "2026-09-26T17:00:00+02:00",
      "p_wind_ok": 1.0,
      "p_gust_ok": 1.0,
      "p_dry": 1.0,
      "p_direction_ok": 1.0,
      "p_flyable": 1.0,
      "wind_kmh": { "median": 8.6, "p10": 7.4, "p90": 10.8 }
    }
  ],
  "model": "icon_d2_eps",
  "n_members": 20,
  "grid_elevation_m": 2173.0,
  "criteria": {
    "max_wind_kmh": 20.0,
    "max_gust_kmh": 30.0,
    "max_precip_mm_h": 0.1,
    "direction_tolerance_deg": 45.0,
    "window_start": "10:00:00",
    "window_end": "17:00:00",
    "min_consecutive_hours": 3
  },
  "method": "Each of the 20 members of the icon_d2_eps ensemble is checked hour by hour against the criteria (all limits inclusive; wind 10 m above the model's terrain). Hourly p_* values are the share of members meeting each criterion. The day-level p_flyable is the share of members with at least 3 consecutive flyable hours between 10:00 and 17:00 Europe/Zurich, both ends included.",
  "generated_at": "2026-09-25T16:19:01+02:00",
  "disclaimer": "Indicator only, computed from a weather-model ensemble. It does not replace pilot judgment, an on-site assessment of conditions, or official aviation weather forecasts.",
  "attribution": "Weather data by Open-Meteo.com (https://open-meteo.com/), CC BY 4.0."
}
```

How to read it:

- `p_flyable: 1.0` means all 20 ensemble members have at least 3 consecutive hours inside the
  criteria. It is a share of members, not a calibrated probability.
- `model` says which ensemble answered: the 2 km `icon_d2_eps` (20 members) for today and
  tomorrow, the 13 km `icon_eu_eps` (40 members) further out.
- `grid_elevation_m` is the altitude of the model cell. Here it is 10 m from the real launch;
  on other sites the gap can be hundreds of metres.
- The LLM client is expected to pass `disclaimer` and `attribution` on to the user.

## Install and run

```bash
uv sync
uv run swiss-outdoor-mcp     # speaks MCP over stdio
```

## Development

```bash
uv run ruff check . && uv run ruff format --check . && uv run mypy src && uv run pytest
```

See [`CONTRIBUTING.md`](CONTRIBUTING.md). Verified facts about every upstream API live in
[`docs/api-notes.md`](docs/api-notes.md) — read it before touching a client.

## Limitations

This is **not a flight-safety tool.** Flyability output is an indicator built from a public
weather model; it does not replace pilot judgment, a site briefing, or official aviation weather.

- `p_flyable` is the **share of ensemble members** that agree, not a calibrated probability.
- Winds are **10 m above the model's terrain**, not at launch height. The model cell can sit
  hundreds of metres from the real launch altitude; `grid_elevation_m` reports it. No thermals,
  no foehn, no upper winds.
- **Near-calm hours can score low**: the direction check applies whatever the wind speed, so a
  1 km/h breeze from the wrong side fails it.
- The forecast reaches about **four days out**. The exact horizon moves with each model run and
  is read from every response.
- The sites' `access_notes` (the last leg from the stop to the launch) are not filled in yet.

## Data sources

- Weather: [Open-Meteo](https://open-meteo.com) Ensemble API — weather data by Open-Meteo.com,
  [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
- Public transport: [transport.opendata.ch](https://transport.opendata.ch) (an unofficial API over
  the Swiss timetable; its terms of use are not stated — see `docs/api-notes.md`).

## Licence

MIT — see [LICENSE](LICENSE).

TDQS

A4.1/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool in the set, there is no possible ambiguity or overlap. The ping tool is the sole functionality, so an agent cannot confuse it with anything else.

Naming Consistency5/5

There is only one tool named 'ping', which is a standard, clear health-check convention. With no other tools, there is no pattern to violate, so consistency is effectively perfect.

Tool Count1/5

A server named swiss-outdoor-mcp with only a ping health check has a severe mismatch between its apparent purpose and its actual tool surface. One trivial tool is far too few for the implied domain.

Completeness1/5

The server's name suggests it should cover Swiss outdoor activities, but the only tool is a reachability check. There is no actual domain functionality, leaving every conceivable outdoor use case unsupported.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive