π¨π Part of the Swiss Public Data MCP Portfolio
πΊ srgssr-mcp
<p align="center"> <img src="assets/demo.svg" alt="Demo: Claude asks a question β srgssr-mcp tool call β grounded answer from SRG SSR Polis API" width="760"> </p>MCP server connecting AI models to SRG SSR public APIs β weather, TV/radio metadata, program guide and Swiss votations/elections since 1900 (SRF, RTS, RSI, RTR, SWI).
Overview
srgssr-mcp gives AI assistants like Claude direct access to the public APIs of SRG SSR β Switzerland's national public broadcaster. Weather forecasts, TV and radio metadata, electronic program guides, and historical democratic data (votations and elections since 1900) are all accessible through a single standardised MCP interface.
The server covers five thematic clusters: SRF Weather, Video, Audio, EPG and Polis (Swiss Democracy). Each cluster maps to a group of purpose-built tools that translate raw SRG SSR API data into clean JSON responses.
Anchor demo query: "What were the cantonal results of the popular vote on initiative X in Zurich?" β answered with historical real-time data from the Polis system, not a hallucination.
Features
- π¦οΈ Weather β location search, current conditions, 24h hourly forecast, 7-day forecast (SRF Meteo)
- πΊ Video β TV show listings, latest episodes, live TV channels across all business units
- ποΈ Audio β radio show listings, audio episodes, live radio stations
- π EPG β daily program schedule for any TV or radio channel
- π³οΈ Polis β popular votes and elections since 1900, national and cantonal results
- π’ Multi-unit β SRF (DE), RTS (FR), RSI (IT), RTR (RM), SWI (multilingual)
- π OAuth2 β automatic token management with Client Credentials flow
- βοΈ Dual transport β stdio for Claude Desktop, Streamable HTTP/SSE for cloud deployment
Prerequisites
- Python 3.11+
- API keys from developer.srgssr.ch (free registration):
- Create an account and log in
- Under "My Apps", create a new application
- Add the product SRG SSR PUBLIC API V2
- Note your Consumer Key and Consumer Secret
β οΈ Terms of use: SRG SSR APIs are available for non-commercial use. For commercial use, contact api@srgssr.ch directly.
Installation
# Clone the repository
git clone https://github.com/malkreide/srgssr-mcp.git
cd srgssr-mcp
# Install
pip install -e .
Or with uvx (no permanent installation):
uvx srgssr-mcp
Or via pip:
pip install srgssr-mcp
Quickstart
# Set credentials
export SRGSSR_CONSUMER_KEY="your-consumer-key"
export SRGSSR_CONSUMER_SECRET="your-consumer-secret"
# Start the server (stdio mode for Claude Desktop)
srgssr-mcp
Try it immediately in Claude Desktop:
"What will the weather be like in Zurich tomorrow?" "What's on SRF 1 tonight?" "Which popular votes took place in the canton of Bern between 2010 and 2020?"
Configuration
Claude Desktop
Minimal (recommended):
{
"mcpServers": {
"srgssr": {
"command": "uvx",
"args": ["srgssr-mcp"],
"env": {
"SRGSSR_CONSUMER_KEY": "your-consumer-key",
"SRGSSR_CONSUMER_SECRET": "your-consumer-secret"
}
}
}
}
Config file locations:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
After saving, restart Claude Desktop completely.
Other MCP Clients
Compatible with Cursor, Windsurf, VS Code + Continue, LibreChat, Cline, and self-hosted models via mcp-proxy. Set the same environment variables.
Cloud Deployment (SSE for browser access)
For use via claude.ai in the browser (e.g. on managed workstations without local software):
SRGSSR_CONSUMER_KEY=... \
SRGSSR_CONSUMER_SECRET=... \
SRGSSR_MCP_TRANSPORT=streamable-http \
SRGSSR_MCP_HOST=0.0.0.0 \
SRGSSR_MCP_PORT=8000 \
python -m srgssr_mcp.server
Transport, host, port and mount path are all driven by environment variables
(see srgssr_mcp.server.Settings). Valid values for SRGSSR_MCP_TRANSPORT
are stdio (default), sse, and streamable-http.
π‘ "stdio for the developer laptop, SSE for the browser."
MCP Primitives
This server exposes all three orthogonal MCP primitives:
| Primitive | Mental model | What's here |
|---|---|---|
| Tools (verbs) | Executable functions / parametrized queries | 15 tools β search, list, fetch, aggregate |
| Resources (nouns) | Cache-friendly passive data behind URIs | EPG entries and immutable votation results |
| Prompts (recipes) | Reusable workflow templates | Voting analysis & daily briefing |
Tools cover parametrized searches (year ranges, free-text, paginated listings) where every call may yield different results. Resources expose stable data points that are safe to cache: a published EPG for a given channel/date, or the final result of a closed Swiss votation. Prompts standardise recurring multi-step analyses so users don't have to phrase them from scratch.
Resources
| URI template | Description |
|---|---|
epg://{bu}/{channel_id}/{date} | Daily TV/radio program guide for SRF, RTS, RSI (e.g. epg://srf/srf1/2026-04-30) |
votation://{votation_id} | Detailed result of a closed Swiss popular vote (e.g. votation://v1) |
Prompts
| Name | Arguments | Purpose |
|---|---|---|
analyse_abstimmungsverhalten | votation_id, focus (stadt_land / sprachregionen / kantone) | Structured analysis of a Swiss popular vote |
tagesbriefing_kanton | location, channel_id, business_unit, date | Daily briefing combining weather and EPG |
Available Tools
Tool Naming Convention
This server uses snake_case for tool names, following Python ecosystem idioms. While MCP best practice favors camelCase for optimal LLM tokenization, snake_case remains acceptable and keeps tool names aligned with the underlying Python function identifiers.
All tools follow the pattern srgssr_<domain>_<action> with the namespace prefix srgssr_ and a semantically meaningful <domain>_<action> suffix (e.g. srgssr_weather_current, srgssr_polis_get_votations).
π¦οΈ SRF Weather (4 tools)
| Tool | Description | Data Source |
|---|---|---|
srgssr_weather_search_location | Search for a location by name or postal code to obtain a geolocationId | SRF Meteo |
srgssr_weather_current | Current weather conditions for a Swiss location | SRF Meteo |
srgssr_weather_forecast_24h | Hourly 24-hour forecast | SRF Meteo |
srgssr_weather_forecast_7day | Daily 7-day forecast | SRF Meteo |
πΊ Video (3 tools)
| Tool | Description | Data Source |
|---|---|---|
srgssr_video_get_shows | List TV shows for a business unit | SRG SSR IL |
srgssr_video_get_episodes | Retrieve latest episodes of a show | SRG SSR IL |
srgssr_video_get_livestreams | List live TV channels | SRG SSR IL |
ποΈ Audio (3 tools)
| Tool | Description | Data Source |
|---|---|---|
srgssr_audio_get_shows | List radio shows for a business unit | SRG SSR IL |
srgssr_audio_get_episodes | Retrieve audio episodes of a show | SRG SSR IL |
srgssr_audio_get_livestreams | List live radio stations | SRG SSR IL |
π EPG β Electronic Program Guide (1 tool)
| Tool | Description | Data Source |
|---|---|---|
srgssr_epg_get_programs | Daily program schedule for a TV or radio channel | SRG SSR IL |
π³οΈ Polis β Swiss Democracy (3 tools)
| Tool | Description | Data Source |
|---|---|---|
srgssr_polis_get_votations | Popular votes since 1900 (national or cantonal) | Polis API |
srgssr_polis_get_votation_results | Detailed results of a specific vote | Polis API |
srgssr_polis_get_elections | Election results since 1900 | Polis API |
Supported Business Units
| Code | Unit | Language |
|---|---|---|
srf | SRF (Schweizer Radio und Fernsehen) | German |
rts | RTS (Radio TΓ©lΓ©vision Suisse) | French |
rsi | RSI (Radiotelevisione svizzera) | Italian |
rtr | RTR (Radiotelevisiun Svizra Rumantscha) | Romansh |
swi | SWI swissinfo.ch | Multilingual |
Example Use Cases
| Query | Tool |
|---|---|
| "Weather in Zurich tomorrow?" | srgssr_weather_forecast_24h |
| "What's on SRF 1 tonight?" | srgssr_epg_get_programs |
| "Latest Tagesschau episodes?" | srgssr_video_get_episodes |
| "Popular votes in Canton Bern 2010β2020?" | srgssr_polis_get_votations |
| "Cantonal results of the mask initiative vote?" | srgssr_polis_get_votation_results |
| "All current RTS radio shows?" | srgssr_audio_get_shows |
β More use cases by audience β
Architecture
βββββββββββββββ
β Claude / LLMβ
ββββββββ¬βββββββ
β MCP (stdio)
ββββββββΌββββββββββββββββββββ
β srgssr-mcp Server β
β ββ Weather Tools (4) β
β ββ EPG Tools (1) β
β ββ Polis Tools (3) β
β ββ Video Tools (3) β
β ββ Audio Tools (3) β
ββββββββ¬ββββββββββββββββββββ
β HTTPS (OAuth2)
ββββββββΌβββββββββββββββ
β SRG SSR Public APIs β
β developer.srgssr.chβ
βββββββββββββββββββββββ
Data Sources
| Source | Data | Access |
|---|---|---|
| developer.srgssr.ch | SRG SSR PUBLIC API V2 (weather, A/V, EPG, Polis) | OAuth2 (free registration) |
Attribution: SRG SSR APIs are subject to the SRG SSR Terms of Use.
Development Phase
This server is in Phase 1: Read-only Wrapper.
The server exposes only GET-style operations against public SRG SSR APIs. There are no write, mutate or delete capabilities by design β see Safety & Limits for the threat-model implications.
Phase 1 Completion Criteria
- 14 read-only tools across five thematic clusters (Weather, Video, Audio, EPG, Polis)
- OAuth2 Client Credentials authentication with token caching
- Bilingual documentation (EN/DE)
- Test suite (unit + live) β see OPS-001
- Structured logging β see OBS-003 and CHANGELOG
- Production-ready error handling (uniform retry/backoff, typed error envelopes)
Future Phases
- Phase 2 (Write): Not planned. The SRG SSR Public APIs are read-only by contract; there is no upstream surface to write to.
- Phase 3 (Multi-Agent): Evaluation deferred. Will be reconsidered once user feedback indicates concrete multi-agent workflows that this server should orchestrate (e.g. cross-server aggregation with
swiss-statistics-mcporswiss-transport-mcp).
MCP Protocol Version
This server is built and tested against MCP protocol version 2025-06-18.
The version is pinned explicitly as PROTOCOL_VERSION in src/srgssr_mcp/_app.py and validated at import time against the installed SDK's SUPPORTED_PROTOCOL_VERSIONS β a fastmcp/mcp upgrade that drops support for the pinned revision will fail fast at startup instead of silently changing wire-level behaviour. Bumps are tracked in CHANGELOG.md under the matching release.
Update Policy
- SDK dependency updates land via Dependabot (
.github/dependabot.yml, monthly cadence, grouped under themcp-sdklabel) and run the full test suite before merge. - Spec bumps are evaluated on a feature branch against the relevant MCP SDK release; the official MCP changelog is the source of truth for breaking changes.
- A spec-version bump is always documented in
CHANGELOG.mdand, if it changes the externally observable wire contract, triggers a minor or major release per Semantic Versioning.
Project Structure
srgssr-mcp/
βββ src/srgssr_mcp/
β βββ __init__.py # Package
β βββ server.py # FastMCP server: 14 tools, OAuth2 client
βββ .github/
β βββ workflows/
β βββ ci.yml # GitHub Actions CI (Python 3.11β3.13)
βββ pyproject.toml # Build configuration (hatchling)
βββ CHANGELOG.md
βββ CONTRIBUTING.md # English
βββ CONTRIBUTING.de.md # German
βββ SECURITY.md # Security policy (English)
βββ SECURITY.de.md # Security policy (German)
βββ LICENSE # MIT
βββ README.md # This file (English)
βββ README.de.md # German version
π‘οΈ Safety & Limits
| Aspect | Details |
|---|---|
| Access | Read-only β the server only reads from SRG SSR APIs and cannot post, modify or delete any content |
| Personal data | No personal data β all endpoints serve public broadcast metadata, weather observations and historical votation/election results |
| Rate limits | Subject to the tier of your OAuth2 application on developer.srgssr.ch; the server adds sensible per-query caps (e.g. max 100 episodes, 50 shows per list call) |
| Timeout | 30 seconds per upstream API call |
| Authentication | OAuth2 Client Credentials (free registration); secrets stay local, never logged |
| Licensing & use | SRG SSR APIs are for non-commercial use; commercial use requires written permission from api@srgssr.ch |
| Terms of Service | Subject to the SRG SSR Developer Terms of Use β users remain responsible for attribution and compliance |
Known Limits
- Rate Limits: SRG SSR APIs enforce rate limits β see developer.srgssr.ch for details on the tier of your OAuth2 application
- Data Freshness: EPG data may be delayed by up to 6 hours
- Historical Data: Polis data goes back to 1900 β older data is not available
- Geo-Restriction: Some streaming APIs are only available within Switzerland
- API keys required: SRG SSR APIs require free OAuth2 credentials from developer.srgssr.ch
- Non-commercial use: SRG SSR API terms restrict commercial use without explicit permission from api@srgssr.ch
- Weather coverage: SRF Meteo covers Switzerland only
Security
For the full security posture, vulnerability reporting process and accepted-risk register, see SECURITY.md (English) Β· SECURITY.de.md (German). The key egress control is summarised below.
Egress Allowlist
The server implements a code-layer egress allowlist (SEC-021, combined with SEC-004 SSRF defense) to prevent unintended external requests. Every outbound HTTP request is validated by _validate_url_safe() in src/srgssr_mcp/_http.py before it is issued.
Three controls per request:
- HTTPS-only β
http://,file://,ftp://and other non-HTTPS schemes are rejected. - Host allowlist β the URL hostname must equal one of
ALLOWED_HOSTS = {"api.srgssr.ch"}(exact match β subdomain tricks likeapi.srgssr.ch.attacker.exampleare blocked). - IP blocklist β every resolved IP for the hostname is checked against private, loopback, link-local (incl.
169.254.169.254cloud-metadata), CGNAT, multicast and reserved ranges (IPv4 + IPv6). Any single match aborts the request β defense-in-depth against DNS rebinding.
Violations surface as ValueError and are mapped to a localized Konfigurationsfehler: β¦ message by _handle_error, so internal network details never leak to the MCP client.
Adding a new SRG SSR domain:
- Update
ALLOWED_HOSTSinsrc/srgssr_mcp/_http.py. - Document the reason in the PR and
CHANGELOG.md. - Add a positive test case in
tests/test_unit.py(mirrortest_validate_url_safe_accepts_public_srgssr_host).
Network-Layer Egress (for future SSE/HTTP deployments): see docs/network-egress.md. For the current stdio transport, network-layer controls do not apply β the process runs in the MCP client's user context.
Logging
The server uses structured logging (OBS-003) via structlog with JSON output to stderr β keeping stdout clean for the stdio transport's JSON-RPC traffic.
Format:
- JSON-encoded events, one per line
- ISO 8601 UTC
timestampon every record - RFC 5424 severity levels:
debug,info,notice,warning,error,critical,alert,emergency - Per-call bound context:
tool,business_unit,channel_id,query, etc.
Example output:
{"event": "tool_invoked", "tool": "srgssr_weather_search_location", "query": "Bern", "level": "info", "logger": "mcp.srgssr.weather", "timestamp": "2026-04-30T14:23:45.123Z"}
{"event": "tool_succeeded", "tool": "srgssr_weather_search_location", "query": "Bern", "result_count": 3, "matched_variant": "Bern", "level": "info", "logger": "mcp.srgssr.weather", "timestamp": "2026-04-30T14:23:45.456Z"}
Log levels (RFC 5424):
| Level | Used for |
|---|---|
debug | OAuth token cache hits, internal state |
info | Tool invocations, successful responses, server lifecycle |
warning | Recoverable conditions (rate-limit approaching, unsupported business unit) |
error | API failures, timeouts (recoverable) |
critical | Credential issues, service degradation |
Configuration:
The default level is info. Override via the SRGSSR_LOG_LEVEL environment variable (debug, info, warning, error, critical):
SRGSSR_LOG_LEVEL=debug srgssr-mcp
JSON output is aggregator-friendly β pipe stderr to Datadog, Splunk, Loki, etc., and filter by structured fields (tool, business_unit, level) without regex parsing.
Testing
# Unit tests (no network required)
PYTHONPATH=src pytest tests/ -m "not live"
# Integration tests (requires SRG SSR API keys)
PYTHONPATH=src pytest tests/ -m "live"
# Linting
ruff check src/
Contributing
See CONTRIBUTING.md (English) Β· CONTRIBUTING.de.md (German)
Changelog
See CHANGELOG.md
Data Sources & Licenses
All data exposed by this server is fetched live from a single upstream
provider, SRG SSR Public API V2 (https://api.srgssr.ch). Every tool
return is a typed Pydantic BaseModel that
embeds source / license / provenance_url / fetched_at at the top
level β so downstream consumers can record the data origin without
round-tripping through this README. FastMCP exposes the corresponding
outputSchema in the tools/list manifest so MCP clients can plan
follow-up calls precisely.
| Cluster | Provider | License | Notes |
|---|---|---|---|
| Weather | SRF Meteo (api.srgssr.ch) | SRG SSR Terms of Use | Geo-restricted to Switzerland |
| Video / Audio / EPG | SRF Β· RTS Β· RSI Β· RTR Β· SWI | SRG SSR Terms of Use | Metadata only β stream URLs are not redistributed |
| Polis (Votations / Elections) | SRG SSR Polis | SRG SSR Terms of Use | Historical data since 1900 |
Use of the SRG SSR APIs
- Non-commercial use: free, no application required.
- Commercial use: written permission required via api@srgssr.ch.
This server's MIT license covers the source code only; it does not relicense the upstream data.
License
MIT License β see LICENSE
The SRG SSR APIs used in this project are subject to the SRG SSR Terms of Use.
Author
Hayal Oezkan Β· github.com/malkreide
Credits & Related Projects
- Data: SRG SSR Developer Portal Β· SRF Meteo Β· Polis API
- Protocol: Model Context Protocol β Anthropic / Linux Foundation
- Related:
| Server | Description |
|---|---|
| zurich-opendata-mcp | City of Zurich open data (OSTLUFT air quality, weather, parking, geodata) |
| swiss-transport-mcp | Swiss public transport β OJP 2.0 journey planning, SIRI-SX disruptions |
| swiss-environment-mcp | BAFU environmental data β air quality, hydrology, natural hazards |
| swiss-statistics-mcp | BFS STAT-TAB β 682 statistical datasets |
| fedlex-mcp | Swiss federal law via Fedlex SPARQL |
Synergy example: "What were the results of the 2020 popular votes in Canton Zurich β and how did turnout compare to the national average?"
β srgssr-mcp (Polis, cantonal results) + swiss-statistics-mcp (BFS, turnout data)
- Portfolio: Swiss Public Data MCP Portfolio
Installation
Run via uv's uvx β no clone or manual install needed. Add to your MCP client config (mcpServers for Claude Desktop, Cursor and Windsurf; use a top-level servers key for VS Code in .vscode/mcp.json):
{
"mcpServers": {
"srgssr-mcp": {
"command": "uvx",
"args": [
"srgssr-mcp"
]
}
}
}