关于 swiss-transport-mcp
swiss-transport-mcp 为出行者和 AI 助手提供瑞士公共交通查询,涵盖行程、实时班次及多种交通数据。它将 OJP 2.0、SIRI-SX 和 REST 等接口转换为 JSON,并支持本地与云端传输。
主要功能
- 01行程规划与实时班次
- 02站点名称和坐标搜索
- 03实时延误与中断信息
- 04列车拥挤度和票价查询
- 05列车编组与开放数据查询
适用场景
- 01规划含换乘的瑞士铁路行程
- 02查询车站下一班车及站台
- 03确认线路中断和列车拥挤度
- 04查询票价、车厢设施或交通数据集
MCP 相关
该 MCP 服务向兼容客户端提供交通查询工具和资源,数据来自瑞士公共交通开放数据接口。支持 stdio 和 Streamable HTTP 传输,部分扩展工具需单独 API 密钥。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
11收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| params | any | 是 |
详细介绍
🇨🇭 属于 瑞士公共数据 MCP 产品组合
🚆 swiss-transport-mcp
连接 AI 模型与瑞士公共交通系统的 MCP 服务——提供行程规划、实时发车信息、运营中断、拥挤程度、票价、列车编组和来自 opentransportdata.swiss 的开放数据。
演示
概览
swiss-transport-mcp 为 Claude 等 AI 助手提供完整的瑞士出行信息系统——不仅有时刻表,还有实时运营中断提醒、拥挤程度预测、票价和完整的列车编组视图。所有信息都可通过一个标准化的 MCP 接口访问。
opentransportdata.swiss 上的各类 API 使用不同的协议——OJP 2.0(XML/SOAP)、SIRI-SX(XML)、REST/JSON。此服务会将所有数据转换为简洁的 JSON,供 AI 模型使用,充当多语言协议解释器。
示例演示查询: “为 25 名学生规划从苏黎世前往温特图尔 Technorama 的学校旅行——检查是否有运营中断,并找到最佳发车班次。” → 按受众查看更多使用场景 →
功能
- 🗺️ 行程规划(A → B,包含换乘、时长、交通方式),通过 OJP 2.0 提供
- 🕐 实时发车信息,包含延误和站台信息
- 🔍 站点搜索,可按名称或坐标搜索
- 🚨 实时运营中断提醒(取消、封闭),通过 SIRI-SX 提供
- 📊 列车拥挤程度预测(SBB、BLS、Thurbo、SOB)
- 💰 票价,支持选择舱等
- 🚃 列车编组——车厢、舱等、设施、无障碍信息
- 📦 开放数据目录——约 90 个交通数据集,通过 CKAN 提供
- 🔑 优雅降级——即使没有可选 API 密钥,服务也能通过核心工具启动
- ☁️ 双传输方式——stdio 用于 Claude Desktop,Streamable HTTP/SSE 用于云端部署
前提条件
- Python 3.11+
- 从 api-manager.opentransportdata.swiss 获取免费的 API 密钥(至少订阅 OJP 2.0)
- 可选:SIRI-SX、Occupancy、Formation、OJP Fare 的其他密钥
安装
# Clone the repository
git clone https://github.com/malkreide/swiss-transport-mcp.git
cd swiss-transport-mcp
# Install
pip install -e .
或者使用 uvx(无需永久安装):
uvx swiss-transport-mcp
快速开始
# Set the minimum required key (OJP core tools)
export TRANSPORT_API_KEY=your_key_here
# Start the server (stdio mode for Claude Desktop)
swiss-transport-mcp
立即在 Claude Desktop 中试用:
“苏黎世 Stadelhofen 接下来有哪些发车班次?” “我怎么从 Wädenswil 坐火车去伯尔尼?”
配置
环境变量
| 变量 | API | 必填 |
|---|---|---|
TRANSPORT_API_KEY | OJP + CKAN 的统一密钥 | ✅(或使用单独的密钥) |
TRANSPORT_OJP_API_KEY | OJP 2.0 行程规划器 | 可选(覆盖) |
TRANSPORT_CKAN_API_KEY | CKAN 数据目录 | 可选(单独订阅) |
SIRI_SX_API_KEY | 中断提醒(SIRI-SX) | 可选 |
OCCUPANCY_API_KEY | 客流量预测 | 可选 |
FORMATION_API_KEY | 列车编组 | 可选 |
OJP_FARE_API_KEY | 票价(OJP Fare) | 可选 |
未配置密钥的 API 会静默禁用——仅使用 6 个核心工具,服务也能正常启动。
运行 / 安全变量:
| 变量 | 作用 | 默认值 |
|---|---|---|
MCP_ENV / ENV | 进程环境。仅当值为 dev/development/local/test 时,才允许禁用 TLS 验证。 | (未设置 → 生产环境) |
TRANSPORT_SSL_VERIFY | 设置为 false 可禁用 TLS 证书验证。仅当 MCP_ENV 指定为开发环境时才会生效——否则会忽略此设置并继续启用验证。 | true |
TRANSPORT_CKAN_URL | 覆盖 CKAN 基础 URL。必须在出站允许列表内(*.opentransportdata.swiss);拒绝站外地址。 | https://api.opentransportdata.swiss/ckan-api |
MCP_CORS_ORIGINS | 允许调用 HTTP 传输的浏览器来源列表,以逗号分隔。使用 * 可允许任意来源(不推荐)。这些来源可读取 Mcp-Session-Id 标头。 | https://claude.ai |
LOG_FORMAT | 设置为 json 可输出结构化日志(RFC 5424 严重性级别);其他值则输出易读文本。日志始终写入 stderr。 | text |
OTEL_TRACES_ENABLED | 设置为 1 可启用 OpenTelemetry 跟踪(需要 otel 扩展:pip install 'swiss-transport-mcp[otel]')。否则不执行任何操作。 | (关闭) |
MCP_STATELESS | 设置为 1 可使 Streamable HTTP 传输以无状态方式运行——不保留服务端会话状态,因此实例无需会话粘性负载均衡。建议用于水平扩展。 | (关闭 → 有状态) |
MCP_ALLOWED_HOSTS | 此服务可通过其访问的主机名列表,以逗号分隔;必要时包含端口(例如 fahrplan.example.ch:8080)。通过任何其他 Host 发来的请求都会被拒绝,并返回 421;仍允许回环地址,以便容器健康检查正常运行。在非回环地址上监听但未设置此变量时,将关闭检查并记录警告。 | (未设置 → 关闭) |
🔒 **出站允许列表:**所有出站请求都限制为发往
opentransportdata.swiss主机的https://请求。发送请求前会拒绝任何其他主机(SSRF / 出站流量加固)。
Claude Desktop 配置
最简配置(仅核心工具):
{
"mcpServers": {
"swiss-transport": {
"command": "swiss-transport-mcp",
"env": {
"TRANSPORT_API_KEY": "your_key_here"
}
}
}
}
完整配置(全部 11 个工具):
{
"mcpServers": {
"swiss-transport": {
"command": "swiss-transport-mcp",
"env": {
"TRANSPORT_API_KEY": "your_ojp_key_here",
"SIRI_SX_API_KEY": "your_siri_key_here",
"OCCUPANCY_API_KEY": "your_occupancy_key_here",
"FORMATION_API_KEY": "your_formation_key_here",
"OJP_FARE_API_KEY": "your_fare_key_here"
}
}
}
}
配置文件位置:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
云端部署(Streamable HTTP)
适用于通过浏览器中的 claude.ai 使用(例如,在没有本地软件的受管工作站上)。云端传输方式为 Streamable HTTP(MCP_TRANSPORT=streamable-http,端点为 /mcp)。仍支持 SSE(/sse),但已弃用。
MCP_TRANSPORT | 用途 | 端点 |
|---|---|---|
stdio(默认) | 本地 Claude Desktop 子进程 | – |
streamable-http(或 http) | 云端/容器(推荐) | /mcp |
sse | 旧版浏览器传输方式(已弃用) | /sse |
Docker(推荐):
# Build + run with explicit resource limits (see docker-compose.yml)
TRANSPORT_API_KEY=xxx docker compose up --build
# → http://127.0.0.1:8000/mcp
该镜像采用多阶段构建,并以非 root 用户身份运行;docker-compose.yml 添加了 read_only、no-new-privileges 以及内存/CPU/PID 限制。
Render.com:
- 将仓库推送到 GitHub 或从中派生仓库
- 在 render.com 上:新建 Web 服务 → 连接 GitHub 仓库(Docker 运行时)
- 设置环境变量
MCP_TRANSPORT=streamable-http和MCP_HOST=0.0.0.0 - 在 claude.ai 的“Settings → MCP Servers”下添加:
https://your-app.onrender.com/mcp
💡 “开发者笔记本使用 stdio,云端使用 Streamable HTTP。”
**水平扩展:**使用 MCP_STATELESS=1 运行。在无状态模式下,服务不会保留任何会话状态,因此任一实例都可以处理任一请求,使用普通的轮询负载均衡器即可——无需粘性会话/Mcp-Session-Id 亲和性。如果需要有状态流式传输,则在边缘负载均衡器处按 Mcp-Session-Id 路由(例如使用 HAProxy stick-tables),确保每个会话固定到同一个实例。
⚠️ 绑定:在网络传输模式下,服务默认绑定到
127.0.0.1,因此本地启动的服务不会暴露给整个网络(例如公共 Wi-Fi)。仅在预期绑定所有接口的容器/云端环境中设置MCP_HOST=0.0.0.0(Docker 镜像已为你完成此设置)。
可用工具
核心工具(OJP 2.0 / CKAN)
| 工具 | 描述 | 数据来源 |
|---|---|---|
transport_search_stop | 按名称搜索车站/站点 | OJP 2.0 |
transport_nearby_stops | 按坐标查找附近站点 | OJP 2.0 |
transport_departures | 实时出发信息,包含延误和站台 | OJP 2.0 |
transport_trip_plan | 规划从 A 到 B 的行程,包含换乘、时长和交通方式 | OJP 2.0 |
transport_search_datasets | 搜索开放数据目录(约 90 个数据集) | CKAN¹ |
transport_get_dataset | 获取特定数据集的完整详情 | CKAN¹ |
¹ CKAN 工具需要在 API Manager 中单独订阅。
扩展工具(可选 API 密钥)
| 工具 | 描述 | 数据来源 |
|---|---|---|
get_transport_disruptions | 🚨 实时交通中断、取消和线路关闭信息 | SIRI-SX |
get_train_occupancy | 📊 特定列车的拥挤度预测 | Occupancy JSON |
get_ticket_price | 💰 区间票价 | OJP Fare |
get_train_composition | 🚃 列车编组、车厢等级和无障碍信息 | Formation REST |
check_transport_api_status | 🔍 检查所有已配置 API 的运行状况 | 全部 |
示例用例
| 查询 | 工具 |
|---|---|
| “从 Zurich Stadelhofen 出发的下一班列车是什么时候?” | transport_departures |
| “为 25 名学生规划从 Zurich 前往 Winterthur Technorama 的行程” | transport_trip_plan |
| “Zurich 和 Bern 之间有交通中断吗?” | get_transport_disruptions |
| “IC 1009 今天有多拥挤?” | get_train_occupancy |
| “从 Wädenswil 到 Bern 的车票多少钱?” | get_ticket_price |
| “IC 708 有餐车吗?” | get_train_composition |
| “Langstrasse 100 附近有哪些站点?” | transport_nearby_stops |
架构
┌─────────────────┐ ┌───────────────────────────┐ ┌──────────────────────────┐
│ Claude / AI │────▶│ Swiss Transport MCP │────▶│ opentransportdata.swiss │
│ (MCP Host) │◀────│ (MCP Server) │◀────│ │
└─────────────────┘ │ │ │ OJP 2.0 (XML/SOAP) │
│ 11 Tools · 2 Resources │ │ SIRI-SX (XML) │
│ Stdio | SSE │ │ CKAN (REST/JSON) │
│ │ │ Occupancy(REST/JSON) │
│ Core: │ │ Formation(REST/JSON) │
│ api_client + ojp_client │ │ OJP Fare (XML/SOAP) │
│ Extensions: │ └──────────────────────────┘
│ siri_sx, occupancy, │
│ ojp_fare, formation │
└───────────────────────────┘
基础设施组件
| 组件 | 比喻 | 功能 |
|---|---|---|
| RateLimiter | 门卫 | 限制每个时间窗口内的 API 调用次数 |
| SimpleCache | 白板 | 缓存响应,以供重复查询使用 |
| APIClient | 总机 | 集中处理身份验证、重定向和错误 |
| APIConfig | 名片 | 每个 API 的密钥、URL 和限额 |
缓存策略
| API | 缓存 TTL | 原因 |
|---|---|---|
| SIRI-SX | 120s | 运营中断不会每秒都发生变化 |
| Occupancy | 300s | 预测以天为单位 |
| Formation | 600s | 列车编组当天保持稳定 |
| OJP Fare | 1800s | 票价在一天内很少变化 |
项目结构
swiss-transport-mcp/
├── src/swiss_transport_mcp/ # Main package
│ ├── server.py # FastMCP server, tool definitions
│ ├── api_client.py # Core OJP + CKAN client
│ ├── ojp_client.py # OJP 2.0 XML/SOAP parser
│ ├── api_infrastructure.py # RateLimiter, SimpleCache, APIClient
│ ├── siri_sx.py # Disruption alerts
│ ├── occupancy.py # Occupancy forecasts
│ ├── ojp_fare.py # Ticket prices
│ └── formation.py # Train formation
├── tests/
│ └── test_server.py # Unit + integration tests
├── .github/workflows/ci.yml # GitHub Actions (Python 3.11/3.12/3.13)
├── claude_desktop_config.json # Example Claude Desktop config
├── pyproject.toml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md # This file (English)
└── README.de.md # German version
安全与限制
- 只读: 所有工具都执行只读请求(仅用于查询的 HTTP GET / OJP XML POST)——不会在任何上游系统中写入、修改或删除数据。
- 不处理个人数据: 行程查询是临时的,本服务不会存储这些查询。API 返回的是计划时刻表和实时运营数据。不会处理或保留任何个人身份信息(PII)。
- 速率限制: opentransportdata.swiss 会对每个密钥实施速率限制(详见 API Manager 文档)。服务内置的
RateLimiter(SIRI-SX:2 req/min,Formation/OJP Fare:5 req/min)会自动遵守这些限制。进行批量查询时,请谨慎使用limit参数。 - 需要 API 密钥: 必须使用从 api-manager.opentransportdata.swiss 获取的免费密钥。密钥与您的账户订阅绑定——请仅订阅您打算使用的 API。
- 数据时效性: 实时工具(出发、运营中断、载客率)反映查询时的上游数据源。本服务会以较短的 TTL(120s–1800s)缓存响应,以减少 API 负载——请参阅上方的缓存策略表。
- 服务条款: 数据受 opentransportdata.swiss 的 ToS 约束。OJP、SIRI-SX 和 CKAN 目录以开放许可证(ODbL / CC BY 4.0)发布,可用于非商业用途和研究。
- 不作保证: 本服务是社区项目,与联邦交通局(BAV/OFT)或 SBB 无关。服务可用性取决于上游 API。
安装前须知(同意事项)
将此服务添加到您的 MCP 客户端后,连接的 AI 模型便可代表您查询瑞士
公共交通信息,使用的是您的 opentransportdata.swiss API 密钥,并向
opentransportdata.swiss 发出 HTTPS 外连请求。不会向上游写入任何内容,
也不会存储 PII,但在配置服务前,请查看上方的工具列表,并确认您愿意授予
此类访问权限。
安全运行 HTTP 传输(无内置身份验证)
此服务本身不提供身份验证。运行 Streamable
HTTP 传输(MCP_TRANSPORT=streamable-http)时,MCP SDK 会为每个会话生成一个
加密随机的 Mcp-Session-Id,但不会将其绑定到任何用户身份。因此:
- 不要将未启用身份验证的实例直接暴露到公共互联网。 请将其置于支持身份验证的反向代理(OAuth2 代理、mTLS 或平台的访问控制)之后,或限制其仅供可信网络访问。
- 本地使用时保留默认值
MCP_HOST=127.0.0.1;只有在受控的容器/云环境中才绑定到0.0.0.0(参见 Deployment)。 - 将
MCP_CORS_ORIGINS限定为你实际信任的源。 - 只要绑定地址超出回环范围,就设置
MCP_ALLOWED_HOSTS。它用于防范DNS 重绑定:网络上的某个页面将自己的主机名解析到此服务的地址,然后从浏览器与其通信。CORS 无法阻止这种情况——从浏览器的角度看,该请求属于同源请求;令牌也无法阻止,因为攻击页面运行在持有令牌的上下文中。只有Host检查能够阻止它。未设置该选项时,检查会保持关闭;只有在服务前方的组件会验证Host时,这才是合适的默认设置。
完整的安全状况和已接受风险的决策(网关级控制)请参阅 SECURITY.md。
已知限制
- OJP 票价: 折扣(Halbtax、GA、区域通票)不一定会反映在票价中
- Formation: 基于站点的数据仅在今天可用(依赖实时数据)
- Occupancy: 仅支持 SBB、BLS、Thurbo 和 SOB,不支持私营铁路
- SIRI-SX: 返回瑞士的所有交通中断信息 → 请使用
filter_text参数 - CKAN: 需要在 API Manager 中单独订阅
MCP 协议版本
此服务通过同一个端点支持两个协议时代。客户端在连接上的第一个请求决定适用哪个时代;如果之后声明属于另一个时代,则会被拒绝。
| 时代 | 修订版本 | 适用客户端 |
|---|---|---|
initialize 握手 | 2024-11-05 … 2025-11-25 | 当前客户端使用的协议。服务会使用请求中指定的修订版本作答;如果请求要求更新的版本,则使用 2025-11-25 这一上限。 |
| 逐请求封套 | 2026-07-28 | 携带 2026-07-28 _meta 封套的请求会建立现代连接。 |
这两个修订版本都固定在
tests/test_protocol_version.py 中,并与已安装的 SDK 进行断言,因此 Dependabot 升级 mcp 时,任一版本都不会悄然变动。此固定项说明了 SDK 提供哪些修订版本。
服务实际支持这些版本的情况,则通过
tests/test_modern_wire.py 进行测量,测试对象正是 main() 交给 uvicorn 的应用:针对 server/discover、tools/list 和一次 tools/call 发送真实的 2026-07-28 单次交互 POST 请求——不发送 initialize,也不使用 Mcp-Session-Id;测试三种拒绝情形(缺少封套、路由标头与请求正文不一致、未提供的修订版本),并在同一端点发送传统的 initialize,以证明两个时代可以共存。
本节的早期版本曾声称仓库没有可供发送请求的 ASGI 应用,并以此解释为何门禁只能断言常量。实际上,仓库确实构建了一个应用。
服务身份。 2026-07-28 没有 initialize,因此也没有客户端读取 serverInfo 的单一位置。该修订版本会将 Implementation(名称、标题、版本、网站)放入 server/discover,并放入每个结果的 _meta。此服务会从自身的发行版元数据中填入这些信息,因此网络传输中的版本就是已安装的版本,不会与 pyproject.toml 不一致。
请注意,SDK 的 LATEST_PROTOCOL_VERSION 是现代时代的别名,而不是握手时代的别名——只针对它设置固定版本,会让当前客户端实际协商使用的时代有机会发生变动。
更新政策。 门禁失败时,不要盲目编辑常量:请阅读两个修订版本之间的规范变更日志,确认服务仍按预期运行,然后一并更新常量、本节、README.de.md 和
CHANGELOG.md。
测试
# Unit tests (no API key required)
PYTHONPATH=src pytest tests/ -m "not live"
# Integration tests (API key required)
TRANSPORT_API_KEY=xxx pytest tests/ -m "live"
测试数据来源
所有四个上游 API 都需要 opentransportdata.swiss API-Manager 提供的 Bearer 令牌,因此 CI 无法记录真实响应——相关信息已测量并保存在
tests/fixtures/upstream_auth_probe.json 中。因此,测试模块中的 XML 载荷都是手工编写的,并非记录所得,无法反驳生产代码:两者都基于对文档的同一解读,如果两者都错,就会错在同一个地方。
可以记录的是契约。OJP 2.0 是 CEN 标准(CEN/TS 17118),并提供公开的 XML 架构;tests/fixtures/ojp_2_0_contract.json 是据此整理的、带日期的索引,包含元素名称、此服务依赖的结构、以值发送的枚举,以及读取的每个架构文件的 SHA-256。tests/test_ojp_contract.py 中包含针对该索引的请求和解析器。架构本身有意不随仓库附带:源代码仓库没有许可文件。
python scripts/record_fixtures.py # re-record
python scripts/record_fixtures.py --check # recompute against the pinned tag
来源、日期、选择规则和哈希值:tests/fixtures/PROVENANCE.md。
更新日志
请参阅 CHANGELOG.md
贡献
请参阅 CONTRIBUTING.md
安全
请参阅 SECURITY.md(德语),了解安全状况以及如何报告漏洞。
许可证
MIT 许可证 — 请参阅 LICENSE
作者
Hayal Oezkan · github.com/malkreide
致谢与相关项目
- 数据: opentransportdata.swiss – 联邦交通局(FOT/BAV)
- 协议: Model Context Protocol – Anthropic / Linux Foundation
- 相关项目: zurich-opendata-mcp – 用于苏黎世市开放数据的 MCP 服务
- 作品集: Swiss Public Data MCP Portfolio
安装
通过 uv 的 uvx 运行,无需克隆或手动安装。将其添加到 MCP 客户端配置中(Claude Desktop、Cursor 和 Windsurf 使用 mcpServers;VS Code 在 .vscode/mcp.json 中使用顶层 servers 键):
{
"mcpServers": {
"swiss-transport-mcp": {
"command": "uvx",
"args": [
"swiss-transport-mcp"
]
}
}
}
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。