SBB Open Data MCP Server
This server exposes 9 read-only tools for querying public Swiss Federal Railways (SBB) open data from data.sbb.ch — no API key required — so an AI model can look up passenger stats, live disruptions, infrastructure, rolling stock and station info.
Passenger frequency (
sbb_get_passenger_frequency): daily, workday and non-workday passenger averages per station and year, filterable by station name, canton ('ZH', 'BE') and year.Live rail disruptions (
sbb_get_rail_disruptions): current Swiss rail traffic messages (title, description, cause, timing, affected lines), refreshed every 5 minutes.Real-estate projects (
sbb_get_real_estate_projects): SBB property development projects with city, phase, construction/move-in dates and floor area.Trains per segment (
sbb_get_trains_per_segment): yearly train counts per route segment for SBB, BLS, SOB, DB and others, split by passenger/freight traffic.Platform data (
sbb_get_platform_data): platform length, gross/net area, type and step-free accessibility per station.Rolling stock (
sbb_get_rolling_stock): vehicle types with seating capacity (1st/2nd class), year built, length and weight.Station comparison (
sbb_compare_stations): side-by-side comparison of 2–10 stations across passenger frequency and platform metrics.Stop search (
sbb_search_stations): search the full Swiss DiDok stop register (all operators) by name and canton, returning UIC numbers and coordinates.Dataset catalogue (
sbb_list_datasets): list all ~89 SBB open datasets with IDs, titles, record counts and update frequency.Common options across tools: markdown or JSON output, pagination (limit/offset), and MCP
structuredContentplus declaredoutputSchemafor programmatic clients.
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., "@SBB Open Data MCP ServerAre there any current disruptions on the Swiss rail network?"
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.
🚆 sbb-opendata-mcp
🇨🇭 Part of the Swiss Public Data MCP Portfolio
MCP server connecting AI models to Swiss Federal Railways (SBB) open data – passenger frequency, live rail disruptions, infrastructure & real-estate projects, train counts, platform data, rolling stock and station search from data.sbb.ch. No API key required.
Demo
Overview
sbb-opendata-mcp gives AI assistants like Claude direct access to public SBB data – no copy-pasting or manual API calls. A question like "How many passengers passed through Zürich HB every day in 2024?" is answered with real measured data.
The SBB Open Data portal speaks the OpenDataSoft REST API (v2.1). This server
translates it into clean Markdown and JSON for the AI model, and adds MCP
structuredContent alongside the human-readable text so programmatic clients can
consume the underlying records without re-parsing. The server is model-agnostic
and works with any MCP-compatible client.
Anchor demo query: "Compare Zürich HB, Bern and Basel SBB by passenger frequency and platform capacity." → More use cases by audience →
Related MCP server: swiss-rail-mcp
Features
📊 Passenger frequency – boardings/alightings by station and year (daily averages)
🚨 Live rail disruptions – traffic messages, updated every 5 minutes
🏗️ Infrastructure projects – station and line construction
🏢 Real-estate projects – SBB property development (daily updates)
🚆 Trains per segment – train counts per route (SBB, BLS, SOB …)
🛤️ Platform data – length, type, area, step-free access
🚃 Rolling stock – capacity and year built
🔁 Station comparison – up to 10 stations across multiple datasets
🔍 Stop search – Swiss DiDok register (all of Switzerland)
📦 Dataset catalogue – list all ~89 SBB open datasets
🔑 No API key – all data is public and free to use
☁️ Dual transport – stdio for Claude Desktop, Streamable HTTP for cloud deployment
Prerequisites
Python 3.11+
No API key — all data comes from the public data.sbb.ch portal
Install uv (recommended):
curl -LsSf https://astral.sh/uv/install.sh | shInstallation
From PyPI:
pip install sbb-opendata-mcpOr with uvx (no permanent installation):
uvx sbb-opendata-mcpFor local development, install from a clone in editable mode:
git clone https://github.com/malkreide/sbb-opendata-mcp.git
cd sbb-opendata-mcp
pip install -e ".[dev]"Quickstart
# Start the server (stdio mode for Claude Desktop)
sbb-opendata-mcpTry it immediately in Claude Desktop:
"How many people boarded at Zürich HB daily in 2024?" "Are there any current disruptions on the Swiss rail network?"
Configuration
Environment Variables
The server needs no configuration to run over stdio. The variables below tune the optional Streamable HTTP transport, logging and observability.
Variable | Effect | Default |
| Bind host for the HTTP transport. Keep |
|
| Port for the HTTP transport. |
|
| Comma-separated host allow-list for DNS-rebinding protection (e.g. | localhost only |
| Comma-separated browser-origin allow-list (e.g. | (none) |
| Log verbosity ( |
|
|
|
|
🔒 DNS-rebinding / Origin protection is always on; localhost is allow-listed so local HTTP development works out of the box. Logs go to stderr — stdout is reserved for the stdio JSON-RPC channel.
Claude Desktop Configuration
{
"mcpServers": {
"sbb-opendata": {
"command": "uvx",
"args": ["sbb-opendata-mcp"]
}
}
}Config file locations:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Restart Claude Desktop — the server is downloaded automatically on first use.
Other MCP Clients
Works with Cursor, Windsurf, VS Code + Continue, LibreChat, Cline and self-hosted
models via mcp-proxy — same configuration as above.
Cloud Deployment (Streamable HTTP)
For use via claude.ai in the browser or remote servers (e.g. Render.com). The cloud transport is Streamable HTTP (endpoint /mcp).
Docker (recommended):
# Build + run with explicit resource limits (see docker-compose.yml)
docker compose up --build
# → http://127.0.0.1:8000/mcpThe image is a multi-stage build running as a non-root user; docker-compose.yml
adds read_only, no-new-privileges and memory/CPU/PID limits.
Manual / Render.com:
pip install -e .
# Bind publicly (behind a rate-limiting reverse proxy) and configure
# DNS-rebinding / Origin protection for your hostname:
export MCP_HOST=0.0.0.0
export MCP_ALLOWED_HOSTS="your-app.onrender.com,your-app.onrender.com:*"
export MCP_ALLOWED_ORIGINS="https://your-app.onrender.com"
python -m sbb_opendata_mcp.server --http --port 8000⚠️ Binding: In a network transport the server binds to
127.0.0.1by default so a locally started server is not exposed to your whole network. SetMCP_HOST=0.0.0.0only in a container/cloud environment where binding to all interfaces is intended (the Docker image does this for you), and place the server behind a reverse proxy that enforces rate limiting (and authentication, if the endpoint should not be public). SeeSECURITY.md.
Available Tools
Tool | Description | Data Update |
| Boardings/alightings by station and year (daily avg.) | Annual |
| Live rail traffic messages | Every 5 min. |
| SBB real estate development projects | Daily |
| Train counts per route segment (SBB, BLS, SOB …) | Annual |
| Platform data (length, type, area) | Ongoing |
| Rolling stock (capacity, year built) | Ongoing |
| Compare up to 10 stations (multi-dataset) | – |
| Search stops (Swiss DiDok register, all CH) | Ongoing |
| List all ~89 SBB open datasets | – |
All tools support response_format: "markdown" (human-readable) and "json"
(machine-readable), plus pagination. Every tool also returns MCP structuredContent
(the underlying records/metadata) alongside the rendered text, and declares its shape
as outputSchema. Error results carry isError: true and
{error, upstream_unavailable} instead.
Example Use Cases
Query | Tool |
"How many people boarded at Zürich HB daily in 2024?" |
|
"Are there any current disruptions on the Swiss rail network?" |
|
"Compare Zürich HB, Bern and Basel SBB" |
|
"Which SBB real-estate construction projects are running?" |
|
"How many trains run yearly on the Zürich–Winterthur route?" |
|
"Which stops exist in Wädenswil?" |
|
Architecture
┌─────────────────┐ ┌───────────────────────────┐ ┌──────────────────────────┐
│ Claude / AI │────▶│ SBB Open Data MCP │────▶│ data.sbb.ch │
│ (MCP Host) │◀────│ (MCP Server) │◀────│ │
└─────────────────┘ │ │ │ OpenDataSoft REST v2.1 │
│ 9 Tools │ │ (public, no API key) │
│ Stdio | Streamable HTTP │ │ │
│ │ │ passagierfrequenz │
│ Shared httpx client │ │ rail-traffic-information │
│ (pooled, lifespan-managed)│ │ construction-projects │
│ ODSQL escaping + Pydantic │ │ perron · rollmaterial │
│ validation │ │ zugzahlen · dienststellen│
└───────────────────────────┘ └──────────────────────────┘Project Structure
sbb-opendata-mcp/
├── src/sbb_opendata_mcp/
│ ├── __init__.py
│ └── server.py # FastMCP server, all 10 tool definitions
├── tests/
│ └── test_server.py # Unit + live API smoke tests
├── audits/ # MCP best-practice audit evidence
├── docs/assets/demo.svg # README demo asset
├── .github/workflows/ci.yml # GitHub Actions (Python 3.11/3.12/3.13)
├── Dockerfile # Multi-stage, non-root runtime image
├── docker-compose.yml # Local run with resource limits
├── claude_desktop_config.json # Example Claude Desktop config
├── pyproject.toml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── SECURITY.md
├── EXAMPLES.md
├── LICENSE
├── README.md # This file (English)
└── README.de.md # German versionSafety & Limits
Read-only: All 9 tools perform read-only HTTP GET requests — no data is written, modified, or deleted upstream.
No personal data: Queries are transient and not stored. The portal returns aggregated statistics, infrastructure and operational metadata. No PII is processed or retained.
No API key: Data is public and free. There is no authentication and no secret to manage.
Injection-hardened:
year/cantonare regex-validated and every value interpolated into an ODSQLwhereclause is escaped via a central helper.Data freshness: Real-time tools (disruptions) reflect the upstream source at query time; statistical datasets update annually/daily (see the tool table).
Terms of service: Data is published under the data.sbb.ch licence (NonCommercialAllowed-CommercialAllowed-ReferenceRequired).
No guarantees: This server is a community project, not affiliated with SBB. Availability depends on the upstream API.
See SECURITY.md for the full security posture.
Known Limitations
Passenger frequency: Updated annually; the latest full year may lag by some months.
Rail disruptions: Returns all current Swiss rail messages → use
limitand pagination.Trains per segment: Counts are yearly aggregates, not real-time.
Station search: Covers the full Swiss DiDok register (all operators), not just SBB.
No rate limiting of its own: Place a public HTTP deployment behind a rate-limiting reverse proxy.
MCP Protocol Version
This server speaks two protocol eras over the same endpoint. The client's first request on a connection decides which one applies; a later claim from the other era is refused.
Era | Revision | Who reaches it |
|
| What today's clients speak. The server answers with the revision asked for, or with the |
Per-request envelope |
| A request carrying the |
Both revisions are pinned in
tests/test_protocol_version.py and asserted
against the installed SDK, so a Dependabot bump of mcp cannot move either one
silently. Two wire tests send a real request per era through
mcp.streamable_http_app() — the app --http serves — and read the revision
from the response: a 2026-07-28 server/discover POST without session, and an
initialize asking for 2026-07-28 that comes back capped at 2025-11-25.
What the server itself contributes to 2026-07-28, beyond what the SDK does on
its own, is pinned in
tests/test_spec_2026_07_28.py: a complete
serverInfo (the modern era stamps it on every result — without version it
read ""), a top-level title on every tool, an outputSchema per tool that
both the server and the SDK client validate against, and ttlMs/cacheScope
on the listing methods.
Note that the SDK's LATEST_PROTOCOL_VERSION is an alias for the modern
era, not for the handshake era — pinning against it alone would leave the era
that current clients actually negotiate free to drift.
Update policy. When the gate fails, do not edit the constant blindly: read
the spec changelog between the two revisions, verify the server still behaves,
then move the constant, this section, README.de.md and
CHANGELOG.md together.
Testing
No API key is required.
# Unit tests (no network required)
PYTHONPATH=src pytest tests/ -m "not live"
# Live API smoke tests (require network access to data.sbb.ch)
PYTHONPATH=src pytest tests/ -m live
# Re-record the fixtures from data.sbb.ch (writes tests/fixtures/PROVENANCE.md)
python scripts/record_fixtures.pyThe unit-test payloads are recorded, not invented. Source, retrieval date,
selection rule and SHA-256 per file are in
tests/fixtures/PROVENANCE.md.
tests/fixtures/dataset_fields.json is not a data excerpt but the contract:
the Explore v2.1 API declares each dataset's field names, and a select or
order_by on a field it does not have is answered with HTTP 400 — not with
fewer columns. TestFieldContract holds every field name the server uses
against that declaration, so the next rename fails a test instead of a user's
request. Until 2026-08-08 three of ten tools were permanently broken for
exactly this reason.
Live tests are not run by CI (
-m "not live"). Two of those three broken tools had live tests covering them —test_live_search_waedenswilandtest_live_list_datasets. The coverage existed; the run did not.
Changelog
See CHANGELOG.md
Contributing
See CONTRIBUTING.md
Security
See SECURITY.md (Deutsch) for the security posture and how to report a vulnerability.
License
MIT License — see LICENSE
Author
Hayal Oezkan · github.com/malkreide
Credits & Related Projects
Data: data.sbb.ch – Swiss Federal Railways (SBB) Open Data, OpenDataSoft REST API v2.1
Protocol: Model Context Protocol – Anthropic / Linux Foundation
Related:
swiss-transport-mcp – real-time timetables, journeys & disruptions (opentransportdata.swiss)
swiss-road-mobility-mcp – micromobility & EV charging
zurich-opendata-mcp – MCP server for Zurich city open data
Portfolio: Swiss Public Data MCP Portfolio
Installation
Run via uv's uvx — no clone or manual install needed. Add to your MCP client config (mcpServers for Claude Desktop, Cursor and Windsurf; use a top-level servers key for VS Code in .vscode/mcp.json):
{
"mcpServers": {
"sbb-opendata-mcp": {
"command": "uvx",
"args": [
"sbb-opendata-mcp"
]
}
}
}Available Tools
9 toolssbb_compare_stationsSBB Bahnhöfe vergleichenARead-onlyIdempotent
Vergleicht mehrere SBB-Bahnhöfe anhand Passagierfrequenz und Perrondaten.
Kombiniert drei Datensätze (Passagierfrequenz, Bahnhofnutzer, Perrons) zu einem kompakten Vergleich. Nützlich für Planungsentscheide, Präsentationen und Standortanalysen.
Args: params (CompareStationsInput): Parameter: - stations (list[str]): Liste von Bahnhofsnamen (2–10), z.B. ['Zürich HB', 'Bern'] - year (Optional[str]): Vergleichsjahr, z.B. '2024'
Returns: str: Tabellarischer Vergleich der Bahnhöfe mit Passagierfrequenz und Perron-Kennzahlen.
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| year | Yes | |
| stations | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare this as a read-only, idempotent, non-destructive, open-world operation. The description adds useful behavioral context by explaining that it combines three datasets and returns a compact tabular comparison, but it does not cover rate limits, data freshness handling, or authentication requirements.
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?
The description is front-loaded with the core purpose and then organized into Args and Returns sections. It is appropriately sized, though some material such as the Args section partially duplicates schema information and the Returns section overlaps with the output schema.
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?
For a read-only comparison tool with rich annotations and an output schema, the description is largely complete: it states the comparison basis, the combined datasets, and the output nature. Its main gap is not mentioning the response_format option, but the schema and output schema cover most remaining details.
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?
The description names the main inputs (stations and year) and repeats the 2–10 station constraint, but much of this is already present in the schema. It omits the response_format parameter entirely, even though that parameter controls whether output is markdown or JSON, so it only partially compensates for the low top-level schema description coverage.
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 ('Vergleicht mehrere SBB-Bahnhöfe') and specifies the comparison dimensions (Passagierfrequenz und Perrondaten). It clearly distinguishes itself from single-dataset siblings like sbb_get_passenger_frequency and sbb_get_platform_data by comparing multiple stations and combining three datasets, though it does not name those alternatives explicitly.
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 clear usage contexts ('Planungsentscheide, Präsentationen und Standortanalysen'), which tells the agent when this tool is appropriate. It does not state when not to use it or explicitly compare it with alternatives such as sbb_get_passenger_frequency, so it falls short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_get_passenger_frequencySBB Passagierfrequenz nach BahnhofARead-onlyIdempotent
Ruft Passagierfrequenzdaten (Ein-/Aussteigende) für SBB-Bahnhöfe ab.
Datensatz wird jährlich aktualisiert. Enthält Tagesschnitt (DTV), Werktagesschnitt (DWV) und Nicht-Werktages-Schnitt (DNWV) pro Bahnhof und Jahr.
Args: params (PassengerFrequencyInput): Filterparameter: - station_name (Optional[str]): Bahnhofsname (Teilsuche) - canton (Optional[str]): Kantonskürzel, z.B. 'ZH' - year (Optional[str]): Jahr, z.B. '2024' - limit (int): Max. Resultate (1–100), Standard 20 - offset (int): Offset für Paginierung - response_format (str): 'markdown' oder 'json'
Returns: str: Passagierfrequenzdaten mit DTV/DWV-Werten und Paginierungsinfo. Schema: {station, year, daily_avg, workday_avg, non_workday_avg, canton, operator}
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | Records as returned by data.sbb.ch |
| pagination | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Die Annotationen decken bereits readOnly, idempotent, openWorld und non-destructive ab. Die Beschreibung ergänzt nützlichen Kontext: jährliche Aktualisierung, enthaltene Kennzahlen (DTV/DWV/DNWV), Rückgabeschema und Paginierungshinweis. Angaben zu Auth oder Rate Limits fehlen, sind aber angesichts der Annotationen nicht kritisch.
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?
Die Zweckbeschreibung steht vorne, gefolgt von strukturierten Args- und Returns-Abschnitten. Die Länge ist für ein Tool mit sechs Parametern angemessen, wenngleich die Parameterliste das Schema teilweise wiederholt.
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?
Für ein Datensuchtool mit Output-Schema, klaren Annotationen und sechs Filterparametern ist die Beschreibung weitgehend vollständig: sie erklärt Zweck, Aktualisierungsfrequenz, Filter und Rückgabefelder. Es fehlt lediglich eine Einordnung, wann dieses Tool gegenüber den Geschwistern zu verwenden ist.
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?
Da die Schema-Beschreibungsabdeckung als 0% angegeben ist, muss die Beschreibung die Parameter erklären. Sie listet alle Filter (station_name, canton, year, limit, offset, response_format) mit Typen, Beispielen und Standardwerten auf und kompensiert damit die Lücke, auch wenn sie teilweise das Schema dupliziert.
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?
Die Beschreibung nennt ein klares Verb (abrufen) und eine spezifische Ressource (Passagierfrequenzdaten für SBB-Bahnhöfe). Sie grenzt sich inhaltlich von Geschwistern wie sbb_get_trains_per_segment oder sbb_compare_stations ab, benennt aber keine konkreten Alternativen.
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?
Es wird nicht gesagt, wann dieses Tool gegenüber Alternativen wie sbb_compare_stations oder sbb_search_stations zu wählen ist. Die Filterparameter implizieren eine Nutzung, aber es fehlen explizite Einsatzbedingungen oder Abgrenzungen.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_get_platform_dataSBB PerrondatenBRead-onlyIdempotent
Ruft Perrondaten (Länge, Fläche, Typ) für SBB-Bahnhöfe ab.
Enthält Perronlänge (m), Netto-/Bruttofläche (m²), Perrontyp und Angaben zur schienenfreien Zugänglichkeit.
Args: params (PlatformDataInput): Parameter: - station_name (Optional[str]): Bahnhofsname, z.B. 'Zürich HB' - platform_type (Optional[str]): Perrontyp, z.B. 'Mittelperron' - limit (int): Max. Resultate - offset (int): Paginierung - response_format (str): 'markdown' oder 'json'
Returns: str: Perrondaten mit Länge, Fläche und Typ. Schema: {station, platform_nr, type, length_m, gross_area_m2, net_area_m2, accessible}
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | Records as returned by data.sbb.ch |
| pagination | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, openWorldHint=true, and destructiveHint=false, so the safety profile is fully covered. The description adds that results can be paginated (limit/offset) and returned as markdown or json, but discloses no rate limits, auth requirements, or data-freshness caveats beyond the annotations.
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?
Purpose is front-loaded and the Args block is useful given the low schema coverage, but the Returns section repeats a field schema that an output schema already defines, which is redundant. Structure is clear (purpose / args / returns) but not maximally economical.
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?
For a read-only tool with rich annotations and an existing output schema, the definition covers purpose and parameters adequately. The only substantive gap is the absence of usage guidance versus sibling tools, which the annotations cannot supply.
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 reported at 0%, so the description carries the burden and largely does: it glosses station_name, platform_type, limit, offset, and response_format, and even supplies enum values ('markdown' oder 'json') and examples ('Zürich HB'). It slightly duplicates schema examples, but meaningfully compensates for the documentation gap.
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: 'Ruft Perrondaten (Länge, Fläche, Typ) für SBB-Bahnhöfe ab', and enumerates what the data contains (length, net/gross area, type, barrier-free access). Clear enough to distinguish from siblings like passenger frequency or rolling stock, though it never names an alternative explicitly.
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 description says what the tool returns but offers no when-to-use guidance, no prerequisites, and no mention of alternatives among the eight sibling tools. The agent must infer the usage context entirely.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_get_rail_disruptionsSBB Bahnverkehrsstörungen (Live)ARead-only
Ruft aktuelle Bahnverkehrsstörungen und -meldungen ab (alle 5 Minuten aktualisiert).
Enthält Titel, Beschreibung, Ursache, Start-/Endzeitpunkt und betroffene Linien. Ideal für Echtzeit-Monitoring und Kommunikation bei Ereignissen.
Args: params (RailDisruptionsInput): Parameter: - limit (int): Max. Meldungen (1–100), Standard 20 - offset (int): Offset für Paginierung - response_format (str): 'markdown' oder 'json'
Returns: str: Liste aktueller Störungsmeldungen mit Zeitfenstern und Beschreibungen. Schema: {title, description, published, start, end, type, author}
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | Records as returned by data.sbb.ch |
| pagination | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, destructiveHint=false, openWorldHint=true and non-idempotence, so the safety profile is covered. The description adds the 5-minute refresh cadence, which is genuinely useful freshness context, but says nothing about rate limits, auth requirements, or failure behavior for a live open-world source.
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?
The prose leads with purpose and freshness, which is well front-loaded, but the Returns block restates the output schema that already exists and the Args block duplicates schema descriptions, adding length without new information.
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?
An output schema exists and annotations cover the safety profile, so the description need not explain return values (its restatement is redundant but harmless). With a single required params object and live data, the definition gives enough for an agent to call it correctly, missing only guidance on alternates and error handling.
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 reported as 0% at the top level, but the Args block mirrors the nested property descriptions (limit 1-100 default 20, offset pagination, response_format markdown/json) without adding new semantics such as pagination interaction or format trade-offs. It compensates for the coverage gap at a minimum-viable level.
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 ('Ruft aktuelle Bahnverkehrsstörungen und -meldungen ab') with a clear scope qualifier (live, updated every 5 minutes). None of the sibling tools cover rail disruptions, so an agent can distinguish it without opening the 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?
'Ideal für Echtzeit-Monitoring und Kommunikation bei Ereignissen' implies the intended use case, but there is no explicit when-to-use vs when-not guidance and no named alternative among the siblings. Usage is inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_get_real_estate_projectsSBB Immobilien-BauprojekteARead-onlyIdempotent
Ruft laufende SBB-Immobilien-Bauprojekte (Wohn- und Geschäftsbauten) ab.
Täglich aktualisiert. Enthält Projektname, Stadt, Bauphase, Nutzfläche, Baudaten und Beschreibungen (DE/FR/IT/EN).
Args: params (RealEstateProjectsInput): Parameter: - city (Optional[str]): Stadt, z.B. 'Zürich', 'Bern', 'Luzern' - phase (Optional[str]): Projektphase, z.B. 'CONSTRUCTION', 'PLANNING' - limit (int): Max. Resultate - offset (int): Paginierung - response_format (str): 'markdown' oder 'json'
Returns: str: Immobilien-Bauprojekte mit Baustart, Einzug und Nutzfläche. Schema: {title, city, phase, start_of_construction, move_in, area_m2, description}
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | Records as returned by data.sbb.ch |
| pagination | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint, and destructiveHint=false, so the safety profile is fully covered by structured data. The description adds genuinely useful non-redundant context — daily refresh cadence and multilingual descriptions (DE/FR/IT/EN) — but omits auth requirements, rate limits, and pagination behavior, and partly restates the output fields.
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?
Purpose is front-loaded well, but the boilerplate Args and Returns blocks largely duplicate what the input and output schemas already define, adding length without new meaning. The Returns section in particular restates fields the output schema already carries.
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?
For a read-only, annotated, output-schema-backed data tool, the definition covers purpose, cadence, language coverage, filter dimensions, and pagination. Complete enough to invoke correctly, with only the phase-enum mismatch and absent auth/limit details as nits.
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 reported at 0%, so the description must carry the parameter burden, and it mostly does: city with concrete examples, phase with example values, limit/offset semantics, and the two response_format options. Minor gap: the phase examples ('CONSTRUCTION', 'PLANNING') omit 'COMPLETED' which the nested schema lists, a small inconsistency an agent could trip over.
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 (abrufen/retrieve), a precise resource (laufende SBB-Immobilien-Bauprojekte), and scopes it to Wohn- und Geschäftsbauten. Nothing in the sibling list overlaps with real estate projects, so an agent can immediately tell this apart from the station, rolling-stock, or disruption tools.
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?
Usage is implied by the resource itself, and the daily-update note hints at freshness expectations. However, there is no explicit when-to-use guidance, no statement of prerequisites, and no exclusions or alternatives named. For a standalone tool with no close sibling this is acceptable but thin.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_get_rolling_stockSBB RollmaterialARead-onlyIdempotent
Ruft technische Daten zum SBB-Rollmaterial (Züge, Triebzüge, Wagen) ab.
Enthält Fahrzeugtyp, Sitzplatzkapazität (1./2. Kl.), Baujahr, Länge und Gewicht.
Args: params (RollingStockInput): Parameter: - vehicle_type (Optional[str]): Fahrzeugtyp, z.B. 'IC 2000', 'TGV', 'FV-Dosto' - limit (int): Max. Resultate - offset (int): Paginierung - response_format (str): 'markdown' oder 'json'
Returns: str: Rollmaterial-Daten mit Kapazität, Baujahr und technischen Kennzahlen. Schema: {vehicle_type, built_year, seats_1st, seats_2nd, total_seats, length_mm, weight_t}
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | Records as returned by data.sbb.ch |
| pagination | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint, so the safety profile is covered externally. The description adds only the field-level content of the response; it discloses no auth needs, rate limits, or pagination caveats beyond the bare offset/limit args.
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?
Front-loaded purpose followed by Args/Returns blocks. Some redundancy between the Args list and the actual schema, but given the 0% schema coverage that restatement is justified rather than wasteful.
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?
An output schema exists, so the description need not explain return values, yet it helpfully lists the response fields. For a simple read-only list query with full annotation coverage and simple params, the definition is complete enough to invoke correctly.
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 0%, so the description carries the param burden and it does so reasonably: it explains vehicle_type with concrete examples, limit as max results, offset as pagination, and response_format as markdown/json. The explanations are terse ('Max. Resultate') but functional.
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 (retrieves) and a well-defined resource (technical data on SBB rolling stock: trains, multiple units, coaches) and lists the contained fields. It is clearly distinct from all siblings (disruptions, stations, frequency), though it 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the data it returns, but there is no explicit when-to-use, when-not-to-use, or alternative-tool guidance relative to the eight sibling tools. The agent must infer that this is the tool for vehicle technical specifications.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_get_trains_per_segmentSBB Züge pro StreckenabschnittBRead-onlyIdempotent
Ruft Anzahl Züge pro Streckenabschnitt und Verkehrstyp ab.
Deckt SBB, BLS, SOB, DB und weitere Infrastrukturbetreiberinnen ab. Enthält Personenverkehr und Güterverkehr, Trassenkilometer und Stromverbrauch.
Args: params (TrainsPerSegmentInput): Parameter: - line_name (Optional[str]): Streckenbezeichnung (Teilsuche) - operator (Optional[str]): Infrastrukturbetreiberin, z.B. 'SBB', 'BLS' - year (Optional[str]): Jahr, z.B. '2025' - traffic_type (Optional[str]): 'Personenverkehr' oder 'Gueterverkehr' - limit (int): Max. Resultate - offset (int): Paginierung - response_format (str): 'markdown' oder 'json'
Returns: str: Zugzahlen pro Streckenabschnitt mit Operator und Traffiktyp. Schema: {segment, line, operator, year, train_count, traffic_type, track_km}
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | Records as returned by data.sbb.ch |
| pagination | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, so the safety profile is fully covered structurally. The description adds useful content-scope context (multiple operators, passenger + freight, track km, electricity consumption) but nothing about rate limits, auth, or pagination behaviour beyond restating offset/limit.
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?
Front-loaded summary followed by a compact scope paragraph and structured Args/Returns blocks. It is somewhat repetitive relative to the schema, but every part is legible and there is no padding.
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 an output schema present, the description need not explain return values, but it still names the returned fields, and it covers the query dimensions an agent needs to know. It is complete enough to call correctly; only the missing sibling routing keeps it from being fully self-sufficient.
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?
The Args block essentially restates the schema, which already carries descriptions for year, operator, line_name and traffic_type. It adds no format hints, matching semantics, or enum details beyond what the schema provides, so it sits at the baseline for schema-documented parameters.
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?
The description gives a specific verb and resource ('Ruft Anzahl Züge pro Streckenabschnitt und Verkehrstyp ab') and clarifies scope by naming the covered operators (SBB, BLS, SOB, DB) and the fact that both passenger and freight traffic are included. It does not, however, differentiate itself from the closest sibling sbb_get_passenger_frequency, which likely overlaps in subject matter.
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 description explains what data the tool returns but offers no guidance on when to choose it over siblings such as sbb_get_passenger_frequency or sbb_get_platform_data. There are no stated preconditions, exclusions, or alternative-routing cues beyond an implied context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_list_datasetsSBB Open Data Datensätze auflistenARead-onlyIdempotent
Listet alle verfügbaren SBB Open Data Datensätze (data.sbb.ch) auf.
Gibt Dataset-ID, Titel, Anzahl Datensätze und Aktualisierungsfrequenz zurück. Nützlich zur Übersicht und Entdeckung neuer Datensätze.
Returns: str: Vollständige Liste aller SBB Open Data Datensätze. Schema: {dataset_id, title, records_count, update_frequency, themes}
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| datasets | Yes | Catalog entries as returned by data.sbb.ch |
| total_count | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, so the safety profile is fully covered. The description adds the return shape (dataset_id, title, records_count, update_frequency, themes), which is useful context, but says nothing about pagination, result size, or whether the list is unfiltered/complete beyond 'alle'.
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?
The purpose and use case are front-loaded in two short sentences, which is efficient. The trailing 'Returns:' block is somewhat redundant since an output schema already exists, but it is brief and does not bloat the definition.
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?
For a no-parameter catalog listing tool with annotations covering the safety profile and an output schema covering the return type, the description supplies enough to call it correctly. The only gap is that it restates return fields already documented in the output schema while omitting any note on result volume.
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?
The tool takes zero parameters, so there is nothing for the description to disambiguate. Baseline of 4 applies for a no-argument tool.
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 ('Listet alle verfügbaren SBB Open Data Datensätze ... auf') and names the source domain (data.sbb.ch). It is clearly distinguishable from the sibling getters by resource (a catalog of datasets vs. domain-specific data), though it never explicitly contrasts itself with them.
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?
'Nützlich zur Übersicht und Entdeckung neuer Datensätze' implies a discovery/overview use case, which is reasonable guidance. However, it gives no explicit when-to-use vs. when-not conditions, no mention of how it relates to the sibling retrieval tools, and no prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sbb_search_stationsSBB Haltestellen suchenARead-onlyIdempotent
Sucht Bahnhöfe und Haltestellen der Schweiz (DiDok-Liste des BAV).
Deckt alle öV-Haltestellen ab (nicht nur SBB). Enthält UIC-Nummern, Koordinaten, Kantone und Betreiberinformationen.
Args: params (StationSearchInput): Parameter: - query (str): Suchbegriff (mind. 2 Zeichen), z.B. 'Wädenswil', 'Zürich' - canton (Optional[str]): Kantonskürzel, z.B. 'ZH' - limit (int): Max. Resultate - response_format (str): 'markdown' oder 'json'
Returns: str: Haltestellenliste mit UIC, Kanton und Koordinaten. Schema: {name, uic, canton, operator, coordinates}
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | DiDok records as returned by data.sbb.ch |
| total_count | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, so safety is covered structurally and the description doesn't need to repeat it. The description usefully adds the data scope (UIC numbers, coordinates, cantons, operator info) and the return format, but omits any note on auth, rate limits, or result completeness.
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?
Purpose is front-loaded and the Args/Returns structure is easy to scan. There is mild redundancy between the Args list and the Returns schema line, but no wasted filler.
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?
For a single-object, four-parameter read tool with an output schema, the description covers purpose, parameters, and the returned fields (name, uic, canton, operator, coordinates). Nothing essential to invoking it correctly is missing.
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?
Reported schema description coverage is 0%, so the description must carry parameter meaning, and it does: it enumerates query (min 2 chars, examples), canton (canton code examples), limit, and response_format with its allowed values. This compensates well for the thin coverage, though it doesn't explain limit's max of 100 or canton's exact format constraints.
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 (Sucht) and resource (Bahnhöfe und Haltestellen der Schweiz) and clarifies the scope as the BAV DiDok list covering all ÖV stops, not just SBB. This disambiguates from an SBB-only assumption, but it never contrasts itself with the related sibling sbb_compare_stations or the other get_* tools.
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?
Usage is implied by the scope clarification ('alle ÖV-Haltestellen, nicht nur SBB'), which tells the agent when this tool is appropriate for broad stop lookups. However there are no explicit when-to-use/when-not rules and no mention of alternatives such as sbb_compare_stations for comparing two stops.
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.
10 tool updates
v0.4.0- Changed
sbb_compare_stations1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "StationComparisonOutput": { + "description": "One requested station. Only `name` is guaranteed: the rest is absent\nwhen the source had no passenger-frequency or platform match.", + "properties": { + "canton": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Canton" + }, + "dtv": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Average daily passengers", + "title": "Dtv" + }, + "dwv": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Average workday passengers", + "title": "Dwv" + }, + "matched_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Matched Name" + }, + "name": { + "description": "Station name as requested", + "title": "Name", + "type": "string" + }, + "platform_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Platform Count" + }, + "total_platform_length_m": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Total Platform Length M" + }, + "year": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Year" + } + }, + "required": [ + "name" + ], + "title": "StationComparisonOutput", + "type": "object" + } + }, + "properties": { + "stations": { + "items": { + "$ref": "#/$defs/StationComparisonOutput" + }, + "title": "Stations", + "type": "array" + }, + "year": { + "title": "Year", + "type": "string" + } + }, + "required": [ + "year", + "stations" + ], + "title": "CompareStationsOutput", + "type": "object" +}
- Removed
sbb_get_infrastructure_construction_projects - Changed
sbb_get_passenger_frequency1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "PaginationOutput": { + "properties": { + "has_more": { + "title": "Has More", + "type": "boolean" + }, + "next_offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Next Offset" + }, + "offset": { + "title": "Offset", + "type": "integer" + }, + "returned": { + "title": "Returned", + "type": "integer" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "returned", + "offset", + "has_more", + "next_offset" + ], + "title": "PaginationOutput", + "type": "object" + } + }, + "description": "A page of raw data.sbb.ch records plus pagination.", + "properties": { + "pagination": { + "$ref": "#/$defs/PaginationOutput" + }, + "results": { + "description": "Records as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Results", + "type": "array" + } + }, + "required": [ + "pagination", + "results" + ], + "title": "PagedRecordsOutput", + "type": "object" +}
- Changed
sbb_get_platform_data1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "PaginationOutput": { + "properties": { + "has_more": { + "title": "Has More", + "type": "boolean" + }, + "next_offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Next Offset" + }, + "offset": { + "title": "Offset", + "type": "integer" + }, + "returned": { + "title": "Returned", + "type": "integer" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "returned", + "offset", + "has_more", + "next_offset" + ], + "title": "PaginationOutput", + "type": "object" + } + }, + "description": "A page of raw data.sbb.ch records plus pagination.", + "properties": { + "pagination": { + "$ref": "#/$defs/PaginationOutput" + }, + "results": { + "description": "Records as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Results", + "type": "array" + } + }, + "required": [ + "pagination", + "results" + ], + "title": "PagedRecordsOutput", + "type": "object" +}
- Changed
sbb_get_rail_disruptions1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "PaginationOutput": { + "properties": { + "has_more": { + "title": "Has More", + "type": "boolean" + }, + "next_offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Next Offset" + }, + "offset": { + "title": "Offset", + "type": "integer" + }, + "returned": { + "title": "Returned", + "type": "integer" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "returned", + "offset", + "has_more", + "next_offset" + ], + "title": "PaginationOutput", + "type": "object" + } + }, + "description": "A page of raw data.sbb.ch records plus pagination.", + "properties": { + "pagination": { + "$ref": "#/$defs/PaginationOutput" + }, + "results": { + "description": "Records as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Results", + "type": "array" + } + }, + "required": [ + "pagination", + "results" + ], + "title": "PagedRecordsOutput", + "type": "object" +}
- Changed
sbb_get_real_estate_projects1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "PaginationOutput": { + "properties": { + "has_more": { + "title": "Has More", + "type": "boolean" + }, + "next_offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Next Offset" + }, + "offset": { + "title": "Offset", + "type": "integer" + }, + "returned": { + "title": "Returned", + "type": "integer" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "returned", + "offset", + "has_more", + "next_offset" + ], + "title": "PaginationOutput", + "type": "object" + } + }, + "description": "A page of raw data.sbb.ch records plus pagination.", + "properties": { + "pagination": { + "$ref": "#/$defs/PaginationOutput" + }, + "results": { + "description": "Records as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Results", + "type": "array" + } + }, + "required": [ + "pagination", + "results" + ], + "title": "PagedRecordsOutput", + "type": "object" +}
- Changed
sbb_get_rolling_stock1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "PaginationOutput": { + "properties": { + "has_more": { + "title": "Has More", + "type": "boolean" + }, + "next_offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Next Offset" + }, + "offset": { + "title": "Offset", + "type": "integer" + }, + "returned": { + "title": "Returned", + "type": "integer" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "returned", + "offset", + "has_more", + "next_offset" + ], + "title": "PaginationOutput", + "type": "object" + } + }, + "description": "A page of raw data.sbb.ch records plus pagination.", + "properties": { + "pagination": { + "$ref": "#/$defs/PaginationOutput" + }, + "results": { + "description": "Records as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Results", + "type": "array" + } + }, + "required": [ + "pagination", + "results" + ], + "title": "PagedRecordsOutput", + "type": "object" +}
- Changed
sbb_get_trains_per_segment1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "PaginationOutput": { + "properties": { + "has_more": { + "title": "Has More", + "type": "boolean" + }, + "next_offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Next Offset" + }, + "offset": { + "title": "Offset", + "type": "integer" + }, + "returned": { + "title": "Returned", + "type": "integer" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "returned", + "offset", + "has_more", + "next_offset" + ], + "title": "PaginationOutput", + "type": "object" + } + }, + "description": "A page of raw data.sbb.ch records plus pagination.", + "properties": { + "pagination": { + "$ref": "#/$defs/PaginationOutput" + }, + "results": { + "description": "Records as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Results", + "type": "array" + } + }, + "required": [ + "pagination", + "results" + ], + "title": "PagedRecordsOutput", + "type": "object" +}
- Changed
sbb_list_datasets1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "properties": { + "datasets": { + "description": "Catalog entries as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Datasets", + "type": "array" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "datasets" + ], + "title": "DatasetListOutput", + "type": "object" +}
- Changed
sbb_search_stations1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "properties": { + "results": { + "description": "DiDok records as returned by data.sbb.ch", + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Results", + "type": "array" + }, + "total_count": { + "title": "Total Count", + "type": "integer" + } + }, + "required": [ + "total_count", + "results" + ], + "title": "StationSearchOutput", + "type": "object" +}
10 tool updates
v0.3.4- Changed
sbb_compare_stations4 fields changed- added
Input schema / $defs / CompareStationsInput / properties / response_formatAdded value: +{ + "$ref": "#/$defs/ResponseFormat", + "default": "markdown", + "description": "Ausgabeformat: 'markdown' (lesbar) oder 'json' (maschinenlesbar)" +} - changed
Input schema / $defs / CompareStationsInput / properties / year / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "pattern": "^\\d{4}$", + "type": "string" + }, + { + "type": "null" + } +] - added
Input schema / $defs / ResponseFormatAdded value: +{ + "enum": [ + "markdown", + "json" + ], + "title": "ResponseFormat", + "type": "string" +} - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_compare_stationsOutput", - "type": "object" -}New value: +null
- Changed
sbb_get_infrastructure_construction_projects1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_get_infrastructure_construction_projectsOutput", - "type": "object" -}New value: +null
- Changed
sbb_get_passenger_frequency3 fields changed- changed
Input schema / $defs / PassengerFrequencyInput / properties / canton / anyOfPrevious value: -[ - { - "maxLength": 2, - "minLength": 2, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 2, + "minLength": 2, + "pattern": "^[A-Za-z]{2}$", + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / $defs / PassengerFrequencyInput / properties / year / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "pattern": "^\\d{4}$", + "type": "string" + }, + { + "type": "null" + } +] - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_get_passenger_frequencyOutput", - "type": "object" -}New value: +null
- Changed
sbb_get_platform_data1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_get_platform_dataOutput", - "type": "object" -}New value: +null
- Changed
sbb_get_rail_disruptions1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_get_rail_disruptionsOutput", - "type": "object" -}New value: +null
- Changed
sbb_get_real_estate_projects1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_get_real_estate_projectsOutput", - "type": "object" -}New value: +null
- Changed
sbb_get_rolling_stock1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_get_rolling_stockOutput", - "type": "object" -}New value: +null
- Changed
sbb_get_trains_per_segment2 fields changed- changed
Input schema / $defs / TrainsPerSegmentInput / properties / year / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "pattern": "^\\d{4}$", + "type": "string" + }, + { + "type": "null" + } +] - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_get_trains_per_segmentOutput", - "type": "object" -}New value: +null
- Changed
sbb_list_datasets1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_list_datasetsOutput", - "type": "object" -}New value: +null
- Changed
sbb_search_stations2 fields changed- changed
Input schema / $defs / StationSearchInput / properties / canton / anyOfPrevious value: -[ - { - "maxLength": 2, - "minLength": 2, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 2, + "minLength": 2, + "pattern": "^[A-Za-z]{2}$", + "type": "string" + }, + { + "type": "null" + } +] - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "title": "Result", - "type": "string" - } - }, - "required": [ - "result" - ], - "title": "sbb_search_stationsOutput", - "type": "object" -}New value: +null
10 tool updates
v0.1.0- First observed
sbb_compare_stations - First observed
sbb_get_infrastructure_construction_projects - First observed
sbb_get_passenger_frequency - First observed
sbb_get_platform_data - First observed
sbb_get_rail_disruptions - First observed
sbb_get_real_estate_projects - First observed
sbb_get_rolling_stock - First observed
sbb_get_trains_per_segment - First observed
sbb_list_datasets - First observed
sbb_search_stations
TDQS
Scored across 9 tools
Each tool targets a distinct dataset or action (passenger frequency, disruptions, real estate, rolling stock, etc.), so most are easy to tell apart. There is mild overlap around station data: search_stations, get_platform_data, get_passenger_frequency, and compare_stations all touch station-level information, though their specific outputs differ.
All tools use the same 'sbb_' prefix followed by a consistent verb_noun pattern (get_, list_, compare_, search_). The convention is uniform throughout with no mixing of styles.
Nine tools is well-scoped for an open-data portal, with each tool mapping to a distinct SBB dataset or a useful aggregation. Nothing feels redundant or padded.
The surface covers the major SBB datasets plus helpful aggregations (compare_stations) and discovery (list_datasets, search_stations). It is read-only by nature, which fits an open-data server, but there is no generic 'get_dataset_by_id' tool to reach datasets beyond the curated set.
Maintenance
Related MCP Connectors
- UnifAPIOAuthcom.unifapi
Hosted MCP server for live public-data APIs and Skills for AI agents.
This MCP server provides seamless access to Malaysia's government open data, including datasets, w…
One AI endpoint to search and call 22k+ MCP servers; 50+ hosted tools work instantly, no key.
MCP server for building and testing AI agents with multi-model experimentation and insights.
Related MCP Servers
- AlicenseAqualityCmaintenanceAn MCP server that enables AI assistants to interact with the Netherlands Railways (NS) API for route planning, pricing, and real-time departure information. It provides tools for searching stations, planning trips with connections, and viewing real-time departure boards.31MIT
- AlicenseAqualityDmaintenanceA zero-authentication MCP server for Swiss public transport, enabling users to query train connections, disruptions, station facilities, and plan journeys using natural language.12MIT
- AlicenseAqualityDmaintenanceMCP server for Swiss public transport — connections, stationboards, real-time delays, and direct booking links for SBB.41MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server for real-time Italian railway data, enabling natural language queries about train schedules, delays, departures, arrivals, and live tracking via the Viaggiatreno API.6MIT