BFS STAT-TAB PxWeb API for official Swiss statistics
Valid MCP server (1 strong, 3 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry.
3 files analyzed Β· 1 issue found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
This plugin requests these system permissions. Most are normal for its category.
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-malkreide-swiss-statistics-mcp": {
"args": [
"swiss-statistics-mcp"
],
"command": "uvx"
}
}
}From the project's GitHub README.
π¨π Part of the Swiss Public Data MCP Portfolio
MCP Server for Swiss Federal Statistical Office (BFS) data via STAT-TAB PxWeb API β 682 datasets across 21 themes, no authentication required
This server is Alpha (0.x) as per the PyPI classifier. Until 1.0:
mainSee CHANGELOG.md for breaking changes.
swiss-statistics-mcp provides AI-native access to the Swiss Federal Statistical Office (BFS) via the STAT-TAB PxWeb API, without authentication:
| Property | Details |
|---|---|
| API | STAT-TAB PxWeb API v1 |
| Endpoint | https://www.pxweb.bfs.admin.ch/api/v1/ |
| Provider | Swiss Federal Statistical Office (BFS) |
| Datasets | 682 tables across 21 thematic areas |
| Languages | German (de), French (fr), Italian (it), English (en) |
| Licence | Open Government Data (OGD) β BFS Terms of Use |
| Authentication | None β fully public |
Anchor demo query: "How many students attended lower secondary schools in the canton of Zurich in 2024?" β real BFS figures, no hallucination.
# Clone the repository
git clone https://github.com/malkreide/swiss-statistics-mcp.git
cd swiss-statistics-mcp
# Install
pip install -e .
# or with uv:
uv pip install -e .
Or with uvx (no permanent installation):
uvx swiss-statistics-mcp
# stdio (for Claude Desktop)
python -m swiss_statistics_mcp.server
# Streamable HTTP, loopback only (default: host=127.0.0.1, port=8000)
python -m swiss_statistics_mcp.server --http --port 8000
# Streamable HTTP, all interfaces (only behind a reverse proxy with access control)
MCP_HOST=0.0.0.0 python -m swiss_statistics_mcp.server --http --port 8000
# or
python -m swiss_statistics_mcp.server --http --host 0.0.0.0 --port 8000
Try it immediately in Claude Desktop:
"How many teachers worked in the canton of Zurich in 2023?" "What is the population of canton Bern broken down by age?" "Compare the social assistance rate across all cantons for 2022."
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"swiss-statistics": {
"command": "python",
"args": ["-m", "swiss_statistics_mcp.server"]
}
}
}
Or with uvx:
{
"mcpServers": {
"swiss-statistics": {
"command": "uvx",
"args": ["swiss-statistics-mcp"]
}
}
}
Config file locations:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonThe configuration syntax is identical to Claude Desktop. The file name depends on the client:
.cursor/mcp.json in the project folder, or ~/.cursor/mcp.json globally~/.codeium/windsurf/mcp_config.json.continue/config.jsonFor use via claude.ai in the browser (e.g. on managed workstations without local software).
β οΈ Security note β this server has no authentication. A public URL turns it into an open proxy to the BFS API on your deployment's IP. Any client with the URL can drive the tools, consume your platform quota, and attribute traffic to your IP. Two mitigations, in order of preference:
- Put it behind access control β Render's Β«Private ServiceΒ», Cloudflare Access, or a reverse proxy with Basic-Auth / IP allowlist in front of the container.
- Accept it as a public open-data proxy β only acceptable because all data is BFS OGD (Public Open Data) and tools are read-only.
The server binds to
127.0.0.1by default. To expose it on a container port you must explicitly setMCP_HOST=0.0.0.0(e.g. as a Render env var) or pass--host 0.0.0.0. Do not do this without one of the mitigations above.
Render.com:
MCP_HOST=0.0.0.0python -m swiss_statistics_mcp.server --http --port 8000https://your-app.onrender.com/sseπ‘ "stdio for the developer laptop, SSE for the browser."
Since v0.2.0, every tool returns a typed Pydantic model rather than a JSON
string. FastMCP serializes these as structured content so MCP clients can
read fields directly.
# Old (pre-0.2.0)
result = await bfs_get_data(...) # str
data = json.loads(result) # dict
print(data["rows_total"])
# New (>= 0.2.0)
result = await bfs_get_data(...) # DataTableResult
print(result.rows_total) # 1000
print(result.truncated) # True
Every result carries error: str | None and hint: str | None at the top
level β result.error is None means success. Data-returning tools
(bfs_get_data, bfs_education_stats, bfs_population,
bfs_compare_cantons) additionally expose truncated: bool,
rows_total: int, and rows_returned: int for machine-readable cap
detection.
| Tool | Result type |
|---|---|
bfs_list_themes | ListThemesResult |
bfs_list_tables_by_theme | ListTablesByThemeResult |
bfs_search_tables | SearchTablesResult |
bfs_get_table_metadata | TableMetadataResult |
bfs_get_data | DataTableResult |
bfs_education_stats | DataTableResult |
bfs_population | DataTableResult |
bfs_compare_cantons | DataTableResult |
bfs_featured_datasets | FeaturedDatasetsResult |
lookup_commune | LookupCommuneResult |
resolve_historical_commune | ResolveHistoricalCommuneResult |
list_communes | ListCommunesResult |
search_historical_series | SearchHistoricalSeriesResult |
Reference-layer results additionally carry source (attribution) and provenance (live_api | cached); SearchHistoricalSeriesResult also carries licence_note with the mandatory HSSO NonCommercial notice.
| Tool | Description |
|---|---|
bfs_featured_datasets | Curated list of highly relevant datasets (focus on education and demographics) |
bfs_list_themes | All 21 BFS themes with number of available datasets |
bfs_list_tables_by_theme | All tables for a given theme (e.g. "15" = Education and Science) |
bfs_search_tables | Full-text search across the entire data catalogue (682 datasets) |
bfs_get_table_metadata | Variables, values and metadata for a specific table |
bfs_get_data | Data retrieval with optional filters by dimensions and values |
bfs_education_stats | Convenience tool: teachers, pupils, demographic scenarios, scholarships |
bfs_population | Resident population by canton, year, age structure or sex |
bfs_compare_cantons | Cross-cantonal comparison for any table and any variable |
lookup_commune | Resolve a commune by name or BFS number as of a given date (canton, validity, LINDAS URI) |
resolve_historical_commune | Map a historical BFS number onto today's number(s) β re-key old statistics across fusions |
list_communes | List all communes of a canton as of a given date |
search_historical_series | Search long-run time series in Historical Statistics of Switzerland (HSSO) |
The last four tools form the reference layer of the portfolio (see Join Keys): they turn official BFS commune numbers into a reliable join key and let you re-key statistics that predate a municipal merger.
| Query | Tool |
|---|---|
| "How many teachers worked in Zurich in 2023?" | bfs_education_stats |
| "How will upper secondary enrolment develop until 2031?" | bfs_education_stats |
| "What is the population of canton Zurich by age?" | bfs_population |
| "Compare the social assistance rate across all cantons" | bfs_compare_cantons |
| "Is there data on school buildings?" | bfs_search_tables |
| "Which Zurich communes have merged since 2000, and onto which of today's BFS numbers must I re-key old statistics?" | resolve_historical_commune |
| "List all communes of canton Glarus today" | list_communes |
| "Find long-run series on population in HSSO" | search_historical_series |
β More use cases by audience β
| Code | Theme | Code | Theme |
|---|---|---|---|
| 01 | Population | 12 | Money, banks, insurance |
| 02 | Territory and environment | 13 | Social security |
| 03 | Work and income | 14 | Health |
| 04 | National economy | 15 | Education and science |
| 05 | Prices | 16 | Culture, media, information society |
| 06 | Industry and services | 17 | Politics |
| 07 | Agriculture and forestry | 18 | General government |
| 08 | Energy | 19 | Crime and criminal justice |
| 09 | Construction and housing | 20 | Economic and social situation |
| 10 | Tourism | 21 | Sustainable development |
| 11 | Mobility and transport |
βββββββββββββββββββ ββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β Claude / AI ββββββΆβ Swiss Statistics MCP ββββββΆβ BFS STAT-TAB β
β (MCP Host) βββββββ (MCP Server) βββββββ PxWeb API v1 β
βββββββββββββββββββ β β ββββββββββββββββββββββββββββ
β 13 Tools β
β + commune & historical ref β
β Stdio | Streamable HTTP β
β β
β No authentication required β
ββββββββββββββββββββββββββββββββ
| Source | Protocol | Coverage | Auth | Licence |
|---|---|---|---|---|
| BFS STAT-TAB | PxWeb REST API | 682 tables, 21 themes | None | OGD |
| BFS AGVCH (commune register) | REST (CSV/XLSX) | Snapshots, mutations, correspondances | None | OGD |
| HSSO (historical statistics) | Static XLSX dumps | ~750 long-run tables | None | CC BY-NC-SA 3.0 |
snapshot / correspondances / mutations / levels) is a clean, versioned, no-auth API β verified live on 2026-07-19 β so the commune tools query it directly with a 24 h in-memory cache and the shared retry policy. No dump fallback is needed. Finding: the live snapshot CSV header uses Inscription,Radiation,Rec_Type_fr (not the Einschreibung,Streichung names printed in the API PDF), and HistoricalCode is not globally unique across levels β the Parent link is disambiguated by tier when deriving a commune's canton./get/{CHAPTER}.{NN}{suffix}.xlsx). search_historical_series builds a cached title index from the chapter pages and returns the stable download URL. HSSO is licensed CC BY-NC-SA 3.0 (NonCommercial) β different from this server's OGD baseline β so every HSSO response carries an explicit NonCommercial notice in licence_note.The reference layer exists so that data from different servers in the Swiss Public Data MCP Portfolio can be joined reliably. Three identifiers are the portfolio-wide keys:
| Key | What it identifies | Canonical form | Notes |
|---|---|---|---|
BFS commune number (BfsCode) | A political commune | integer, e.g. 261 (ZΓΌrich) | The primary join key across statistics, geo, education and health data. Stable LINDAS/Linked-Data URI: https://ld.admin.ch/municipality/{BfsCode}. Not stable over time β a merger issues a new number, so historical data must be re-keyed via resolve_historical_commune. |
| EGID | A single building (Eidg. GebΓ€udeidentifikator) | 9-digit integer | The join key for building/dwelling-level data (GWR, energy, addresses). A commune contains many EGIDs; BfsCode is the commune each EGID sits in. |
| Canton abbreviation | A canton | two letters, e.g. ZH | The coarsest geographic key. Derivable from any commune via its Parent chain (exposed as canton_abbr). |
Why re-keying matters. BFS commune numbers change whenever communes merge, split, or move canton. Statistics published before a merger use the old number; joining them to today's data without re-keying silently drops or misattributes rows. resolve_historical_commune(bfs_number, from_date, to_date) returns the resolves_to set β the current number(s) old figures must be aggregated onto β plus the mutation_path (the fusions/renamings, with dates). Other portfolio servers are meant to mirror this contract conceptually so the same key resolves the same way everywhere.
Example (anchor query). "Which Zurich communes have merged since 2000?" β e.g. old 132 Hirzel and 133 Horgen both re-key onto today's 295 Horgen; 134/140/142 onto 293 WΓ€denswil.
swiss-statistics-mcp/
βββ src/swiss_statistics_mcp/
β βββ __init__.py # Package
β βββ server.py # 13 tools
βββ tests/
β βββ test_server.py # Unit + integration tests (mocked HTTP)
βββ .github/workflows/ci.yml # GitHub Actions (Python 3.11/3.12/3.13)
βββ pyproject.toml
βββ CHANGELOG.md
βββ CONTRIBUTING.md # English
βββ CONTRIBUTING.de.md # German version
βββ SECURITY.md # English
βββ SECURITY.de.md # German version
βββ LICENSE
βββ README.md # This file (English)
βββ README.de.md # German version
The server emits one JSON log line per tool call on stderr:
{"ts": "2026-05-20T04:02:28", "level": "INFO", "logger": "swiss_statistics_mcp",
"event": "tool_start", "tool": "bfs_list_themes", "rid": "1091cb73", "params_keys": ["lang"]}
{"ts": "2026-05-20T04:02:28", "level": "INFO", "logger": "swiss_statistics_mcp",
"event": "tool_end", "tool": "bfs_list_themes", "rid": "1091cb73", "status": "ok", "duration_ms": 303}
rid β 8-char correlation id linking tool_start and tool_end for the same callparams_keys β sorted list of input field names (no values, no PII)duration_ms β per-call latency on the tool_end eventstatus β "ok" or "error"; error_type is added when a tool raisesRender and other cloud platforms can index these directly for per-tool latency
dashboards and error-rate alerts. Set MCP_LOG_LEVEL=DEBUG for verbose output
or WARNING to suppress per-call events.
βΉοΈ Logs go to stderr so they never collide with the MCP protocol on stdio transport (which uses stdout).
The server absorbs transient BFS-API hiccups before they reach the LLM:
5xx, 429, and network errors are retried up to 3 times with
exponential backoff (0.5s β 4s). 4xx errors surface immediately so client
bugs aren't masked. Tunable via MCP_RETRY_MAX_ATTEMPTS,
MCP_RETRY_WAIT_INITIAL, MCP_RETRY_WAIT_MAX env vars.(table_id, lang) for 1h. Cold list/detail flows
warm the cache; subsequent calls return instantly.bfs_list_tables_by_theme
run in parallel bounded by FANOUT_CONCURRENCY = 5. For limit=20 this
cuts wall-clock from ~20s sequential to ~4s, without overwhelming the
upstream API.Inscription/Radiation/Rec_Type_fr (not the Einschreibung/Streichung names in the API PDF); HistoricalCode is not globally unique across levels, so the canton is derived by walking the Parent chain one tier at a time. Snapshots/mutations are cached for 24 h.licence_note. HSSO exposes no per-table period filter, so search_historical_series's period argument is an informational hint only β verify the actual span in the XLSX. search_historical_series returns the stable XLSX download URL, not the parsed series values.# Unit tests (no API key required)
PYTHONPATH=src pytest tests/ -m "not live"
# Integration tests (live API calls)
pytest tests/ -m "live"
See CHANGELOG.md
See CONTRIBUTING.md
Read-only, no PII, no authentication, single fixed BFS endpoint. See SECURITY.md for the full security posture and accepted-risk decisions.
MIT License β see LICENSE
Hayal Oezkan Β· malkreide
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": {
"swiss-statistics-mcp": {
"command": "uvx",
"args": [
"swiss-statistics-mcp"
]
}
}
}
Be the first to review this server!
by Modelcontextprotocol Β· Developer Tools
Web content fetching and conversion for efficient LLM usage
by Toleno Β· Developer Tools
Toleno Network MCP Server β Manage your Toleno mining account with Claude AI using natural language.
by mcp-marketplace Β· Developer Tools
Create, build, and publish Python MCP servers to PyPI β conversationally.