Mobiliteit.lu MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Mobiliteit.lu MCPFind stops near me and show next departures."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Mobiliteit.lu MCP
An open-source Model Context Protocol server for Luxembourg public-transport data from the Mobiliteit.lu HAFAS API. It provides nearby-stop lookup, departures, and change notifications for subscribed stops.
The upstream API exposes real-time data as HTTP request/response snapshots. This server polls those snapshots and notifies MCP subscribers when departure data changes; this is near-real-time polling, not an upstream push feed.
Unofficial project. This server is an independent community project. It is not affiliated with, endorsed by, or operated by mobiliteit.lu, the Luxembourg Ministry of Mobility and Public Works, the Administration des transports publics, or HaCon/HAFAS.
Get an API key
You need your own API key before the server can return any data. This
server does not contain transport data. It forwards each request to the
mobiliteit.lu HAFAS open-data API at https://cdt.hafas.de/opendata/apiserver/,
which rejects requests without a valid key (HTTP 401). No key is
included with this project; each deployment should use its own.
Keys are issued by the Administration des transports publics (ATP) on request:
Email opendata-api@atp.etat.lu and ask for an API key (
accessId) for the mobiliteit.lu open-data API. Say briefly what you are building and roughly how many requests you expect; see Quotas below.Wait for the ATP to reply with your key, its request quota, and the usage terms. Check that those terms allow what you plan to do, especially if you will host the server for other people.
Store the key outside the repository and give it to the server, either in a file referenced by
MOBILITEIT_API_KEY_FILE(preferred) or in theMOBILITEIT_API_KEYenvironment variable. See configuration and client setup.
Never commit the key, paste it into prompts or chat, or include it in issue
reports. .env.example documents the supported settings but is not loaded
automatically. Open-sourcing this server does not grant API access or rights
to redistribute the provider's data. The ATP publishes the data under
CC BY 4.0; credit the
ATP as the data source in anything you build on it.
Related MCP server: RMV MCP Server
Quotas
Each unique active stop feed is shared by its subscribers and polled once per
interval. Defaults are a 30-second interval and one active stop feed. One
continuously active feed at that interval makes about 2,880 upstream requests
per day, before ordinary lookups and retries. Increase
MOBILITEIT_MAX_ACTIVE_FEEDS only after confirming the quota issued for your
API key. All upstream calls (tool calls, resource reads and polling) also share
a rolling cap, MOBILITEIT_MAX_REQUESTS_PER_MINUTE (default 60), so one client
cannot exhaust the key's quota. Failed upstream requests are logged to stderr;
existing clients are not given fabricated or silently substituted results.
Transports and clients
Requires Node.js 22 or newer.
npm install
npm test
npm run typecheck
npm run buildFor a hosted endpoint, see the step-by-step VPS deployment guide, or the serverless Vercel option for a tools-only endpoint without live subscriptions. It deploys the app with Docker Compose and Caddy-managed HTTPS. This is the recommended route for a trusted pilot; the current HTTP authentication is one shared bearer token, not per-user OAuth.
The default transport for a local installation is MCP over stdio:
node dist/src/index.jsMCP_TRANSPORT supports stdio, http, or both. HTTP listens on
127.0.0.1:3000 by default at /mcp; it requires
MCP_HTTP_BEARER_TOKEN or MCP_HTTP_BEARER_TOKEN_FILE. Do not expose the
service directly to the public internet without HTTPS and suitable
authentication.
Cursor and Claude Desktop can launch the stdio server. Streamable HTTP enables remote clients where their MCP implementation supports it. Ollama support depends on the MCP host used with the model.
MCP tools and resources
find_nearby_stops: look up stops by WGS84 coordinates.get_departures: request departures for a stop, including real-time data where supplied by the API.mobiliteit://stops/{stopId}/departures: a resource clients can read and subscribe to. The server sends standard MCP resource-update notifications when a polled snapshot changes; clients can then read the resource again.
The polling interval can be configured with
MOBILITEIT_POLL_INTERVAL_SECONDS (minimum 15 seconds). The maximum number of
distinct simultaneously active feeds is configured with
MOBILITEIT_MAX_ACTIVE_FEEDS.
Security
See SECURITY.md for how to report a vulnerability privately.
License
The server source code is licensed under Apache-2.0. This license does not apply to the upstream transport data, the HAFAS API, or their trademarks and usage terms.
More documentation
Available Tools
2 toolsfind_nearby_stopsFind nearby public transport stopsA
Find public transport stops near a WGS84 coordinate in Luxembourg. Use the returned stop id with get_departures or subscribe to its departure resource.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| language | No | fr | |
| latitude | Yes | Latitude in degrees | |
| longitude | Yes | Longitude in degrees | |
| radiusMeters | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It does disclose that the result is usable as a stop id and that a departure resource exists, but it says nothing about result ordering, how radius/limit shape the answer, rate limits, or auth requirements. Adequate but thin for an annotation-free tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, front-loaded with the core action and scope, then the follow-up usage. No filler, nothing redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and no annotations, the description does the important work of identifying the geographic domain, the coordinate system, and the primary return value (stop id) plus its downstream use. It stops short of explaining radius/limit semantics, but is otherwise sufficient for a straightforward lookup.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 40% — limit, language, and radiusMeters have no descriptions anywhere, and the description adds no parameter guidance at all (latitude/longitude and WGS84 are already in the schema). With five parameters and low coverage, the description should compensate and does not.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Find public transport stops') with both the geographic scope ('near a WGS84 coordinate') and the coverage area ('in Luxembourg'). It also implicitly distinguishes itself from the sibling by describing its role as the stop-discovery step that feeds get_departures.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The second sentence gives clear workflow context: take the returned stop id into get_departures or subscribe to its departure resource. That routes the agent to the correct alternative, though it never states when not to use this tool (e.g., if you already have a stop id) — a minor gap given there is only one sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_departuresGet departures from a stopA
Get scheduled and real-time public transport departures for a Mobiliteit.lu stop id. Use the id returned by find_nearby_stops.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | Local travel date in YYYY-MM-DD format | |
| time | No | Local 24-hour time in HH:mm or HH:mm:ss format | |
| limit | No | ||
| stopId | Yes | ||
| language | No | fr | |
| durationMinutes | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It usefully discloses that results mix scheduled and real-time data, but says nothing about rate limits, authentication, pagination, or how stale the real-time feed may be.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler, with the core action front-loaded and the dependency on find_nearby_stops placed second where it belongs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and no annotations, the description should do more: it never explains the shape of a departure result, the limit/duration window behavior, or the language enum. It is enough to call the tool, but not enough to use its output confidently.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 33% (date and time are documented in the schema, not in the description). The description adds genuinely useful provenance for the required stopId, but leaves limit, language, and the durationMinutes departure window entirely unexplained in both places.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Get scheduled and real-time public transport departures') scoped to a named provider and id type. It explicitly names the sibling find_nearby_stops as the source of the id, so an agent can tell the two tools apart without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear prerequisite/entry point: use the id returned by find_nearby_stops, which tells the agent how to obtain the required parameter. It does not state any when-not condition or alternative for retrieving departures, but with only one sibling, the routing guidance is adequate.
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.
2 tool updates
v0.2.1- First observed
find_nearby_stops - First observed
get_departures
TDQS
Scored across 2 tools
The two tools target clearly distinct actions: locating stops by coordinate versus retrieving departures for a known stop id. Their descriptions even cross-reference each other, so an agent can't confuse which to call first.
Both names use a consistent snake_case verb_noun form (find_nearby_stops, get_departures) with well-chosen verbs that match their actions.
Two tools is thin for a public transport server, which normally implies routing, lines, and schedules. The pair covers a narrow departure-board workflow but leaves the broader domain unaddressed.
Stop lookup and departures form a workable read-only core, but there's no journey planning, line/route info, or name-based stop search — notable gaps for a mobility domain.
Maintenance
Related MCP Connectors
Keyless MCP access to official Luxembourg public data: laws, statistics, mobility, and more.
Read-only public transit departures, stop search, and city coverage for bus and train users.
Real-time transit stops, routes, arrivals, vehicle positions, and schedules via OneBusAway APIs.
Provide real-time transportation data including bus arrivals, train service alerts, carpark availa…
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceProvides real-time Dutch Railways (NS) data for journey planning, live departures, disruptions, and station search.3-
- FlicenseNot gradedqualityDmaintenanceEnables querying nearby stations and departure boards using the RMV public transport API.1-
- FlicenseNot gradedqualityDmaintenanceEnables querying Swiss public transport (trains, buses, trams, boats) for stations, connections, and station boards via the opendata.ch API.-
- AlicenseAqualityDmaintenanceProvides Estonian public transport timetables, trip planning, stop search, and real-time vehicle tracking.8MIT