Skip to main content
Glama
malkreide

SBB Open Data MCP Server

by malkreide

🚆 sbb-opendata-mcp

🇨🇭 Part of the Swiss Public Data MCP Portfolio

PyPI License: MIT Python 3.11+ MCP Data Source CI

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.

🇩🇪 Deutsche Version

Demo

Demo: Claude queries SBB passenger frequency


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 | sh

Installation

From PyPI:

pip install sbb-opendata-mcp

Or with uvx (no permanent installation):

uvx sbb-opendata-mcp

For 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-mcp

Try 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

MCP_HOST

Bind host for the HTTP transport. Keep 127.0.0.1 locally; only bind 0.0.0.0 inside a controlled container/cloud environment.

127.0.0.1

MCP_PORT

Port for the HTTP transport.

8000

MCP_ALLOWED_HOSTS

Comma-separated host allow-list for DNS-rebinding protection (e.g. your-app.onrender.com,your-app.onrender.com:*).

localhost only

MCP_ALLOWED_ORIGINS

Comma-separated browser-origin allow-list (e.g. https://your-app.onrender.com).

(none)

LOG_LEVEL

Log verbosity (DEBUG/INFO/WARNING/…).

INFO

LOG_FORMAT

json for structured logs; anything else for human-readable text. Always written to stderr.

text

🔒 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.json

  • Windows: %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/mcp

The 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.1 by default so a locally started server is not exposed to your whole network. Set MCP_HOST=0.0.0.0 only 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). See SECURITY.md.


Available Tools

Tool

Description

Data Update

sbb_get_passenger_frequency

Boardings/alightings by station and year (daily avg.)

Annual

sbb_get_rail_disruptions

Live rail traffic messages

Every 5 min.

sbb_get_real_estate_projects

SBB real estate development projects

Daily

sbb_get_trains_per_segment

Train counts per route segment (SBB, BLS, SOB …)

Annual

sbb_get_platform_data

Platform data (length, type, area)

Ongoing

sbb_get_rolling_stock

Rolling stock (capacity, year built)

Ongoing

sbb_compare_stations

Compare up to 10 stations (multi-dataset)

–

sbb_search_stations

Search stops (Swiss DiDok register, all CH)

Ongoing

sbb_list_datasets

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?"

sbb_get_passenger_frequency

"Are there any current disruptions on the Swiss rail network?"

sbb_get_rail_disruptions

"Compare Zürich HB, Bern and Basel SBB"

sbb_compare_stations

"Which SBB real-estate construction projects are running?"

sbb_get_real_estate_projects

"How many trains run yearly on the Zürich–Winterthur route?"

sbb_get_trains_per_segment

"Which stops exist in Wädenswil?"

sbb_search_stations

→ More use cases by audience


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 version

Safety & 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/canton are regex-validated and every value interpolated into an ODSQL where clause 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 limit and 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

initialize handshake

2024-11-05 … 2025-11-25

What today's clients speak. The server answers with the revision asked for, or with the 2025-11-25 ceiling when the request asks for something newer.

Per-request envelope

2026-07-28

A request carrying the 2026-07-28 _meta envelope opens a modern connection.

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

The 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_waedenswil and test_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


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 tools
sbb_compare_stationsSBB Bahnhöfe vergleichenA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
yearYes
stationsYes

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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 BahnhofA
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesRecords as returned by data.sbb.ch
paginationYes

TDQS

A3.6/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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 PerrondatenB
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesRecords as returned by data.sbb.ch
paginationYes

TDQS

B3.3/5.0
Behavior3/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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)A
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesRecords as returned by data.sbb.ch
paginationYes

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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-BauprojekteA
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesRecords as returned by data.sbb.ch
paginationYes

TDQS

A3.8/5.0
Behavior3/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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 RollmaterialA
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesRecords as returned by data.sbb.ch
paginationYes

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/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 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.

Purpose4/5

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.

Usage Guidelines3/5

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 StreckenabschnittB
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesRecords as returned by data.sbb.ch
paginationYes

TDQS

B3.2/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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 auflistenA
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
datasetsYesCatalog entries as returned by data.sbb.ch
total_countYes

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 suchenA
Read-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}

ParametersJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesDiDok records as returned by data.sbb.ch
total_countYes

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

  1. 10 tool updatesv0.4.0
    • Changedsbb_compare_stations1 field changed
      • changedOutput 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"
        +}
    • Removedsbb_get_infrastructure_construction_projects
    • Changedsbb_get_passenger_frequency1 field changed
      • changedOutput 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"
        +}
    • Changedsbb_get_platform_data1 field changed
      • changedOutput 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"
        +}
    • Changedsbb_get_rail_disruptions1 field changed
      • changedOutput 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"
        +}
    • Changedsbb_get_real_estate_projects1 field changed
      • changedOutput 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"
        +}
    • Changedsbb_get_rolling_stock1 field changed
      • changedOutput 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"
        +}
    • Changedsbb_get_trains_per_segment1 field changed
      • changedOutput 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"
        +}
    • Changedsbb_list_datasets1 field changed
      • changedOutput 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"
        +}
    • Changedsbb_search_stations1 field changed
      • changedOutput 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"
        +}
  2. 10 tool updatesv0.3.4
    • Changedsbb_compare_stations4 fields changed
      • addedInput schema / $defs / CompareStationsInput / properties / response_format
        Added value: +{
        +  "$ref": "#/$defs/ResponseFormat",
        +  "default": "markdown",
        +  "description": "Ausgabeformat: 'markdown' (lesbar) oder 'json' (maschinenlesbar)"
        +}
      • changedInput schema / $defs / CompareStationsInput / properties / year / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "pattern": "^\\d{4}$",
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / $defs / ResponseFormat
        Added value: +{
        +  "enum": [
        +    "markdown",
        +    "json"
        +  ],
        +  "title": "ResponseFormat",
        +  "type": "string"
        +}
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_compare_stationsOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_get_infrastructure_construction_projects1 field changed
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_get_infrastructure_construction_projectsOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_get_passenger_frequency3 fields changed
      • changedInput schema / $defs / PassengerFrequencyInput / properties / canton / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 2,
        -    "minLength": 2,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 2,
        +    "minLength": 2,
        +    "pattern": "^[A-Za-z]{2}$",
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / $defs / PassengerFrequencyInput / properties / year / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "pattern": "^\\d{4}$",
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_get_passenger_frequencyOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_get_platform_data1 field changed
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_get_platform_dataOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_get_rail_disruptions1 field changed
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_get_rail_disruptionsOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_get_real_estate_projects1 field changed
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_get_real_estate_projectsOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_get_rolling_stock1 field changed
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_get_rolling_stockOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_get_trains_per_segment2 fields changed
      • changedInput schema / $defs / TrainsPerSegmentInput / properties / year / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "pattern": "^\\d{4}$",
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_get_trains_per_segmentOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_list_datasets1 field changed
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_list_datasetsOutput",
        -  "type": "object"
        -}New value: +null
    • Changedsbb_search_stations2 fields changed
      • changedInput schema / $defs / StationSearchInput / properties / canton / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 2,
        -    "minLength": 2,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 2,
        +    "minLength": 2,
        +    "pattern": "^[A-Za-z]{2}$",
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedOutput schema / (root)
        Previous value: -{
        -  "properties": {
        -    "result": {
        -      "title": "Result",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "result"
        -  ],
        -  "title": "sbb_search_stationsOutput",
        -  "type": "object"
        -}New value: +null
  3. 10 tool updatesv0.1.0
    • First observedsbb_compare_stations
    • First observedsbb_get_infrastructure_construction_projects
    • First observedsbb_get_passenger_frequency
    • First observedsbb_get_platform_data
    • First observedsbb_get_rail_disruptions
    • First observedsbb_get_real_estate_projects
    • First observedsbb_get_rolling_stock
    • First observedsbb_get_trains_per_segment
    • First observedsbb_list_datasets
    • First observedsbb_search_stations

TDQS

A3.7/5.0

Scored across 9 tools

Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivityActive
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    An 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.
    3
    1
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    A zero-authentication MCP server for Swiss public transport, enabling users to query train connections, disruptions, station facilities, and plan journeys using natural language.
    12
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    An 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.
    6
    MIT