Skip to main content
Glama

idfm-mcp

CI Python 3.11+ License: MIT

An open-source MCP server that lets any AI assistant (Claude Desktop, Cursor, …) answer real-time questions about Paris public transport, using the official Île-de-France Mobilités PRIM API.

"When is the next RER B from Gentilly?" · "Are there disruptions on Metro line 7?" · "How do I get from Bastille to Orly at 18:30?"

Demo

Tools

Tool

What it does

PRIM API

search_stop(query)

Find stations by name, with their IDs and lines

Navitia /places

next_departures(stop_id, line=None)

Real-time departures: line, destination, minutes to wait, delay, platform

SIRI Lite /stop-monitoring

plan_journey(from_place, to_place, departure_time=None)

Up to 3 routes with duration, transfers and steps

Navitia /journeys

traffic_disruptions(line=None)

Current and planned incidents/works, most severe first

Navitia /line_reports

Related MCP server: TFL MCP Server for Poke

Architecture

flowchart LR
    U([User]) --> C["MCP client<br/>(Claude Desktop, Cursor)"]
    C <-->|"MCP over stdio"| S

    subgraph S["idfm-mcp"]
        direction TB
        SV["server.py<br/>4 tools · input resolution<br/>errors → ToolError"]
        F["formatting.py<br/>short LLM-readable text"]
        CL["client.py<br/>auth · timeouts · typed errors<br/>ID conversion · parsing"]
        M["models.py<br/>pydantic models"]
        SV --> CL
        SV --> F
        CL --> M
        F --> M
    end

    CL <-->|"HTTPS + apikey header"| P[("IDFM PRIM API<br/>SIRI Lite + Navitia")]

Each layer has one job: client.py is the only code that knows HTTP and raw JSON; server.py is the only code that knows MCP; formatting.py decides what the LLM reads. See docs/prim-api.md for the API details and gotchas.

Install

Requirements: uv and a free PRIM API key.

git clone https://github.com/Drathod-24/idfm-mcp.git
cd idfm-mcp
uv sync
cp .env.example .env    # then paste your key into .env

Get a key: create an account on prim.iledefrance-mobilites.fr, open Mes jetons d'authentification → API → Générer mon jeton. The token is shown only once.

Use with Claude Desktop

Open Settings → Developer → Edit Config (claude_desktop_config.json) and add:

{
  "mcpServers": {
    "idfm": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/idfm-mcp", "run", "idfm-mcp"],
      "env": { "IDFM_API_KEY": "your-prim-api-key" }
    }
  }
}

On Windows, use a path like "C:\\Users\\you\\idfm-mcp". Restart Claude Desktop; the tools appear under the 🔌 icon. (env is optional if the key is already in .env.)

Cursor uses the same JSON in ~/.cursor/mcp.json.

Test with MCP Inspector

# Interactive web UI
npx @modelcontextprotocol/inspector uv --directory . run idfm-mcp

# Or scripted (CLI mode)
npx @modelcontextprotocol/inspector --cli uv --directory . run idfm-mcp --method tools/list
npx @modelcontextprotocol/inspector --cli uv --directory . run idfm-mcp \
  --method tools/call --tool-name search_stop --tool-arg query=Gentilly

Example questions and answers

These are the tools' real output formats, produced from the test fixtures (times are illustrative):

"When is the next RER B from Gentilly?" → next_departures(stop_id="Gentilly", line="RER B")

Next departures at Gentilly (stop_area:IDFM:63244) (now 09:25):
- RER B → Aéroport CDG 2 TGV: now, 09:26
- RER B → Saint-Rémy-lès-Chevreuse: in 8 min, 09:33 (+2 min vs schedule, platform 2)

"Are there disruptions on Metro line 7?" → traffic_disruptions(line="Metro 7")

1 disruption(s) for Métro 7:
- [Métro 7] Métro 7 : trafic perturbé — reduced service
  En raison de travaux, le trafic est interrompu entre Place d'Italie et Villejuif. Des bus de remplacement sont mis en place.
  Until 05/11 23:00
