Swisstopo Mcp

io.github.malkreide由 GitHub 账号 malkreide 发布v0.5.1更新于 Nov 1, 2026

Swiss federal geodata: geocoding, height, STAC, WMTS, OEREB and more

已验证STDIO仅桌面位置与生活数据与分析

概览

AI 生成的概览

面向瑞士联邦地理数据的本地 MCP 服务器:地理编码、高程、地图图层、地籍 ÖREB 摘录、STAC 数据下载与行政区查询。

可以做什么
  • 将地址、地名和邮编转为坐标,反向解析坐标,并在 WGS84 与 LV95 之间转换
  • 查询国家地图目录:搜索图层、查看字段、按点位或属性查找要素、获取单条记录
  • 查询某点高程、计算沿线高程剖面、生成可分享的地图链接
  • 查询建筑分区、含 BFS 编号的市镇、地块 EGRID 以及 ÖREB 用地限制
  • 检索 STAC 目录中的可下载数据集,并获取集合详情与下载链接
  • 解析邮编、市镇与街道名称,并查找某点周边的 OpenStreetMap 兴趣点或州级地理数据
适用场景
适合需要回答瑞士地点相关问题的场景:地址对应坐标、高程、适用分区或所属市镇、地块受哪些限制,或有哪些地理数据集可下载。也便于通过官方 BFS 市镇编号与其他瑞士行政数据关联。
运行要求
以 Python 包形式通过 stdio 在本地运行(需 Python 3.11+,可用 uv 或 pip;uvx swisstopo-mcp 无需永久安装)。无需 API 密钥或账号。需要访问 swisstopo、geo.admin.ch、各州 ÖREB、OpenPLZ 与 Overpass 的网络。ÖREB 工具需提供州参数。
安装前请注意
只读且无身份验证,仅提供公开开放数据,没有写入、发送或删除类工具。部分结果不具法律效力:协调化建筑分区图层为联邦综合数据,ÖREB 摘录遵循各州条款。上游限流可能影响高频使用,数据来自多个来源且许可不同(瑞士 OGD、各州 ÖREB、OpenStreetMap 的 ODbL、OpenPLZ 署名要求)。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Swisstopo Mcp,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

🇨🇭 Part of the Swiss Public Data MCP Portfolio

swisstopo-mcp

[Version] [License: MIT] [Python 3.11+] [MCP] [No Auth Required] [CI]

MCP server for Swiss federal geodata -- maps, elevation, geocoding, cadastral extracts, and downloadable datasets via Swisstopo APIs

🇩🇪 Deutsche Version


Overview

swisstopo-mcp gives AI assistants access to Switzerland's official geodata infrastructure through 20 tools, all without authentication:

SourceDataAPI
Swisstopo REST API500+ geodata layers (buildings, boundaries, land use)REST/JSON
GeocodingOfficial addresses, place names, postal codesREST/JSON
Height ServiceElevation above sea level, elevation profilesREST/JSON
STAC CatalogOrthophotos, elevation models, 3D buildingsSTAC 0.9
WMTSNational maps, aerial images, zoning mapsURL builder
OEREB CadastrePublic-law restrictions, parcelsREST/JSON (cantonal)
geodienste.chInterkantonale Basisgeodaten (cadastral survey, contaminated sites, hazard maps, …)OGC API Features / WMS / WFS
OpenStreetMapPoints of interest (schools, playgrounds, pharmacies, …)Overpass API (ODbL)
OpenPLZ APIAdministrative address level: postal codes → commune (BFS number) → district → cantonREST/JSON (BFS + swisstopo OGD)

Anchor demo query: "Which communes are in the Uster district, and what are their BFS numbers for joining with BFS statistics data?" (The BFS commune number is the official join key to swiss-statistics-mcp and zurich-opendata-mcp — this is what turns a geodata wrapper into a semantic connector at the commune level.) → More use cases by audience →

Demo

[Demo: Claude using swisstopo_find_commune and swisstopo_lookup_postal_code]


Features

  • 🗺️ 20 tools (REST, Geocoding, Height, STAC, WMTS, OEREB, geodienste.ch, OpenStreetMap/Overpass, OpenPLZ)
  • 🏛️ Resolve the administrative address level (PLZ → commune/BFS number → district → canton) via OpenPLZ
  • 🔍 Geocode Swiss addresses and reverse-geocode coordinates
  • 🏔️ Query elevation and compute elevation profiles
  • 📦 Discover and download geodatasets (orthophotos, 3D buildings, historical maps)
  • 🏗️ Identify map features at coordinates across 500+ Swisstopo layers
  • 🔗 Generate shareable map.geo.admin.ch links
  • 📋 Look up cadastral property IDs (EGRID) and retrieve OEREB extracts
  • 🔓 No API key required for all tools (OEREB extract needs a supported canton)
  • ☁️ Dual transport -- stdio (Claude Desktop) + Streamable HTTP (cloud)

Prerequisites

  • Python 3.11+
  • uv (recommended) or pip

Installation

