io.github.malkreide/swiss-road-mobility-mcp
GBFS shared mobility, EV charging, DATEX II traffic, Park & Rail
Open source Open in the app JSON README (API)
About
GBFS shared mobility, EV charging, DATEX II traffic, Park & Rail
Details
- Kind
- MCP servers
- Topic
- No topic detected
- Publisher
- malkreide
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.5.4
- Stars
- 1
- Forks
- 1
- Last push
- 2026-08-30T08:14:43Z
- Repository state
- ativo
- Language
- Python
- License
- MIT
- Added
- 2026-08-29 04:00:27
- Updated
- 2026-08-29 04:00:27
- Origin id
io.github.malkreide/swiss-road-mobility-mcp
README
> π¨π **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide/swiss-public-data-mcp)**
# π Swiss Road & Mobility MCP Server

[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
[](https://modelcontextprotocol.io/)

> MCP Server for Swiss road mobility β shared vehicles, EV charging, traffic alerts, Park & Rail, and multimodal trip planning
[π©πͺ Deutsche Version](README.de.md)
---
## Demo

---
## Overview
`swiss-road-mobility-mcp` provides AI-native access to Swiss road and mobility data sources:
| Source | Data | API | Auth |
|--------|------|-----|------|
| **sharedmobility.ch** | Bikes, e-scooters, cars (GBFS) | REST/JSON | None |
| **ich-tanke-strom.ch** | EV charging stations | GeoJSON | None |
| **opentransportdata.swiss** | Traffic events, counting stations | DATEX II / SOAP+XML | Free key |
| **data.sbb.ch** | Park & Rail facilities | REST/JSON (Opendatasoft) | None |
| **transport.opendata.ch** | Public transport connections | REST/JSON | None |
| **geo.admin.ch** | Address geocoding, road classification | REST/JSON | None |
If the Swiss Transport MCP is the GA pass for rail, this server is the vignette + Park & Rail card + sharing subscription for the road β together they paint the complete multimodal picture of Swiss mobility.
**Anchor demo query:** *"I'm in Dietikon with my car. I need to get to Bern. Where can I park? Which train should I take?"*
---
## Features
- **15 tools** across six data sources (Phase 1β4)
- **`road_mobility_snapshot`** β aggregated mobility overview for any location
- **`road_multimodal_plan`** β car + Park & Rail + public transport in one plan
- No API key required for 12 of 15 tools
- Dual transport β stdio (Claude Desktop) + SSE (cloud)
- Rate limiting + caching for all endpoints
---
## Prerequisites
- Python 3.11+
- [uv](https://github.com/astral-sh/uv) (recommended) or pip
---
## Installation
```bash
# Clone the repository
git clone https://github.com/malkreide/swiss-road-mobility-mcp.git
cd swiss-road-mobility-mcp
# Install
pip install -e .
# or with uv:
uv pip install -e .
```
Or with `uvx` (no permanent installation):
```bash
uvx swiss-road-mobility-mcp
```
---
## Quickstart
```bash
# stdio (for Claude Desktop)
swiss-road-mobility-mcp
# or:
python -m swiss_road_mobility_mcp.server
# SSE (for cloud / Render.com)
MCP_TRANSPORT=sse MCP_PORT=8001 swiss-road-mobility-mcp
```
Try it immediately in Claude Desktop:
> *"Show me everything mobility-related at Zurich HB."*
> *"Find shared bikes near Bern Bahnhof."*
> *"Where can I charge my EV near Lucerne?"*
β [More use cases by audience](EXAMPLES.md) β
---
## Configuration
### Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"swiss-road-mobility": {
"command": "uvx",
"args": ["swiss-road-mobility-mcp"],
"env": {
"OPENTRANSPORTDATA_API_KEY": "<your-token>"
}
}
}
}
```
Or with `python`:
```json
{
"mcpServers": {
"swiss-road-mobility": {
"command": "python",
"args": ["-m", "swiss_road_mobility_mcp.server"],
"env": {
"OPENTRANSPORTDATA_API_KEY": "<your-token>"
}
}
}
}
```
> Shared mobility, EV charging, Park & Rail, and the multimodal planner work **without** an `OPENTRANSPORTDATA_API_KEY`. The key is only required for the DATEX II traffic tools.
**Config file locations:**
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
### Cloud Deployment (SSE for browser access)
For use via **claude.ai in the browser** (e.g. on managed workstations without local software):
**Render.com (recommended):**
1. Push/fork the repository to GitHub
2. On [render.com](https://render.com): New Web Service -> connect GitHub repo
3. Set start command: `MCP_TRANSPORT=sse MCP_PORT=8001 swiss-road-mobility-mcp`
4. In claude.ai under Settings -> MCP Servers, add: `https://your-app.onrender.com/sse`
#### SSE security (SEC-009)
A public SSE endpoint is reachable by anyone. To protect it (and your upstream
API quota), configure these environment variables on the host:
| Variable | Effect |
|---|---|
| `MCP_AUTH_TOKEN` | When set, every SSE request must send `Authorization: Bearer <token>`. **Unset = unauthenticated** (the server logs a loud warning at startup). Strongly recommended for any public deployment. |
| `MCP_RATE_LIMIT` | Max requests per client IP per window (default `60`, `0` disables). |
| `MCP_RATE_WINDOW` | Window length in seconds (default `60`). |
| `ALLOWED_ORIGINS` | Comma-separated CORS origins for browser clients. **Empty by default: no cross-origin access.** `*` allows any origin and is logged as a warning. Does not affect stdio or other non-browser clients. |
The local **stdio** transport needs none of this β it runs in the user's
trusted context.
#### Observability (OBS-003 / OBS-006)
| Variable | Effect |
|---|---|
| `MCP_LOG_LEVEL` | Log level: `DEBUG` / `INFO` (default) / `WARNING` / `ERROR`. |
| `MCP_LOG_FORMAT` | `text` (default) or `json` for structured logs. All logs go to **stderr**. |
| `MCP_TRACING_ENABLED` | `true` enables OpenTelemetry tracing (off by default). |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector URL; setting it also enables tracing. Standard `OTEL_*` vars are honoured by the SDK. |
Tracing needs the optional extra: `pip install 'swiss-road-mobility-mcp[tracing]'`.
Once enabled, every upstream API call is traced automatically (httpx
instrumentation) and SSE requests get server spans with W3C trace-context
propagation.
---
## Available Tools
### Shared Mobility & EV Charging (no API key required)
| Tool | Description | Cache |
|------|-------------|-------|
| `road_find_sharing` | Shared mobility nearby (bikes, e-scooters, cars) | 60s |
| `road_search_sharing` | Search sharing stations by name | 5min |
| `road_sharing_providers` | All sharing providers in Switzerland | 1h |
| `road_find_charger` | EV charging stations nearby | 5min |
| `road_charger_status` | Real-time availability of charging stations | 1min |
| `road_check_status` | Server & API health check | - |
### Traffic (free API key required)
| Tool | Description | Cache |
|------|-------------|-------|
| `road_traffic_situations` | Accidents, roadworks, congestion from ASTRA/VMZ-CH | 2min |
| `road_traffic_counters` | Vehicles/h + km/h at counting stations near a position | 1min |
| `road_counter_sites` | List counting stations nearby | 24h |
### Park & Rail + Multimodal (no API key required)
| Tool | Description | Cache |
|------|-------------|-------|
| `road_park_rail` | Find SBB Park+Rail facilities nearby | 5min |
| `road_mobility_snapshot` | Complete mobility overview for a location | - |
| `road_multimodal_plan` | Plan car -> Park+Rail -> public transport -> destination | - |
### Geography & Addresses β Phase 4 (no API key required)
| Tool | Description | Cache |
|------|-------------|-------|
| `road_geocode_address` | Swiss address -> GPS (official building address register) | - |
| `road_reverse_geocode` | GPS -> official address with EGID/EGAID (GWR) | - |
| `road_classify_road` | Road classification via swissTLM3D | - |
### Example Use Cases
| Query | Tool |
|-------|------|
| *"Find shared bikes near Zurich HB"* | `road_find_sharing` |
| *"Where can I charge my EV near Bern?"* | `road_find_charger` |
| *"Any traffic incidents on the A1?"* | `road_traffic_situations` |
| *"Where can I park near Winterthur station?"* | `road_park_rail` |
| *"Plan my trip from Dietikon to Bern by car + train"* | `road_multimodal_plan` |
---
## API Key for Traffic Tools
1. Register: <https://api-manager.opentransportdata.swiss>
2. Create a new application -> subscribe to the "Strassenverkehr" API
3. Copy the token
```bash
export OPENTRANSPORTDATA_API_KEY=<your-token>
```
Without a key, the traffic tools return a descriptive error message including the exact registration link β no crash.
---
## Architecture
```
swiss_road_mobility_mcp/
βββ server.py # FastMCP server, 15 tools
βββ api_infrastructure.py # Rate limiter, cache, HTTP client, geo utilities
βββ shared_mobility.py # sharedmobility.ch
βββ ev_charging.py # ich-tanke-strom.ch
βββ traffic_situations.py # DATEX II traffic alerts (SOAP/XML)
βββ traffic_counters.py # DATEX II counting stations (SOAP/XML)
βββ park_rail.py # SBB Open Data Park & Rail
βββ multimodal.py # Snapshot + trip planner (cross-source)
βββ geo_admin.py # geo.admin.ch geocoding + road classification
```
### Data Source Characteristics
| Source | Protocol | Coverage | Auth |
|--------|----------|----------|------|
| sharedmobility.ch | REST/JSON (GBFS) | All CH sharing providers | None |
| ich-tanke-strom.ch | GeoJSON | All public EV chargers | None |
| opentransportdata.swiss | DATEX II / SOAP+XML | ASTRA traffic data | Free key |
| data.sbb.ch | REST/JSON (Opendatasoft) | SBB Park & Rail | None |
| transport.opendata.ch | REST/JSON | Public transport schedules | None |
| geo.admin.ch | REST/JSON | Official addresses, roads | None |
**In-depth docs:** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) (MCP primitives,
tool namespacing, use-case catalogue, phase roadmap) Β·
[`docs/SECURITY.md`](docs/SECURITY.md) (credential model, egress allow-list, auth,
MCP conformance table) Β·
[`docs/OPERATIONS.md`](docs/OPERATIONS.md) (resource limits, restart policy, health).
---
## Project Structure
```
swiss-road-mobility-mcp/
βββ src/swiss_road_mobility_mcp/
β βββ __init__.py # Package
β βββ server.py # FastMCP server, 15 tools
β βββ api_infrastructure.py # Rate limiter, cache, HTTP client
β βββ shared_mobility.py # Shared vehicles
β βββ ev_charging.py # EV charging stations
β βββ traffic_situations.py # Traffic events
β βββ traffic_counters.py # Vehicle counting
β βββ park_rail.py # Park & Rail
β βββ multimodal.py # Snapshot + trip planning
β βββ geo_admin.py # Geocoding + roads
βββ tests/
β βββ test_integration.py # Live API tests
β βββ test_phase3.py # Park & Rail + multimodal tests
βββ .github/workflows/ci.yml # GitHub Actions (Python 3.11/3.12/3.13)
βββ pyproject.toml
βββ CHANGELOG.md
βββ CONTRIBUTING.md # Contribution guide (English)
βββ CONTRIBUTING.de.md # Contribution guide (German)
βββ SECURITY.md # Security policy (English)
βββ SECURITY.de.md # Security policy (German)
βββ LICENSE
βββ README.md # This file (English)
βββ README.de.md # German version
```
---
## Known Limitations
- **Shared Mobility:** The `sharedmobility.ch` API does not enforce strict radius filtering; vehicles slightly outside the specified radius may appear
- **EV Charging:** Station naming conventions vary between operators; some stations may appear without detailed names
- **Traffic (DATEX II):** Requires a free API key; without it, traffic tools return helpful error messages
- **Park & Rail:** SBB occasionally renames endpoints; the server includes a fallback chain
- **Multimodal Planner:** Response time depends on the slowest of the queried sources
---
## MCP Protocol Version
This server speaks **two protocol eras** over the same endpoint. The client's
first request on a connection decides which one applies; a later claim from the
other era is refused.
| Era | Revision | Who reaches it |
|---|---|---|
| `initialize` handshake | `2024-11-05` β¦ **`2025-11-25`** | What today's clients speak. The server answers with the revision asked for, or with the `2025-11-25` ceiling when the request asks for something newer. |
| Per-request envelope | **`2026-07-28`** | A request carrying the `2026-07-28` `_meta` envelope opens a modern connection. |
Both revisions are pinned in
[`tests/test_protocol_version.py`](tests/test_protocol_version.py) and asserted
against the installed SDK, so a Dependabot bump of `mcp` cannot move either one
silently. This server builds no ASGI app to send an `initialize` through, so
the gate asserts the SDK constants rather than a measured response β the
weaker form, named rather than left unsaid.
Note that the SDK's `LATEST_PROTOCOL_VERSION` is an alias for the **modern**
era, not for the handshake era β pinning against it alone would leave the era
that current clients actually negotiate free to drift.
**Update policy.** When the gate fails, do not edit the constant blindly: read
the spec changelog between the two revisions, verify the server still behaves,
then move the constant, this section, `README.de.md` and
[`CHANGELOG.md`](CHANGELOG.md) together.
---
## Testing
```bash
# All tests
pytest tests/ -v
# Quick check (without pytest)
python tests/test_phase3.py
```
---
## Safety & Limits
- **Read-only:** All tools perform HTTP GET requests only β no data is written, modified, or deleted on any upstream system.
- **No personal data:** Location coordinates passed as tool inputs are not stored, logged, or forwarded beyond the immediate API request. API responses contain no PII β only vehicle counts, charger availability, traffic events, and geographic metadata.
- **Rate limiting:** The server enforces client-side rate limits (Shared Mobility: 30 req/60s; EV Charging: 10 req/60s) to protect upstream APIs. The DATEX II key is subject to opentransportdata.swiss fair-use terms.
- **Caching:** Responses are cached in-process (Sharing: 60s Β· EV: 5 min Β· Park+Rail: 5 min Β· Traffic: 1β2 min). Real-time data reflects the cache age, not necessarily the current second.
- **Terms of service:** Data is subject to the ToS of each upstream source β [sharedmobility.ch](https://sharedmobility.ch), [ich-tanke-strom.ch](https://ich-tanke-strom.ch), [opentransportdata.swiss](https://opentransportdata.swiss), [data.sbb.ch](https://data.sbb.ch) (CC BY), [geo.admin.ch](https://www.geo.admin.ch/de/geo-dienstleistungen/geodienste/terms-of-use.html) (BGDI).
- **No guarantees:** This server is an independent community project, not affiliated with SBB, ASTRA, sharedmobility.ch, or any API provider. Availability depends on upstream services.
---
## Changelog
See [CHANGELOG.md](CHANGELOG.md)
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)
---
## Security
See [SECURITY.md](SECURITY.md) for the security posture and vulnerability reporting.
---
## License
MIT License β see [LICENSE](LICENSE)
---
## Author
Hayal Oezkan Β· [malkreide](https://github.com/malkreide)
---
## Credits & Related Projects
- **sharedmobility.ch:** [sharedmobility.ch](https://sharedmobility.ch/) β Swiss shared mobility platform
- **ich-tanke-strom.ch:** [ich-tanke-strom.ch](https://ich-tanke-strom.ch/) β Swiss EV charging network
- **ASTRA / opentransportdata.swiss:** [opentransportdata.swiss](https://opentransportdata.swiss/) β Federal traffic data
- **SBB Open Data:** [data.sbb.ch](https://data.sbb.ch/) β Swiss Federal Railways
- **geo.admin.ch:** [geo.admin.ch](https://api3.geo.admin.ch/) β Federal geospatial services
- **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) β Anthropic / Linux Foundation
- **Related:** [swiss-transport-mcp](https://github.com/malkreide/swiss-transport-mcp) β Public transport (trains, buses, trams)
- **Related:** [zurich-opendata-mcp](https://github.com/malkreide/zurich-opendata-mcp) β 900+ datasets from the City of Zurich
- **Portfolio:** [Swiss Public Data MCP Portfolio](https://github.com/malkreide/swiss-public-data-mcp)
<!-- mcp-name: io.github.malkreide/swiss-road-mobility-mcp -->
<!-- BEGIN GENERATED: install -->
## Installation
Run via [`uv`](https://docs.astral.sh/uv/)'s `uvx` β no clone or manual install needed. Add to your MCP client config (`mcpServers` for Claude Desktop, Cursor and Windsurf; use a top-level `servers` key for VS Code in `.vscode/mcp.json`):
```json
{
"mcpServers": {
"swiss-road-mobility-mcp": {
"command": "uvx",
"args": [
"swiss-road-mobility-mcp"
]
}
}
}
```
<!-- END GENERATED: install -->