Swiss Democracy Mcp

io.github.malkreide由 GitHub 帳號 malkreide 發布v0.3.0更新於 Oct 24, 2026

Swiss popular votes and elections: Swissvotes 1848+, BFS opendata.swiss, SRGSSR Polis 1900+

已驗證STDIO僅桌面位置與生活資料與分析

概覽

AI 產生的概覽

本機 MCP 伺服器,讓助理以唯讀方式查詢 1848 年以來的瑞士聯邦公民投票與 1900 年以來的選舉,含邦級、政黨及市鎮級結果。

可以做什麼
依關鍵字、日期範圍、法律形式、結果或政策領域搜尋並列出聯邦公民投票,並列出各投票日期與議案數量。取得單次公投的完整詳情:官方標題、議會立場、全國結果與連署數。取得政黨推薦立場與競選經費資料,以及全部 26 個邦的贊成率、投票率與是否通過。查詢 BFS 全國、邦或市鎮層級的即時與封存結果。設定選用的 SRGSSR Polis 憑證後,可列出 1900 年以來的歷史公投與選舉並取得市鎮級詳情。
適用情境
適合回答關於瑞士直接民主的問題:某邦如何就某項議案投票、哪些政黨支持該議案、歷史投票趨勢,或比較各市鎮結果。較適合對彙總公共資料做研究與分析,而非取得即時新聞。
執行需求
透過 uvx 以 Python 套件形式在本機經 stdio 執行(需安裝 uv),僅支援桌面用戶端。需能連線 swissvotes.ch、opendata.swiss、bfs.admin.ch 與 api.srgssr.ch。Swissvotes 與 BFS 工具無需憑證;Polis 工具需免費註冊取得的 SRGSSR_CONSUMER_KEY 與 SRGSSR_CONSUMER_SECRET。HTTP 模式可選設定 MCP_TRANSPORT、MCP_HOST、MCP_PORT、MCP_CORS_ORIGINS 與 MCP_ALLOWED_HOSTS。
安裝前請注意
唯讀:工具僅發出 HTTP GET 請求,不會向上游寫入任何資料。選用的 SRGSSR_CONSUMER_KEY 與 SRGSSR_CONSUMER_SECRET 屬於機密,README 稱其以 SecretStr 保存且不會寫入日誌。Polis 資料為免費額度、僅限非商業用途,各資料來源另有條款並要求註明出處。Swissvotes 資料集會在記憶體中快取 24 小時,佔用較多記憶體。將 MCP_HOST 設為 0.0.0.0 會把伺服器暴露到網路,只應在容器中進行。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Swiss Democracy Mcp,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

🗳️ swiss-democracy-mcp

[CI] [PyPI] [Python] [License: MIT] [MCP] [Swiss Public Data MCP]

🇨🇭 Part of the Swiss Public Data MCP Portfolio — connecting AI models to Swiss institutional data sources.

🌐 English | 🇩🇪 Deutsche Version

An MCP server providing access to Swiss direct democracy data, covering all federal popular votes since 1848 and elections since 1900.

[Demo: Claude using democracy_search_votes, democracy_get_cantonal_results and democracy_get_party_positions]


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 →


Data Sources

SourceCoverageAuth
Swissvotes (Uni Bern)All federal votes since 1848 · 874 columns · party positions · cantonal resultsNone ✓
BFS / opendata.swissReal-time & archive (since 1981) · municipality levelNone ✓
SRGSSR PolisVotes & elections since 1900 · municipality detailOAuth2 key

Tools

Phase 1 — Swissvotes (No Auth Required)

ToolDescription
democracy_search_votesSearch all federal popular votes since 1848 by keyword, date range, legal form, outcome, policy domain
democracy_get_vote_detailFull details for a specific vote: official title, parliamentary positions, national result, signatures
democracy_get_party_positionsParty recommendations (FDP, SP, SVP, Die Mitte, GPS, GLP, …) with campaign finance data
democracy_get_cantonal_resultsResults for all 26 cantons: yes%, turnout, accepted flag
democracy_list_vote_datesList all voting dates with number of proposals per date

Phase 2 — BFS Real-Time (No Auth Required)

ToolDescription
democracy_bfs_list_vote_datesList all BFS voting dates (archive + current)
democracy_bfs_get_vote_resultsReal-time or archived results at national, cantonal, or municipality level

Phase 3 — SRGSSR Polis (API Key Required)

ToolDescription
democracy_polis_list_votationsHistorical votations since 1900 with municipality-level data
democracy_polis_get_votation_detailFull Polis detail, optionally with all municipality results
democracy_polis_list_electionsNational 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):

