Skip to main content
Glama

uestra-mcp

MCP-Server (stdio) für Fahrplandaten von ÜSTRA und GVH in der Region Hannover. Er stellt drei Tools bereit: Haltestellensuche, Live-Abfahrten mit Echtzeit und Verbindungssuche.

Repository: https://github.com/Schneggox/uestra-mcp

Datenquelle

Die Daten kommen von der EFA-Schnittstelle der Region Hannover (https://www.efa.de/efa, Betreiber HannIT/Region Hannover, Software MENTZ EFA). Abgefragt wird outputFormat=rapidJSON, ohne API-Key und ohne Anmeldung. Es wird nichts gescrapt.

Die Schnittstelle ist offen erreichbar, aber nicht offiziell dokumentiert. Parameter, Fehlercodes und Antwortformate können sich ohne Ankündigung ändern. Das Verhalten in diesem Server ist gegen die Live-Schnittstelle gemessen, nicht gegen eine Spezifikation.

Related MCP server: RMV MCP Server

Voraussetzungen

  • Node.js 20 oder neuer

  • Internetzugang zu www.efa.de

Setup

git clone https://github.com/Schneggox/uestra-mcp.git
cd uestra-mcp
npm install
npm run build

npm run build schreibt nach dist/ (nicht eingecheckt). Der Server startet mit npm start bzw. node dist/index.js; im Normalbetrieb startet ihn der MCP-Client selbst.

Smoke-Test

npm run smoke

Der Smoke-Test ruft ohne MCP-Client die Tool-Logik gegen die Live-EFA auf: Suche nach "Kröpcke", Abfahrten an de:03241:11 (Kröpcke) und eine Verbindung von de:03241:11 nach de:03241:31 (Hauptbahnhof). Er gibt die Ergebnisse als Text aus und endet mit Exit-Code 1, wenn eine Antwort leer ist oder ein Aufruf fehlschlägt.

Einbindung in Claude Code

claude mcp add -s user uestra -- node <pfad-zum-repo>/dist/index.js

Beispiel unter Windows (Vorwärts-Slashes):

claude mcp add -s user uestra -- node C:/Users/sk8er/ClaudeWorkspace/uestra-mcp/dist/index.js

Ohne -s user gilt der Standard-Scope local: der Server ist dann nur in dem Projektverzeichnis verfügbar, in dem der Befehl ausgeführt wurde. Kontrolle:

claude mcp list

Die Zeile zum Server muss so aussehen:

uestra: node C:/Users/sk8er/ClaudeWorkspace/uestra-mcp/dist/index.js - ✔ Connected

Entfernen: claude mcp remove uestra -s user.

Andere MCP-Clients

Für Clients mit mcpServers-Konfiguration (z. B. Claude Desktop, claude_desktop_config.json):

{
  "mcpServers": {
    "uestra": {
      "command": "node",
      "args": ["<pfad-zum-repo>/dist/index.js"]
    }
  }
}

Unter Windows den Pfad mit Vorwärts-Slashes oder doppelten Backslashes schreiben.

Tools

Jedes Tool liefert einen Text-Block (lesbar, Zeiten in Berliner Zeit) und zusätzlich structuredContent (maschinenlesbar). Zeitstempel im structuredContent sind ISO 8601 in UTC (...Z), nicht Berliner Zeit. Bei Fehlern kommt isError: true mit dem Text Fehler: <Meldung>.

Eine Haltestelle kann überall als Name oder als globale ID im Format de:03241:11 angegeben werden. Namen werden über die Haltestellensuche automatisch zur ID des besten Treffers aufgelöst (Treffer aus der Region Hannover vor allen anderen) und führen ohne Treffer zu Fehler: Keine Haltestelle zu "…" gefunden; eine unbekannte de:-ID bei departures zu Fehler: Haltestelle <ID> unbekannt; wo es auf Genauigkeit ankommt, vorher mit stop_search die ID bestimmen.

Sucht Haltestellen nach Name. Liefert Name, Stop-ID, Ort, Typ und Koordinaten. Zurückgegeben werden nur Treffer vom Typ stop, sortiert nach Region (IDs de:03241:… zuerst), dann EFA-Bestwert. Ohne Treffer antwortet das Tool mit Keine Haltestellen gefunden.

Parameter

Typ

Default

Beschreibung

query

string, min. 1 Zeichen

(Pflicht)

Haltestellenname oder Teil davon

limit

Ganzzahl, 1 bis 25

8

Maximale Trefferzahl

Aufruf: { "query": "Kröpcke", "limit": 2 }

1. Hannover, Kröpcke  [de:03241:11]  Ort: Hannover  Typ: stop  Koord (lat, lon): 52.37448, 9.73858
2. Hannover, Kröpcke/Theaterstraße  [de:03241:1]  Ort: Hannover  Typ: stop  Koord (lat, lon): 52.37381, 9.74221

structuredContent: { "stops": [{ "id", "name", "locality", "type", "lat", "lon" }] }

departures

Live-Abfahrten an einer Haltestelle: Sollzeit, Echtzeit (bei Abweichung mit Verspätung in Minuten), Linie, Produkt, Ziel und Steig.

Parameter

Typ

Default

Beschreibung

stop

string, min. 1 Zeichen

(Pflicht)

Haltestellenname oder ID wie de:03241:11

limit

Ganzzahl, 1 bis 40

10

Maximale Zahl Abfahrten

Aufruf: { "stop": "Hauptbahnhof", "limit": 4 }

Abfahrten Hannover, Hauptbahnhof (de:03241:31)
12:31 -> 12:44 (+13 min)  ICE 857 InterCityExpress  (Zug)  -> S+U Berlin Hauptbahnhof [Gleis 1-8]  Steig 9
12:31 -> 12:44 (+13 min)  ICE 847 InterCityExpress  (Zug)  -> S+U Berlin Hauptbahnhof [Gleis 1-8]  Steig 9
12:42  Stadtbahn 13  -> Hannover/Fasanenkrug  Steig UB2
12:44  Stadtbahn 7  -> Hannover/Wettbergen  Steig UB1

Lesart: Soll -> Ist (Differenz) bei Abweichung, sonst nur die Sollzeit. Hinter der Linie steht das Produkt in Klammern, wenn es nicht schon im Linien-Namen vorkommt. ENTFAELLT markiert ausfallende Fahrten, (nur Fahrplan) Fahrten ohne Echtzeitdaten.

structuredContent: { "stop": { "id", "name" }, "departures": [{ "planned", "estimated", "delayMinutes", "line", "product", "destination", "platform", "cancelled", "realtime" }] }

journey_planner

Verbindungssuche zwischen zwei Haltestellen, mit Echtzeitdaten (GVH, ÜSTRA, DB). Standard ist Abfahrt jetzt.

Parameter

Typ

Default

Beschreibung

from

string, min. 1 Zeichen

(Pflicht)

Start: Haltestellenname oder de:-ID

to

string, min. 1 Zeichen

(Pflicht)

Ziel: Haltestellenname oder de:-ID

departure

string, optional

"now"

Abfahrtszeit, ISO 8601 (z. B. 2026-10-19T08:15) oder "now"

arrival

string, optional

nicht gesetzt

Gewünschte Ankunftszeit, ISO 8601. Schließt departure aus

results

Ganzzahl, 1 bis 6

3

Anzahl Verbindungen

Zeitangaben:

  • Ohne Zeitzone (2026-10-19T08:15) gilt die Eingabe als Berliner Ortszeit.

  • Mit Zone (Z, +02:00, +0200 oder +02) wird nach Berliner Zeit umgerechnet.

  • Erlaubt ist nur YYYY-MM-DD[THH:MM[:SS]][Zone] (Trenner T, t oder Leerzeichen, Stunde und Minute zweistellig). Alles andere wird mit Fehler: Zeitangabe nicht lesbar, erwartet YYYY-MM-DD[THH:MM][Zone] abgelehnt.

  • Nur ein Datum (2026-10-19) bedeutet 00:00 Uhr.

  • Leere oder nur aus Leerzeichen bestehende Werte zählen als nicht angegeben.

  • departure und arrival nie zugleich angeben; der Server antwortet sonst mit Fehler: Entweder "departure" oder "arrival" angeben, nicht beides.

Aufruf: { "from": "Kröpcke", "to": "Flughafen Hannover", "departure": "2026-10-19T08:15", "results": 2 }

Verbindungen Hannover, Kröpcke -> Langenhagen (Han), Hannover Flughafen
1) Mo., 19.10. Abfahrt 07:57, Ankunft 08:36, Dauer 39 min, Umstiege 1
   Stadtbahn 1 -> Langenhagen: Hannover/Kröpcke 07:57 bis Langenhagen/Zentrum 08:16
   Bus 470 -> Flughafen: Langenhagen Zentrum 08:21 bis Flughafen 08:36
