{
  "markdown": "> 🇨🇭 **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)**\n\n# swiss-food-safety-mcp\n\n![Version](https://img.shields.io/badge/version-1.1.5-blue)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)\n[![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-purple)](https://modelcontextprotocol.io/)\n[![Data Source](https://img.shields.io/badge/Data-opendata.swiss%20%2F%20BLV-red)](https://opendata.swiss/de/organization/bundesamt-fur-lebensmittelsicherheit-und-veterinaerwesen-blv)\n![No Auth Required](https://img.shields.io/badge/auth-none%20required-brightgreen)\n![CI](https://github.com/malkreide/swiss-food-safety-mcp/actions/workflows/ci.yml/badge.svg)\n\n🌐 **English** | **[Deutsch](README.de.md)**\n\n> MCP server connecting AI models to Swiss Federal Food Safety and Veterinary Office (BLV) open data — food recalls, animal disease surveillance, food control results, antibiotic usage, children's nutrition surveys and the pesticide register. No authentication required.\n\n---\n\n## Overview\n\n**swiss-food-safety-mcp** gives AI assistants like Claude direct access to official Swiss food safety and veterinary data from the Federal Food Safety and Veterinary Office (BLV / *Bundesamt für Lebensmittelsicherheit und Veterinärwesen*). It provides 11 tools covering food recalls, animal disease surveillance, food control results, antibiotic usage in veterinary medicine, nutrition surveys for children, and the pesticide register.\n\nAll data comes from official Swiss federal sources (opendata.swiss, lindas.admin.ch, news.admin.ch). No API keys or authentication are required.\n\nThis server follows the **No-Auth-First** philosophy and is part of a Swiss public sector MCP portfolio.\n\n**Anchor demo query:** *\"Are there any current BLV food warnings relevant to Zurich school canteens — and which notifiable animal diseases are currently reported in the canton?\"*\n\n### Demo\n\n![Demo: Claude using blv_get_public_warnings and blv_search_animal_diseases](docs/assets/demo.svg)\n[→ More use cases by audience →](EXAMPLES.md)\n\n---\n\n## Features\n\n- 🚨 **Public warnings & recalls** — Live RSS feed of BLV product recalls and health warnings\n- 🐄 **Animal disease surveillance** — Notifiable animal diseases since 1991 (InfoSM) via the LINDAS SPARQL cube\n- 🐦 **Avian influenza monitoring** — Wild bird surveillance data with geodata\n- 🥩 **Food control results** — Cantonal food inspection results and violation rates\n- 💊 **Antibiotic usage veterinary** — ISABV data on antibiotic use in animal medicine\n- 🧒 **Children's nutrition survey** — menuCH-Kids questionnaire tallies (answer counts, not nutrient intake)\n- 🌿 **Pesticide register** — Swiss approved pesticide products and active ingredients\n- 📊 **Dataset discovery** — Browse all 28 BLV datasets on opendata.swiss via CKAN API\n- 🔗 **Dual transport** — stdio (Claude Desktop) + Streamable HTTP (cloud/Render.com)\n- 🗣️ **Bilingual** — English-first documentation, German secondary\n\n---\n\n## Prerequisites\n\n- Python 3.11+\n- `uv` or `uvx` (recommended) — [install uv](https://docs.astral.sh/uv/getting-started/installation/)\n\n---\n\n## Installation\n\n### Using uvx (recommended — no install needed)\n\n```bash\nuvx swiss-food-safety-mcp\n```\n\n### Using uv\n\n```bash\nuv tool install swiss-food-safety-mcp\nswiss-food-safety-mcp\n```\n\n### From source\n\n```bash\ngit clone https://github.com/malkreide/swiss-food-safety-mcp\ncd swiss-food-safety-mcp\nuv sync\nuv run swiss-food-safety-mcp\n```\n\n---\n\n## Quickstart\n\nAdd to `claude_desktop_config.json`:\n\n**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`  \n**Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n```json\n{\n  \"mcpServers\": {\n    \"swiss-food-safety\": {\n      \"command\": \"uvx\",\n      \"args\": [\"swiss-food-safety-mcp\"]\n    }\n  }\n}\n```\n\nTry it immediately in Claude Desktop:\n\n> *\"Which BLV food warnings are currently active?\"*  \n> *\"Are there any notifiable animal diseases reported in Zurich canton this year?\"*\n\n### Other MCP Clients (Cursor, Windsurf, VS Code + Continue)\n\n```json\n{\n  \"mcpServers\": {\n    \"swiss-food-safety\": {\n      \"command\": \"uvx\",\n      \"args\": [\"swiss-food-safety-mcp\"]\n    }\n  }\n}\n```\n\n### Cloud Deployment (Streamable HTTP)\n\nFor use via **claude.ai in the browser** (e.g. on managed workstations without local software):\n\n```bash\n# Loopback only (default) — safe for local testing:\nswiss-food-safety-mcp --http\n# Server runs on 127.0.0.1:8002\n\n# External exposure (e.g. behind the Render TLS proxy):\nswiss-food-safety-mcp --http --host 0.0.0.0\n```\n\n> ⚠️ The HTTP transport binds to `127.0.0.1` by default. Pass `--host 0.0.0.0`\n> **only** when external exposure is intended. Set `BLV_MCP_ALLOWED_ORIGINS`\n> (comma-separated, no wildcard) to permit browser clients; it defaults to\n> `https://claude.ai`.\n\n**Render.com (recommended):**\n1. Push/fork the repository to GitHub\n2. On [render.com](https://render.com): New Web Service → connect GitHub repo\n3. Set the start command to: `swiss-food-safety-mcp --http --host 0.0.0.0`\n4. In claude.ai under Settings → MCP Servers, add: `https://your-app.onrender.com/mcp`\n\n**Docker:**\n\n```bash\ndocker build -t swiss-food-safety-mcp .\ndocker run -p 8002:8002 swiss-food-safety-mcp\n# or, with explicit CPU/memory limits:\ndocker compose up\n```\n\nThe image is a non-root, multi-stage build; the container already binds\n`0.0.0.0` and includes a healthcheck. `docker-compose.yml` additionally caps\nCPU and memory.\n\n> 💡 *\"stdio for the developer laptop, Streamable HTTP for the browser.\"*\n\n> 🔧 **Configuration** — every runtime setting is overridable via `BLV_MCP_*`\n> environment variables (`BLV_MCP_HTTP_HOST`, `BLV_MCP_HTTP_PORT`,\n> `BLV_MCP_ALLOWED_ORIGINS`, `BLV_MCP_TIMEOUT`, `BLV_MCP_OTEL_ENDPOINT`, …).\n> Outbound requests are restricted to Swiss federal hosts (`*.admin.ch`,\n> `opendata.swiss`). Optional OpenTelemetry tracing: install with\n> `pip install swiss-food-safety-mcp[otel]` and set `BLV_MCP_OTEL_ENDPOINT`.\n\n---\n\n## Available Tools\n\n| Tool | Description | Data Source |\n|---|---|---|\n| `blv_get_public_warnings` | Current food recalls & health warnings | news.admin.ch RSS |\n| `blv_list_datasets` | Browse all 28 BLV open datasets | opendata.swiss CKAN |\n| `blv_get_dataset_info` | Dataset details & resource URLs | opendata.swiss CKAN |\n| `blv_search_animal_diseases` | Notifiable animal diseases since 1991 | LINDAS SPARQL (`/query`) |\n| `blv_get_animal_health_stats` | Annual animal health statistics | opendata.swiss CSV/JSON |\n| `blv_get_food_control_results` | Cantonal food inspection results | opendata.swiss CSV |\n| `blv_get_antibiotic_usage_vet` | Veterinary antibiotic usage (ISABV) | opendata.swiss CSV |\n| `blv_get_avian_influenza` | Wild bird avian influenza surveillance | opendata.swiss CSV |\n| `blv_get_nutrition_data_children` | menuCH-Kids: questionnaire tallies (not nutrient intake) | opendata.swiss CSV |\n| `blv_search_pesticide_products` | Swiss approved pesticide register | opendata.swiss XML |\n| `blv_get_meat_inspection_stats` | Slaughterhouse inspection statistics | opendata.swiss CSV/JSON |\n\n### Example Queries\n\n| Query | Tool |\n|---|---|\n| *\"Which BLV food warnings are currently active?\"* | `blv_get_public_warnings` |\n| *\"Are there animal diseases in Zurich canton in 2024?\"* | `blv_search_animal_diseases` |\n| *\"What is the avian influenza situation in Switzerland 2024?\"* | `blv_get_avian_influenza` |\n| *\"What do Swiss children actually eat?\"* | `blv_get_nutrition_data_children` |\n| *\"Which copper-based pesticides are approved in Switzerland?\"* | `blv_search_pesticide_products` |\n\n---\n\n## Architecture\n\n```\n┌─────────────────┐     ┌─────────────────────────────┐     ┌──────────────────────────────┐\n│   Claude / AI   │────▶│   Swiss Food Safety MCP     │────▶│  Swiss Federal Open Data     │\n│   (MCP Host)    │◀────│   (MCP Server)              │◀────│                              │\n└─────────────────┘     │                             │     │  opendata.swiss (CKAN/CSV)   │\n                        │  11 Tools · No Auth         │     │  lindas.admin.ch (SPARQL)    │\n                        │  Stdio | Streamable HTTP    │     │  news.admin.ch (RSS/XML)     │\n                        └─────────────────────────────┘     └──────────────────────────────┘\n```\n\n---\n\n## Synergies with Related MCP Servers\n\n| Combination | Use Case |\n|---|---|\n| `swiss-food-safety-mcp` + `zurich-opendata-mcp` | Geo-mapped animal disease risk near school locations |\n| `swiss-food-safety-mcp` + `fedlex-mcp` | Link recalls to food law (Lebensmittelgesetz) |\n| `swiss-food-safety-mcp` + `swiss-statistics-mcp` | Nutrition data × socioeconomics by school district |\n| `swiss-food-safety-mcp` + `global-education-mcp` | Swiss children's nutrition vs. OECD benchmarks |\n\n---\n\n## Project Structure\n\n```\nswiss-food-safety-mcp/\n├── src/\n│   └── swiss_food_safety_mcp/\n│       ├── __init__.py        # Package metadata\n│       └── server.py          # All tools, resources, prompts\n├── tests/\n│   ├── __init__.py\n│   └── test_server.py         # Unit tests (no live API calls)\n├── .github/\n│   └── workflows/\n│       └── ci.yml             # Python 3.11–3.13 matrix\n├── pyproject.toml             # hatchling build, uv-compatible\n├── CHANGELOG.md\n├── CONTRIBUTING.md            # Contribution guide (English)\n├── CONTRIBUTING.de.md        # Contribution guide (German)\n├── SECURITY.md               # Security policy (English)\n├── SECURITY.de.md            # Security policy (German)\n├── LICENSE                    # MIT\n├── README.md                  # This file (English)\n└── README.de.md               # German version\n```\n\n---\n\n## Data Sources\n\n| Source | Description | Format |\n|---|---|---|\n| [opendata.swiss/BLV](https://opendata.swiss/de/organization/bundesamt-fur-lebensmittelsicherheit-und-veterinaerwesen-blv) | 28 open datasets | CSV, JSON, Parquet, SPARQL, XML |\n| [lindas.admin.ch/sparql](https://lindas.admin.ch/sparql) | Swiss linked data SPARQL endpoint | RDF/SPARQL |\n| [news.admin.ch RSS](https://www.newsd.admin.ch/newsd/feeds/rss?lang=de&org-nr=1079) | BLV public warnings & recalls | RSS/XML |\n| [blv.admin.ch](https://www.blv.admin.ch) | BLV website (DE/FR/IT/EN) | HTML |\n\nAll data is open government data (OGD) under Creative Commons with attribution requirement.\n\n---\n\n## Known Limitations\n\n- **RSS feed:** Limited to the most recent BLV publications; no historical archive\n- **Pesticide register:** XML parsing may be slow for queries returning large result sets\n- **CKAN datasets:** Opendata.swiss rate limits apply under heavy usage\n- **Animal disease data:** Canton-level filtering depends on data completeness in the source\n- **Datasets are pinned, not searched:** each data tool names its dataset slug and the resource that carries the data (see `DATENQUELLEN` in `server.py`). A keyword search takes the *first* hit and therefore falls back silently onto something plausible — that is how `blv_get_animal_health_stats` came to return antibiotics data, and how `blv_get_food_control_results` came to return a code list out of a dataset whose 26 resources include 18 of them. `scripts/record_fixtures.py` re-measures the pinned pairs on every run; a renamed dataset now fails loudly.\n- **Children's nutrition is questionnaire tallies, not nutrient intake:** the only menuCH-Kids dataset published on opendata.swiss carries answer counts (`Geschlecht, Sprachregion, Altersgruppe, Frage, Antwort, Anzahl`). The docstring previously promised nutrient intake against dietary recommendations and offered \"Energie\", \"Zucker\", \"Eisen\" as filter examples — those matched nothing and returned an empty list. Adult food-consumption data exists as a separate dataset that this server does not cover.\n- **The SPARQL-to-CSV fallback is gone.** It could never work: the one CSV resource of the fallback dataset is a ZIP file declared as `format: CSV`. With the endpoint corrected the fallback is also unnecessary — and a fallback that hides a broken query is worse than none.\n\n---\n\n## Safety & Limits\n\n- **Read-only:** All tools perform HTTP GET requests only — no data is written, modified, or deleted.\n- **No personal data:** The APIs return aggregated public health and food safety statistics. No personally identifiable information is processed or stored by this server.\n- **Rate limits:** opendata.swiss CKAN and lindas.admin.ch SPARQL are public APIs; use `limit` and filtering parameters conservatively. The server enforces a 30-second timeout per request.\n- **Data freshness:** RSS warnings reflect the latest BLV publications at query time. Statistical datasets (animal diseases, food control, antibiotics) are updated periodically by the BLV. No caching is performed by this server.\n- **Terms of service:** Data is subject to the ToS of each source — [opendata.swiss](https://opendata.swiss/de/terms-of-use), [lindas.admin.ch](https://lindas.admin.ch), [news.admin.ch](https://www.admin.ch/gov/de/start/rechtliches.html). BLV data is published under Creative Commons with attribution.\n- **No guarantees:** This server is a community project, not affiliated with the BLV or the Swiss federal administration. Availability depends on upstream APIs.\n\n---\n\n## Deployment & Scaling\n\nThis server is **Phase 1 — read-only** (see [`ROADMAP.md`](ROADMAP.md)): all\n11 tools are read-only queries with no write surface.\n\nRun it as a **single instance**. The Streamable HTTP transport keeps\nper-session state, so horizontal scaling would require `Mcp-Session-Id` sticky\nrouting at the load balancer plus a shared session store — neither is\nimplemented, by design, for a server of this scope. A single Render instance\n(or one container) is the supported deployment; `docker-compose.yml` sets\nexplicit CPU/memory limits for self-hosting.\n\n---\n\n## Testing\n\n```bash\n# Unit + contract tests (no network) — this is what CI runs\nPYTHONPATH=src pytest tests/ -m \"not live\"\n\n# All tests including live API checks\nPYTHONPATH=src pytest tests/\n\n# Re-measure which dataset and resource each tool hits\nPYTHONPATH=src python scripts/record_fixtures.py\n```\n\n**54 tests** — 53 offline, 1 live.\n\n### Why the fixtures are recorded rather than written\n\nA hand-written mock encodes its author's assumption and therefore cannot\nrefute it: production code and fixture come from the same head, the same hour,\nthe same reading of the docs. Where both are wrong, both are wrong together —\nand the suite stays green.\n\nThis repo had it in pure form. Every mocked CKAN resource was named\n`\"name\": \"CSV\"`. On opendata.swiss the same field reads `Food establishments\n2025` or `Food establishments codelist administrative measures` — and that\ndifference alone decided whether a tool returned inspection results or a code\nlegend. The mocks could not express the distinction, so no test could fail on\nit.\n\nWhat is recorded is therefore the **selection**: for each tool, the pinned\ndataset slug, the resource that was hit, and that file's header line. The\nheader is the object of the exercise — it separates data from a legend, and it\nshows whether the BOM and the delimiter were handled. `PROVENANCE.md` names the\nsource, the date, the selection rule and the SHA-256 for each file.\n\nTwo of the recorded measurements are **controls**: an invented path under\n`lindas.admin.ch` (POST 404, so the 404 on `/sparql` is real) and an invented\nclass in the `fsvo` namespace (0 instances, so the previously queried `foag`\nclass genuinely does not exist). Without them each measurement would only show\nwhat *we* received. The recorder aborts if a control stops discriminating, if a\npinned resource disappears, if a header line is empty or starts with a BOM, or\nif one of the findings is superseded.\n\n---\n\n## MCP Protocol Version\n\nThe protocol version is negotiated at the `initialize` handshake by the SDK,\nnot chosen by this server. The revision it is built and audited against is\n**`2025-11-25`**, which is `LATEST_PROTOCOL_VERSION` in the pinned `mcp`\nrelease that fastmcp brings in.\n\n`tests/test_protocol_version.py` holds three things against each other: this\nline, that SDK constant, and the revision a real handshake against the server\nobject actually returns. An SDK bump that changes the revision therefore fails\nCI instead of drifting silently.\n\nThe sister servers in this portfolio pin a *pair* of revisions — a handshake\nceiling and a modern one — because `mcp` 2.x serves two protocol eras over the\nsame server. fastmcp 3.x pins `mcp` 1.x, where `mcp.types.version` does not\nexist and one revision is the whole story. `test_das_sdk_kennt_hier_nur_eine_aera`\nis tied to the SDK rather than to this paragraph and fails the day an upgrade\nbrings the two-era constants in.\n\n---\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md)\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md)\n\n---\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for the security policy and how to report a vulnerability.\n\n---\n\n## License\n\nMIT License — see [LICENSE](LICENSE)\n\n---\n\n## Author\n\nHayal Oezkan · [github.com/malkreide](https://github.com/malkreide)\n\n---\n\n## Credits & Related Projects\n\n- **Data:** [opendata.swiss / BLV](https://opendata.swiss/de/organization/bundesamt-fur-lebensmittelsicherheit-und-veterinaerwesen-blv) – Federal Food Safety and Veterinary Office (BLV)\n- **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) – Anthropic / Linux Foundation\n- **Related:** [zurich-opendata-mcp](https://github.com/malkreide/zurich-opendata-mcp) – MCP server for Zurich city open data\n- **Portfolio:** [Swiss Public Data MCP Portfolio](https://github.com/malkreide)\n\n<!-- mcp-name: io.github.malkreide/swiss-food-safety-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    \"swiss-food-safety-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"swiss-food-safety-mcp\"\n      ]\n    }\n  }\n}\n```\n<!-- END GENERATED: install -->\n",
  "bytes": 18212,
  "sha": "1e00be15c6459b271e6ac5f326b8fbee141311c91550dca04a32d22b72dc3ee6",
  "repo_slug": "malkreide/swiss-food-safety-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_malkreide_swiss_food_safety_mc_aedd0f31/readme"
}