VariableDefaultPurpose
MCP_TRANSPORTstdiostdio (local) or streamable_http (cloud)
MCP_HOST127.0.0.1HTTP bind address — set 0.0.0.0 only in a container
MCP_PORT8000HTTP port
LOG_LEVELINFOstructured 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 and 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); 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.

EraRevisionWho reaches it
initialize handshake2024-11-05 … 2025-11-25What 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 envelope2026-07-28A request carrying the 2026-07-28 _meta envelope opens a modern connection.

Both revisions are pinned in 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 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 together.


Portfolio Synergy

Combine with other servers in the Swiss Public Data MCP portfolio:

Example multi-server query:
«Vergleiche die Abstimmungsresultate zur AHV-Reform mit der Altersstruktur der Kantone»
→ swiss-democracy-mcp + swiss-statistics-mcp


Safety & Limits

  • Read-only: All tools perform HTTP GET requests only — no data is written, modified, or deleted upstream.
  • Egress allow-list (SSRF protection): Outbound requests are restricted to a fixed allow-list of trusted hosts (swissvotes.ch, opendata.swiss, *.bfs.admin.ch, api.srgssr.ch), HTTPS only. Caller-supplied URLs (the BFS result_url) are additionally resolved and rejected if they point at private, loopback or cloud-metadata IP ranges.
  • No personal data: Swissvotes, BFS and SRGSSR Polis expose aggregate democratic data (vote results, party positions, cantonal/municipality tallies). No personally identifiable information (PII) is processed or stored by this server.
  • Rate limits: Swissvotes is a single CSV downloaded once per 24h and cached in memory. BFS / opendata.swiss and SRGSSR Polis are public APIs without documented rate limits — keep limit and date ranges conservative. The server enforces a 30s timeout per request.
  • Data freshness: Real-time BFS results reflect the upstream feed at query time (no local caching). Swissvotes is refreshed every 24h from the Uni Bern mirror.
  • Terms of service: Data is subject to the ToS of each source — swissvotes.ch (CC BY 4.0, Uni Bern), opendata.swiss (mostly CC-BY / Open by Default), SRGSSR Polis (free tier, non-commercial use only). Always cite the upstream source in downstream products.
  • No guarantees: This server is a community project, not affiliated with the University of Bern, the Federal Statistical Office, or SRG SSR. Availability depends on upstream APIs.

Known Limitations

  • Swissvotes coverage: Cantonal-level results are available since 1848; municipality-level results only via SRGSSR Polis (since ~1990s depending on the vote).
  • BFS archive: Real-time service covers federal votes since 1981 only.
  • Polis tools: Require free registration at developer.srgssr.ch. Non-commercial use only.
  • CSV cache: The Swissvotes dataset is ~several MB and is cached in memory for 24 hours. Memory footprint is accordingly higher than API-only servers.

Testing

bash
# Unit tests (no network required)PYTHONPATH=src pytest tests/ -m "not live" -v
# Live tests (require network access)PYTHONPATH=src pytest tests/ -m "live" -v

Where the test data comes from

The fixtures under tests/fixtures/ are recorded from the live sources and dated. Source, retrieval date, selection rule and SHA-256 for every file: tests/fixtures/PROVENANCE.md.

bash
python scripts/record_fixtures.py   # re-record

The selection rule is the point here. The Swissvotes dataset has 714 rows and 874 columns, and the recorded rows are chosen by property, not by position. "The first N rows" would cut away exactly the cells the fixture exists for: the placeholders 9999 ("no information") and . ("not applicable"), which appear in 667 of the 714 votes. A fixture without them would look clean and prove nothing — which is precisely why nobody noticed the tools were passing them through as if they were values.

The byte-order mark stays in the file too, because the source sets one and the server strips it explicitly. Without it the fixture could not show that it has to.

SRGSSR Polis needs OAuth credentials and is listed under NOT RECORDED in PROVENANCE.md rather than given a date it never had.


Contributing

See CONTRIBUTING.md (🇩🇪 Deutsch).


Security

See SECURITY.md (🇩🇪 Deutsch) for the security posture and vulnerability reporting.


License

MIT — see LICENSE.

Data licenses:


Author

Hayal Oezkan · github.com/malkreide

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):

json
{  "mcpServers": {    "swiss-democracy-mcp": {      "command": "uvx",      "args": [        "swiss-democracy-mcp"      ]    }  }}

來源:README.md,提交 c0477a9

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

2
  1. v0.3.0最新Sep 26, 2026
  2. v0.2.5Sep 16, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分