{
  "markdown": "# bag-health-mcp\n\n[![PyPI](https://img.shields.io/pypi/v/bag-health-mcp)](https://pypi.org/project/bag-health-mcp/)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Swiss Public Data MCP Portfolio](https://img.shields.io/badge/Portfolio-Swiss%20Public%20Data%20MCP-red)](https://github.com/malkreide)\n\n> Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide) — connecting AI models to Swiss public data sources.\n\n**[🇩🇪 Deutsche Version](README.de.md)**\n\nMCP server for Swiss public health data. Its core is the Swiss Federal Office of Public Health (BAG) **Infectious Disease Dashboard (IDD)** — epidemiological surveillance for 51 pathogens (influenza, COVID-19, measles, wastewater surveillance, and more) — extended with a **multi-source health-indicator layer** over the Swiss Health Observatory (**Obsan**), the **Versorgungsatlas** (health-care supply atlas, with cantonal series) and **Sucht Schweiz** (HBSC youth survey). All read-only, public Open Government Data.\n\n---\n\n## What You Can Do\n\n```\n\"Wie ist die aktuelle Grippesituation im Kanton Zürich verglichen mit den letzten Wochen?\"\n→ bag_health_mcp__get_canton_situation(canton=\"ZH\")\n\n\"Gibt es aktuell einen Masernausbruch in der Schweiz?\"\n→ bag_health_mcp__get_disease_data(series_id=\"measles/cases/incValue/year\", canton=\"all\")\n\n\"Wie entwickelt sich das SARS-CoV-2-Signal im Abwasser?\"\n→ bag_health_mcp__list_series(topic=\"wastewater_viral_load\")\n→ bag_health_mcp__get_disease_data(series_id=\"wastewater_viral_load/NA/value/date\", ...)\n\n\"Welche Krankheitsdaten stellt das BAG aktuell bereit?\"\n→ bag_health_mcp__list_diseases()\n\n\"Wie hat sich der Alkoholkonsum bei 15-Jährigen seit 2010 entwickelt?\"   # 🎯 anchor query\n→ bag_health_mcp__search_health_indicators(source=\"suchtschweiz\", topic=\"alkohol\")\n→ bag_health_mcp__get_indicator_series(source=\"suchtschweiz\",\n      indicator_id=\"monam/alkoholkonsum-alter-11-15\", region=\"ZH\", year_from=2010)\n→ More use cases by audience →\n```\n\n> **🎯 Anchor demo query** — *«Wie hat sich der Alkoholkonsum bei 15-Jährigen im\n> Kanton Zürich seit 2010 entwickelt, und wie steht der Kanton im Schweizer\n> Vergleich da?»* The HBSC youth series (via Obsan) answers the **Switzerland-wide**\n> trend since 2010 with 95% confidence intervals. This particular indicator is\n> **national only**, so the response includes a `region_note` saying so\n> (HBSC is not cantonally representative). Most other Obsan indicators *are*\n> published by canton — see the note on cuts below. These are **aggregated\n> population statistics — not individual advice.**\n> See [`docs/tool-design-health-indicators.md`](docs/tool-design-health-indicators.md).\n\n---\n\n## Tools\n\nInfectious-disease surveillance (BAG IDD):\n\n| Tool | Description |\n|------|-------------|\n| `bag_health_mcp__list_diseases` | List all 51 disease topics, grouped by category |\n| `bag_health_mcp__list_series` | List data series for a specific disease |\n| `bag_health_mcp__get_series_details` | Get available filter dimensions (canton, age, sex) |\n| `bag_health_mcp__get_disease_data` | Fetch time-series surveillance data |\n| `bag_health_mcp__get_canton_situation` | Situational overview for a canton (Schulamt use case) |\n| `bag_health_mcp__list_export_files` | List available complete export datasets |\n| `bag_health_mcp__download_export` | Download raw CSV/JSON export |\n| `bag_health_mcp__get_data_version` | Current data version (updated every Wednesday) |\n\nHealth indicators — Obsan, Versorgungsatlas & Sucht Schweiz (multi-source):\n\n| Tool | Description |\n|------|-------------|\n| `bag_health_mcp__search_health_indicators` | Search indicators by `source` (`obsan` / `versorgungsatlas` / `suchtschweiz`), topic, region, year range |\n| `bag_health_mcp__get_indicator_series` | Fetch one indicator's time series, naming which **cut** it is (`variant`: national / by canton / by age class / by social position / distribution) and which others exist. Pass `region='ZH'` for the cantonal cut; 95% CIs throughout |\n\n> ⚠️ **Aggregated population statistics only.** The indicator tools serve\n> population-level aggregates (prevalences/metrics by age/sex/region) — **not\n> individual advice, diagnosis or case assessment, and no personal data.** This is\n> stated in both tool descriptions and every response (`aggregate_statistics_notice`),\n> and matters especially for `suchtschweiz` (HBSC), which touches prevention topics\n> in a school context. Sources: Obsan `ind.obsan.admin.ch` (clean JSON API);\n> Sucht Schweiz HBSC via the Obsan mirror (national); **Versorgungsatlas** returns a\n> **cantonal** year/value series (26 cantons + a `CH` national total, with 95% CIs and\n> a canton-vs-CH ratio) from the Tarifpool. See the per-source [probe notes](docs/).\n\n> **Obsan publishes an indicator in several cuts, not one series.** Measured over\n> 60 catalogue entries on 2026-08-08: 50 have a cantonal cut (`kg`), 49 one by age\n> class (`ag`), 24 one by social position (`sd`) — and only 3 the plain national\n> one (`g`). They are different measurements with different units, so\n> `get_indicator_series` names the cut it returned in `variant` and lists the rest\n> in `variants_available`, rather than presenting one as a stand-in for another.\n> Eight of the 60 publish no series at all; that case fails with its reason\n> instead of returning an empty result. The census is recorded and dated in\n> [`tests/fixtures/obsan_variant_census.json`](tests/fixtures/obsan_variant_census.json).\n\n### Tool annotations\n\nAll tools carry MCP [tool annotations](https://modelcontextprotocol.io/) so a\nhost can reason about them without calling. Every tool is identical here — it\nonly ever reads from the public, allow-listed data sources (BAG IDD, Obsan,\nVersorgungsatlas):\n\n| Annotation | Value | Meaning |\n|------------|-------|---------|\n| `readOnlyHint` | `true` | No tool mutates any state. |\n| `destructiveHint` | `false` | No destructive side effects. |\n| `idempotentHint` | `true` | Repeating a call has no additional effect. |\n| `openWorldHint` | `true` | Tools reach an external system (the upstream data APIs). |\n\nA host may therefore treat all calls as safe, cacheable reads. The values are\ndeclared once as `READ_ONLY` in `server.py` and applied to all 10 tools.\n\n## MCP Primitives\n\nThis server uses all three MCP primitives, each for what it is best at:\n\n**Tools** (10) — live, parameterised actions that call the IDD API (above).\n\n**Resources** — static, read-only reference data a host can fetch and cache, no\narguments or upstream call needed:\n\n| Resource URI | Description |\n|--------------|-------------|\n| `bag://reference/cantons` | Canton codes accepted by the tools (incl. FL, `all`) |\n| `bag://reference/disease-categories` | Disease-topic taxonomy by category |\n| `bag://reference/data-licence` | Source, attribution and licence terms |\n\n**Prompts** — reusable, parameterised workflows a host can surface (e.g. as\nslash-commands):\n\n| Prompt | Arguments | Purpose |\n|--------|-----------|---------|\n| `canton_situation_brief` | `canton` | Draft a Schulamt public-health situation brief |\n| `outbreak_check` | `disease`, `canton` | Check whether a disease is currently elevated |\n\nLive surveillance data stays behind Tools (it is parameterised and changes\nweekly); fixed reference data is exposed as Resources; recommended multi-tool\nworkflows are packaged as Prompts.\n\n---\n\n## Relevance for Schools & City Administration\n\n**Schulamt / Kreisschulbehörden:**\n- Monitor influenza and ARI incidence in your canton\n- Single measles case → alert for schools with low vaccination coverage\n- Pertussis tracking → protect unvaccinated infants (siblings of school children)\n\n**Stadtverwaltung / KI-Fachgruppe:**\n- Public Health Reporting with structured weekly data\n- Wastewater surveillance as 1-week lead indicator before clinical cases\n\n**Synergy with portfolio:**\n- `bag-epl-mcp` → \"What treatments are listed?\" (EPL medication database)\n- `bag-health-mcp` → \"What is currently spreading?\" (surveillance data)\n\n---\n\n## Data Source\n\n- **IDD API**: `https://api.idd.bag.admin.ch` — No authentication required\n- **Update cycle**: Every Wednesday\n- **Coverage**: Switzerland + Liechtenstein (FL), 26 cantons\n- **Topics**: 51 pathogens, 1386 data series\n\n### Datenquellen & Lizenzen / Data sources & licences\n\n| Source | Provider | Licence | Attribution required |\n|--------|----------|---------|----------------------|\n| Infectious Disease Dashboard (IDD) | Federal Office of Public Health (FOPH / BAG) | [opendata.swiss](https://opendata.swiss) Open Government Data — *free use, source attribution required* (Swiss OGD terms, CC BY-equivalent) | Yes |\n| Health indicators | Obsan — Swiss Health Observatory (`ind.obsan.admin.ch`) | No explicit machine-readable licence; treat as Swiss OGD practice — *free use, cite the per-indicator source* | Yes |\n| Health-care supply atlas | Versorgungsatlas (BAG/Obsan, `versorgungsatlas.ch`) | Same (Swiss OGD practice, cite source) | Yes |\n| HBSC youth survey | Sucht Schweiz — HBSC, obtained via the Obsan mirror | Same (Swiss OGD practice, cite «Sucht Schweiz — HBSC») | Yes |\n\n**Required citation:** *Federal Office of Public Health FOPH — Infectious Disease\nDashboard (IDD), open data via opendata.swiss.* For the indicator tools, each\nresponse's `provenance.source` names the concrete upstream (e.g. «Sucht Schweiz —\nHBSC» via Obsan). Every tool response carries attribution in a `provenance` block\n(`attribution` + `license` fields) so downstream consumers can surface it\nautomatically.\n\n```\nArchitecture:\n                    ┌─────────────────┐    api.idd.bag.admin.ch (IDD API, no auth)\n  MCP Host          │  bag-health-mcp │──▶ ind.obsan.admin.ch   (Obsan JSON API)\n  (Claude, etc.) ──▶│  MCP SDK        │──▶ versorgungsatlas.ch  (indicator catalogue)\n                    │  10 Tools       │    all HTTPS, egress allow-listed, no auth\n                    └─────────────────┘\n```\n\n---\n\n## Installation\n\n### Claude Desktop (stdio)\n\n```json\n{\n  \"mcpServers\": {\n    \"bag-health\": {\n      \"command\": \"uvx\",\n      \"args\": [\"bag-health-mcp\"]\n    }\n  }\n}\n```\n\n### Cloud / HTTP\n\n```bash\npip install bag-health-mcp\npython -m bag_health_mcp.server --http --port 8000\n```\n\nTransport, host and port are set via environment variables — `MCP_TRANSPORT`\n(`http`/`stdio`), `MCP_HOST`, `MCP_PORT` — which is the recommended way for\ndeployments (the `--http` flag still works for local use). The server binds to\n`127.0.0.1` by default so a local HTTP server is **not** exposed to the network.\nContainer/cloud deployments bind all interfaces by setting `MCP_HOST=0.0.0.0`\nexplicitly — the provided `Dockerfile` does this.\n\n> ⚠️ **Security:** HTTP transport exposes the server on the network. Only bind\n> beyond `127.0.0.1` in a **network-isolated** environment — never directly on a\n> public/shared network. Binding to a non-localhost host logs a warning at\n> startup. The default stdio transport has no network surface. See\n> [`docs/security-posture.md`](docs/security-posture.md).\n\n**HTTP auth (optional):** set `MCP_AUTH_TOKEN` to require\n`Authorization: Bearer <token>` on every HTTP request (401 otherwise). Unset =\nno auth (fine for stdio/local). This gates *who may invoke* the server; for real\nuser identity, front it with a gateway.\n\n**CORS (browser clients):** set `MCP_CORS_ORIGINS` to a comma-separated origin\nallow-list to enable cross-origin browser access; the `Mcp-Session-Id` header is\nexposed so stateful sessions work. Empty = no cross-origin (never a wildcard).\n\n**Host allow-list (DNS rebinding):** set `MCP_ALLOWED_HOSTS` to a comma-separated\nlist of the names this server is reachable under, including the port, e.g.\n`bag.example.ch:8000`. Requests arriving under any other `Host` are rejected\nwith **421**; loopback stays allowed so container health checks keep working.\n\nUnset on a non-localhost bind, the check is left off and a warning is logged —\nthat is the gateway-fronted deployment, where the gateway validates `Host`. It\nis not guessed: on `0.0.0.0` the reachable name is unknowable here, and a wrong\nguess would reject the very deployment it is meant to protect.\n\nThis is independent of `MCP_AUTH_TOKEN`. The token says *who* is asking; this\nsays *under which name* the server is addressed. A rebinding attack runs in a\nbrowser that already holds the token.\n\nFor running at scale (session affinity, resource limits, MCP gateway), see the\n[deployment & scaling guide](docs/deployment-scaling.md) and the reference\nmanifests in [`deploy/`](deploy/).\n\n**Logging:** the server emits structured JSON logs (one object per line, with an\nRFC 5424 severity) to **stderr** — stdout is reserved for the stdio JSON-RPC\ntransport. Set the level with `MCP_LOG_LEVEL` (default `INFO`).\n\n**Tracing (optional):** install the telemetry extra and point the server at an\nOTLP collector to get OpenTelemetry spans per tool-call plus instrumented\noutbound HTTP:\n\n```bash\npip install \"bag-health-mcp[telemetry]\"\nexport OTEL_EXPORTER_OTLP_ENDPOINT=\"http://otel-collector:4318\"\n# optional: OTEL_SERVICE_NAME=bag-health-mcp\n```\n\nTracing is a **no-op** unless both the extra is installed and an `OTEL_*`\nendpoint is set. Spans carry only the tool name and (on error) the exception\nclass — never tool arguments, cantons or surveillance data.\n\n---\n\n## Available Disease Topics\n\n| Category | Topics |\n|----------|--------|\n| Respiratory | influenza, covid19, acute_respiratory_infection, respiratory_pathogens |\n| Enteric | campylobacteriosis, salmonellosis, ehec, listeriosis, hepatitis_a/e |\n| STI & Bloodborne | hiv, aids, syphilis, gonorrhea, hepatitis_b/c, chlamydiosis |\n| Vaccine-preventable | measles, pertussis, rubella, tetanus, diphtheria, ipd, meningo |\n| Vector-borne | lyme_borreliosis, tick-borne_encephalitis, dengue, malaria, zika |\n| Wastewater | wastewater_viral_load, wastewater_sequencing |\n\n---\n\n## Demo\n\n![Demo: Claude queries BAG IDD via bag-health-mcp](assets/demo.svg)\n\n*Claude asking about the influenza situation in canton Zurich — single tool call, structured result, actionable German-language summary.*\n\n---\n\n## MCP Protocol Version\n\nThis server speaks **two protocol eras** over the same endpoint. The client's\nfirst request on a connection decides which one applies; a later claim from the\nother era is refused.\n\n| Era | Revision | Who reaches it |\n|---|---|---|\n| `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. |\n| Per-request envelope | **`2026-07-28`** | A request carrying the `2026-07-28` `_meta` envelope opens a modern connection. |\n\nBoth revisions are pinned in\n[`tests/test_protocol_version.py`](tests/test_protocol_version.py) and asserted\nagainst the installed SDK, so a Dependabot bump of `mcp` cannot move either one\nsilently. The handshake ceiling is measured against a live `initialize` through\nthe assembled ASGI stack, not read off a constant name.\n\nNote that the SDK's `LATEST_PROTOCOL_VERSION` is an alias for the **modern**\nera, not for the handshake era — pinning against it alone would leave the era\nthat current clients actually negotiate free to drift.\n\n**Update policy.** When the gate fails, do not edit the constant blindly: read\nthe spec changelog between the two revisions, verify the server still behaves,\nthen move the constant, this section, `README.de.md` and\n[`CHANGELOG.md`](CHANGELOG.md) together.\n\n---\n\n## Safety & Limits\n\n| Aspect | Details |\n|--------|---------|\n| Access | Read-only — no write operations possible |\n| Egress | Code-layer allow-list: the server only contacts three public data hosts (`api.idd.bag.admin.ch`, `ind.obsan.admin.ch`, `www.versorgungsatlas.ch`), HTTPS-only, enforced on every request incl. redirect hops (SSRF/SEC-004 + SEC-021). Network-layer companion policy in [`deploy/networkpolicy.yaml`](deploy/networkpolicy.yaml) |\n| Personal data | None — all sources are aggregated/anonymised (BAG IDD at canton level by law; indicators are population aggregates by age/sex/region) |\n| Rate limits | No published IDD API rate limit; server caps responses at 104 data points per call by default (`limit_weeks` param) |\n| Timeout | 30 s per API call |\n| Authentication | No API keys required — all data publicly accessible |\n| Data licence | opendata.swiss OGD — **free use, source attribution required** (CC BY-equivalent). FOPH IDD must be cited; see [Data sources & licences](#datenquellen--lizenzen--data-sources--licences) |\n| Terms of Service | Subject to [BAG IDD API ToS](https://api.idd.bag.admin.ch) |\n\n---\n\n## Known Limitations\n\n- **Beta API**: IDD API is labelled `v0.1 beta` — schema may change without notice\n- **Weekly cadence**: Data is not real-time; updated Wednesdays only\n- **Canton granularity**: Some rare diseases have insufficient cases for canton-level data (suppressed for privacy)\n- **Age groups**: Available dimensions vary by disease series; use `bag_health_mcp__get_series_details` to check\n\n---\n\n## Compliance\n\n- **ISDS (Stadt Zürich):** a draft information-security protection-needs\n  classification (Schutzbedarfsanalyse per Grundwert + measures mapping) is in\n  [`docs/isds-klassifikation.md`](docs/isds-klassifikation.md). It is a\n  technically-grounded **draft pending ISBO/OIZ sign-off** — not a binding\n  classification.\n- **Data classification (Schulamt):** the data is classified **ÖFFENTLICH / BUI**\n  (public OGD, no personal data, aggregated at canton level with small cells\n  suppressed at source). Draft scheme + aggregation-risk note in\n  [`docs/datenklassifikation-schulamt.md`](docs/datenklassifikation-schulamt.md);\n  the aggregating `bag_health_mcp__get_canton_situation` tool surfaces this in its response.\n- **Security posture:** lethal-trifecta assessment (the server is strictly\n  read-only → not affected), secret-management decision (no secrets — public\n  data), and network-exposure notes are in\n  [`docs/security-posture.md`](docs/security-posture.md).\n- **Phase architecture:** this is a **Phase 1 (read-only)** server; write/send\n  capabilities are deferred behind documented prerequisites. See\n  [`docs/roadmap.md`](docs/roadmap.md).\n- **Reporting vulnerabilities:** see the [security policy](SECURITY.md) for how to\n  report security issues privately.\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) ([Deutsch](CONTRIBUTING.de.md)).\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) ([Deutsch](SECURITY.de.md)) for the security\nposture and how to report a vulnerability confidentially.\n\n## License\n\n**Code:** MIT (see [LICENSE](LICENSE)).\n\n**Data:** BAG IDD is Open Government Data on [opendata.swiss](https://opendata.swiss)\nunder *free use with mandatory source attribution* (Swiss OGD terms, CC BY-equivalent)\n— **not** public domain. Cite the Federal Office of Public Health FOPH (IDD) when\nreusing the data; see [Data sources & licences](#datenquellen--lizenzen--data-sources--licences).\n\n## Author\n\n**Hayal Oezkan** · [github.com/malkreide](https://github.com/malkreide)\n\n## Related Portfolio Servers\n\n- [`swiss-statistics-mcp`](https://github.com/malkreide/swiss-statistics-mcp) — BFS demographic data\n- [`bag-epl-mcp`](https://github.com/malkreide/bag-epl-mcp) — BAG medication reimbursement list\n- [`zurich-opendata-mcp`](https://github.com/malkreide/zurich-opendata-mcp) — City of Zurich open data\n\n<!-- mcp-name: io.github.malkreide/bag-health-mcp -->\n\n<!-- BEGIN GENERATED: install -->\n## Installation\n\nRun via [`uv`](https://docs.astral.sh/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`):\n\n```json\n{\n  \"mcpServers\": {\n    \"bag-health-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"bag-health-mcp\"\n      ]\n    }\n  }\n}\n```\n<!-- END GENERATED: install -->\n",
  "bytes": 19983,
  "sha": "d72e5b99d0704c24c6a76e8126ac7649e1e514a2ed42b86d986faf3f0bd58ec4",
  "repo_slug": "malkreide/bag-health-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_malkreide_bag_health_mcp_bb46b743/readme"
}