Wsl Envidat Mcp

io.github.malkreidePublished by GitHub account malkreidev0.3.1Updated Oct 24, 2026

WSL / EnviDat environmental research datasets via CKAN

Overview

AI-generated overview

Lets an assistant search and retrieve Swiss WSL environmental research datasets from the public EnviDat CKAN catalog, including forest, snow, avalanche and…

What you can do
Exposes the EnviDat CKAN API as ten read-only tools and two resources. Tools cover unified search with query, domain, organization and bounding-box filters, dataset metadata and DOI lookup, organization and tag listings, recent datasets, catalog statistics, and curated thematic queries for avalanche and snow data, forest data including the National Forest Inventory, and natural hazards such as landslides, rockfall and floods. Resources describe research units and domains.
When to use it
Useful when an assistant needs Swiss environmental research data: avalanche accident records, forest condition and inventory data, snow and ice, biodiversity, landscape, or natural hazard datasets, filtered by keyword, research domain, organization or geographic area.
Requirements
Python 3.11+ with pip or uv/uvx; runs locally over stdio, or as a Streamable HTTP endpoint. Internet access is needed because every tool makes live calls to envidat.ch. No API key or account is required. Optional environment variables MCP_TRANSPORT, MCP_HOST and PORT configure HTTP mode.
Before you install
Read-only: all tools issue HTTP GET requests and nothing is written, modified or deleted. No credentials or personal data are involved. Results depend on the upstream public API, which has no documented rate limits, so limit and rows parameters should be used conservatively. Streamable HTTP mode has no authentication layer, so it is intended for single-user deployments or behind an authenticating gateway; binding MCP_HOST to 0.0.0.0 is advised only inside a container.

Installation

In SourceWeft

  1. Open Wsl Envidat Mcp in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

🇨🇭 Part of the Swiss Public Data MCP Portfolio

wsl-envidat-mcp 🌲❄️⛰️

[Version] [License: MIT] [Python 3.11+] [MCP] [Data Source] [No API Key] [CI]

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.

🇩🇪 Deutsche Version


Phase

This server is in Phase 1: Read-only Wrapper.

PropertyStatus
Read tools✅ 10 tools, all readOnlyHint: true
Write tools❌ none (EnviDat is read-only public data)
Semantic Layer⚠️ partial — three domain tools curate Solr queries
OAuth / Auth Gateway❌ not required (Public Open Data, no API key)
Container hardening✅ multi-stage Dockerfile, non-root
Test suite✅ 38 offline unit tests + 31 live integration tests
Audit run✅ 2026-05-27 (mcp-audit-skill v1.0.0)

Phase-2 ideas (caching layer, semantic aggregation tool combining forest + snow + hazard data into a "Lage-Übersicht"): tracked under docs/.


Overview

The 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 provides access to 1,000+ research datasets, time series of up to 130 years, and data from 6,000+ monitoring stations.

This 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.

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?"

Demo

[Demo: Claude using wsl_get_avalanche_data, wsl_get_forest_data and wsl_catalog_stats]


Features

  • 10 tools covering full-text search, domain-specific queries, spatial search, and curated thematic tools (avalanche, forest, natural hazards)
  • 2 MCP resources for organizations and research domains
  • 5 research domains: Forest · Biodiversity · Natural Hazards · Snow & Ice · Landscape
  • 815+ datasets, time series since 1890, data from the SLF avalanche research institute
  • No API key required — all data publicly accessible via open licenses
  • Dual transport: stdio (Claude Desktop / local) + Streamable HTTP (cloud deployment)
  • Model-agnostic: works with Claude, GPT-4, and any MCP-compatible client

Prerequisites

  • Python 3.11+
  • pip or uv / uvx
  • Internet connection (live API calls to envidat.ch)

Installation

