Swisstopo Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Swisstopo Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
🇨🇭 Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)
MCP server for Swiss federal geodata -- maps, elevation, geocoding, cadastral extracts, and downloadable datasets via Swisstopo APIs
swisstopo-mcp gives AI assistants access to Switzerland's official geodata infrastructure through 13 tools across 6 API families, all without authentication:
| Source | Data | API |
|---|---|---|
| Swisstopo REST API | 500+ geodata layers (buildings, boundaries, land use) | REST/JSON |
| Geocoding | Official addresses, place names, postal codes | REST/JSON |
| Height Service | Elevation above sea level, elevation profiles | REST/JSON |
| STAC Catalog | Orthophotos, elevation models, 3D buildings | STAC 0.9 |
| WMTS | National maps, aerial images, zoning maps | URL builder |
| OEREB Cadastre | Public-law restrictions, parcels | REST/JSON (cantonal) |
Anchor demo query: "What land-use restrictions apply to the parcel at Musterstrasse 5, Zurich? Show me the location on a map." → More use cases by audience →
# Clone the repository
git clone https://github.com/malkreide/swisstopo-mcp.git
cd swisstopo-mcp
# Install
pip install -e .
# or with uv:
uv pip install -e .Or with uvx (no permanent installation):
uvx swisstopo-mcp# stdio (for Claude Desktop)
python -m swisstopo_mcp.server
# Streamable HTTP (port 8000)
python -m swisstopo_mcp.server --http --port 8000Try it immediately in Claude Desktop:
"Where is Bahnhofstrasse 1, Zurich? Give me the coordinates." "What is the elevation at the Uetliberg summit?" "What buildings are at coordinates 2683500, 1247500 (LV95)?"
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"swisstopo": {
"command": "python",
"args": ["-m", "swisstopo_mcp.server"]
}
}
}Or with uvx:
{
"mcpServers": {
"swisstopo": {
"command": "uvx",
"args": ["swisstopo-mcp"]
}
}
}Config file locations:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonFor use via claude.ai in the browser (e.g. on managed workstations without local software):
Render.com (recommended):
python -m swisstopo_mcp.server --http --port 8000https://your-app.onrender.com/sse| Tool | Description |
|---|---|
swisstopo_search_layers | Search the Swisstopo layer catalog (500+ layers) by keyword |
swisstopo_identify_features | Find map features at a specific coordinate (spatial query) |
swisstopo_find_features | Search features by attribute value within a layer (e.g. buildings by EGID) |
swisstopo_get_feature | Retrieve full attributes and geometry for a feature by ID |
| Tool | Description |
|---|---|
swisstopo_geocode | Convert Swiss addresses, place names, or postal codes to coordinates |
swisstopo_reverse_geocode | Find the nearest address for given coordinates |
| Tool | Description |
|---|---|
swisstopo_get_height | Get elevation above sea level (m a.s.l.) at a coordinate |
swisstopo_elevation_profile | Compute an elevation profile along a line |
| Tool | Description |
|---|---|
swisstopo_search_geodata | Search the STAC catalog for downloadable geodatasets |
swisstopo_get_collection | Get details and download links for a STAC collection |
| Tool | Description |
|---|---|
swisstopo_map_url | Generate a map.geo.admin.ch URL for browser display |
| Tool | Description |
|---|---|
swisstopo_get_egrid | Resolve a cadastral property ID (EGRID) from coordinates |
swisstopo_get_oereb_extract | Retrieve public-law land-use restrictions (OEREB) for a parcel |
| Query | Tool |
|---|---|
| "Where is Bahnhofstrasse 1, Zurich?" | swisstopo_geocode |
| "What is the elevation at the Uetliberg summit?" | swisstopo_get_height |
| "What buildings are at coordinates 2683500, 1247500?" | swisstopo_identify_features |
| "Find orthophoto datasets for download" | swisstopo_search_geodata |
| "Show me a map of Bern at zoom level 10" | swisstopo_map_url |
| "What restrictions apply to parcel at Musterstrasse 5?" | swisstopo_get_egrid + swisstopo_get_oereb_extract |
┌─────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────┐
│ Claude / AI │────▶│ swisstopo-mcp │────▶│ Swisstopo REST API │
│ (MCP Host) │◀────│ (MCP Server) │◀────│ api3.geo.admin.ch │
└─────────────────┘ │ │ ├──────────────────────────┤
│ 13 Tools │────▶│ Geocoding │
│ Stdio | Streamable HTTP │◀────│ api3.geo.admin.ch │
│ │ ├──────────────────────────┤
│ No authentication required │────▶│ STAC Catalog │
│ (11 of 13 tools) │◀────│ data.geo.admin.ch │
│ │ ├──────────────────────────┤
│ │────▶│ OEREB Cadastre │
│ │◀────│ (cantonal endpoints) │
└──────────────────────────────┘ └──────────────────────────┘swisstopo-mcp/
├── src/swisstopo_mcp/
│ ├── __init__.py # Package version
│ ├── server.py # MCP server wiring (tool registrations)
│ ├── api_client.py # Shared HTTP client (httpx + error handling)
│ ├── geocoding.py # swisstopo_geocode, swisstopo_reverse_geocode
│ ├── rest_api.py # swisstopo_search_layers, identify, find, get_feature
│ ├── height.py # swisstopo_get_height, swisstopo_elevation_profile
│ ├── stac.py # swisstopo_search_geodata, swisstopo_get_collection
│ ├── wmts.py # swisstopo_map_url
│ └── oereb.py # swisstopo_get_egrid, swisstopo_get_oereb_extract
├── tests/
│ ├── test_api_client.py
│ ├── test_geocoding.py
│ ├── test_height.py
│ ├── test_oereb.py
│ ├── test_rest_api.py
│ ├── test_stac.py
│ └── test_wmts.py
├── .github/workflows/ci.yml # GitHub Actions (Python 3.11/3.12/3.13)
├── pyproject.toml
├── 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
├── README.md # This file (English)
└── README.de.md # German versionThe full security policy and posture is documented in SECURITY.md.
This server is in Phase 1 — Read-only wrapper. All 13 tools are readOnlyHint: true / destructiveHint: false; there are no write or send capabilities. See docs/roadmap.md for later phases.
| Capability | Status | Rationale |
|---|---|---|
| Access to private data | ❌ No | Public Open Data only (federal/cantonal geodata) |
| Exposure to untrusted content | ⚠️ Limited | Reads only from a fixed allow-list of trusted geo.admin / OEREB hosts |
| External communication (write/send) | ❌ No | Read-only; no mail/webhook/write tools |
Trifecta score: at most 1 of 3 — safe by design.
Outbound requests are restricted to an explicit code-layer allow-list and redirects are disabled — see docs/network-egress.md.
For containerised HTTP deployments, a hardened Dockerfile and Kubernetes manifests (non-root, read-only root filesystem, dropped capabilities, egress NetworkPolicy) are provided — see docs/deployment.md.
The MCP protocol version is negotiated by the mcp SDK, which is pinned to the 1.x major in pyproject.toml so an update cannot silently change the negotiated version. SDK bumps are proposed monthly via Dependabot and tracked in CHANGELOG.md.
The server is unauthenticated by design — it serves only public open data. Over HTTP, session IDs are managed entirely by the FastMCP framework; there is no per-user state, so there is nothing user-specific to bind a session to. If an authenticated deployment is ever introduced, session IDs must be bound to the validated user identity (audit finding SEC-009).
ToolResponse with is_error: true and a user-friendly summary; raw exception text is never leaked to the client (it is logged to stderr instead).
the MCP SDK as JSON-RPC errors with standard codes (e.g. -32602 invalid params). Input validation happens at the Pydantic boundary (SEC-018).
This server intentionally exposes Tools only (no Resources or Prompts): it is a Phase-1 read-only wrapper, and every result is a live, parameterised API query rather than a static addressable document. Resources/Prompts may be added in a later phase if stable URI schemes emerge.
Most tools return a thought-complete result in a single call. Two domains use a short, documented discovery chain (each tool's description states the next step):
swisstopo_search_layers (find layer IDs) →swisstopo_identify_features / swisstopo_find_features → swisstopo_get_feature (full detail).
swisstopo_geocode → swisstopo_get_egrid →swisstopo_get_oereb_extract.
swisstopo_search_geodata → swisstopo_get_collection.Every tool returns a structured ToolResponse (FastMCP emits it as structured content with an output schema, plus a JSON text block):
| Field | Meaning |
|---|---|
summary | Human-readable Markdown summary |
results | Machine-readable structured records |
count | Number of results |
match_type | exact / fuzzy / none (search-style tools) |
source / license | Data attribution (OGD-CH, CC/OGD terms) |
provenance / retrieved_at | How and when the data was obtained |
is_error | true for handled errors |
# Unit tests (no network required)
pytest tests/ -m "not live"
# Integration tests (live API calls)
pytest tests/ -m "live"See CHANGELOG.md
See CONTRIBUTING.md
MIT License -- see LICENSE
Data provided by swisstopo under Open Government Data terms.
Hayal Oezkan · malkreide
<!-- mcp-name: io.github.malkreide/swisstopo-mcp -->
<!-- BEGIN GENERATED: install -->
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": {
"swisstopo-mcp": {
"command": "uvx",
"args": [
"swisstopo-mcp"
]
}
}
}<!-- END GENERATED: install -->
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.