MCP server for Swiss direct democracy: Swissvotes (1848+), BFS opendata.swiss, SRGSSR Polis (1900+)
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
claude mcp add swiss-democracy-mcp -- python -m swiss-democracy-mcp{
"mcpServers": {
"swiss-democracy-mcp": {
"command": "python",
"args": ["-m", "swiss_democracy_mcp.server"],
"env": {
"MCP_HOST": "<mcp_host>"
}
}
}
}MCP_HOSTMCP Servers overview
# 🗳️ swiss-democracy-mcp
[](https://github.com/malkreide/swiss-democracy-mcp/actions/workflows/ci.yml)
[](https://pypi.org/project/swiss-democracy-mcp/)
[](https://pypi.org/project/swiss-democracy-mcp/)
[](LICENSE)
[](https://modelcontextprotocol.io/)
[](https://github.com/malkreide/swiss-public-data-mcp)
> 🇨🇭 **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide/swiss-public-data-mcp)** — connecting AI models to Swiss institutional data sources.
🌐 **English** | **[🇩🇪 Deutsche Version](README.de.md)**
An MCP server providing access to Swiss direct democracy data, covering all federal popular votes since 1848 and elections since 1900.

---
## Demo Query
```
«Wie hat der Kanton Zürich bei der AHV 21 Initiative 2022 abgestimmt,
und welche Parteien unterstützten die Vorlage?»
```
→ `democracy_search_votes(keyword="AHV 21", year_from=2022)`
→ `democracy_get_cantonal_results(vote_number="551")`
→ `democracy_get_party_positions(vote_number="551")`
[→ More use cases by audience →](EXAMPLES.md)
---
## Data Sources
| Source | Coverage | Auth |
|---|---|---|
| **[Swissvotes](https://swissvotes.ch)** (Uni Bern) | All federal votes since 1848 · 874 columns · party positions · cantonal results | None ✓ |
| **[BFS / opendata.swiss](https://opendata.swiss/de/dataset/echtzeitdaten-am-abstimmungstag-zu-eidgenoessischen-abstimmungsvorlagen)** | Real-time & archive (since 1981) · municipality level | None ✓ |
| **[SRGSSR Polis](https://developer.srgssr.ch/api-catalog/srgssr-polis)** | Votes & elections since 1900 · municipality detail | OAuth2 key |
---
## Tools
### Phase 1 — Swissvotes (No Auth Required)
| Tool | Description |
|---|---|
| `democracy_search_votes` | Search all federal popular votes since 1848 by keyword, date range, legal form, outcome, policy domain |
| `democracy_get_vote_detail` | Full details for a specific vote: official title, parliamentary positions, national result, signatures |
| `democracy_get_party_positions` | Party recommendations (FDP, SP, SVP, Die Mitte, GPS, GLP, …) with campaign finance data |
| `democracy_get_cantonal_results` | Results for all 26 cantons: yes%, turnout, accepted flag |
| `democracy_list_vote_dates` | List all voting dates with number of proposals per date |
### Phase 2 — BFS Real-Time (No Auth Required)
| Tool | Description |
|---|---|
| `democracy_bfs_list_vote_dates` | List all BFS voting dates (archive + current) |
| `democracy_bfs_get_vote_results` | Real-time or archived results at national, cantonal, or municipality level |
### Phase 3 — SRGSSR Polis (API Key Required)
| Tool | Description |
|---|---|
| `democracy_polis_list_votations` | Historical votations since 1900 with municipality-level data |
| `democracy_polis_get_votation_detail` | Full Polis detail, optionally with all municipality results |
| `democracy_polis_list_elections` | National Council, Council of States, and cantonal elections since 1900 |
---
## Installation
### Claude Desktop (stdio)
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"swiss-democracy": {
"command": "uvx",
"args": ["swiss-democracy-mcp"],
"env": {
"SRGSSR_CONSUMER_KEY": "your_key_here",
"SRGSSR_CONSUMER_SECRET": "your_secret_here"
}
}
}
}
```
> The `SRGSSR_*` variables are optional. Without them, all Swissvotes and BFS tools remain fully functional. Only the Polis tools require credentials.
### Cloud / Render.com (Streamable HTTP)
```bash
pip install swiss-democracy-mcp
# Bind to all interfaces ONLY inside a container/cloud environment:
MCP_TRANSPORT=streamable_http MCP_HOST=0.0.0.0 MCP_PORT=8000 python -m swiss_democracy_mcp.server
```
`MCP_HOST` defaults to `127.0.0.1` (loopback). Set `MCP_HOST=0.0.0.0` **only**
inside a sandboxed container/cloud deployment — never on a local machine, where
it would expose the server to your whole network.
---
## Architecture
```
┌─────────────────────────────────────────────┐
│ Claude / LLM Host │
└──────────────┬──────────────────────────────┘
│ MCP (stdio / Streamable HTTP)
┌──────────────▼──────────────────────────────┐
│ swiss-democracy-mcp │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Swissvotes CSV Cache (24h TTL) │ │
│ │ All 874 columns, since 1848 │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────────┐ │
│ │ BFS / opendata.swiss (no auth) │ │
│ │ Real-time & archive since 1981 │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────────┐ │
│ │ SRGSSR Polis (OAuth2, optional) │ │
│ │ Votes & elections since 1900 │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```
**Transport:** stdio for Claude Desktop · Streamable HTTP for cloud/Render.com
**Auth pattern:** No-Auth-First — Swissvotes & BFS work without any credentials
**Cache:** Swissvotes CSV is loaded once at startup and cached for 24 hours
---
## Configuration
All configuration is via environment variables (see [`.env.example`](.env.example)):
| Variable | Default | Purpose |
|---|---|---|
| `MCP_TRANSPORT` | `stdio` | `stdio` (local) or `streamable_http` (cloud) |
| `MCP_HOST` | `127.0.0.1` | HTTP bind address — set `0.0.0.0` **only** in a container |
| `MCP_PORT` | `8000` | HTTP port |
| `LOG_LEVEL` | `INFO` | structured JSON logs go to **stderr** |
| `MCP_CORS_ORIGINS` | — | comma-separated CORS allow-origins for the HTTP transport. **Empty means no browser client is permitted**; `*` allows any origin and is logged as a warning. stdio clients are unaffected |
| `MCP_ALLOWED_HOSTS` | — | comma-separated inbound `Host` allow-list. Only needed for a non-loopback bind |
| `SRGSSR_CONSUMER_KEY` / `SRGSSR_CONSUMER_SECRET` | — | optional, only for `democracy_polis_*` tools |
Secrets are held as `SecretStr` and never logged. See
[docs/secret-management.md](docs/secret-management.md) and
[docs/security.md](docs/security.md).
## MCP Primitives
This server intentionally exposes **Tools only** (no Resources or Prompts). It is
a Phase-1 read-only data wrapper (see [docs/roadmap.md](docs/roadmap.md)); the
tool surface is small (10 use-case-oriented tools) and every response is
self-contained with source provenance. Resources (e.g. `vote://{anr}`) are a
candidate for a later phase once the URI scheme stabilises.
## 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`](tests/test_protocol_version.py) and asserted
against the installed SDK, so a Dependabot bump of `mcp` cannot move either one
silently. Next to them,
[`tests/test_server_identity.py`](tests/test_server_identity.py) sends real
requests through the assembled ASGI app: a per-request envelope is answered
with the tool list, and an `initialize` that offers the modern revision still
gets the handshake ceiling back. The constants say which revisions the SDK
knows; the measurement says which ones this server serves.
**Server identity.** Under the handshake era `serverInfo` is named once, in the
`initialize` reply. The modern era has no handshake and stamps the identity
onto **every** response instead, as
`_meta["io.modelcontextprotocol/serverInfo"]`. `Implementation.version` is a
required schema field that the SDK pre-fills with `""` when the constructor
omits it, so an unset version is not a gap but a value on the wire — and one
that a green test suite will not show you. This server passes its distribution
version explicitly; `test_server_identity.py` measures it on both wires, with a
bare `MCPServer` as the negative control.
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`](CHANGELOG.md) together.
---
## Portfolio Synergy
Combine with other servers in the Swiss Public Data MCP portfolio:
- **[zurich-opendata-mcp](https://github.com/malkreide/zurich-opendata-mcp)** — add Zurich city-level statistics to vote analysis
- **[swiss-statistics-mcp](https://github.com/malkreide/swiss-statistics-mcp)** — enrich with BFS demographic data by canton
- **[srgssr-mcp](https://github.com/malkreide/srgssr-mcp)** — link SRF news archive content to specific vote dates
Example multi-server query:
*«Vergleiche die AbWhat people ask about swiss-democracy-mcp
What is malkreide/swiss-democracy-mcp?
+
malkreide/swiss-democracy-mcp is mcp servers for the Claude AI ecosystem. MCP server for Swiss direct democracy: Swissvotes (1848+), BFS opendata.swiss, SRGSSR Polis (1900+) It has 2 GitHub stars and its last recorded update is dated 2026-09-26.
How do I install swiss-democracy-mcp?
+
You can install swiss-democracy-mcp by cloning the repository (https://github.com/malkreide/swiss-democracy-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is malkreide/swiss-democracy-mcp safe to use?
+
Our security agent has analyzed malkreide/swiss-democracy-mcp and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.
Who maintains malkreide/swiss-democracy-mcp?
+
malkreide/swiss-democracy-mcp is maintained by malkreide. The last recorded GitHub activity is dated 2026-09-26, with 0 open issues.
Are there alternatives to swiss-democracy-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy swiss-democracy-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/malkreide-swiss-democracy-mcp)<a href="https://claudewave.com/repo/malkreide-swiss-democracy-mcp"><img src="https://claudewave.com/api/badge/malkreide-swiss-democracy-mcp" alt="Featured on ClaudeWave: malkreide/swiss-democracy-mcp" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
The fastest path to AI-powered full stack observability, even for lean teams.