rhein-ruhr-fahrplan-mcp
MCP-Server für ÖPNV-Fahrplandaten im Rhein-Ruhr-Gebiet (Datenbasis: Open Data des Verkehrsverbunds Rhein-Ruhr). Auf PyPI: https://pypi.org/project/rhein-ruhr-fahrplan-mcp/
Zwei Datenpfeiler, beide keyless:
| Pfeiler | Quelle | Inhalt |
|---|---|---|
| Sollfahrplan (optional) | VRR-GTFS (Open Data ÖPNV, CKAN-API) → SQLite | Nur die konfigurierten Verkehrsunternehmen (FAHRPLAN_AGENCY_IDS); z. B. NIAG: 123 Routen, 6.975 Fahrten, 3.869 Haltestellen |
| Echtzeit (immer aktiv) | VRR-EFA efa.vrr.de |
Live-Abfahrten/-Ankünfte mit Prognose, Verspätungen, Steige, Störungshinweise – im gesamten Verbundgebiet |
Default = Live-Only: Ohne Konfiguration startet der Server sofort und liefert alle Live-Tools über die EFA-Schnittstelle. GTFS-Tools (Sollplan, Haltestellen-Index) melden dann einen klaren Hinweis mit Einrichtungsanleitung – bewusst so, weil ein ungefilterter Import die komplette Datenbank (~500 MB, ~7 Mio. Stop-Times) anlegen würde.
Tools (9)
| Tool | Quelle | Zweck |
|---|---|---|
next_departures |
EFA (live) | Live-Abfahrten mit Echtzeitprognose |
next_arrivals |
EFA (live) | Live-Ankünfte mit Echtzeitprognose |
plan_connection |
EFA (live) | Verbindungen von A nach B im Gesamtnetz |
line_disruptions |
EFA (live) | Aktuelle Störungen/Baustellen/Umleitungen |
find_stop |
GTFS → EFA-Fallback | Haltestelle finden (DHID auflösen) |
stop_schedule |
GTFS | Sollfahrplan (auch morgen/übermorgen) |
sync_status |
GTFS | Feed-Status: Version, Gültigkeit, Zeilenzahlen |
update_feed |
GTFS | Feed aktualisieren (nur bei neuer Ressource – idempotent) |
get_default_start |
GTFS → EFA | Konfigurierte Standard-Start-Haltestelle (FAHRPLAN_DEFAULT_START) auflösen |
Schnellstart
Variante A: uvx (stdio, ohne Docker)
uvx rhein-ruhr-fahrplan-mcp
Oder dauerhaft installiert:
uv tool install rhein-ruhr-fahrplan-mcp
rhein-ruhr-fahrplan-mcp
- Startet sofort (Live-Only). Für den GTFS-Sollplan einmalig die Umgebungsvariablen setzen (siehe unten) und
update_feedaufrufen bzw. Server neu starten – der Import läuft dann automatisch im Hintergrund (~1–2 min, 39 MB Download). - Datenbank-Pfad fest:
~/.local/share/rhein-ruhr-fahrplan-mcp/fahrplan.db(XDG, cwd-unabhängig; viaFAHRPLAN_DB_PATHüberschreibbar).
mcphub/Client-Registrierung (stdio-Typ):
{
"fahrplan-mcp": {
"type": "stdio",
"command": "uvx",
"args": ["rhein-ruhr-fahrplan-mcp"],
"env": {"FAHRPLAN_AGENCY_IDS": "nia-25"}
}
}
(env optional – ohne Agencies reiner Live-Only-Betrieb.)
Variante B: Docker (Streamable HTTP)
docker compose up -d --build
Im Hub als Streamable-HTTP-Server registrieren (Netz-Anbindung via compose.override.yaml, siehe Deployment):
http://fahrplan-mcp:8080/mcp
Konfiguration: Verkehrsunternehmen (FAHRPLAN_AGENCY_IDS)
Der GTFS-Import filtert auf die agency_ids des VRR-Feeds. Setze eine oder mehrere:
# z. B. nur NIAG (Kreise Kleve & Wesel):
export FAHRPLAN_AGENCY_IDS=nia-25
# oder mehrere Unternehmen:
export FAHRPLAN_AGENCY_IDS=nia-25,swk-02,dvg-20
in compose.yaml:
environment:
- FAHRPLAN_AGENCY_IDS=nia-25
# - FAHRPLAN_OPERATOR=NIAG # optional: Live-Abfahrten auch filtern
Ohne FAHRPLAN_AGENCY_IDS:
- Live-Tools (next_departures, next_arrivals, plan_connection, line_disruptions) funktionieren normal (ganzes Gebiet, alle Unternehmen)
- GTFS-Tools (find_stop mit Index, stop_schedule, sync_status) und der Auto-Import antworten mit einem Konfigurationshinweis statt zu importieren
Verkehrsunternehmen (Auswahl)
Die agency_ids stammen aus dem VRR-GTFS-Feed (agency.txt, Stand August 2026). Häufige Unternehmen:
| agency_id | Unternehmen | Gebiet/Angebot |
|---|---|---|
nia-25 |
Niederrheinische Verkehrsbetriebe | Bus, Kreise Kleve & Wesel |
dvg-20 |
Duisburger Verkehrsgesellschaft | Bus/Tram Duisburg |
swk-02 |
SWK MOBIL | Krefeld |
new-80 |
NEW mobil und aktiv M'gladbach | Mönchengladbach |
vvg-65 |
NEW mobil und aktiv Viersen | Viersen |
btm-70, rbg-70 |
Rheinbahn | Düsseldorf |
bgs-00, bgs-30–bgs-34 |
BOGESTRA | Bochum/Gelsenkirchen |
btm-13, eva-10–eva-12 |
Ruhrbahn | Essen/Mülheim |
dsw-36–dsw-39 |
DSW21 | Dortmund |
sto-15, btm-15, eva-15 |
STOAG | Oberhausen |
wsw-16, wsw-66 |
WSW mobil | Wuppertal |
hst-00, hst-50 |
Hagener Straßenbahn | Hagen |
sws-18 |
Stadtwerke Solingen | Solingen |
swr-16 |
Stadtwerke Remscheid | Remscheid |
ver-45 |
VER | Ennepe-Ruhr-Kreis |
ves-40 |
Vestische | Recklinghausen/Marl |
bvr-88 |
BVR Busverkehr Rheinland | Rheinland-Bus |
bsm-76 |
Bahnen der Stadt Monheim | Monheim |
hcr-35 |
Straßenbahn Herne-Castrop | Herne/Castrop |
swn-60 |
Stadtwerke Neuss | Neuss |
ddb-8003 |
DB Regio NRW | Regionalzug NRW |
ddb-NX |
National Express | Regionalzug Ruhrgebiet |
ddb-RR |
RheinRuhrBahn | Regionalzug |
ddb-R2 |
eurobahn | Regionalzug |
ddb-W3 |
WestfalenBahn | Regionalzug |
ddb-N2 |
NordWestBahn | Regionalzug |
ddb-M2 |
REGIOBAHN | Regionalzug |
Rail-Freight-/Charter-Einträge im Feed (Train Charter Services, Train Rental International, Ulmer Eisenbahnfreunde u. a.) sind für den Personenverkehr nicht relevant.
Komplette Liste: agency.txt aus dem aktuellen Feed (Download via CKAN-API, siehe unten) – alle 58 IDs mit Namen. Ein Blick per Python:
python3 -c "import csv; [print(r['agency_id'], '|', r['agency_name'])
for r in csv.DictReader(open('agency.txt'))]"
Architektur
┌─────────────────────── Server (stdio | http) ─────────────────────────┐
│ │
│ FastMCP │
│ ├── Live: EFA-Client (urllib, keyless) ← immer aktiv │
│ │ efa.vrr.de/standard/XML_DM_REQUEST (rapidJSON) │
│ │ XML_TRIP_REQUEST2, XML_STOPFINDER_REQUEST │
│ └── Soll: GTFS-SQLite (nur mit FAHRPLAN_AGENCY_IDS) ← Auto-Update │
│ CKAN-API (alle 6 h) → bei Neuheit: atomarer Import │
└────────────────────────────────────────────────────────────────────────┘
Eigenschaften
- Live-Only-Default: Ohne Konfiguration voll nutzbar (Live-Tools); GTFS-Teil schaltet sich mit
FAHRPLAN_AGENCY_IDSdazu. - Selbst-aktualisierend (mit Agencies): Daemon-Thread prüft alle
FAHRPLAN_FEED_CHECK_INTERVALSekunden (Default 6 h) die CKAN-API; Import nur bei neuer Ressource – atomar per.tmp+os.replace(). Initial- und Auto-Import laufen über dasselbe Lock (kein Doppel-Import). - Operator-Filter für Live-Daten:
FAHRPLAN_OPERATOR(z. B.NIAG) filtert next_departures/next_arrivals; plan_connection sortiert passend bevorzugt. Leer = alle Unternehmen. - Keine API-Keys, keine Accounts. VRR-Open-Data-Nutzungsbedingungen: Test-/Hobby-/Entwicklungsnutzung erlaubt; bei dauerhafter öffentlicher Nutzung Mail an openvrr@vrr.de.
Deployment (Docker)
Der HTTP-Modus lauscht nur im Container-Netz auf :8080/mcp (kein Host-Port). Für die Anbindung an einen MCP-Hub in einem bestehenden Docker-Netz eine lokale Datei compose.override.yaml neben compose.yaml anlegen:
services:
fahrplan-mcp:
networks:
- mein-hub-netz # Name des Netzes, in dem der Hub läuft
networks:
mein-hub-netz:
external: true
docker compose up -d merged das Override automatisch. Die Override-Datei ist umgebungsspezifisch und gehört nicht ins Repository (bereits in .gitignore).
Konfiguration (Env)
| Variable | Default | Bedeutung |
|---|---|---|
FAHRPLAN_AGENCY_IDS |
(leer) | Kommaseparierte GTFS-Agency-IDs für den Sollplan-Import. Leer = Live-Only; GTFS-Tools antworten mit Konfigurationshinweis |
FAHRPLAN_DEFAULT_START |
(leer) | Standard-Start-Haltestelle (DHID, EFA-ID oder eindeutiger Name – passende IDs liefert find_stop); abrufbar via get_default_start |
FAHRPLAN_OPERATOR |
(leer) | Live-Operator-Filter (z. B. NIAG); leer = alle Unternehmen |
FAHRPLAN_DB_PATH |
~/.local/share/rhein-ruhr-fahrplan-mcp/fahrplan.db |
SQLite-DB-Pfad (Dockerfile: /data/fahrplan.db) |
FAHRPLAN_MCP_TRANSPORT |
stdio |
stdio oder http (Dockerfile setzt http) |
FAHRPLAN_MCP_HOST / FAHRPLAN_MCP_PORT |
0.0.0.0:8080 (Dockerfile) |
nur http-Modus |
FAHRPLAN_FEED_CHECK_INTERVAL |
21600 (6 h) |
CKAN-Prüfung; 0 deaktiviert Auto-Update |
Verifizierte EFA-Eigenheiten (Pitfalls)
outputFormat=rapidJSON: Response-Keys heißenstopEvents/journeys(nichtdepartureList/trips).- Freie Haltestellennamen können mehrdeutig sein → Antwort mit
locations[]-Vorschlägen und leeren Events. Deshalb: IMMER per DHID/ID auflösen. …_Parent-Suffix aus GTFSparent_stationwird von EFA nicht akzeptiert → DHID ohne Suffix.- Bei Ankünften (
itdTripDateTimeDepArr=arr) heißen die Zeitfelder trotzdemdepartureTimePlanned/-Estimated. - Der EFA liefert alle Zeiten mit
Z-Suffix (UTC) – der Client konvertiert sie nach Europe/Berlin, bevor er sie ausgibt (früher zeigten die Tools versehentlich UTC, was wie ein ignorierterdeparture_time-Parameter aussah; gefixt in v1.2.1). - Verspätungen werden als UTC-Differenz berechnet – zeitzonenunabhängig korrekt.
- MS_REQUEST (Meldungs-Endpoint) liefert HTTP 400 → Störungen aus den eingebetteten
infos[]der DM-Antworten extrahieren. calcNumberRequestsgreift in rapidJSON nicht zuverlässig →plan_connectionkann mehr Verbindungen liefern alsmax_results(Ergebnisse korrekt, nur Begrenzung weich).
GTFS-Feed-Details
- CKAN-Dataset:
496eea5d-d6ef-4dc2-aeb0-d15c4fbf3178(„Soll-Fahrplandaten VRR“), API:https://opendata.ruhr/api/3/action/package_show?id=… - Ressourcen-UUID wechselt monatlich → Import ermittelt sie immer frisch per CKAN-API.
- Filter:
agency_id IN (FAHRPLAN_AGENCY_IDS); transitiv referenzierende Zeilen bleiben erhalten. stop_times.txtdes Gesamtfeeds (~7 Mio. Zeilen) wird gestreamt (erste Listen-Version wurde OOM-killed).- Ungefilterter Gesamtfeed wäre ~500 MB Datenbank – deshalb bewusst kein Default-Import.
Entwicklung
git clone … && cd rhein-ruhr-fahrplan-mcp
uv venv && source .venv/bin/activate
uv pip install -e .
rhein-ruhr-fahrplan-mcp # stdio starten (Live-Only)
FAHRPLAN_AGENCY_IDS=nia-25 rhein-ruhr-fahrplan-mcp # mit GTFS-Sollplan
python3 tests/stdio_e2e.py $(command -v rhein-ruhr-fahrplan-mcp) # stdio-E2E
FAHRPLAN_AGENCY_IDS=nia-25 python3 tests/stdio_fullcycle.py $(command -v rhein-ruhr-fahrplan-mcp) # inkl. Autoimport
Releases: Version in pyproject.toml hochziehen → Commit → git tag vX.Y.Z && git push origin vX.Y.Z – die GitHub-Action published automatisch nach PyPI (Trusted Publishing).
Metadata
Release files for rhein-ruhr-fahrplan-mcp 1.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rhein_ruhr_fahrplan_mcp-1.2.1.tar.gz | 25.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rhein_ruhr_fahrplan_mcp-1.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 51.1 kB
Release files / rhein_ruhr_fahrplan_mcp-1.2.1.tar.gz
| Download URL | rhein_ruhr_fahrplan_mcp-1.2.1.tar.gz |
|---|---|
| Size | 25.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
89acade432fc28b0b47069c2e67c181d962ba52a3555c86c66c196bc345bd72c
|
|
BLAKE2b-256 checksum How to use checksums |
06611b60fdeaac55d50020d186ae17438d90ce2880a2486eff5ca42179833301
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / rhein_ruhr_fahrplan_mcp-1.2.1-py3-none-any.whl
| Download URL | rhein_ruhr_fahrplan_mcp-1.2.1-py3-none-any.whl |
|---|---|
| Size | 25.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ebfc19b06a11fb23d317b9f279052f0b0c828f788d1c524c2e5cb66eafc6c752
|
|
BLAKE2b-256 checksum How to use checksums |
b6d9eeea8ec5cb3eaa45e08f1d80f85a4231f86d6a6c8503f124905836b75fab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log