2) Mo., 19.10. Abfahrt 08:24, Ankunft 08:53, Dauer 29 min, Umstiege 1
   Stadtbahn 13 -> Hannover/Fasanenkrug: Hannover/Kröpcke 08:24 bis Hannover Hauptbahnhof 08:26
   S-Bahn S5 -> Langenhagen Flughafen: Hannover Hauptbahnhof 08:35 bis Langenhagen Flughafen 08:53

Das Datum steht nur, wenn die Abfahrt nicht heute ist. Fußwege erscheinen als Fussweg A -> B (Dauer). Die Verbindungen werden nach Abfahrtszeit sortiert ausgegeben.

structuredContent: { "from": { "id", "name" }, "to": { "id", "name" }, "journeys": [{ "departure", "arrival", "durationSeconds", "interchanges", "legs": [{ "mode": "walk" | "transit", "line", "from", "to", "departure", "arrival", "destination", "durationSeconds" }] }] }. departure und arrival sind hier die Sollzeiten in UTC.

Bekannte Eigenheiten der EFA

  • Der Stopfinder meldet bei mehrdeutigen Namen den Fehler -8011 mit leerem Text und liefert trotzdem Treffer. Der Server behandelt das nicht als Fehler, solange Treffer vorhanden sind. Die Verbindungssuche löst Namen deshalb immer zuerst über den Stopfinder auf, weil Freitext direkt als Ziel mit -8011 scheitert.

  • Zeiten kommen mit Sekunden (z. B. Ankunft 10:39:50Z). Der Server kappt auf volle Minuten, damit keine scheinbaren Verspätungen von einer Minute entstehen, wo Soll und Ist in derselben Minute liegen.

  • Verbindungen sind von der EFA nach Rating sortiert, nicht nach Zeit, und es kommen teils mehr oder früher abfahrende Verbindungen zurück als angefragt (im Beispiel oben: Wunschzeit 08:15, erste Verbindung 07:57). Der Server sortiert nach Abfahrt und kappt dann auf results; der Zeitpunkt ist aber nur ein Richtwert.

  • Reine Fußweg-Verbindungen zählen als Verbindung und belegen einen der results-Plätze.

  • Die EFA gewichtet nicht nach Region (Messe/Ost liefert dort zuerst Friedrichshafen). Der Server bevorzugt deshalb Treffer mit ID de:03241:… (Kreisschlüssel der Region Hannover), dann den EFA-Bestwert, dann die Trefferqualität; Treffer außerhalb bleiben als Fallback erhalten (Hildesheim Hauptbahnhof findet Hildesheim). Im Zweifel mit stop_search die ID prüfen.

  • Ein Stopfinder-Aufruf ohne Treffer meldet -8020 mit leerem Text und leerer Trefferliste; der Server wertet das als leeres Ergebnis. Eine unbekannte de:-ID bei den Abfahrten liefert keine Fehlermeldung, sondern Fuzzy-Treffer fremder Haltestellen ohne Abfahrten; der Server meldet sie als unbekannt.