bash
# Clone the repositorygit clone https://github.com/malkreide/swisstopo-mcp.gitcd swisstopo-mcp
# Installpip install -e .# or with uv:uv pip install -e .

Or with uvx (no permanent installation):

bash
uvx swisstopo-mcp

Quickstart

bash
# stdio (for Claude Desktop)python -m swisstopo_mcp.server
# Streamable HTTP (port 8000)python -m swisstopo_mcp.server --http --port 8000

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


Configuration

Claude Desktop

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

json
{  "mcpServers": {    "swisstopo": {      "command": "python",      "args": ["-m", "swisstopo_mcp.server"]    }  }}

Or with uvx:

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

Config file locations:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Cloud Deployment (SSE for browser access)

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

Render.com (recommended):

  1. Push/fork the repository to GitHub
  2. On render.com: New Web Service -> connect GitHub repo
  3. Set start command: python -m swisstopo_mcp.server --http --port 8000
  4. In claude.ai under Settings -> MCP Servers, add: https://your-app.onrender.com/sse

Available Tools

REST API (Layer & Feature Queries)

ToolDescription
swisstopo_map_queryThe national map catalogue (api3.geo.admin.ch). One operation per call — see the five below
swisstopo_zoning_atHarmonised building zone at a coordinate — one call, no layer lookup (not legally binding)
swisstopo_municipality_atMunicipality, canton and official BFS number at a coordinate
swisstopo_map_query operations
operationAnswersRequired arguments
search_layersWhich layers exist for a keyword? (500+ catalogue)query
layer_infoWhat fields can I query on this layer, and what is its legend?layer
features_at_pointWhat is at this coordinate?layers + a point (lat/lon or easting/northing)
features_by_attributeWhich features carry this value? (e.g. buildings by EGID)layer, search_field, search_text
feature_by_idGive me this one feature in full, with geometrylayer, feature_id

Arguments belonging to a different operation are rejected, not ignored — the error names the ones the chosen operation accepts. Silently dropping a misplaced search_field would return a plausible answer to a question nobody asked, which is the failure mode the whole envelope design is against.

Geocoding

ToolDescription
swisstopo_geocodeConvert Swiss addresses, place names, or postal codes to coordinates
swisstopo_reverse_geocodeFind the nearest address for given coordinates

Height Service

ToolDescription
swisstopo_get_heightGet elevation above sea level (m a.s.l.) at a coordinate
swisstopo_elevation_profileCompute an elevation profile along a line
swisstopo_convert_coordinatesOfficial WGS84 ↔ LV95 conversion via the swisstopo REFRAME service

STAC Catalog (Geodata Downloads)

ToolDescription
swisstopo_search_geodataSearch the STAC catalog for downloadable geodatasets
swisstopo_get_collectionGet details and download links for a STAC collection

WMTS (Map URLs)

ToolDescription
swisstopo_map_urlGenerate a map.geo.admin.ch URL for browser display

OEREB Cadastre

ToolDescription
swisstopo_get_egridResolve a cadastral property ID (EGRID) from coordinates
swisstopo_get_oereb_extractRetrieve public-law land-use restrictions (OEREB) for a parcel

Consolidated Geodata Façade

One façade over several map/layer sources, kept under the 25-tool budget (see docs/geodaten-erweiterung-phase1.md):

ToolDescription
swisstopo_list_available_layersDiscover layer keys for swisstopo_query_geodata (strassenverzeichnis, oereb-verfuegbarkeit, geodienste:<topic>:<canton>); filters to contract-free geodienste datasets
swisstopo_query_geodataQuery a chosen layer by point / bbox / commune — amtliches Strassenverzeichnis, interkantonale geodienste.ch data (OGC API Features), or ÖREB availability
swisstopo_query_osm_featuresOpenStreetMap POIs (schools, playgrounds, pharmacies, …) around a point via Overpass — separate source, ODbL (© OpenStreetMap contributors)

Administrative Address Level (OpenPLZ)

The amtliche address hierarchy PLZ → commune → district → canton, served by the OpenPLZ API (data: BFS municipal directory + swisstopo street directory, Swiss OGD — a separate source and licence from the swisstopo geodata above). Every commune-bearing response exposes bfs_commune_number as a named top-level field: the official join key to BFS statistics (swiss-statistics-mcp) and zurich-opendata-mcp.

ToolDescription
swisstopo_lookup_postal_codeResolve a Swiss postal code → locality, commune (+BFS number), district, canton
swisstopo_find_communeResolve a commune both directions (name ↔ bfs_number) or list all communes of a canton / district. Accepts canton abbreviation (ZH) or key (1); resolution happens server-side
swisstopo_search_addressFull-text search over Swiss streets and localities, returning commune + BFS number per hit

Example Use Cases