(1 elevator/escalator notices not shown.)

"How do I get from Gentilly to Étoile at 9:30?" → plan_journey(from_place="Gentilly", to_place="Étoile", departure_time="09:30")

Routes from Gentilly to Étoile:

Option 1: 09:30 → 10:02 (32 min, 1 transfer)
  1. Walk 4 min to Gentilly
  2. RER B toward Aéroport CDG 2 TGV: Gentilly → Châtelet les Halles (09:34–09:45)
  3. Change: walk 5 min to Châtelet
  4. Métro 1 toward La Défense: Châtelet → Charles de Gaulle - Étoile (09:50–10:02)

Errors are answers too: a missing key, a bad stop ID, a 429 quota error or a timeout comes back as a clear MCP error (isError: true), never a crash:

IDFM API quota exceeded (default: 5 requests/second, 1000/day). Wait a bit before retrying.

Development

uv run pytest                # 68 tests; every PRIM call is mocked with respx, no key needed
uv run pytest -m live        # optional: ~5 real calls to check fixtures match the real API
uv run ruff check . && uv run ruff format --check .

CI (GitHub Actions) runs ruff, pytest and the eval dataset check on Python 3.11–3.13 on every push.

Evals

evals/questions.json has 20 natural-language questions (English and French, plus one off-topic) with the acceptable first tool call for each. The runner reads the tool definitions from the server itself, asks Claude which tool it would call, and scores tool name + arguments. It tests the docstrings, which are what an LLM actually sees.

uv run python evals/run_evals.py --offline          # validate the dataset (runs in CI)
uv run --group evals python evals/run_evals.py      # live run, needs ANTHROPIC_API_KEY

Category

Cases

Passed

Next departures

6

pending

Stop search

3

pending

Journey planning

5

pending

Disruptions

5

pending

Off-topic (no tool)

1

pending

Total

20

pending

Full per-question results: evals/results.md. The live eval has not been run yet.

Limitations

  • Real-time coverage is partial: PRIM does not provide live data for every stop yet; an empty list is a normal answer.

  • Default quota is 1,000 requests/day per API. next_departures uses 1–3 calls (name lookup, line names, departures). There is no caching yet.

  • Test fixtures are synthetic, built from the docs; run pytest -m live with a key to check them against the real API.

  • Line resolution is heuristic: a bare number ("7") is assumed to be a Metro line.

  • plan_journey takes the best match for free-text places and does not ask when names are ambiguous.

  • The eval tests only the first tool call, not multi-turn conversations or answer quality.

Next steps

  • Cache stop/line lookups (they rarely change) to save quota

  • Record real API responses as fixtures

  • Multi-turn evals: score the final answer, not just the routing

  • Streamable HTTP transport for remote deployment

  • Accessibility: expose elevator outages as their own tool

  • Publish to PyPI so it runs with uvx idfm-mcp

License

MIT. Data from Île-de-France Mobilités via PRIM, under the Mobility License / ODbL. This project is not affiliated with Île-de-France Mobilités, RATP or SNCF.

Available Tools

4 tools
next_departuresA
Read-only

Get the next real-time departures at a station, with minutes until departure.

Use for questions like "When is the next RER B from Gentilly?" or "Next metro at
Bastille?". Shows line, destination, minutes to wait, delay and platform.

Args:
    stop_id: Station ID from search_stop (e.g. "stop_area:IDFM:63244"). A plain
        station name also works; the best match is used.
    line: Optional line filter, e.g. "RER B", "Metro 7", "Tram T3a", "Bus 57".
        Leave empty to get all lines at the station.
NameRequiredDescriptionDefault
lineNo
stop_idYes

TDQS

A4.9/5.0
Behavior5/5

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

The description goes beyond the structured fields by explicitly listing the behavioral traits of the output: 'Shows line, destination, minutes to wait, delay and platform.' This provides valuable data context missing from the schema.

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?

The definition is highly efficient and front-loaded, though the argument definition section is a standard listing rather than prose flow.

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

Completeness5/5

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