Lizenz

MIT, siehe LICENSE.

Available Tools

3 tools
departuresAbfahrten (live)B

Live departures at a stop in the Hannover region: planned time, real-time time (with delay in minutes), line, product, destination and platform. Accepts a stop name or a de: stop ID.

ParametersJSON Schema
NameRequiredDescriptionDefault
stopYesStop name (resolved automatically) or ID like "de:03241:11"
limitNoMax. number of departures

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden, and it does disclose that results are 'live' with real-time delay in minutes — useful since there is no output schema. It omits error behavior for unresolvable stops, whether live data can be missing/degraded, and any rate-limit or result-ordering details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the primary action and followed by the returned fields — every clause earns its place, and the field list compensates for the absent output schema. Slightly dense but no waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations and no output schema, the description usefully enumerates return fields and input flexibility, which covers the essentials. It still leaves gaps on failure modes (invalid stop), empty-result handling, and how limit affects output ordering, so it is adequate but not complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters (stop, limit) are already documented in the schema, including the 'de:03241:11' ID format and the 1–40 limit range. The description's note that a stop name is resolved automatically adds only marginal value beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — live departures at a stop — with geographic scope (Hannover region) and the exact fields returned (planned/real-time time, delay, line, product, destination, platform). It is distinguishable from stop_search and journey_planner by implication, but never names or contrasts them explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: an agent can infer this is the tool for departure boards, and 'stop name (resolved automatically)' hints that a prior stop_search lookup may be unnecessary. However, there is no explicit when-to-use, when-not-to-use, or routing to journey_planner for actual trip planning.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