bash
# Recommended: uvx (no installation needed)uvx wsl-envidat-mcp
# Or with pippip install wsl-envidat-mcp
# Developmentgit clone https://github.com/malkreide/wsl-envidat-mcp.gitcd wsl-envidat-mcppip install -e ".[dev]"

Quickstart

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

json
{  "mcpServers": {    "wsl-envidat": {      "command": "uvx",      "args": ["wsl-envidat-mcp"]    }  }}

Restart Claude Desktop, then ask:

  • "What WSL datasets exist on fatal avalanche accidents in Switzerland?"
  • "Show me forest inventory data from the LFI for the canton of Zurich."
  • "Which natural hazard research data does the SLF publish on EnviDat?"
  • "Are there WSL datasets on drought conditions in summer 2022?"
  • "What biodiversity data is available for alpine ecosystems?"

Configuration

No API key required. Optional environment variables:

VariableDefaultDescription
MCP_TRANSPORTstdioTransport mode: stdio or streamable-http (legacy streamable_http is accepted)
MCP_HOST127.0.0.1Bind address for streamable-http. Use 0.0.0.0 only inside a container.
PORT8000Port for Streamable HTTP mode

Cloud Deployment (Streamable HTTP)

For use via claude.ai in the browser (e.g. on managed workstations without local software):

bash
# Local: keep MCP_HOST at its default 127.0.0.1MCP_TRANSPORT=streamable-http PORT=8000 python -m wsl_envidat_mcp.server
# Container: bind to all interfaces inside the container onlyMCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 PORT=8000 python -m wsl_envidat_mcp.server

💡 "stdio for the developer laptop, streamable-http for the browser."

⚠️ Multi-Replica Cloud Deployments: Session state lives in the server. Run a single replica or enable sticky sessions (Railway/Render setting, or sessionAffinity: ClientIP on Kubernetes Services).

⚠️ Multi-Tenant / Unauthenticated Streamable HTTP: This server has no auth layer (auth_model: none). Streamable HTTP without a reverse-proxy + OAuth/API-Gateway is intended only for single-user deployments (e.g. one user's claude.ai browser session). For multi-tenant use, front the server with an authenticating gateway.

Container image (recommended for cloud)

A hardened multi-stage image is published to GitHub Container Registry on every main push and semver tag. Runs as non-root (uid=1000), no build tools in the runtime layer, multi-arch (linux/amd64 + linux/arm64).

The publishing workflow is .github/workflows/container.yml — not publish.yml, which only uploads the Python package to PyPI on a release. container.yml is delivery, not a gate: it does not gate a pull request, and a red run there means the image is missing, not that the code is broken.

bash
docker run --rm -p 8000:8000 \  --read-only --tmpfs /tmp \  --cap-drop=ALL --security-opt=no-new-privileges \  ghcr.io/malkreide/wsl-envidat-mcp:latest

Kubernetes hardening (excerpt):

yaml
securityContext:  runAsNonRoot: true  runAsUser: 1000  readOnlyRootFilesystem: true  allowPrivilegeEscalation: false  capabilities: { drop: ["ALL"] }

Available Tools

ToolDescription
wsl_searchUnified search — combine query, domain, organization, and bbox filters
wsl_get_datasetFull metadata, DOI, download URLs for a specific dataset
wsl_list_organizationsList all WSL research units on EnviDat
wsl_get_organizationDetails of a specific research unit incl. datasets
wsl_list_tagsBrowse available tags/keywords
wsl_get_recent_datasetsMost recently updated datasets
wsl_get_avalanche_dataSLF avalanche & snow data (incl. fatal accidents since 1936)
wsl_get_forest_dataForest data incl. National Forest Inventory (LFI) & Sanasilva
wsl_get_naturgefahren_dataNatural hazard datasets (landslides, rockfall, floods)
wsl_catalog_statsCatalog overview and statistics

Example Use Cases

QueryTool
"Fatal avalanche accidents in Valais since 2000?"wsl_get_avalanche_data
"Forest health data for canton Zurich?"wsl_get_forest_data
"Landslide risk datasets near Brienz?"wsl_get_naturgefahren_data
"Most recent WSL publications on biodiversity?"wsl_search(domain="biodiversitaet")
"Which datasets cover the area around Lake Constance?"wsl_search(bbox=[9.0, 47.5, 9.7, 47.8])
"How many datasets does SLF publish?"wsl_get_organization

Resources

URIDescription
envidat://organization/{name}Research unit (e.g. slf, wsl)
envidat://domain/{domain}Domain overview with top datasets

Valid domain values: wald, biodiversitaet, naturgefahren, schnee_eis, landschaft


Architecture

┌─────────────────┐     ┌───────────────────────────┐     ┌──────────────────────────┐│   Claude / AI   │────▶│    WSL EnviDat MCP        │────▶│       envidat.ch          ││   (MCP Host)    │◀────│    (MCP Server)           │◀────│                          │└─────────────────┘     │                           │     │  CKAN API  (REST/JSON)   │                        │  10 Tools · 2 Resources   │     │  Solr full-text search   │                        │  Stdio | Streamable HTTP  │     │  1,000+ research datasets│                        │                           │     │  815+ open datasets      │                        │  server.py                │     │  Time series since 1890  │                        │  api_client.py            │     └──────────────────────────┘                        └───────────────────────────┘

Infrastructure Components

ComponentMetaphorFunction
api_client.pyLibrarianHandles all HTTP requests to EnviDat CKAN API
server.pyReception deskRegisters all 10 tools and 2 resources with FastMCP
Domain filtersFiling cabinetPre-configured keyword sets per research domain
Bounding box searchMap overlaySpatial filtering via lat/lon coordinates

Project Structure

wsl-envidat-mcp/├── src/wsl_envidat_mcp/│   ├── __init__.py         # Package│   ├── server.py           # MCP server — 10 tools, 2 resources│   └── api_client.py       # HTTP client for EnviDat CKAN API├── tests/│   └── test_integration.py # 11 live API integration tests├── .github/workflows/│   ├── ci.yml              # Gates: lint, format, syntax, import, tests, version sync│   ├── live.yml            # Scheduled live tests against envidat.ch (cron 05:47 UTC)│   ├── container.yml       # Builds & pushes the ghcr.io image (delivery, not a gate)│   └── publish.yml         # Uploads the Python package to PyPI on a release├── pyproject.toml          # Project config (hatchling build backend)├── CHANGELOG.md├── CONTRIBUTING.md         # Contribution guide (English)├── CONTRIBUTING.de.md      # Contribution guide (German)├── SECURITY.md             # Security policy & posture (English)├── SECURITY.de.md          # Security policy & posture (German)├── LICENSE                 # MIT├── README.md               # This file (English)└── README.de.md            # German version

Combination with Other MCP Servers

This server is part of the Swiss Open Data MCP Portfolio and integrates well with:

CombinationUse Case
+ zurich-opendata-mcpUrban climate + forest condition around Zurich
+ swiss-statistics-mcpPopulation data + environmental quality
+ swiss-transport-mcpAvalanche risk + public transport connections
+ fedlex-mcpForest protection law + actual LFI forest condition
+ global-education-mcpCompare environmental education data internationally

Known Limitations

  • Solr search: OR is treated as a stopword — use single, specific search terms per query
  • Domain search: Results depend on WSL's internal keyword tagging — not all datasets are tagged consistently
  • Spatial search: Bounding box filtering is approximate; verify coordinates with individual dataset metadata
  • Live API: All tools make live calls to envidat.ch — results depend on availability of the public API
  • Languages: Dataset metadata is primarily in English and German; some older entries may be in German only

Safety & Limits

  • Read-only: All tools perform HTTP GET requests only — no data is written, modified, or deleted on EnviDat.
  • 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.
  • 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.
  • 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.
  • Terms of service: Data is subject to the EnviDat Terms of Use. Individual datasets are published under various open licenses (Creative Commons, CC0) — see dataset metadata.
  • No guarantees: This is a community project, not affiliated with WSL or EnviDat. Availability depends on the upstream EnviDat API.

For the full security posture (egress allow-list, redirect handling, accepted risks) see SECURITY.md.


MCP Protocol 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.

EraRevisionWho reaches it
initialize handshake2024-11-05 … 2025-11-25What 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 envelope2026-07-28A 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. This server builds no ASGI app to send an initialize through, so the gate asserts the SDK constants rather than a measured response — the weaker form, named rather than left unsaid.

SUPPORTED_MCP_PROTOCOL_VERSION in server.py names the modern era; a mismatch against the SDK logs a warning at startup. A warning is not a gate — that is what the test file is for.

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.

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.


What the server uses from 2026-07-28

Pinning a revision is not the same as speaking it. Until now this server pinned 2026-07-28 and used the shape of the era before it; the table above was true and the wire was older than it sounded.

Two channels per tool result. The revision gives a tool result content for the reader and structuredContent for the application, with outputSchema as the contract between them. Every tool now fills both: the same human-readable Markdown as before in content, and the data behind it — typed, schema-backed — in structuredContent.

This is not a new interface. The fields are exactly the payload that response_format="json" has always produced. That option is the predecessor of structuredContent: it exists only because, before 2026-07-28, there was one channel, so the machine-readable version had to travel as a JSON string inside the reader's channel. response_format still works and still controls only the text block.

What it replaces is worse than nothing. A signature of -> str makes the SDK publish an outputSchema of {"result": string} and repeat the Markdown block underneath it. Measured on wsl_search against the recorded fixture: 2058 characters of text, 2058 characters of structuredContent.result, character-identical. An outputSchema is a promise; that one promised structure and delivered prose under a key called result.

serverInfo carries its six fields. It carried two, one of them empty. version was the expensive one: the number is kept in step across pyproject.toml, server.json and both README badges by scripts/check_version_sync.py — and reached every place except the one a client reads. It now comes from the package metadata, so no literal enters src/.

Tool titles moved to title. They sat in annotations.title, the slot from before this revision. A client that follows the schema and reads tool.title got None and displayed the slug.

Deliberately not used: icons on server, tools and resources, and the task-augmented call flow (execution). Both are optional, and neither has anything real to point at here — this repository ships no icon asset, and every call is a short CKAN query that finishes well inside a request. Declaring either would be a claim without a thing behind it.

tests/test_structured_output.py holds all of the above against a live ClientSession, with a negative control that shows what the SDK does when a server does not do this — so the day the default changes, the assertions say so instead of quietly passing.

Testing

bash
# Unit tests — offline, no network access, all CKAN responses mocked via respxPYTHONPATH=src pytest -m "not live"
# Live integration tests — actual HTTP calls to envidat.chPYTHONPATH=src pytest -m live
# Lintingruff check src/ruff format --check src/

CI runs the offline suite on every PR. The live suite runs only on main pushes and manual workflow_dispatch triggers, so build status is not coupled to upstream availability.


Changelog

See CHANGELOG.md


Contributing

See CONTRIBUTING.md


Security

See SECURITY.md (Deutsch) for the security posture and how to report a vulnerability.


License

MIT License — see LICENSE

Data on EnviDat is published under various open licenses (Creative Commons, CC0) — see individual dataset metadata.


Author

Hayal Oezkan · malkreide


Credits & Related Projects

Installation

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

json
{  "mcpServers": {    "wsl-envidat-mcp": {      "command": "uvx",      "args": [        "wsl-envidat-mcp"      ]    }  }}

Source: README.md at commit 4c370e9

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

3
  1. v0.3.1LatestSep 20, 2026
  2. v0.3.0Sep 20, 2026
  3. v0.2.5Sep 16, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts