Skip to main content
Glama

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:

  1. 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.

  2. 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.

  3. 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 the MOBILITEIT_API_KEY environment 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 build

For 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.js

MCP_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 tools
find_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
languageNofr
latitudeYesLatitude in degrees
longitudeYesLongitude in degrees
radiusMetersNo

TDQS

A3.9/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateNoLocal travel date in YYYY-MM-DD format
timeNoLocal 24-hour time in HH:mm or HH:mm:ss format
limitNo
stopIdYes
languageNofr
durationMinutesNo

TDQS

A3.9/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 2 tool updatesv0.2.1
    • First observedfind_nearby_stops
    • First observedget_departures

TDQS

A3.9/5.0

Scored across 2 tools

Disambiguation5/5

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.

Naming Consistency5/5

Both names use a consistent snake_case verb_noun form (find_nearby_stops, get_departures) with well-chosen verbs that match their actions.

Tool Count3/5

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.

Completeness3/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers