Runs on your machine from its package; the steps are below.
At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted β your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx bakom-mcp
MCP client config
{
"mcpServers": {
"bakom": {
"command": "uvx",
"args": [
"bakom-mcp"
]
}
}
}π¨π Part of the Swiss Public Data MCP Portfolio
MCP server for BAKOM open data β broadband, mobile coverage, media and Swiss telecom statistics.
bakom-mcp connects AI assistants like Claude to the Swiss Federal Office of Communications (BAKOM) open data infrastructure. It enables natural-language queries about broadband availability, 5G/4G coverage, mobile antenna locations, BAKOM datasets on radio and television, and telecommunications statistics β all without API keys.
All data is published as Open Government Data (OGD) on opendata.swiss / geo.admin.ch under CC BY 4.0 β see the Data Licence section below for attribution requirements.
Anchor demo query: "Which school buildings in district 7 do not yet have fibre optic connectivity?"
bakom_multi_standort_konnektivitaetdelivers the comparison table automatically.
β More use cases by audience β
β Read-only queries against three public BAKOM/Confederation APIs:
api3.geo.admin.ch / wms.geo.admin.ch (broadband, mobile coverage, antennas)ckan.opendata.swiss (telecom statistics, dataset metadata)β Returns aggregated, anonymous data β no personal data, no household-level identification.
β Bound to Swiss WGS84 coordinates (lat 45.8β47.9, lon 5.9β10.6) via Pydantic input validation.
β Egress is locked to a code-layer allow-list of the six known data-source hosts.
β Send data anywhere (read-only, no write tools).
β Access the local filesystem (no path-traversal surface).
β Use authentication tokens (none required β all sources are public OGD).
β Cache or persist user inputs across calls.
β Execute shell commands or arbitrary code (no subprocess/os.system/eval).
uv or pip for installation# Recommended: uvx (no permanent installation required)
uvx bakom-mcp
# Or install with pip
pip install bakom-mcp
# Development install
git clone https://github.com/malkreide/bakom-mcp
cd bakom-mcp
pip install -e ".[dev]"
Add to claude_desktop_config.json:
{
"mcpServers": {
"bakom": {
"command": "uvx",
"args": ["bakom-mcp"]
}
}
}
Config file locations:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonpython -m bakom_mcp.server --http
# Server running at http://localhost:8050/mcp
Configuration via environment variables (see .env.example):
| Variable | Default | Purpose |
|---|---|---|
BAKOM_MCP_HOST | 127.0.0.1 | Bind address. Set to 0.0.0.0 only on trusted networks (warning logged). |
BAKOM_MCP_PORT | 8050 | TCP port. |
BAKOM_MCP_CORS_ORIGINS | (empty) | Comma-separated allowed origins for browser clients. Empty = CORS disabled. |
A hardened container image is provided. Suitable for cloud deployments behind a reverse proxy (Caddy, Traefik, nginx).
# Build & run via compose (recommended)
docker compose up --build
# Or via docker run
docker build -t bakom-mcp:latest .
docker run --rm \
--read-only \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--tmpfs /tmp:rw,size=16M \
-p 127.0.0.1:8050:8050 \
bakom-mcp:latest
The image runs as non-root (UID 10001), uses a read-only filesystem, drops all Linux capabilities and refuses privilege escalation. Resource limits are configured in docker-compose.yml (256 MB memory, 0.5 CPU, 64 PIDs). The default port mapping binds to 127.0.0.1 only β for public exposure, terminate TLS and CORS at a reverse proxy.
{
"bakom": {
"command": "uvx",
"args": ["bakom-mcp"],
"transport": "stdio"
}
}
π‘ "stdio for the developer laptop, HTTP/SSE for the browser."
| Tool | Description |
|---|---|
bakom_broadband_coverage | Fixed-line coverage at a coordinate (30β1000 Mbit/s) |
bakom_glasfaser_verfuegbarkeit | FTTB/FTTH fibre availability |
bakom_multi_standort_konnektivitaet | Connectivity comparison for up to 20 locations |
| Tool | Description |
|---|---|
bakom_mobilfunk_abdeckung | 5G/4G/3G outdoor coverage |
bakom_sendeanlagen_suche | Mobile antennas within a configurable radius |
bakom_frequenzdaten | Radio/TV transmitter sites near a location |
| Tool | Description |
|---|---|
bakom_rtv_suche | Search BAKOM radio/TV datasets on opendata.swiss |
bakom_medienstruktur_info | Swiss media landscape datasets |
bakom_aktuell | Recently updated BAKOM datasets on a topic (catalogue, not press releases) |
bakom_medien_statistik | Market shares, reach and programme structure from the BAKOM cubes on LINDAS |
| Tool | Description |
|---|---|
bakom_telekomstatistik_uebersicht | Telecom statistics from opendata.swiss |
bakom_breitbandatlas_datensaetze | Full catalogue of Broadband Atlas layers |
bakom_check_api_status | π Health check for all configured data sources |
What is the broadband situation at Schulhaus Leutschenbach (47.4148, 8.5654)?
Compare 5G and fibre coverage for these three school buildings: [coordinates]
Which radio stations are licensed in canton Zurich?
Show me the current BAKOM position on 5G frequency allocation.
List all Broadband Atlas datasets available via geo.admin.ch.
| Aspect | Details |
|---|---|
| Access | Read-only (readOnlyHint: true) β the server cannot modify or delete any data |
| Personal data | No personal data β all sources are aggregated, public open data |
| Rate limits | Built-in per-query caps (max 50 antennas, max 20 locations, max 50 RTV results) |
| Timeout | 20 seconds per API call |
| Authentication | No API keys required β all 3 APIs are publicly accessible |
| Licences | All data under CC0 / open licences (Open Government Data) |
| Terms of Service | Subject to ToS of the respective data sources: geo.admin.ch, opendata.swiss |
| Source | Data | Authentication |
|---|---|---|
| geo.admin.ch | Broadband Atlas, mobile coverage, antenna locations | None |
| opendata.swiss | BAKOM datasets, telecom statistics | None |
| lindas.admin.ch | BAKOM media statistics cubes (SPARQL) | None |
All data is published under open licences (CC0 / OGD).
bakom_medien_statistik uses Architecture A (live API only), verified live on 2026-08-13:
https://lindas.admin.ch/query; the /sparql path documented elsewhere returns 404.https://lindas.admin.ch/ofcom/cube. Without an explicit FROM, a query hits the default graph and sees 2010 cubes from every federal office instead of 540.Scope, stated in the tool description because the README does not reach the model: the cubes cover the programmes that were surveyed, not the full inventory. LINDAS's own count cube reports 199+39+17 radio programmes for 2020, while the Programm dimension across all cubes exposes 128 labels β some of which are the same station spelled differently (Energy BE / Energy Bern), plus Durchschnitt, which is an aggregate rather than a station. This tool is a statistics source, not a broadcaster register; licensed broadcasters are listed in the RTV database, which offers no machine-readable interface.
bakom-mcp can be combined with other servers in the portfolio for multi-dimensional queries:
zurich-opendata-mcp β school building addresses
+
bakom-mcp β fibre and 5G status
=
"Digital equity" dashboard for all school districts
Further combinations:
srgssr-mcp + bakom-mcp β Media coverage and broadcast networkswiss-statistics-mcp + bakom-mcp β Telecom market developmentfedlex-mcp + bakom-mcp β Regulatory context (RTVG, FMG)bakom-mcp/
βββ src/bakom_mcp/
β βββ __init__.py # Package
β βββ server.py # MCP server (12 tools, 2 resources)
β βββ py.typed # PEP 561 type marker
βββ tests/
β βββ test_integration.py # Integration tests (live APIs)
βββ assets/
β βββ demo.svg # Demo flow diagram
βββ .github/workflows/
β βββ ci.yml # CI: lint, syntax, import, tests
β βββ publish.yml # PyPI publish on release
βββ .gitignore
βββ pyproject.toml # Build config (hatchling)
βββ CHANGELOG.md
βββ CONTRIBUTING.md # Contribution guide (English)
βββ CONTRIBUTING.de.md # Contribution guide (German)
βββ SECURITY.md # Security policy (English)
βββ SECURITY.de.md # Security policy (German)
βββ LICENSE # MIT License
βββ README.md # This file (English)
βββ README.de.md # German 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 and asserted
against the installed SDK, so a Dependabot bump of mcp cannot move either one
silently. The handshake ceiling is measured against a live initialize through
the assembled ASGI stack, not read off a constant name.
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.
Native in the envelope era. The server uses nothing the 2026-07-28
spec deprecates (SEP-2577: logging, sampling, roots). The status text of
bakom_multi_standort_konnektivitaet travels in the progress notification
instead of ctx.info() β in the modern era logging is delivered only when the
request opts in with a log level in _meta, and without that opt-in the line
was silently dropped. serverInfo carries the package version on initialize
and on server/discover alike. A tool call needs neither a handshake nor an
Mcp-Session-Id. tests/test_modern_era.py
checks this over real connections in both eras, and the suite treats every
MCPDeprecationWarning as an error.
What the server announces. No change notifications: tools, resources and
prompts are fixed at import, so the server reports listChanged and
resources.subscribe as false in both eras and rejects
subscriptions/listen. No outputSchema: tools return text (Markdown or a
JSON string) in content. Both are checked by
tests/test_capabilities.py.
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.
# Unit tests (no network required)
PYTHONPATH=src pytest tests/ -m "not live"
# Integration tests (live APIs, internet required)
PYTHONPATH=src pytest tests/ -m "live"
See CHANGELOG.md
Contributions are welcome! See CONTRIBUTING.md (π©πͺ Deutsch) for guidelines on reporting bugs, suggesting features, and submitting pull requests.
To report a vulnerability, see the Security Policy (π©πͺ Deutsch). Please report security issues privately via GitHub Security Advisories rather than public issues.
MIT License β see LICENSE.
The BAKOM open data delivered through this server is published under CC BY 4.0. When using or redistributing tool outputs, attribute the source as:
Source: Federal Office of Communications (BAKOM) via opendata.swiss / geo.admin.ch Β· Licence: CC BY 4.0
Tool outputs already include this footer automatically. The Markdown response format ends with the attribution line; downstream applications that consume the JSON format should propagate the source/licence metadata to their end users.
Hayal Oezkan Β· github.com/malkreide
Part of the Swiss Open Data MCP portfolio β public data deserves public interfaces.
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": {
"bakom-mcp": {
"command": "uvx",
"args": [
"bakom-mcp"
]
}
}
}