Metadata-Version: 2.3
Name: swiss-rail-mcp
Version: 0.3.0
Summary: MCP server exposing Swiss public transport data (SBB, ZVV, PostAuto) via transport.opendata.ch.
Keywords: mcp,swiss,transport,sbb,opendata,claude
Author: Yannik Kintscher
Author-email: Yannik Kintscher <yannik.kintscher@gmail.com>
License: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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 :: Software Development :: Libraries
Requires-Dist: fastmcp>=2.0
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/kintscher/swiss-rail-mcp
Project-URL: Repository, https://github.com/kintscher/swiss-rail-mcp
Project-URL: Issues, https://github.com/kintscher/swiss-rail-mcp/issues
Description-Content-Type: text/markdown

# swiss-rail-mcp

A zero-auth MCP server for Swiss public transport. Wraps
[`transport.opendata.ch`](https://transport.opendata.ch) and
[`data.sbb.ch`](https://data.sbb.ch) into a single MCP server with
**12 tools, 3 resources, and 4 prompts**.

[![PyPI](https://img.shields.io/pypi/v/swiss-rail-mcp.svg)](https://pypi.org/project/swiss-rail-mcp/)
[![Python](https://img.shields.io/pypi/pyversions/swiss-rail-mcp.svg)](https://pypi.org/project/swiss-rail-mcp/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

## What it does

Lets an MCP client (Claude Desktop, Claude Code, …) answer real Swiss
transport questions without an API key:

- *"When's the next train from Regensdorf to Zürich HB?"*
- *"What's the last realistic train home from Bern tonight?"*
- *"Any disruptions on the Gotthard line?"*
- *"Does Lugano have step-free access and WiFi?"*

Communication is stdio only. No keys, no accounts, no rate-limited
proxy — just two public, unauthenticated upstream APIs.

## Install

Run on demand without installing globally:

```sh
uvx swiss-rail-mcp
```

Or install it as a persistent `uv` tool:

```sh
uv tool install swiss-rail-mcp
```

Both options pull from PyPI and expose the `swiss-rail-mcp` console
script. Requires Python 3.11+.

## Use with Claude Desktop

Add the server to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "swiss-rail": {
      "command": "uvx",
      "args": ["swiss-rail-mcp"]
    }
  }
}
```

Restart Claude Desktop. The tools, resources, and `/swiss-rail:*`
slash-commands should appear in the picker.

## Use with Claude Code

```sh
claude mcp add swiss-rail -- uvx swiss-rail-mcp
```

## Tools

### Core (timetable, via `transport.opendata.ch`)

| Tool | Purpose |
| --- | --- |
| `find_station(query)` | Resolve a place name to canonical stations. |
| `get_connections(from_station, to_station, limit=4, time=None, date=None)` | Connections between two stations. |
| `get_stationboard(station, limit=10)` | Next departures from a station. |

### SBB open data (via `data.sbb.ch`)

| Tool | Purpose |
| --- | --- |
| `get_disruptions(scope=None, limit=10)` | Active rail-traffic disruptions, optionally filtered. |
| `get_station_facilities(station)` | Elevator, WiFi, step-free access, waiting room. |
| `get_passenger_frequency(station)` | Most recent average daily passenger count. |

### Conversational (compose the above)

| Tool | Purpose |
| --- | --- |
| `last_train_home(home_station, current_station, after_time=None)` | Latest realistic train tonight. |
| `plan_evening_out(from_station, to_station, latest_return_time)` | Outbound options plus latest safe return. |
| `commuter_summary(home_station, work_station)` | Next connections + disruption verdict for a daily commute. |
| `journey_with_layover(from_station, to_station, layover_station, layover_minutes)` | Two-leg journey with a deliberate stop. |
| `accessible_route(from_station, to_station, limit=3)` | Connections annotated with endpoint accessibility. |
| `bike_friendly_route(from_station, to_station, limit=3)` | Connections annotated with a per-leg bike-allowed heuristic. |

All datetimes returned by the tools are ISO 8601 with a Europe/Zurich
offset.

## Resources

Read-only URIs the client can fetch. Useful for letting the LLM pull
formatted markdown views of live data without a tool call.

| URI | Returns |
| --- | --- |
| `transport://disruptions/active` | Markdown list of all active disruptions. |
| `transport://station/{name}/board` | Markdown table of the next departures. |
| `transport://station/{name}/info` | Markdown summary of facilities + frequency. |

## Prompts

Slash-commands in Claude Desktop (`/swiss-rail:<name>`) and
callable prompts in other MCP clients.

| Prompt | Use case |
| --- | --- |
| `plan_weekend_trip(from_station, max_hours=8)` | Day-trip planner with disruption check. |
| `morning_commute_check(home_station, work_station)` | Quick sanity-check before leaving the house. |
| `last_train_check(home_station, current_station=None)` | "What's the last train home?" from where you are. |
| `evening_out_planner(home_station, destination_city)` | Plan a night out with a safe return. |

## Example sessions

Real responses from Claude with this MCP server wired up, captured via
`claude -p --mcp-config …` against the live upstream APIs. Reproduce with
the snippet at the bottom of this section.

> **Q:** *When's the next train from Bern to Zürich HB?*
>
> **A:** Next IR from Bern at 03:00, arriving Zürich HB at 04:11
> (direct, 71 min, platform 5).

> **Q:** *When's the last realistic train home from Bern to Regensdorf
> tonight?*
>
> **A:** Your last realistic option is the IC at **23:02 from Bern**
> (platform 6) → Zürich HB 00:16 → SN to Regensdorf-Watt, arriving
> **00:49**. After that you're into Nachtnetz SN trains (last one
> departs Bern 01:06, arrives 02:49) which usually carry a night
> surcharge.

> **Q:** *Are there any active disruptions on the Swiss rail network
> right now? Give me just the headline count and the most severe one.*
>
> **A:** 10 active events on the feed. The most severe is a critical
> interruption on the Basel Bad Bf–Freiburg line between Haltingen and
> Efringen-Kirchen (accident involving a person), affecting ICE, ECE,
> EC, NJ, RE 7 and RB 27 — service has since resumed but residual
> delays/cancellations are possible through 05:00.

> **Q:** *Does Zürich HB have step-free access and WiFi?*
>
> **A:** Yes — Zürich HB has both step-free access and WiFi (elevators
> are also available).

> **Q:** *How busy is Lugano station compared to Bern?*
>
> **A:** Bern is much busier, averaging ~177,000 daily passengers in
> 2025 versus ~32,300 at Lugano — roughly 5.5× the foot traffic.

> **Q:** *I'm in Bern and want to grab a coffee in Olten for 30 minutes
> on my way to Basel SBB. Plan the journey.*
>
> **A:** Take the IR from Bern at 03:00 (platform 5), arriving Olten
> 03:27. Note: the next onward train to Basel SBB isn't until 04:48
> (platform 10AB, arriving 05:26), so your "30 min" coffee stretches to
> about 81 minutes at this hour — if you're traveling later in the day,
> ask again with a specific time and I'll find a tighter 30-min
> layover.

### Reproduce

```sh
cat > /tmp/swiss-mcp.json <<'EOF'
{ "mcpServers": { "swiss-rail": {
    "command": "uvx", "args": ["swiss-rail-mcp"]
}}}
EOF

claude -p \
  --mcp-config /tmp/swiss-mcp.json \
  "When's the next train from Bern to Zürich HB?"
```

Times and disruption counts will differ on each run — these were
captured live.

## Data sources

Two zero-auth sources. Both are unaffiliated with this project.

- [`transport.opendata.ch`](https://transport.opendata.ch) — proxies
  SBB's HAFAS timetable endpoints. Community-funded; be considerate
  with request volume.
- [`data.sbb.ch`](https://data.sbb.ch) — SBB's public Opendatasoft
  Explore API. Disruptions, station facilities, passenger frequency.

For SBB data that requires an API key (real-time delays, occupancy,
fares, train compositions, OJP, SIRI), see
[malkreide/swiss-transport-mcp](https://github.com/malkreide/swiss-transport-mcp).
This server stays zero-auth on purpose; see
[LIMITATIONS.md](LIMITATIONS.md) for the full list of what that excludes
and the heuristics used in its place.

## Development

```sh
uv sync
uv run pytest                          # all tests (hits live APIs)
uv run pytest -m "not integration"     # offline only
uv run ruff check
uv run ruff format --check
```

Enable verbose HTTP logging with `SWISS_TRANSPORT_DEBUG=1`.

## License

MIT — see [LICENSE](LICENSE).