QueryTool
"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_map_query (operation='features_at_point')
"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_oereb_at
"Which schools are within 500 m of Bederstrasse 109, 8002 Zürich, and which streets lead there?"swisstopo_query_osm_features + swisstopo_query_geodata (strassenverzeichnis)
"Which contaminated-sites data is free for canton ZH?"swisstopo_list_available_layers + swisstopo_query_geodata (geodienste:kataster_belasteter_standorte:ZH)
"Which communes are in the Uster district and what are their BFS numbers?"swisstopo_find_commune (district=109)
"Which commune and canton does postal code 8001 belong to?"swisstopo_lookup_postal_code
"What is the BFS number of Winterthur (to join with BFS statistics)?"swisstopo_find_commune (name=Winterthur)

Architecture

┌─────────────────┐     ┌──────────────────────────────┐     ┌──────────────────────────┐│   Claude / AI   │────▶│  swisstopo-mcp               │────▶│  Swisstopo REST API      ││   (MCP Host)    │◀────│  (MCP Server)                │◀────│  api3.geo.admin.ch       │└─────────────────┘     │                              │     ├──────────────────────────┤                        │  20 Tools                    │────▶│  Geocoding               │                        │  Stdio | Streamable HTTP     │◀────│  api3.geo.admin.ch       │                        │                              │     ├──────────────────────────┤                        │  No authentication required  │────▶│  STAC Catalog            │                        │  (all tools; OEREB canton opt) │◀────│  data.geo.admin.ch       │                        │                              │     ├──────────────────────────┤                        │                              │────▶│  OEREB Cadastre          │                        │                              │◀────│  (cantonal endpoints)    │                        │                              │     ├──────────────────────────┤                        │                              │────▶│  geodienste.ch (OGC API) │                        │                              │◀────│  overpass.osm.ch (ODbL)  │                        │                              │     ├──────────────────────────┤                        │  BFS-Nr = join key to        │────▶│  OpenPLZ API             │                        │  swiss-statistics-mcp        │◀────│  openplzapi.org (BFS/OGD)│                        └──────────────────────────────┘     └──────────────────────────┘

Project Structure

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_map_query (5 operations), zoning_at, municipality_at│   ├── 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│   ├── geodata.py               # swisstopo_query_geodata + swisstopo_list_available_layers (façade)│   ├── overpass.py              # swisstopo_query_osm_features (OpenStreetMap / Overpass)│   └── openplz.py               # swisstopo_lookup_postal_code, swisstopo_find_commune, swisstopo_search_address (OpenPLZ)├── tests/│   ├── test_api_client.py│   ├── test_geocoding.py│   ├── test_height.py│   ├── test_oereb.py│   ├── test_rest_api.py│   ├── test_stac.py│   ├── test_wmts.py│   ├── test_geodata.py│   ├── test_overpass.py│   ├── test_openplz.py│   └── test_retry.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 version

Security & Limits

The full security policy and posture is documented in SECURITY.md.

Phase

This server is in Phase 2.5 — Consolidation of swiss-geodata-mcp (see docs/roadmap.md, the single authority for phase state).

PropertyStatus
Read tools24, all readOnlyHint: true / destructiveHint: false
Write toolsnone — Phase 3, not planned
Transportstdio (default) and Streamable-HTTP
ISDS classificationdocs/isds-dsg.md — low protection requirement
DSG processing recordnot maintained, with reasons — docs/isds-dsg.md §5
Last auditaudits/2026-07-27T162602-Z-swisstopo-mcp/

A phase advance requires: the phase's roadmap items checked off, a re-run audit with no open critical findings, and a CHANGELOG entry naming the new phase. Phase 3 (write tools) additionally requires re-running the Lethal-Trifecta assessment and a security review before any implementation starts.

Tool budget and aggregation

20 tools against a self-imposed budget of 25. The check's ideal is ≤12, so the count still needs an argument, not just a number. Per cluster:

The five api3 tools are merged (0.4.1, breaking). search_layers, layer_info, identify_features, find_features and get_feature are now operation values on swisstopo_map_query. They were the textbook one-tool-per-REST-endpoint mapping the check names, and they are gone as such.

Earlier releases argued the opposite here, and the argument is worth keeping visible because it was not wrong so much as outweighed: merging relocates the decision from tool selection into schema navigation, where a model has less help, because tool descriptions are what it actually reads. Three things address that directly rather than hoping it does not matter:

  • The operations are named for questions, not endpoints. identify and find are ESRI vocabulary — they say which MapServer route is called, not what is being asked, and nobody without ArcGIS experience can tell them apart. features_at_point and features_by_attribute can be picked correctly from the operation list alone.
  • A misplaced argument is an error, not a silent drop. With five tools the schema made a wrong pairing impossible. With one tool it does not, so validation does: each operation declares the fields it accepts, and anything else is refused with a message naming the alternatives.
  • The note hints from ARCH-003 still name the next step, now as operations rather than tool names — an empty attribute search points at operation='layer_info' for the valid field names, and so on.

Observability was the other cost, and it is not paid: each operation keeps its own log and trace label (swisstopo_map_query:features_at_point), so per-operation timing and error rates survive the merge.