journey_plannerVerbindungssucheA

Plan public-transport journeys in the Hannover region (GVH/ÜSTRA/DB) from one stop to another, with real-time data. Names are resolved to the best matching stop automatically. Default: departing now; set departure or arrival (ISO 8601, local Berlin time if no zone) to plan for a time.

ParametersJSON Schema
NameRequiredDescriptionDefault
toYesDestination stop name or de: ID
fromYesOrigin stop name or de: ID
arrivalNoDesired arrival time, ISO 8601. Mutually exclusive with departure
resultsNoNumber of journeys
departureNoDeparture time, ISO 8601 (e.g. "2026-10-19T08:15") or "now" (default)

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses that the data is real-time, that stop names are fuzzy-resolved to the best match automatically (so exact IDs are not required), and how timestamps are interpreted (local Berlin time when no zone). It still omits failure behavior for unresolvable names and any rate/auth constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences, front-loaded with the core action, then name resolution, then timing defaults. Every clause conveys a constraint or behavior; nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter read tool with no annotations and no output schema, the description adequately covers scope, resolution behavior, and time handling. It stops short of describing the shape of a returned journey or what happens when no stop matches, which leaves a small gap given the absence of an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not: the timezone fallback ('local Berlin time if no zone') for departure/arrival and the fact that from/to accept free-text names rather than requiring de: IDs. This meaningfully improves how an agent fills the parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a concrete verb+resource+scope: plan public-transport journeys from one stop to another in the Hannover GVH/ÜSTRA/DB network. This is clearly distinct from the sibling stop_search (stop lookup) and departures (departure board) without naming them, because the resource is a route between two stops.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit operating context: default is depart-now, and either departure or arrival can be set to plan for a specific time, with arrival/departure being mutually exclusive. It does not, however, say when to prefer this over stop_search or departures, so there is no alternative routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observeddepartures
    • First observedjourney_planner
    • First observedstop_search

TDQS

A3.8/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct task: stop_search resolves stop IDs, departures returns live departure boards, and journey_planner handles routing between stops. There is no meaningful overlap—finding a stop, checking departures, and planning a trip are clearly separate actions.

Naming Consistency3/5

All names are snake_case, but the patterns differ: stop_search (noun_verb), departures (bare plural noun), and journey_planner (noun_noun). The lack of a single verb_noun convention makes the set less predictable, though each name is still readable and descriptive.

Tool Count4/5

Three tools map cleanly onto the core transit workflow (find stop, view departures, plan journey) with no redundancy. The surface is slightly thin—no coordinate-based or nearby-stop lookup—but each tool earns its place.

Completeness4/5

The set covers the essential lifecycle: resolving stops, real-time departures, and journey planning with time parameters. Minor gaps exist (no nearby-stop-by-coordinates search or service-disruption/line-info lookup), but agents can work around them using stop_search.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers