{
  "markdown": "> 🇨🇭 **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)**\n\n# wsl-envidat-mcp 🌲❄️⛰️\n\n![Version](https://img.shields.io/badge/version-0.2.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-envidat.ch-green)](https://www.envidat.ch/)\n[![No API Key](https://img.shields.io/badge/API%20key-not%20required-brightgreen)](https://www.envidat.ch/)\n![CI](https://github.com/malkreide/wsl-envidat-mcp/actions/workflows/ci.yml/badge.svg)\n\n> MCP server connecting AI models to Swiss environmental research data from WSL via EnviDat — forest, snow, avalanches, natural hazards and biodiversity, no API key required.\n\n[🇩🇪 Deutsche Version](README.de.md)\n\n---\n\n## Phase\n\nThis server is in **Phase 1: Read-only Wrapper**.\n\n| Property | Status |\n|---|---|\n| Read tools | ✅ 10 tools, all `readOnlyHint: true` |\n| Write tools | ❌ none (EnviDat is read-only public data) |\n| Semantic Layer | ⚠️ partial — three domain tools curate Solr queries |\n| OAuth / Auth Gateway | ❌ not required (Public Open Data, no API key) |\n| Container hardening | ✅ multi-stage Dockerfile, non-root |\n| Test suite | ✅ 38 offline unit tests + 31 live integration tests |\n| Audit run | ✅ 2026-05-27 (mcp-audit-skill v1.0.0) |\n\nPhase-2 ideas (caching layer, semantic aggregation tool combining forest +\nsnow + hazard data into a \"Lage-Übersicht\"): tracked under `docs/`.\n\n---\n\n## Overview\n\nThe **WSL** (Eidgenössische Forschungsanstalt für Wald, Schnee und Landschaft / Swiss Federal Research Institute for Forest, Snow and Landscape) is one of Europe's leading environmental research institutes. Its open data platform **[EnviDat](https://www.envidat.ch)** provides access to 1,000+ research datasets, time series of up to 130 years, and data from 6,000+ monitoring stations.\n\nThis MCP server exposes the EnviDat CKAN API as 10 tools and 2 resources, enabling AI assistants to search, filter and retrieve WSL research data by keyword, domain, or geographic bounding box — all without an API key.\n\n**Anchor demo query:** *\"How was air quality and forest health around Schulhaus Leutschenbach in Zurich — and what does the WSL say about the current forest condition in the canton?\"*\n\n### Demo\n\n![Demo: Claude using wsl_get_avalanche_data, wsl_get_forest_data and wsl_catalog_stats](docs/assets/demo.svg)\n\n---\n\n## Features\n\n- **10 tools** covering full-text search, domain-specific queries, spatial search, and curated thematic tools (avalanche, forest, natural hazards)\n- **2 MCP resources** for organizations and research domains\n- **5 research domains**: Forest · Biodiversity · Natural Hazards · Snow & Ice · Landscape\n- **815+ datasets**, time series since 1890, data from the SLF avalanche research institute\n- **No API key required** — all data publicly accessible via open licenses\n- **Dual transport**: stdio (Claude Desktop / local) + Streamable HTTP (cloud deployment)\n- **Model-agnostic**: works with Claude, GPT-4, and any MCP-compatible client\n\n---\n\n## Prerequisites\n\n- Python 3.11+\n- `pip` or `uv` / `uvx`\n- Internet connection (live API calls to envidat.ch)\n\n---\n\n## Installation\n\n```bash\n# Recommended: uvx (no installation needed)\nuvx wsl-envidat-mcp\n\n# Or with pip\npip install wsl-envidat-mcp\n\n# Development\ngit clone https://github.com/malkreide/wsl-envidat-mcp.git\ncd wsl-envidat-mcp\npip install -e \".[dev]\"\n```\n\n---\n\n## Quickstart\n\n### Claude Desktop\n\nEdit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\n\n```json\n{\n  \"mcpServers\": {\n    \"wsl-envidat\": {\n      \"command\": \"uvx\",\n      \"args\": [\"wsl-envidat-mcp\"]\n    }\n  }\n}\n```\n\nRestart Claude Desktop, then ask:\n\n- *\"What WSL datasets exist on fatal avalanche accidents in Switzerland?\"*\n- *\"Show me forest inventory data from the LFI for the canton of Zurich.\"*\n- *\"Which natural hazard research data does the SLF publish on EnviDat?\"*\n- *\"Are there WSL datasets on drought conditions in summer 2022?\"*\n- *\"What biodiversity data is available for alpine ecosystems?\"*\n\n---\n\n## Configuration\n\nNo API key required. Optional environment variables:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `streamable-http` (legacy `streamable_http` is accepted) |\n| `MCP_HOST` | `127.0.0.1` | Bind address for `streamable-http`. Use `0.0.0.0` **only** inside a container. |\n| `PORT` | `8000` | Port for Streamable HTTP mode |\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# Local: keep MCP_HOST at its default 127.0.0.1\nMCP_TRANSPORT=streamable-http PORT=8000 python -m wsl_envidat_mcp.server\n\n# Container: bind to all interfaces inside the container only\nMCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 PORT=8000 python -m wsl_envidat_mcp.server\n```\n\n> 💡 *\"stdio for the developer laptop, streamable-http for the browser.\"*\n\n> ⚠️ **Multi-Replica Cloud Deployments:** Session state lives in the server.\n> Run a single replica or enable sticky sessions (Railway/Render setting,\n> or `sessionAffinity: ClientIP` on Kubernetes Services).\n\n> ⚠️ **Multi-Tenant / Unauthenticated Streamable HTTP:** This server has no\n> auth layer (`auth_model: none`). Streamable HTTP without a reverse-proxy +\n> OAuth/API-Gateway is intended only for single-user deployments\n> (e.g. one user's claude.ai browser session). For multi-tenant use,\n> front the server with an authenticating gateway.\n\n#### Container image (recommended for cloud)\n\nA hardened multi-stage image is published to GitHub Container Registry on\nevery `main` push and semver tag. Runs as non-root (`uid=1000`), no build\ntools in the runtime layer, multi-arch (`linux/amd64` + `linux/arm64`).\n\nThe publishing workflow is [`.github/workflows/container.yml`](.github/workflows/container.yml) —\nnot `publish.yml`, which only uploads the Python package to PyPI on a\nrelease. `container.yml` is delivery, not a gate: it does not gate a pull\nrequest, and a red run there means the image is missing, not that the code\nis broken.\n\n```bash\ndocker run --rm -p 8000:8000 \\\n  --read-only --tmpfs /tmp \\\n  --cap-drop=ALL --security-opt=no-new-privileges \\\n  ghcr.io/malkreide/wsl-envidat-mcp:latest\n```\n\nKubernetes hardening (excerpt):\n\n```yaml\nsecurityContext:\n  runAsNonRoot: true\n  runAsUser: 1000\n  readOnlyRootFilesystem: true\n  allowPrivilegeEscalation: false\n  capabilities: { drop: [\"ALL\"] }\n```\n\n---\n\n## Available Tools\n\n| Tool | Description |\n|------|-------------|\n| `wsl_search` | Unified search — combine `query`, `domain`, `organization`, and `bbox` filters |\n| `wsl_get_dataset` | Full metadata, DOI, download URLs for a specific dataset |\n| `wsl_list_organizations` | List all WSL research units on EnviDat |\n| `wsl_get_organization` | Details of a specific research unit incl. datasets |\n| `wsl_list_tags` | Browse available tags/keywords |\n| `wsl_get_recent_datasets` | Most recently updated datasets |\n| `wsl_get_avalanche_data` | SLF avalanche & snow data (incl. fatal accidents since 1936) |\n| `wsl_get_forest_data` | Forest data incl. National Forest Inventory (LFI) & Sanasilva |\n| `wsl_get_naturgefahren_data` | Natural hazard datasets (landslides, rockfall, floods) |\n| `wsl_catalog_stats` | Catalog overview and statistics |\n\n### Example Use Cases\n\n| Query | Tool |\n|-------|------|\n| *\"Fatal avalanche accidents in Valais since 2000?\"* | `wsl_get_avalanche_data` |\n| *\"Forest health data for canton Zurich?\"* | `wsl_get_forest_data` |\n| *\"Landslide risk datasets near Brienz?\"* | `wsl_get_naturgefahren_data` |\n| *\"Most recent WSL publications on biodiversity?\"* | `wsl_search(domain=\"biodiversitaet\")` |\n| *\"Which datasets cover the area around Lake Constance?\"* | `wsl_search(bbox=[9.0, 47.5, 9.7, 47.8])` |\n| *\"How many datasets does SLF publish?\"* | `wsl_get_organization` |\n\n---\n\n## Resources\n\n| URI | Description |\n|-----|-------------|\n| `envidat://organization/{name}` | Research unit (e.g. `slf`, `wsl`) |\n| `envidat://domain/{domain}` | Domain overview with top datasets |\n\nValid domain values: `wald`, `biodiversitaet`, `naturgefahren`, `schnee_eis`, `landschaft`\n\n---\n\n## Architecture\n\n```\n┌─────────────────┐     ┌───────────────────────────┐     ┌──────────────────────────┐\n│   Claude / AI   │────▶│    WSL EnviDat MCP        │────▶│       envidat.ch          │\n│   (MCP Host)    │◀────│    (MCP Server)           │◀────│                          │\n└─────────────────┘     │                           │     │  CKAN API  (REST/JSON)   │\n                        │  10 Tools · 2 Resources   │     │  Solr full-text search   │\n                        │  Stdio | Streamable HTTP  │     │  1,000+ research datasets│\n                        │                           │     │  815+ open datasets      │\n                        │  server.py                │     │  Time series since 1890  │\n                        │  api_client.py            │     └──────────────────────────┘\n                        └───────────────────────────┘\n```\n\n### Infrastructure Components\n\n| Component | Metaphor | Function |\n|-----------|----------|----------|\n| `api_client.py` | Librarian | Handles all HTTP requests to EnviDat CKAN API |\n| `server.py` | Reception desk | Registers all 10 tools and 2 resources with FastMCP |\n| Domain filters | Filing cabinet | Pre-configured keyword sets per research domain |\n| Bounding box search | Map overlay | Spatial filtering via lat/lon coordinates |\n\n---\n\n## Project Structure\n\n```\nwsl-envidat-mcp/\n├── src/wsl_envidat_mcp/\n│   ├── __init__.py         # Package\n│   ├── server.py           # MCP server — 10 tools, 2 resources\n│   └── api_client.py       # HTTP client for EnviDat CKAN API\n├── tests/\n│   └── test_integration.py # 11 live API integration tests\n├── .github/workflows/\n│   ├── ci.yml              # Gates: lint, format, syntax, import, tests, version sync\n│   ├── live.yml            # Scheduled live tests against envidat.ch (cron 05:47 UTC)\n│   ├── container.yml       # Builds & pushes the ghcr.io image (delivery, not a gate)\n│   └── publish.yml         # Uploads the Python package to PyPI on a release\n├── pyproject.toml          # Project config (hatchling build backend)\n├── CHANGELOG.md\n├── CONTRIBUTING.md         # Contribution guide (English)\n├── CONTRIBUTING.de.md      # Contribution guide (German)\n├── SECURITY.md             # Security policy & posture (English)\n├── SECURITY.de.md          # Security policy & posture (German)\n├── LICENSE                 # MIT\n├── README.md               # This file (English)\n└── README.de.md            # German version\n```\n\n---\n\n## Combination with Other MCP Servers\n\nThis server is part of the Swiss Open Data MCP Portfolio and integrates well with:\n\n| Combination | Use Case |\n|-------------|----------|\n| + `zurich-opendata-mcp` | Urban climate + forest condition around Zurich |\n| + `swiss-statistics-mcp` | Population data + environmental quality |\n| + `swiss-transport-mcp` | Avalanche risk + public transport connections |\n| + `fedlex-mcp` | Forest protection law + actual LFI forest condition |\n| + `global-education-mcp` | Compare environmental education data internationally |\n\n---\n\n## Known Limitations\n\n- **Solr search**: `OR` is treated as a stopword — use single, specific search terms per query\n- **Domain search**: Results depend on WSL's internal keyword tagging — not all datasets are tagged consistently\n- **Spatial search**: Bounding box filtering is approximate; verify coordinates with individual dataset metadata\n- **Live API**: All tools make live calls to envidat.ch — results depend on availability of the public API\n- **Languages**: Dataset metadata is primarily in English and German; some older entries may be in German only\n\n---\n\n## Safety & Limits\n\n- **Read-only:** All tools perform HTTP GET requests only — no data is written, modified, or deleted on EnviDat.\n- **No personal data:** The API returns research metadata, dataset descriptions, and download URLs. No personally identifiable information (PII) is processed or stored by this server.\n- **Rate limits:** The EnviDat CKAN API is public without documented rate limits. Use `limit` and `rows` parameters conservatively. The server enforces a 30-second timeout per request.\n- **Data freshness:** All tools make live API calls — results reflect the current state of the EnviDat catalog at query time. No caching is performed by this server.\n- **Terms of service:** Data is subject to the [EnviDat Terms of Use](https://www.envidat.ch/about). Individual datasets are published under various open licenses (Creative Commons, CC0) — see dataset metadata.\n- **No guarantees:** This is a community project, not affiliated with WSL or EnviDat. Availability depends on the upstream EnviDat API.\n\nFor the full security posture (egress allow-list, redirect handling, accepted risks) see [SECURITY.md](SECURITY.md).\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. This server builds no ASGI app to send an `initialize` through, so\nthe gate asserts the SDK constants rather than a measured response — the\nweaker form, named rather than left unsaid.\n\n`SUPPORTED_MCP_PROTOCOL_VERSION` in\n[`server.py`](src/wsl_envidat_mcp/server.py) names the **modern** era; a\nmismatch against the SDK logs a warning at startup. A warning is not a gate —\nthat is what the test file is for.\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## Testing\n\n```bash\n# Unit tests — offline, no network access, all CKAN responses mocked via respx\nPYTHONPATH=src pytest -m \"not live\"\n\n# Live integration tests — actual HTTP calls to envidat.ch\nPYTHONPATH=src pytest -m live\n\n# Linting\nruff check src/\nruff format --check src/\n```\n\nCI runs the offline suite on every PR. The live suite runs only on `main`\npushes and manual `workflow_dispatch` triggers, so build status is not\ncoupled to upstream availability.\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) ([Deutsch](SECURITY.de.md)) for the security\nposture and how to report a vulnerability.\n\n---\n\n## License\n\nMIT License — see [LICENSE](LICENSE)\n\nData on EnviDat is published under various open licenses (Creative Commons, CC0) — see individual dataset metadata.\n\n---\n\n## Author\n\nHayal Oezkan · [malkreide](https://github.com/malkreide)  \n\n---\n\n## Credits & Related Projects\n\n- **Data:** [EnviDat](https://www.envidat.ch/) – WSL Swiss Federal Research Institute for Forest, Snow and Landscape\n- **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) – Anthropic / Linux Foundation\n- **Portfolio:** [Swiss Public Data MCP Portfolio](https://github.com/malkreide)\n\n<!-- mcp-name: io.github.malkreide/wsl-envidat-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    \"wsl-envidat-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"wsl-envidat-mcp\"\n      ]\n    }\n  }\n}\n```\n<!-- END GENERATED: install -->\n",
  "bytes": 16716,
  "sha": "e2e252855cbbb47c02f83bdd494b9a318f3adc8a52edd6478deb3c00177c33cf",
  "repo_slug": "malkreide/wsl-envidat-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_malkreide_wsl_envidat_mcp_83992afe/readme"
}