The two pairs that stay separate, both named by the audit:

  • geocode + reverse_geocode do hit the same SearchServer endpoint, so on the API axis this is a 1:1 mapping twice over. They stay separate on the axis that matters for tool selection: "address → coordinates" and "coordinates → address" are different questions with different input types, and collapsing them into one tool with a mode would make the model choose a variant instead of a tool. Sharing an endpoint is an implementation detail of the upstream.
  • search_geodata → get_collection is a genuine search → detail pair over STAC; see below.

The naming ambiguity is resolved, which the audit did not raise but the previous version of this section recorded for "the next breaking release" — this is it. swisstopo_search_layers and swisstopo_list_available_layers both said "layers" while fronting different catalogues. The first is now swisstopo_map_query with operation='search_layers', which puts the national catalogue in the tool name and leaves list_available_layers unambiguously the consolidated façade.

Search → detail pairs. search_geodata → get_collection is a genuine pair: STAC collection metadata is large and callers usually want one of many search hits. get_egrid → get_oereb_extract was the same shape and has been collapsed: swisstopo_oereb_at answers the actual question in one call and resolves the EGRID internally, because the EGRID is an upstream identifier rather than something a caller asked for. get_egrid remains for callers who want the parcel ID itself.

Genuine aggregation already in place. query_geodata fronts three sources behind one tool; zoning_at and municipality_at each collapse a discovery chain that previously took two calls.

When the next source is added, the choice is a raise or a consolidation. With the api3 five merged there is no obvious consolidation left holding headroom, so the next surface growth is a real conversation about the ceiling rather than a deferred cleanup. tests/test_tool_namespace.py::TestToolBudget is where that conversation is forced: raising the budget means editing the number there and in both READMEs.

Data sources and licences

Every response carries source and license. ARE is a different federal office from swisstopo, so its licence is asserted rather than inherited.

SourceServed byLicence
swisstopo / geo.admin.chmost toolsSwiss OGD (opendata.swiss)
swisstopo REFRAME (geodesy.geo.admin.ch)swisstopo_convert_coordinatesSwiss OGD (opendata.swiss)
swissBOUNDARIES3D (swisstopo)swisstopo_municipality_atSwiss OGD (opendata.swiss)
ch.are.bauzonen (ARE)swisstopo_zoning_atSwiss OGD — Bundesamt für Raumentwicklung ARE
Cantonal ÖREB cadastreswisstopo_get_egrid, swisstopo_get_oereb_extract, swisstopo_oereb_at, swisstopo_query_geodataCantonal ÖREB terms
geodienste.ch (cantons)swisstopo_query_geodataFree use — attribution required
OpenStreetMap (Overpass)swisstopo_query_osm_featuresODbL — © OpenStreetMap contributors
OpenPLZ (BFS + swisstopo)swisstopo_lookup_postal_code, swisstopo_find_commune, swisstopo_search_addressFree use — attribution required

ch.are.bauzonen is a federal synthesis for cross-cantonal comparability and is not legally binding — only the cantonal or communal Nutzungsplanung is. That caveat is carried on every swisstopo_zoning_at result record.

Project structure

The tool modules sit flat under src/swisstopo_mcp/ rather than in a tools/ sub-package. Each module maps to exactly one upstream API family — rest_api.py → api3 MapServer, stac.py → STAC, oereb.py → cantonal ÖREB, openplz.py → OpenPLZ, overpass.py → OSM, coords.py → REFRAME — which is the axis along which this server's code actually varies. A tools/ level would add a directory without adding a distinction.

server.py contains tool registrations only; every tool body lives in its domain module. Splitting it further is a readability question, not a structural one.

Lethal Trifecta assessment

CapabilityStatusRationale
Access to private data❌ NoPublic Open Data only (federal/cantonal geodata)
Exposure to untrusted content⚠️ LimitedReads only from a fixed allow-list of trusted geo.admin / OEREB hosts
External communication (write/send)❌ NoRead-only; no mail/webhook/write tools

Trifecta score: at most 1 of 3 — safe by design.

Egress

Outbound requests are restricted to an explicit code-layer allow-list and redirects are disabled — see docs/network-egress.md.

Container deployment

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.

MCP Protocol Version

The server speaks 2026-07-28 natively and keeps every older revision reachable. Which one you get is decided by how you connect, not by configuration — one process serves both eras at once.

Modern eraHandshake era
Revisions2026-07-282024-11-05 … 2025-11-25
Opening exchangenone — every request stands aloneinitialize + notifications/initialized
Protocol version travels inparams._meta and the MCP-Protocol-Version headerthe initialize params
SessionnoneMcp-Session-Id, closed with DELETE /mcp
Directoryserver/discoverthe initialize result
Server identitythe io.modelcontextprotocol/serverInfo stamp in every response's _metaserverInfo in the initialize result
Gone in this revisioninitialize, ping, logging/setLevel, resources/subscribe—

A 2026-07-28 request is a self-contained POST to /mcp:

