Skip to main content

MCP Server for Swiss Federal Statistical Office (BFS) data via STAT-TAB PxWeb API

Project description

๐Ÿ‡จ๐Ÿ‡ญ Part of the Swiss Public Data MCP Portfolio

๐Ÿ“Š swiss-statistics-mcp

Version License: MIT Python 3.11+ MCP No Auth Required CI

MCP Server for Swiss Federal Statistical Office (BFS) data via STAT-TAB PxWeb API โ€” 682 datasets across 21 themes, no authentication required

๐Ÿ‡ฉ๐Ÿ‡ช Deutsche Version


Demo

Demo: Claude using bfs_education_stats


Maturity

This server is Alpha (0.x) as per the PyPI classifier. Until 1.0:

  • Tool names, input schemas, and output JSON keys MAY change between minor versions
  • Pin cloud deployments to a specific git tag, not main
  • Production use is acceptable for read-only Open Data scenarios; consider it experimental for anything user-facing

See CHANGELOG.md for breaking changes.


Overview

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.


Features

  • ๐Ÿ“Š 9 tools across 21 statistical themes (682 datasets)
  • ๐Ÿ” Full-text search across the entire BFS data catalogue
  • ๐ŸŽ“ Convenience tools for education statistics and population data
  • ๐Ÿ”๏ธ Cross-cantonal comparison for any table and variable
  • ๐Ÿ”“ No API key required โ€” all data under open licences
  • โ˜๏ธ Dual transport โ€” stdio (Claude Desktop) + Streamable HTTP (cloud)

Prerequisites

  • Python 3.11+
  • uv (recommended) or pip

Installation

# 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

Quickstart

# 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."


Configuration

Claude Desktop

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:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Cursor / Windsurf / VS Code + Continue

The configuration syntax is identical to Claude Desktop. The file name depends on the client:

  • Cursor: .cursor/mcp.json in the project folder, or ~/.cursor/mcp.json globally
  • Windsurf: ~/.codeium/windsurf/mcp_config.json
  • VS Code + Continue: .continue/config.json

Cloud Deployment (SSE for browser access)

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

  1. 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.
  2. 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.1 by default. To expose it on a container port you must explicitly set MCP_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:

  1. Push/fork the repository to GitHub
  2. On render.com: New Web Service โ†’ connect GitHub repo
  3. Set environment variable: MCP_HOST=0.0.0.0
  4. Set start command: python -m swiss_statistics_mcp.server --http --port 8000
  5. In claude.ai under Settings โ†’ MCP Servers, add: https://your-app.onrender.com/sse

๐Ÿ’ก "stdio for the developer laptop, SSE for the browser."


Output Schema

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

Available Tools

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

Example Use Cases

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

โ†’ More use cases by audience โ†’


Themes

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

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Claude / AI   โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚  Swiss Statistics MCP          โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚  BFS STAT-TAB            โ”‚
โ”‚   (MCP Host)    โ”‚โ—€โ”€โ”€โ”€โ”€โ”‚  (MCP Server)                โ”‚โ—€โ”€โ”€โ”€โ”€โ”‚  PxWeb API v1            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚                              โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                        โ”‚  9 Tools                     โ”‚
                        โ”‚  682 datasets ยท 21 themes    โ”‚
                        โ”‚  Stdio | Streamable HTTP     โ”‚
                        โ”‚                              โ”‚
                        โ”‚  No authentication required  โ”‚
                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Data Source Characteristics

Source Protocol Coverage Auth
BFS STAT-TAB PxWeb REST API 682 tables, 21 themes None

Project Structure

swiss-statistics-mcp/
โ”œโ”€โ”€ src/swiss_statistics_mcp/
โ”‚   โ”œโ”€โ”€ __init__.py              # Package
โ”‚   โ””โ”€โ”€ server.py                # 9 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
โ”œโ”€โ”€ LICENSE
โ”œโ”€โ”€ README.md                    # This file (English)
โ””โ”€โ”€ README.de.md                 # German version

Observability

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 call
  • params_keys โ€” sorted list of input field names (no values, no PII)
  • duration_ms โ€” per-call latency on the tool_end event
  • status โ€” "ok" or "error"; error_type is added when a tool raises

Render 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).


Resilience

The server absorbs transient BFS-API hiccups before they reach the LLM:

  • Retries โ€” 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.
  • Metadata cache โ€” Table metadata (variables, value domains, last_updated) is cached in-memory per (table_id, lang) for 1h. Cold list/detail flows warm the cache; subsequent calls return instantly.
  • Concurrency cap โ€” Fan-out metadata fetches in 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.

Known Limitations

  • PxWeb API: Rate limiting may apply for rapid successive queries; the server uses a 1-hour cache for the catalogue index and a 1-hour cache for table metadata
  • Language: Dataset titles and dimension values are in German by default; French, Italian and English coverage varies by table
  • JSON-STAT2: Some complex cross-tabulations may return large result sets; use dimension filters to narrow queries

Testing

# Unit tests (no API key required)
PYTHONPATH=src pytest tests/ -m "not live"

# Integration tests (live API calls)
pytest tests/ -m "live"

Safety & Limits

  • Read-only: All tools perform HTTP GET requests only โ€” no data is written, modified, or deleted.
  • No personal data: STAT-TAB returns aggregated statistical datasets. No personally identifiable information (PII) is processed or stored by this server.
  • Rate limits: The PxWeb API is a public endpoint without documented rate limits; avoid tight loops over the full 682-table catalogue. The server enforces a 30s timeout per request and caches the catalogue index for 1 hour.
  • Data freshness: BFS publishes updated figures periodically (not real-time). Figures reflect the state of the upstream database at query time.
  • Terms of service: Data is subject to the BFS Terms of Use (OGD). All STAT-TAB data is published as Open Government Data and may be freely used with attribution.
  • No guarantees: This server is a community project, not affiliated with the Swiss Federal Statistical Office. Availability depends on the upstream BFS API.

Changelog

See CHANGELOG.md


Contributing

See CONTRIBUTING.md


License

MIT License โ€” see LICENSE


Author

Hayal Oezkan ยท malkreide


Credits & Related Projects

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

swiss_statistics_mcp-0.2.0.tar.gz (87.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swiss_statistics_mcp-0.2.0-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

Details for the file swiss_statistics_mcp-0.2.0.tar.gz.

File metadata

  • Download URL: swiss_statistics_mcp-0.2.0.tar.gz
  • Upload date:
  • Size: 87.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for swiss_statistics_mcp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 6d130f97d95760fc925f95e7bcd51ae4b4d4421e9dc1f552177290ec803b6add
MD5 6138dc107361a4426fd0e040be4b286a
BLAKE2b-256 f91ba37116fd0d8b99ba36fbf0958ca59b224a558a6cd239a5c187e296b64a24

See more details on using hashes here.

Provenance

The following attestation bundles were made for swiss_statistics_mcp-0.2.0.tar.gz:

Publisher: publish.yml on malkreide/swiss-statistics-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file swiss_statistics_mcp-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for swiss_statistics_mcp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8e5fb4a5e8d66d4a8a8ad2d3113acdd70886fd32158188eececf50554da281fc
MD5 91088225e2c135a2938c12b29b9ba35b
BLAKE2b-256 ac274fcd6564b08f89fa40c817227d8552e31a2ec318c0d9120c66314651c3e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for swiss_statistics_mcp-0.2.0-py3-none-any.whl:

Publisher: publish.yml on malkreide/swiss-statistics-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page