🇨🇭 Part of the Swiss Public Data MCP Portfolio
📺 srgssr-mcp

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).
🇩🇪 Deutsche Version
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
git clone https://github.com/malkreide/srgssr-mcp.git
cd srgssr-mcp
pip install -e .
Or with uvx (no permanent installation):
uvx srgssr-mcp
Or via pip:
pip install srgssr-mcp
Quickstart
export SRGSSR_CONSUMER_KEY="your-consumer-key"
export SRGSSR_CONSUMER_SECRET="your-consumer-secret"
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:
| 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
epg://{bu}/{channel_id}/{date} | Daily TV/radio program guide for SRF, RTS, RSI (e.g. epg://srf/srf-1/2026-04-30) |
votation://{votation_id} | Detailed result of a closed Swiss popular vote (e.g. votation://v1) |
EPG station ids — hyphenated, and not the same as the livestream ids:
| SRF | srf-1, srf-2, srf-info | srf-1, srf-2, srf-2-kultur, srf-3, srf-4, srf-musikwelle, srf-virus |
| RTS | rts-1, rts-2, rts-info | LA1ERE, ESPACE2, COULEUR3, OPTION_MUSIQUE |
| RSI | la-1, la-2 | rete-uno, rete-due, rete-tre |
Prompts
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)
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)
srgssr_video_get_shows | List TV shows for a business unit (character_filter a–z/# selects one initial; omit it to fan out over all 27) | 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)
srgssr_audio_get_shows | List radio shows for one radio channel (channel_id required — the v2 API has no per-unit listing) | 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)
srgssr_epg_get_programs | Daily program schedule for a TV or radio channel | SRG SSR IL |
🗳️ Polis – Swiss Democracy (3 tools)
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
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
| "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_livestreams → 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
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
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-mcp or swiss-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 the mcp-sdk label) 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.md and, 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: 15 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
| 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
Contributing
See CONTRIBUTING.md (English) · CONTRIBUTING.de.md (German)
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
ALLOWED_HOSTS = {"api.srgssr.ch"} (exact match — subdomain tricks like api.srgssr.ch.attacker.example are blocked). One host covers everything: the OAuth2 token endpoint and every data endpoint.
- IP blocklist — every resolved IP for the hostname is checked against private, loopback, link-local (incl.
169.254.169.254 cloud-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_HOSTS in src/srgssr_mcp/_http.py.
- Document the reason in the PR and
CHANGELOG.md.
- Add a positive test case in
tests/test_unit.py (mirror test_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
timestamp on 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):
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
PYTHONPATH=src pytest tests/ -m "not live"
PYTHONPATH=src pytest tests/ -m "live"
ruff check src/
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.
| 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
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)
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"
]
}
}
}