http
POST /mcpMCP-Protocol-Version: 2026-07-28Mcp-Method: tools/callMcp-Name: swisstopo_map_urlContent-Type: application/jsonAccept: application/json, text/event-stream
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{  "name":"swisstopo_map_url",  "arguments":{"params":{"lat":46.9481,"lon":7.4474}},  "_meta":{    "io.modelcontextprotocol/protocolVersion":"2026-07-28",    "io.modelcontextprotocol/clientCapabilities":{}  }}}

The MCP-Protocol-Version header is what routes the request to the modern path; without it the same body lands on the handshake transport. Mcp-Method and Mcp-Name must agree with the body, or the request is rejected with -32020. All three are in the CORS allow-list so a browser client's preflight passes.

What this means in practice:

  • Existing clients are unaffected. A client asking for 2025-06-18 still gets 2025-06-18; one asking over initialize for 2026-07-28 gets the handshake ceiling 2025-11-25 back. Measured, not assumed — tests/test_protocol_version.py.
  • Both eras expose the same 20 tools. Nothing is gated on the revision (tests/test_modern_protocol.py).
  • Sticky sessions matter only for the handshake era. A modern request carries no session, so it can be served by any replica — see docs/deployment.md.

Neither revision is author-settable: the SDK negotiates the handshake era and routes the modern one, and pyproject.toml pins mcp to the 2.x major so an update cannot move either silently. tests/test_protocol_version.py pins both ceilings and fails if a Dependabot bump changes one.

Update policy

  • SDK updates are tested on a feature branch before merge.
  • A change to either protocol ceiling is recorded in CHANGELOG.md under ### Changed, naming the old and the new version.
  • A protocol change that breaks existing clients triggers a major release.

Sessions & Authentication

The server is unauthenticated by design — it serves only public open data.

Sessions exist in the handshake era only. There, session IDs are managed entirely by the MCP SDK; there is no per-user state, so there is nothing user-specific to bind a session to. Idle sessions are reaped after SWISSTOPO_SESSION_IDLE_TIMEOUT seconds (default 1800) so a client that disconnects without sending DELETE /mcp does not leak one for the lifetime of the process. A 2026-07-28 request opens no session at all, so neither the timeout nor the reaper applies to it.

If an authenticated deployment is ever introduced, session IDs must be bound to the validated user identity (audit finding SEC-009).

Error handling

  • Execution errors (upstream failure, invalid value) are returned as a ToolResponse with is_error: true and a user-friendly summary; unexpected exception text is masked and logged to stderr instead. No upstream body or internal configuration is forwarded: Overpass error pages are classified against a fixed signature table, and an egress refusal returns a fixed message rather than the allow-list (OBS-002).
  • Protocol errors (unknown tool, malformed/invalid arguments) come back from the SDK as tool results with the protocol isError flag set — not as JSON-RPC error objects. Verified against mcp 1.28.1 by runtime probe; an earlier version of this section claimed -32602 and was wrong. Input validation happens at the Pydantic boundary (SEC-018).
  • Both flags agree. A handled execution error sets the protocol isError flag and the payload field is_error, so a client can branch on either. The envelope — including source and license — survives on the error path (OBS-001).

MCP Primitives

Tools are the surface, and almost all of it: every result is a live, parameterised API query rather than a static addressable document.

One Resource — swisstopo://catalogue/layers — serves the façade layer catalogue. It is the one thing here that behaves like a document: deterministic, idempotent, and already served with provenance: "cached". swisstopo_list_available_layers remains for filtered queries; the resource is for a client that wants the catalogue itself, addressably.

Two Prompts encode the workflows below, including the precedence rule for point questions. That rule lives in the tool descriptions and in the server instructions too, but a prompt is the one place a model reads it as guidance rather than as one of 24 descriptions (audit ARCH-007/ARCH-008):

PromptArguments
swisstopo_feature_lookuport, was
swisstopo_geodata_downloadthema

Tool workflows

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

  • Feature query: all four steps are swisstopo_map_query with a different operation: search_layers (find layer IDs) → layer_info (see the queryable fields) → features_at_point / features_by_attribute → feature_by_id (full detail).
  • Cadastre: swisstopo_geocode → swisstopo_oereb_at (one call: coordinates → EGRID → extract). Use swisstopo_get_egrid → swisstopo_get_oereb_extract only when the parcel ID itself is wanted.
  • Downloads: swisstopo_search_geodata → swisstopo_get_collection.

Response Format

Every tool returns a structured ToolResponse (FastMCP emits it as structured content with an output schema, plus a JSON text block):

FieldMeaning
summaryHuman-readable Markdown summary
resultsMachine-readable structured records
countNumber of results
match_typeexact / fuzzy / none (search-style tools)
source / licenseData attribution (OGD-CH, CC/OGD terms)
provenance / retrieved_atHow and when the data was obtained
is_errortrue for handled errors

Known Limitations

  • OEREB tools require a canton parameter; not all cantons expose the same API format
  • STAC catalog uses Swisstopo's v0.9 endpoint; some collections may lack complete metadata
  • Geocoding covers Swiss addresses only (no Liechtenstein)
  • Rate limits are enforced by Swisstopo; high-frequency usage may be throttled

