关于 swiss-holidays-mcp
swiss-holidays-mcp 是面向 AI 助手的瑞士假期 MCP 服务,提供全部 26 个州的公共假期和学校假期数据。它按需区分学校类型,并支持跨州规划;数据来自无需 API 密钥的公开来源。
主要功能
- 01按州和学校类型查询学校假期
- 02查询州级及地方公共假期
- 03比较跨州假期重叠情况
- 04查询长周末和桥接日
- 05导出 iCalendar 并提供假期资源
- 06查看数据源状态与来源信息
适用场景
- 01查询某州的学校假期日期
- 02比较不同州的假期重叠情况
- 03为联合规划寻找共同假期
- 04将某州假期导出为 ICS 日历
MCP 相关
向 MCP 客户端提供只读假期工具和假期订阅资源,支持 stdio 与基于 HTTP 的传输方式。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
🇨🇭 属于 瑞士公共数据 MCP 项目集
这是一个私人项目,与任何雇主或机构均无关联,也不代表任何主管部门的官方立场。
📅 swiss-holidays-mcp
面向 AI Agent 的瑞士假日日历——涵盖全部 26 个州的公共假日、学校假期和长周末,并支持跨州比较。学校假期按 Schulart(学校类型)区分,这一点比乍看之下更重要。无需 API 密钥。
概述
swiss-holidays-mcp 是一款面向 Claude 等 AI 助手的瑞士假日日历——涵盖全部 26 个州的公共假日、学校假期和长周末,无需 API 密钥。公共假日因州而异(Berchtoldstag、Fronleichnam 等节日各州安排不同,而不只是遵循联邦最低标准)。学校假期由各州设定,有时细化到地区级别;在六个州,还会按学校类型分别安排。瑞士并不存在统一的联邦日历;否则,任何跨州规划的人都只能逐一打开 26 个 PDF 页面。
该服务涵盖两个主题类别:公共假日 / 长周末和学校假期(按 Schulart 区分)。每个类别都对应一组专用工具,可将机构的原始数据转换为干净且带有来源信息的 JSON 响应。所有数据均来自 OpenHolidays API(CC BY 4.0)和 Nager.Date(MIT)。
**记忆要点:**瑞士学校数据中的重复项,往往暗藏着学校类型的区别。底层 API 会在某个州按学校类型区分假期时,多次发布同一假期时段。这看起来像是重复数据,也容易让人贸然去重——但这样会恰恰抹去学校主管部门需要的区别。
演示查询: “2026 年的哪些周里,苏黎世州、楚格州和阿尔高州的义务教育学校会同时放假——各州之间分别有多少天重叠?”
→ 该查询在一次对话中调用 find_common_free_window、compare_school_holidays 和 list_school_types,并回答了每个跨州协调规划周期都会遇到的问题。
→ 按受众查看更多使用场景 →
演示
功能
- 🏫 学校假期 — 按州和日期范围列出时段,并按 Schulart(
VS/MS/BS/EO)区分 - 🎌 公共假日 — 按州列出假日,不仅限于联邦最低标准(包括 Berchtoldstag 等)
- 🔍 日期查询 — 查询指定日期是否为某个州的学校假期或公共假日
- 🔗 跨州比较 — 各州假期天数的成对重叠矩阵
- 🪟 共同假期时段 — 所列各州同时放假的日期范围
- 🌉 长周末和桥接假日 — 根据联邦公共假日计算(Nager.Date)
- 🏘️ 地方和市镇假日 — 区级和市镇级的特定假日,例如苏黎世的 Sechseläuten 和 Knabenschiessen,并附带
scope标记,以免误认为是全州假日 - 📆 iCal / ICS 导出 — 将某州某年的假日导出为可直接导入的
.ics日历 - 🔖 假日源资源 —
holidays://<canton>/<year>MCP 资源,附带 Markdown 摘要 - 📌 “今天是假日吗?” — 一次调用即可回答日常问题
- 🩺 来源状态 — 可随时检查两个上游来源的可访问性和延迟
- 🔑 无需身份验证 — 两个数据源均可公开访问
- ☁️ 双传输方式 — stdio 适用于 Claude Desktop,Streamable HTTP/SSE 适用于云端部署
- 🧾 每次响应均注明来源 —
live_api|cached|degraded,绝不会悄然返回空列表
数据源
| 来源 | 数据 | 许可证 |
|---|---|---|
| OpenHolidays API | 各州、Schularten、学校假期、公共假日 | CC BY 4.0 |
| Nager.Date | 长周末和必要的桥接假日 | MIT |
两个数据源均可公开访问,无需身份验证。 必须注明来源: 使用其数据时,必须将 OpenHolidays(CC BY 4.0)和 Nager.Date 列为来源。
工具
| 工具 | 用途 | 数据源 |
|---|---|---|
list_cantons | 列出 26 个州及其 ISO 代码和官方语言 | OpenHolidays |
list_school_types | 按州列出 Schulart 分组(如 CH-ZH-VS) | OpenHolidays |
get_school_holidays | 查询某个州和日期范围内的学校假期 | OpenHolidays |
get_public_holidays | 查询某个州某一年的公共假日 | OpenHolidays |
get_local_holidays | 查询某个市镇或区的公共假日,包括地方特定假日 | OpenHolidays |
check_date | 查询指定日期是否为学校假期或公共假日 | OpenHolidays |
compare_school_holidays | 各州学校假期的成对重叠矩阵 | OpenHolidays |
find_common_free_window | 查找所列各州同时放假的时段 | OpenHolidays |
next_school_holidays | 查询接下来即将到来的假期时段 | OpenHolidays |
get_long_weekends | 查询长周末和必要的桥接假日 | Nager.Date |
export_holidays_ics | 将某州某年的假日导出为 iCalendar(.ics)文档 | OpenHolidays |
is_holiday_today | 查询今天是否为某个州的学校假期或公共假日 | OpenHolidays |
source_status | 检查两个上游来源的可访问性和延迟 | 内置 |
资源
| 资源 URI | 内容 |
|---|---|
holidays://{canton}/{year} | 所有公共假日和学校假日的 Markdown 摘要,例如 holidays://CH-ZH/2026 |
所有工具均带有完整的注释集——readOnlyHint: true、destructiveHint: false、idempotentHint: true、openWorldHint: true(会访问外部 API)。没有任何工具会写入数据。输入均经过架构验证(州代码必须属于已知的 26 个州,日期格式为 YYYY-MM-DD,year 限定在指定范围内,language/school_type 仅允许使用白名单中的值)。
示例用例
| 查询 | 工具 |
|---|---|
| “有哪些州,它们的代码是什么?” | list_cantons |
| “显示苏黎世州 2026 年春季的义务教育假期” | get_school_holidays |
| “2026 年 4 月 3 日是提契诺州的公共假日吗?” | check_date |
| “今年苏黎世州和楚格州的学校假期有重叠吗?” | compare_school_holidays |
| “ZH、ZG、AG 何时可以共同安排一周学校假期?” | find_common_free_window |
| “巴塞尔城市州学校接下来的假期有哪些?” | next_school_holidays |
| “2026 年有哪些长周末,需要搭配哪些桥接日?” | get_long_weekends |
| “苏黎世市有哪些本地假日,而州内其他地方没有?” | get_local_holidays |
| “将苏黎世州 2026 年的假日导出为可导入的 .ics 日历” | export_holidays_ics |
| “今天阿尔高州放假吗?” | is_holiday_today |
🛡️ 安全与限制
| 方面 | 详情 |
|---|---|
| 访问权限 | 只读(readOnlyHint: true)——服务无法修改或删除任何数据 |
| 个人数据 | 不涉及个人数据——所有数据源均为汇总后的公共假日日历 |
| 缓存 | 12 小时内存 TTL(假日表每年只会更改少数几次) |
| 重试 | 指数退避 2s / 4s / 8s;除 429 外的 4xx 错误不会重试 |
| 超时 | 每次 API 调用 20 秒(健康探测为 8 秒) |
| 身份验证 | 无需 API 密钥——两个上游服务均可公开访问 |
| 降级 | 上游故障时会返回带有说明性 note 的 degraded 封装,绝不会静默返回空列表 |
| 服务条款 | 受相应数据源的服务条款约束:OpenHolidays、Nager.Date |
架构
此服务采用 架构 A(仅实时 API,使用内存缓存)。
┌──────────────────────────┐
Claude / any ───▶│ swiss-holidays-mcp │
MCP host │ (MCPServer · 13 tools) │
└────────┬─────────────────┘
│ retry 2s/4s/8s · 12h cache
┌────────┴─────────┐
▼ ▼
OpenHolidays API Nager.Date
(CC BY 4.0) (MIT)
cantons · Schularten long weekends
school + public bridge days
理由(于 2026-07-19 实时验证):
- 所有十个已记录的 OpenHolidays 端点均返回 HTTP 200,且响应内容合理;
/Subdivisions?countryIsoCode=CH恰好返回 26 个州,与官方数量一致。 - 构建时无法验证公开的批量数据转储(
openpotato/openholidays.data的原始访问返回 404),因此无法采用架构 B。 - 假日表每年只会更改少数几次,因此 12 小时的内存 TTL 几乎可以消除所有上游负载,同时避免数据过时。
影响:
- 每个响应都包含
provenance(live_api|cached|degraded)。 - 上游故障时会返回带有说明性
note的degraded封装,绝不会静默返回空列表。 source_status始终返回可评估的健康报告。
实时探测结果(2026-07-19)
| 端点 | HTTP | 状态 | 记录数 | 备注 |
|---|---|---|---|---|
/Countries | 200 | ✅ 正常 | 36 | |
/Subdivisions?countryIsoCode=CH | 200 | ✅ 正常 | 26 | 与官方州数量一致 |
/Groups?countryIsoCode=CH | 200 | ✅ 正常 | 11 | Schulart 分组,仅涵盖 6 个州 |
/PublicHolidays(CH,2026) | 200 | ✅ 正常 | 39 | 包含州级范围 |
/SchoolHolidays(CH,2026) | 200 | ✅ 正常 | 193 | 按学校类型拆分后有 183 个不同记录 |
/SchoolHolidaysByDate | 200 | ✅ 正常 | – | |
/SchoolHolidays?countryIsoCode=XX | 200 | ⚠️ 静默返回空结果 | 0 | 无效国家 ≠ 错误 |
/Subdivisions?languageIsoCode=ZZ | 200 | ⚠️ 静默回退到英语 | 26 | 无效语言 ≠ 错误 |
/SchoolHolidays(未指定日期范围) | 400 | ✅ 正确报错 | – | RFC 9110 problem+json |
Nager /PublicHolidays/2026/CH | 200 | ✅ 正常 | 33 | 29 条记录包含 counties |
Nager /LongWeekend/2026/CH | 200 | ✅ 正常 | 3 | |
Nager /PublicHolidays/2026/XX | 404 | ✅ 正确报错 | – | 校验比 OpenHolidays 更严格 |
已知发现
- 看似重复的记录其实对应不同学校类型。 Zurich 会返回两条 Frühlingsferien 2026:一条对应
CH-ZH-VS(Volksschulen,标记为Recommended),另一条对应CH-ZH-BS+CH-ZH-MS(Berufsfach- 和 Mittelschulen)。请使用school_type参数(VS/MS/BS/EO),不要进行去重。 - 只有六个州会按学校类型区分(AI、AR、BE、GR、SO、ZH)。其他地区不包含
groups,一张表涵盖所有学校。因此,筛选器会将缺少groups字段视为“适用于所有学校”。 - 细分区域代码的层级并不一致。 记录可能包含
CH-AI-AP或CH-BE-TH-BL。请始终按CH-XX前缀匹配,切勿进行字符串全等匹配。 - 空列表不等于有效答案。 未知的国家或州代码会返回 HTTP 200 和
[]。此服务会设置说明性note,以区分“没有假期”和“筛选条件无效”。
前置条件
- Python 3.10 或更高版本
- uv / uvx(推荐)或 pip
- 可访问互联网(两个 API 均可公开访问)
安装
通过 uv 的 uvx 运行——无需克隆或手动安装:
uvx swiss-holidays-mcp
开发
git clone https://github.com/malkreide/swiss-holidays-mcp
cd swiss-holidays-mcp
pip install -e ".[dev]"
配置
Claude Desktop
添加到 claude_desktop_config.json:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"swiss-holidays": {
"command": "uvx",
"args": ["swiss-holidays-mcp"]
}
}
}
重启 Claude Desktop——服务会在首次使用时自动启动。
云端部署(SSE / Streamable HTTP,供浏览器访问)
通过浏览器中的 claude.ai 使用时(例如,在无法安装本地软件的受管工作站上):
MCP_TRANSPORT=sse PORT=8000 python -m swiss_holidays_mcp
SDK 在 /sse 而非 /mcp 提供 SSE。
| 变量 | 默认值 | 说明 |
|---|---|---|
MCP_TRANSPORT | stdio | 传输方式:stdio、sse、streamable-http(又称 http) |
PORT / MCP_PORT | 8000 | HTTP 传输使用的端口 |
MCP_HOST | 127.0.0.1 | HTTP 传输的绑定地址。默认仅绑定环回地址;选择 0.0.0.0 时会记录警告——请在需要身份验证的反向代理后运行。 |
MCP_CORS_ORIGINS | (空) | 浏览器客户端的额外 CORS 来源,以逗号分隔(审计 SDK-004)。始终允许环回来源;请添加 UI 所在的公开来源,例如 https://ui.example.ch。切勿使用 *。 |
HTTP 传输会添加一个显式的 CORS 层,公开
Mcp-Session-Id 标头,以便浏览器 MCP 客户端读取会话 ID 并
发送后续请求。允许列表绝不会使用通配符。
在负载均衡器后运行多个 HTTP 实例时,需要根据 Mcp-Session-Id
保持会话粘性——nginx/Traefik/Kubernetes 示例请参阅 docs/scaling.md。
单个实例(最常见的情况)无需配置会话亲和性。
💡 “开发者笔记本用 stdio,浏览器用 SSE。”
项目结构
swiss-holidays-mcp/
├── src/
│ └── swiss_holidays_mcp/
│ ├── __init__.py # Package init
│ ├── __main__.py # Entry point: stdio / SSE / Streamable HTTP
│ ├── server.py # MCPServer: lifespan, 13 tools, 1 resource, op_* logic
│ ├── client.py # Shared HTTP client: retry, 12h cache, egress guard
│ ├── guard.py # Egress / SSRF guard (HTTPS + allow-list + IP blocklist)
│ ├── pinning.py # DNS-pinning transport (TOCTOU-free connect, SEC-005)
│ ├── ical.py # RFC 5545 iCalendar (.ics) writer
│ ├── settings.py # Pydantic-Settings config (loopback default)
│ ├── logging_setup.py # Structured logging to stderr
│ ├── constants.py # Canton codes, Schulart suffixes, API bases, allow-list
│ └── models.py # Pydantic v2 response envelopes
├── tests/
│ ├── conftest.py # respx fixtures
│ ├── test_tools.py # Tool unit tests (mocked, no network)
│ ├── test_resilience.py # Degradation / retry / cache behaviour
│ └── test_live.py # Live smoke tests (marker: live)
├── docs/ # roadmap.md, security.md, network-egress.md
├── deploy/ # Network-layer egress manifests (Cilium / NetworkPolicy)
├── audits/ # mcp-audit run artifacts
├── Dockerfile # Non-root multi-stage container
├── .github/
│ ├── dependabot.yml # Monthly dependency / action update PRs
│ └── workflows/ # ci.yml, live-tests.yml, publish.yml
├── pyproject.toml
├── CHANGELOG.md
├── CONTRIBUTING.md # Contributing guide (English)
├── CONTRIBUTING.de.md # Contributing guide (German)
├── SECURITY.md # Security policy (English)
├── SECURITY.de.md # Security policy (German)
├── EXAMPLES.md # Use cases by audience
├── server.json # MCP registry manifest
├── LICENSE
├── README.md # This file (English)
└── README.de.md # German version
关于单文件 server.py(审计 ARCH-011)。 这 13 个工具有意放在一个模块中,而不是拆分到 tools/ 包中。每个工具都是一个精简、统一的封装(@mcp.tool → @_safe_tool → op_*),位于与传输方式无关的 op_* 操作之上;所有操作共用一小组辅助函数(_to_period、_matches_school_type、_require_known_canton 等)和唯一的 HolidayClient。将这些内容拆分到多个文件会分散这部分共用核心,并重复导入,却无法带来隔离上的好处——文件按统一的区块组织(别名 → 辅助函数 → op_* 逻辑 → 工具封装 → 资源),而且每个 op_* 都会在不经过传输层的情况下直接进行单元测试。只有在 Phase 2 大幅增加工具数量时,才计划将其拆分到 tools/。
生命周期阶段
此服务器处于阶段 1(只读)——所有工具均为只读,无身份验证,也无副作用。13 个工具的预算(低于建议的 15–20 个上限)仍有余量。苏黎世的 Sechseläuten 和 Knabenschiessen 等本地及市镇级信息,均可直接通过 OpenHolidays 的 get_local_holidays 获取(实时探测显示,上游已在 Gemeinde 级别发布这些信息),因此无需单独的城市数据源。
MCP 原语与协议版本
-
原语——工具 + 资源。 这 13 个工具均为幂等、无副作用的
GET。资源提供稳定的 URI 数据流(holidays://<canton>/<year>),客户端无需调用工具,即可将某州的日历作为可缓存的上下文读取。没有重复使用的模板化工作流,因此不使用提示词(如有变化,将重新评估)。 -
MCP 协议版本——两个时代,均由原生方式支持。
mcp2.x 通过同一服务器同时提供两种版本,具体适用哪种版本由客户端在连接上的首个请求决定:initialize握手最高支持2025-11-25;按请求封装的时代(规范2026-07-28)则完全没有握手。在该模式下,每个请求都会在params._meta中携带其协议版本、客户端信息和客户端能力,并通过Mcp-Protocol-Version/Mcp-Method/Mcp-Name路由标头镜像传递;server/discover则取代initialize。source_status在mcp_protocol_version中报告此请求所用的版本,并逐个请求从Context.protocol_version读取。过去,它会向所有调用方报告同一个常量——握手版本上限,因此协商了2026-07-28的客户端会被告知其连接使用的是2025-11-25。该常量仅作为op_*层的后备值保留,因为该层没有进行协商;它现在取自 SDK 的LATEST_HANDSHAKE_VERSION,而不是硬编码,因此不会再像过去那样与实际情况脱节——曾有两个版本期间,它一直是2025-06-18,每次调用却都将其报告为事实。这是实测结果,而非推断:
tests/test_protocol_version.py通过本仓库在生产环境中提供的同一个 ASGI 应用测试两个时代,并按时代比较实际返回的字段。该文件的上一版本曾声称本仓库无法构建此类应用——实际上可以;这一说法在实测后又沿用了两个版本。客户端在握手中请求2026-07-28,仍会收到2025-11-25;如今这一点已在此处实测,而非在关联服务器上测得。线上版本由固定版本的mcpSDK(mcp>=2.0.0,<3)协商。 -
每个结果都会携带服务器身份信息。
2026-07-28没有initialize结果,因此serverInfo会作为_meta标记附加在每个结果上——这是调用方了解由哪台服务器、哪个构建版本提供响应的唯一途径。服务器会向MCPServer提供其软件包版本和主页;若不提供,SDK 会将版本标记为version: "",且不会用自身的版本替代。空版本比没有版本更糟糕:它看起来像是一个有效答案。 -
列表方法提供新鲜度提示(SEP-2549):
tools/list、resources/list、resources/templates/list和server/discover的响应包含ttlMs300000 和cacheScope: public。没有这些提示时,SDK 的默认行为是“立即过期,绝不共享”,导致每个客户端每次连接时都重新获取列表——而这些列表在导入时就已固定。resources/read不携带提示:holidays://{canton}/{year}是实时查询,而非目录。 -
更新策略。 SDK 和依赖项的升级通过 Dependabot 提交(每月一次,分组处理);协议版本或工具定义变更会记录在
CHANGELOG.md中,并同时升级版本号。
数据分类
所有数据均为 Öffentlich / Public Open Data——汇总的节日日历,不含个人数据(DSG/DSGVO)。这是此服务器处理的最高分类;完整模型见 docs/security.md。
已知限制
- 非官方来源。 OpenHolidays 汇总各州发布的信息。对于具有法律约束力的日期,应以州主管部门的信息为准。每次响应都会说明这一点。
- 市镇级覆盖范围取决于上游数据。 OpenHolidays 确实提供区级和市镇级公共假日(例如
CH-ZH-ZH-ZH的 Sechseläuten、Knabenschiessen),并通过get_local_holidays提供。这些数据在 Gemeinde 级别的完整性取决于上游数据的质量,而各州的数据质量不尽相同。市镇级学校假期不会单独建模。 - Nager 的长周末计算不考虑各州假日。 计算仅基于全国性假日。
- 不保证历史数据的覆盖深度。 约 2020 年以前的年份,数据覆盖并不均衡。
测试
# Unit tests (no network required — respx-mocked)
PYTHONPATH=src pytest tests/ -m "not live"
# Live smoke tests (hits the real upstream APIs)
PYTHONPATH=src pytest tests/ -m "live"
# Linting
ruff check src/ tests/ scripts/
ruff format --check src/ tests/ scripts/
贡献
欢迎贡献!请阅读 CONTRIBUTING.md(英语)· CONTRIBUTING.de.md(德语),了解如何报告错误、设置开发环境、遵循代码风格和满足测试要求。
本项目遵循 Swiss Public Data MCP Portfolio 的惯例。
安全
如需报告漏洞,请遵循 SECURITY.md(英语)· SECURITY.de.md(德语)中的负责任披露流程。该服务为只读服务,无需 API 密钥;有关安全模型,请参阅上文的安全与限制部分。
更新日志
请参阅 CHANGELOG.md
面向瑞士公共行政部门的部署
如果你为瑞士学校主管部门或市镇使用场景自托管此服务:
- 数据驻留: 查询模式本身(公务人员正在比较哪些州)可能会泄露正在进行的规划,因此最好将其保留在瑞士或可信的基础设施上。
- 上游调用会发送至 OpenHolidays(托管于欧盟的 OGD 项目)和 Nager.Date。不会有个人数据离开你的环境;仅请求假日历。
- 日志记录: 日志写入 stderr;请据此配置 IT 保留策略。
- HTTP 传输应在带有身份验证和按 IP 限速的反向代理后运行——该服务没有内置身份验证。
许可证
MIT License — 参见 LICENSE
源数据受 OpenHolidays (CC BY 4.0) 和 Nager.Date (MIT) 的条款约束;使用其数据时必须注明来源。
作者
Hayal Oezkan · github.com/malkreide
致谢与相关项目
- 数据: OpenHolidays API (CC BY 4.0) · Nager.Date (MIT)
- 协议: Model Context Protocol — Anthropic / Linux Foundation
- 构建遵循
mcp-data-source-probe方法:设计前先进行实时探测,依赖 API 前准备转储回退方案,先重试再认输。 - 项目集: Swiss Public Data MCP Portfolio
| 服务 | 描述 |
|---|---|
zh-education-mcp | 苏黎世州教育数据 |
zurich-opendata-mcp | 苏黎世市开放数据 |
swiss-statistics-mcp | BFS STAT-TAB — 瑞士联邦统计数据 |
swisstopo-mcp | 瑞士联邦地理数据(swisstopo) |
采用 MIT 许可。公共资金,公共代码。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。