The definition is highly complete, detailing the expected inputs (IDs vs. names) and the specific attributes returned (line, delay, platform), making it robust for complex real-time queries.

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

Parameters5/5

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

With 0% schema description coverage, the description must be comprehensive. It provides critical context that the stop_id can be a formal ID (e.g. 'stop_area:IDFM:63244') or a 'plain station name,' resolving ambiguity for the agent.

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?

The purpose is crystal clear, stating the tool gets 'next real-time departures at a station' and using specific questions (e.g., 'When is the next RER B?') to define its scope clearly against journey planning tools.

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

Usage Guidelines5/5

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

The description provides explicit usage patterns and examples, showing agents exactly what kind of queries this tool is intended to resolve versus general travel inquiries.

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

plan_journeyA
Read-only

Plan a public transport journey between two places in Île-de-France.

Use for "How do I get from X to Y?" or "Fastest way to Orly from Bastille at 18:00?".
Returns up to 3 options with total duration, number of transfers and each step
(lines to take, where to change, walking time).

Args:
    from_place: Origin: station name, address or landmark (e.g. "Gentilly",
        "Tour Eiffel", "10 rue de Rivoli Paris") or a stop ID from search_stop.
    to_place: Destination, same formats as from_place.
    departure_time: Optional, Paris local time: "18:30" (today) or
        "2026-11-05T18:30". Defaults to now.
NameRequiredDescriptionDefault
to_placeYes
from_placeYes
departure_timeNo

TDQS

A4.3/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. Beyond that the description adds genuinely useful disclosure: at most 3 options returned, the shape of each option (duration, transfers, step list) and that departure_time defaults to now in Paris local time. It says nothing about real-time versus scheduled data, ambiguous place-name handling, or error behavior.

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?

Front-loaded with what the tool does, then when to use it, then what it returns, then the arguments — a logical order with no filler sentences. The example values inside the Args block are doing real work rather than padding.

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 three-parameter tool with no output schema, the description covers inputs, defaults and the return shape, so an agent can call it confidently. The gap is behavioral edge cases: routing ambiguity resolution, real-time versus timetable data, and how it relates to the disruption sibling.

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

Parameters5/5

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

Schema description coverage is 0%, so the description carries the whole burden and does so well: from_place is defined as station name, address, landmark or a stop ID from search_stop, with examples; to_place inherits those formats; departure_time is documented as optional with both accepted formats and a 'defaults to now' rule. That fully compensates for the empty schema.

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?

The opening sentence names a specific verb and resource ('Plan a public transport journey') and pins the geographic scope to Île-de-France. That is enough to separate it from siblings like search_stop, next_departures and traffic_disruptions without opening any 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?

Real user phrasings ('How do I get from X to Y?', 'Fastest way to Orly from Bastille at 18:00?') make the intended trigger concrete. It stops short of naming alternatives — nothing says to use next_departures for a single stop's real-time board or traffic_disruptions for network status — so there is no explicit exclusion rule.

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

search_stopA
Read-only

Find Paris / Île-de-France stations and stops by name and return their IDs.

Use this first when the user names a station (e.g. "Gentilly", "Châtelet",
"Gare de Lyon") and you need its ID for next_departures. Returns up to 5 matches
with the lines serving each one, so you can pick the right station.

Args:
    query: Station or stop name, in French or English spelling. e.g. "Gentilly".
NameRequiredDescriptionDefault
queryYes

TDQS

A4.1/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds useful behavior beyond that — a capped result set of 'up to 5 matches with the lines serving each one' — but says nothing about behavior when zero matches are found or how ambiguous partial names are resolved, which is the main risk for a name-search tool.

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?