Known findings — OpenPLZ live probe (2026-07-20)

The OpenPLZ endpoints were probed live before implementation. Findings baked into the tools:

Endpoint / behaviourResultHandling
/Cantons200, 26 records, key = BFS canton number (ZH = 1)canton abbreviation resolved from this list
/Cantons/{key}/Districts|Communes200path param is the numeric key
/Cantons/ZH/Districts (abbreviation)200 + [] — not an errorZH→1 resolved server-side; empty answer gets an explanatory note
/Localities?postalCode=8001200, commune.key = 261 (BFS Zürich)bfs_commune_number surfaced top-level
/Localities?postalCode=9999 (unknown)200 + []reported as a note — empty ≠ absent
list endpoints paginationdefault pageSize=10, hard max 50 (100 → HTTP 400)tools iterate pages via x-total-count
raw umlaut in query (?name=Zürich)HTTP 400httpx URL-encodes params automatically
historicalCode field≠ key for communes (historized-directory id)not used; the join key is the current key
bulk dumpnone from OpenPLZ (only /swagger)Architecture A (live-API-only) — adequate for a lookup connector

The abbreviation-vs-key trap in one line: an empty OpenPLZ list is almost never proof that something does not exist — it usually means a wrong path parameter (an abbreviation where a numeric key was expected). The tools resolve abbreviations server-side and annotate every empty result.


Testing

bash
# Unit tests (no network required)pytest tests/ -m "not live"
# Integration tests (live API calls)pytest tests/ -m "live"

Changelog

See CHANGELOG.md


Contributing

See CONTRIBUTING.md


Security

Read-only, no authentication, public geodata only. See SECURITY.md (Deutsch) for the security posture and how to report a vulnerability.


License

MIT License -- see LICENSE

Data provided by swisstopo under Open Government Data terms.


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": {    "swisstopo-mcp": {      "command": "uvx",      "args": [        "swisstopo-mcp"      ]    }  }}

来源:README.md,提交 c1efaa0

工具

20

swisstopo_geocode

Adresse geocodieren

Wandelt eine Adresse, einen Ortsnamen oder eine PLZ in Koordinaten um (Geocoding). <use_case>Startpunkt für ortsbezogene Abfragen: Adresse → Koordinaten, die danach an swisstopo_get_height, swisstopo_map_query (operation='features_at_point') oder swisstopo_get_egrid übergeben werden.</use_case>

输入参数params必填

swisstopo_reverse_geocode

Koordinaten zu Adresse

Findet die nächstgelegene Adresse zu gegebenen WGS84-Koordinaten (Reverse Geocoding). <use_case>Koordinaten aus Karte oder GPS in eine lesbare Adresse auflösen.</use_case>

输入参数params必填

swisstopo_map_query

Nationaler Kartenkatalog: Layer und Features

Fragt den nationalen Swisstopo-Kartenkatalog ab (500+ Layer): Layer finden, Felder ansehen, Features abfragen. <use_case>Der übliche Ablauf ist eine Kette: operation='search_layers' liefert Layer-IDs, operation='layer_info' zeigt deren abfragbare Felder, danach operation='features_at_point' (räumlich) oder 'features_by_attribute' (nach Attributwert), und operation='feature_by_id' holt ein einzelnes Feature vollständig.</use_case> <important_notes>Genau eine operation pro Aufruf. Felder, die zu einer anderen operation gehören, werden abgelehnt statt ignoriert — die Fehlermeldung nennt die zulässigen.</important_notes> <important_notes>Für Bauzone bzw. Gemeinde gibt es direkte Tools (swisstopo_zoning_at, swisstopo_municipality_at). 'features_at_point' nur nutzen, wenn zusätzliche Rohattribute gebraucht werden oder ein anderer Layer gefragt ist.</important_notes> <important_notes>Das ist der nationale Katalog von api3.geo.admin.ch. Für interkantonale und kantonale Quellen ist swisstopo_list_available_layers → swisstopo_query_geodata zuständig, für herunterladbare Datensätze swisstopo_search_geodata.</important_notes>

输入参数params必填

swisstopo_search_geodata

Geodaten suchen

Durchsucht den STAC-Katalog nach herunterladbaren Geodaten. <use_case>Findet Orthophotos, Höhenmodelle (swissALTI3D), 3D-Gebäude und historische Karten zum Download.</use_case> <important_notes>Liefert Collections/Metadaten; Download-Links via swisstopo_get_collection.</important_notes>

输入参数params必填

swisstopo_get_collection

Geodaten-Details abrufen

Ruft Detailinformationen und Download-Links einer STAC-Collection ab. <use_case>Zweiter Schritt nach swisstopo_search_geodata, um Assets/Download-URLs einer Collection zu erhalten.</use_case>

输入参数params必填

swisstopo_map_url

Karten-URL generieren

Generiert eine teilbare map.geo.admin.ch-URL zum Öffnen im Browser. <use_case>Einen Kartenausschnitt mit optionalen Layern als Link bereitstellen (kein Datenabruf).</use_case>

输入参数params必填

swisstopo_get_height

Höhe abfragen

Gibt die Höhe über Meer (m ü. M.) an einer Koordinate zurück. <use_case>Punkthöhe für eine Adresse/Koordinate; für Linien siehe swisstopo_elevation_profile.</use_case> <important_notes>Koordinaten entweder als lat/lon (WGS84) ODER als easting/northing (LV95) angeben — nicht beides.</important_notes>

输入参数params必填

swisstopo_elevation_profile

Höhenprofil berechnen

Berechnet ein Höhenprofil entlang einer Linie aus mehreren Koordinatenpaaren. <use_case>Höhenverlauf z.B. für Wander-/Schulweg-Analysen.</use_case> <important_notes>Benötigt ≥2 Koordinatenpaare. Standard ist WGS84 ('lat1,lon1;lat2,lon2;…'); für LV95 coordinate_system='lv95' setzen und die Paare als 'easting,northing' übergeben.</important_notes>

输入参数params必填

swisstopo_zoning_at

Bauzone an Koordinate

Gibt die harmonisierte Bauzone an einer Koordinate zurück (ch.are.bauzonen, ARE). <use_case>«Welche Bauzone gilt hier?» in einem Aufruf — ohne vorher die Layer-ID via swisstopo_map_query erst suchen zu müssen.</use_case> <important_notes>Der harmonisierte ARE-Layer ist eine Synthese für die schweizweite Vergleichbarkeit und NICHT rechtsverbindlich — verbindlich ist allein die kantonale/kommunale Nutzungsplanung. Der Hinweis steht in jedem Resultat. Koordinaten als lat/lon (WGS84) oder easting/northing (LV95).</important_notes>

输入参数params必填

swisstopo_municipality_at

Gemeinde an Koordinate

Gibt Gemeinde, BFS-Nummer und Kanton an einer Koordinate zurück (swissBOUNDARIES3D). <use_case>Koordinate → amtliche BFS-Gemeindenummer, der Join-Key zu swiss-statistics-mcp und zurich-opendata-mcp.</use_case> <important_notes>Der Layer führt eine Fläche pro historischem Jahrgang; es wird der aktuelle Stand zurückgegeben. Auf einer Gemeindegrenze oder ausserhalb der Schweiz bleibt das Resultat leer.</important_notes>

输入参数params必填

swisstopo_convert_coordinates

Koordinaten umrechnen (REFRAME)

Rechnet Koordinaten amtlich zwischen WGS84 und LV95 um (swisstopo REFRAME). <use_case>Wenn Koordinaten in LV95 (EPSG:2056) vorliegen und für die übrigen Tools nach WGS84 gebracht werden müssen — oder umgekehrt für den Katasterbezug, wo Zentimeter zählen.</use_case> <important_notes>Achsenreihenfolge beachten: REFRAME benennt beide Eingaben easting/northing. Bei wgs84_to_lv95 ist easting der LÄNGENgrad und northing der BREITENgrad — umgekehrt zur lat/lon-Reihenfolge der übrigen Tools. Vertauschte Achsen werden abgewiesen, nicht stillschweigend umgerechnet.</important_notes>

输入参数params必填

swisstopo_get_egrid

Grundstück-ID (EGRID) ermitteln

Ermittelt die EGRID (Grundstück-ID) aus Koordinaten für einen bestimmten Kanton. <use_case>Grundstück-ID (EGRID) zu einer Koordinate, wenn die ID selbst gebraucht wird. Für die Beschränkungen direkt swisstopo_oereb_at nutzen — das löst den EGRID intern auf.</use_case> <important_notes>Erfordert einen unterstützten Kanton (z.B. ZH, BE).</important_notes>

输入参数params必填

swisstopo_get_oereb_extract

ÖREB-Auszug abrufen

Ruft öffentlich-rechtliche Eigentumsbeschränkungen (ÖREB) für ein Grundstück (EGRID) ab. <use_case>ÖREB-Auszug zu einem bereits bekannten EGRID. Wer von einer Koordinate ausgeht, nimmt swisstopo_oereb_at — das erledigt beide Schritte in einem Aufruf.</use_case> <important_notes>Erfordert einen unterstützten Kanton.</important_notes>

输入参数params必填

swisstopo_oereb_at

ÖREB-Auszug an Koordinate

Gibt die ÖREB-Eigentumsbeschränkungen an einer Koordinate zurück (ein Aufruf). <use_case>«Welche Beschränkungen gelten auf diesem Grundstück?» — löst den EGRID intern auf. Das ist der normale Weg; swisstopo_get_egrid braucht es nur, wer die Parzellen-ID selbst benötigt.</use_case> <important_notes>Nur für Kantone mit angebundenem ÖREB-Dienst (siehe SWISSTOPO_OEREB_CANTONS). Koordinaten als lat/lon (WGS84) oder easting/northing (LV95).</important_notes>

输入参数params必填

swisstopo_list_available_layers

Verfügbare Geodaten-Layer auflisten

Discovery-Tool: listet die Layer-Kennungen, die swisstopo_query_geodata akzeptiert. <use_case>Erster Schritt vor swisstopo_query_geodata: herausfinden, welche Datensätze (Strassenverzeichnis, ÖREB-Verfügbarkeit, interkantonale geodienste.ch-Topics) verfügbar und ohne Vertrag frei nutzbar sind. Für konkrete geodienste-Kennungen einen Kanton angeben (z.B. canton='ZH').</use_case>

输入参数params必填

swisstopo_query_geodata

Geodaten abfragen (Fassade)

Einheitliche Fassade über mehrere Geodaten-Quellen anhand einer Layer-Kennung. <use_case>Ein Tool für drei Quellen: 'strassenverzeichnis' (Strassen um einen Punkt), 'oereb-verfuegbarkeit' (ÖREB-Status/Zuständigkeit an einem Punkt) und 'geodienste:&lt;topic&gt;:&lt;KANTON&gt;' (interkantonale Basisgeodaten via OGC API Features). Genau eine Ortsangabe (point | bbox | commune) übergeben.</use_case> <important_notes>Gültige Layer-Kennungen via swisstopo_list_available_layers. geodienste-Layer erfordern bbox oder point (mit radius_m).</important_notes>

输入参数params必填

swisstopo_query_osm_features

OpenStreetMap-POIs abfragen (Overpass)

Findet OpenStreetMap-POIs (Schulen, Spielplätze, Apotheken …) im Umkreis. <use_case>Beantwortet «Welche Schulhäuser/Spielplätze liegen im Umkreis von R Metern um diesen Punkt/diese Adresse?». Ergänzt die amtlichen swisstopo-Daten um Points-of-Interest aus OpenStreetMap.</use_case> <important_notes>Quelle OpenStreetMap (ODbL, © OpenStreetMap contributors), nicht swisstopo. Overpass hat Rate-Limits/Timeouts — kleiner Radius bevorzugt; bei Überlastung kommt eine sprechende Fehlermeldung statt Daten.</important_notes>

输入参数params必填

swisstopo_lookup_postal_code

PLZ zu Gemeinde/BFS-Nummer auflösen

Löst eine Schweizer PLZ in Ort, Gemeinde, Bezirk und Kanton auf (OpenPLZ, amtlich). <use_case>Beantwortet «Zu welcher Gemeinde/welchem Kanton gehört PLZ 8001?» und liefert die BFS-Gemeindenummer (`bfs_commune_number`).</use_case> <important_notes>`bfs_commune_number` ist der amtliche Join-Schlüssel zu BFS-Statistikdaten (swiss-statistics-mcp) und zu zurich-opendata-mcp. Quelle OpenPLZ (BFS/swisstopo OGD), nicht die swisstopo-Geodaten. Eine unbekannte PLZ liefert eine leere Trefferliste mit erklärendem Hinweis.</important_notes>

输入参数params必填

swisstopo_find_commune

Gemeinde auflösen (Name ↔ BFS-Nummer, Kanton/Bezirk)

Löst Gemeinden auf: Name→BFS-Nummer, BFS-Nummer→Name, oder alle Gemeinden eines Kantons/Bezirks. <use_case>Vier Modi (genau einen Parameter angeben): `name` (Name → BFS-Nummer), `bfs_number` (BFS-Nummer → Gemeinde), `canton` (alle Gemeinden eines Kantons) oder `district` (alle Gemeinden eines Bezirks). Beantwortet z.B. «Welche Gemeinden liegen im Bezirk Uster und wie lauten ihre BFS-Nummern?».</use_case> <important_notes>`bfs_commune_number` ist der amtliche Join-Schlüssel zu BFS-Statistikdaten (swiss-statistics-mcp). `canton` akzeptiert Kürzel ('ZH') oder BFS-Nummer ('1') — die Kürzel→Schlüssel-Auflösung erfolgt serverseitig, weil der Pfad sonst still eine leere Liste liefert. Gemeindelisten sind vollständig (interne Pagination), nicht auf 10 Einträge gekürzt.</important_notes>

输入参数params必填

swisstopo_search_address

Adressen/Orte per Volltext suchen

Volltextsuche über Schweizer Strassen und Ortschaften (OpenPLZ FullTextSearch). <use_case>Findet Strassen und Orte zu einem freien Suchbegriff und liefert je Treffer Gemeinde und BFS-Nummer, wenn vorhanden.</use_case> <important_notes>Quelle OpenPLZ (BFS/swisstopo OGD). Ergebnisse sind paginiert (max. 50/Abfrage); die Gesamttrefferzahl wird ausgewiesen. Für die exakte PLZ→Gemeinde-Auflösung ist swisstopo_lookup_postal_code präziser.</important_notes>

输入参数params必填

版本历史

3
  1. v0.5.1最新Oct 31, 2026
  2. v0.5.0Sep 26, 2026
  3. v0.4.1Sep 16, 2026

评分

已满足适用检查项的 90%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分
  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分