The intent and timing are front-loaded in the first two sentences, which is the right ordering. The trailing 'Args:' block partly repeats the station examples already given in the body ('Gentilly' appears twice), which is mild waste for a one-parameter tool.

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 single-parameter tool with no output schema, the definition covers what it does, when to call it, and roughly what comes back (up to 5 matches plus their lines). The notable omission is the no-match / ambiguity case, though the small surface area limits how much damage that causes.

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 description coverage is 0% — the schema only says 'Query: string' — so the description carries the burden and does add real meaning: the value is a station or stop name, accepted in French or English spelling, with worked examples ('Gentilly', 'Châtelet', 'Gare de Lyon'). It does not address partial matches or accents, but the core semantics are supplied.

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 Paris / Île-de-France stations and stops by name and return their IDs') and scopes it geographically. It also ties itself to a sibling by naming next_departures as the consumer of the returned IDs, so an agent can separate it from the other tools without opening a 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?

'Use this first when the user names a station... and you need its ID for next_departures' gives clear triggering context and a downstream dependency. It stops short of explicit exclusions — nothing says when to skip this in favor of plan_journey or traffic_disruptions, which matters since those siblings also take station input.

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

traffic_disruptionsA
Read-only

Get current traffic disruptions, incidents and works on the Île-de-France network.

Use for "Are there problems on Metro 7?", "Is the RER A running?" or "Any
disruptions right now?". Returns active and planned disruptions, most severe first.
Elevator/escalator outages are counted but not listed.

Args:
    line: Optional line, e.g. "Metro 7", "RER B", "Tram T2", "Transilien L".
        Leave empty for a network-wide summary.
NameRequiredDescriptionDefault
lineNo

TDQS

A4.3/5.0
Behavior4/5

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

Annotations already cover the safety profile (readOnlyHint, openWorldHint), so the description only needs to add context — and it does: results include both active and planned disruptions, are ordered most-severe-first, and elevator/escalator outages are counted but not listed. That last caveat is genuinely useful because it pre-empts an agent assuming a complete outage picture.

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?

Purpose and usage examples are front-loaded, and the args block is compact. The example questions are somewhat repetitive with the opening sentence, but each still earns its place as a concrete trigger, so there is little waste.

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

Completeness5/5

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

For a one-optional-parameter read tool with no output schema, the definition covers purpose, trigger examples, identifier format, default behavior, result ordering, and a content caveat. An agent has everything it needs to decide to call it and to construct a valid argument.

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

Parameters5/5

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

Schema description coverage is 0%, so the description must carry the whole burden for the single optional 'line' parameter, and it does: it supplies the canonical naming convention with four examples ('Metro 7', 'RER B', 'Tram T2', 'Transilien L') and defines the null/default behavior as a network-wide summary. This removes the main failure mode of a free-text line identifier.

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?

The description opens with a specific verb and resource — 'Get current traffic disruptions, incidents and works on the Île-de-France network' — plus a stated scope (active and planned). That is far more precise than the bare name 'traffic_disruptions', but it never names or contrasts the siblings (search_stop, next_departures, plan_journey), so an agent must infer the boundary itself.

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?

Three concrete example questions ('Are there problems on Metro 7?', 'Is the RER A running?', 'Any disruptions right now?') give a clear trigger context, and the args block states that leaving 'line' empty yields a network-wide summary. What is missing is any when-not guidance, e.g. that live departure times belong to next_departures or routing to plan_journey.

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. 4 tool updatesv0.1.0
    • First observednext_departures
    • First observedplan_journey
    • First observedsearch_stop
    • First observedtraffic_disruptions

TDQS

A4.3/5.0

Scored across 4 tools

Disambiguation5/5

Each tool maps to a clearly distinct transit task: station lookup, real-time departures, journey planning, and disruption status. There is little risk of misselection, and descriptions reinforce the boundaries between tools.

Naming Consistency3/5

Two tools follow a verb_noun pattern (search_stop, plan_journey), but next_departures is an adjective_noun phrase and traffic_disruptions is a noun_noun phrase. The names are readable and individually clear, but the set does not follow one predictable convention.

Tool Count5/5

Four tools is well-scoped for a focused Île-de-France transit query server. Each tool serves a distinct core user need, and there are no redundant or filler tools.

Completeness4/5

The server covers the main transit workflows: finding stops, checking next departures, planning journeys, and viewing disruptions. Minor gaps remain, such as line-level details, fare information, or accessibility-specific queries, but agents can handle core tasks without dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers