mcpField
S
swisstopo-mcp

swisstopo-mcp

本地服务
github.com地图与出行125 周前更新

关于 swisstopo-mcp

swisstopo-mcp 是面向 AI 助手的 MCP 服务,可访问瑞士联邦及相关地理数据,涵盖地图查询、地址与海拔服务、地籍信息、数据集和行政区查询,且无需 API 密钥。

主要功能

  1. 01查询 500 多个地图图层
  2. 02地址地理编码与坐标反查
  3. 03获取海拔和海拔剖面
  4. 04搜索可下载地理数据并生成地图链接
  5. 05查询地籍限制和行政区信息

适用场景

  1. 01查找瑞士地址的坐标
  2. 02检查路线沿线海拔
  3. 03识别地点的地图要素
  4. 04查询行政区 BFS 编号或地块限制

MCP 相关

通过 stdio 或 Streamable HTTP 向 MCP 客户端提供 20 个瑞士地理数据工具。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"malkreide-swisstopo": {
"command": "uvx",
"args": [
"swisstopo-mcp"
]
}
}
}
启动命令uvx swisstopo-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypiswisstopo-mcp0.5.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

🇨🇭 属于瑞士公共数据 MCP 组合

swisstopo-mcp

版本 许可证:MIT Python 3.11+ MCP 无需身份验证 持续集成

用于瑞士联邦地理数据的 MCP 服务——通过 Swisstopo API 提供地图、高程、地理编码、地籍摘录和可下载数据集

🇩🇪 德语版


概览

swisstopo-mcp 通过 20 个工具,让 AI 助手无需身份验证即可访问瑞士官方地理数据基础设施:

来源数据API
Swisstopo REST API500 多个地理数据图层(建筑物、边界、土地利用)REST/JSON
地理编码官方地址、地名、邮政编码REST/JSON
高程服务海拔高度、高程剖面REST/JSON
STAC 目录正射影像、高程模型、3D 建筑物STAC 0.9
WMTS国家地图、航拍影像、分区地图URL 构建器
OEREB 地籍公法限制、地块REST/JSON(州级)
geodienste.ch州际基础地理数据(土地测量、污染场地、危险地图等)OGC API Features / WMS / WFS
OpenStreetMap兴趣点(学校、游乐场、药房等)Overpass API(ODbL)
OpenPLZ API行政地址层级:邮政编码 → 市镇(BFS 编号)→ 区 → 州REST/JSON(BFS + swisstopo OGD)

示例核心查询: “乌斯特区有哪些市镇?它们的 BFS 编号是什么,以便与 BFS 统计数据进行关联?” (BFS 市镇编号是与 swiss-statistics-mcp 和 zurich-opendata-mcp 关联时使用的官方键——正是它让地理数据封装器成为市镇级的语义连接器。) → 查看面向不同受众的更多用例 →

演示

演示:Claude 使用 swisstopo_find_commune 和 swisstopo_lookup_postal_code


功能

  • 🗺️ 20 个工具(REST、地理编码、高程、STAC、WMTS、OEREB、geodienste.ch、OpenStreetMap/Overpass、OpenPLZ)
  • 🏛️ 通过 OpenPLZ 解析行政地址层级(邮政编码 → 市镇/BFS 编号 → 区 → 州)
  • 🔍 对瑞士地址进行地理编码,并对坐标进行反向地理编码
  • 🏔️ 查询高程并计算高程剖面
  • 📦 查找并下载地理数据集(正射影像、3D 建筑物、历史地图)
  • 🏗️ 识别 500 多个 Swisstopo 图层中坐标对应的地图要素
  • 🔗 生成可分享的 map.geo.admin.ch 链接
  • 📋 查询地籍房产 ID(EGRID)并获取 OEREB 摘录
  • 🔓 所有工具均无需 API 密钥(OEREB 摘录需要使用受支持的州)
  • ☁️ 双传输方式——stdio(Claude Desktop)+ Streamable HTTP(云端)

前提条件

  • Python 3.11+
  • uv(推荐)或 pip

安装

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

或使用 uvx(无需永久安装):

uvx swisstopo-mcp

快速开始

# stdio (for Claude Desktop)
python -m swisstopo_mcp.server

# Streamable HTTP (port 8000)
python -m swisstopo_mcp.server --http --port 8000

立即在 Claude Desktop 中试用:

“苏黎世的 Bahnhofstrasse 1 在哪里?请给我坐标。” “于特利贝格山顶的海拔是多少?” “坐标 2683500, 1247500(LV95)处有哪些建筑物?”


配置

Claude Desktop

编辑 ~/Library/Application Support/Claude/claude_desktop_config.json(macOS)或 %APPDATA%\Claude\claude_desktop_config.json(Windows):

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

或者使用 uvx:

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

配置文件位置:

  • macOS:~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows:%APPDATA%\Claude\claude_desktop_config.json
云端部署(用于浏览器访问的 SSE)

通过浏览器中的 claude.ai 使用(例如在没有本地软件的受管工作站上):

Render.com(推荐):

  1. 将仓库推送到 GitHub 或从中派生仓库
  2. 在 render.com 上:新建 Web 服务 -> 连接 GitHub 仓库
  3. 设置启动命令:python -m swisstopo_mcp.server --http --port 8000
  4. 在 claude.ai 的 Settings -> MCP Servers 中,添加:https://your-app.onrender.com/sse

可用工具

REST API(图层与要素查询)
工具说明
swisstopo_map_query国家地图目录(api3.geo.admin.ch)。每次调用执行一个 operation —— 见下方五种操作
swisstopo_zoning_at坐标处的协调统一建筑分区 —— 单次调用,无需查询图层(不具法律约束力)
swisstopo_municipality_at坐标处的市镇、州以及官方 BFS 编号
swisstopo_map_query 操作
operation回答的问题必需参数
search_layers某个关键词有哪些图层?(目录中有 500 多个)query
layer_info此图层有哪些字段可供查询?图例是什么?layer
features_at_point此坐标处有什么?layers + 一个点(lat/lon 或 easting/northing)
features_by_attribute哪些要素具有此值?(例如按 EGID 查询建筑物)layer、search_field、search_text
feature_by_id提供此要素的完整信息,包括几何数据layer、feature_id

属于其他操作的参数会被拒绝,而非忽略——错误信息会列出所选操作接受的参数。静默丢弃放错位置的 search_field,会针对一个没人问过的问题返回看似合理的答案;整个封装设计正是为了避免这种失效模式。

地理编码
工具说明
swisstopo_geocode将瑞士地址、地名或邮政编码转换为坐标
swisstopo_reverse_geocode查找给定坐标附近的地址
高程服务
工具说明
swisstopo_get_height获取坐标处的海拔高度(米)
swisstopo_elevation_profile计算沿线的高程剖面
swisstopo_convert_coordinates通过 swisstopo REFRAME 服务进行官方 WGS84 ↔ LV95 坐标转换
STAC 目录(地理数据下载)
工具说明
swisstopo_search_geodata在 STAC 目录中搜索可下载的地理数据集
swisstopo_get_collection获取 STAC 集合的详情和下载链接
WMTS(地图 URL)
工具说明
swisstopo_map_url生成供浏览器显示的 map.geo.admin.ch URL
OEREB 地籍
工具说明
swisstopo_get_egrid根据坐标解析地籍地块 ID(EGRID)
swisstopo_get_oereb_extract获取地块的公法土地使用限制信息(OEREB)
综合地理数据门面

在 25 个工具的预算内整合多个地图/图层来源(参见 docs/geodaten-erweiterung-phase1.md):

工具描述
swisstopo_list_available_layers查找 swisstopo_query_geodata 的图层键(strassenverzeichnis、oereb-verfuegbarkeit、geodienste:<topic>:<canton>);仅筛选免合约的 geodienste 数据集
swisstopo_query_geodata按 point / bbox / commune 查询所选图层——官方街道目录、州际 geodienste.ch 数据(OGC API Features)或 ÖREB 可用性
swisstopo_query_osm_features通过 Overpass 查询某点周围的 OpenStreetMap 兴趣点(学校、游乐场、药房等)——独立数据源,采用 ODbL(© OpenStreetMap 贡献者)
行政地址层级(OpenPLZ)

官方地址层级:PLZ → commune → district → canton,由 OpenPLZ API 提供(数据:BFS 市镇目录 + swisstopo 街道目录,瑞士 OGD——与上述 swisstopo 地理数据使用不同的数据源和许可证)。每个包含市镇信息的响应都会将 bfs_commune_number 作为具名顶层字段提供:这是连接 BFS 统计数据(swiss-statistics-mcp)和 zurich-opendata-mcp 的官方连接键。

工具描述
swisstopo_lookup_postal_code查询瑞士邮政编码 → 地区、市镇(+BFS 编号)、区、州
swisstopo_find_commune双向查询市镇(name ↔ bfs_number),或列出某个 canton / district 的所有市镇。接受州缩写(ZH)或键(1);由服务端解析
swisstopo_search_address对瑞士街道和地区进行全文搜索,每条结果均返回市镇和 BFS 编号
示例用例
查询工具
“苏黎世 Bahnhofstrasse 1 在哪里?”swisstopo_geocode
“乌特利贝格山顶的海拔是多少?”swisstopo_get_height
“坐标 2683500、1247500 处有哪些建筑?”swisstopo_map_query(operation='features_at_point')
“查找可供下载的正射影像数据集”swisstopo_search_geodata
“显示一张缩放级别为 10 的伯尔尼地图”swisstopo_map_url
“Musterstrasse 5 地块有哪些限制?”swisstopo_oereb_at
“Bederstrasse 109, 8002 Zürich 周围 500 m 内有哪些学校,哪些街道通往那里?”swisstopo_query_osm_features + swisstopo_query_geodata(strassenverzeichnis)
“ZH 州有哪些免费的污染场地数据?”swisstopo_list_available_layers + swisstopo_query_geodata(geodienste:kataster_belasteter_standorte:ZH)
“乌斯特区有哪些市镇,它们的 BFS 编号是什么?”swisstopo_find_commune(district=109)
“邮政编码 8001 属于哪个市镇和州?”swisstopo_lookup_postal_code
“温特图尔的 BFS 编号是多少(用于关联 BFS 统计数据)?”swisstopo_find_commune(name=Winterthur)

架构

┌─────────────────┐     ┌──────────────────────────────┐     ┌──────────────────────────┐
│   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)│
                        └──────────────────────────────┘     └──────────────────────────┘

项目结构

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.md 中。

阶段

此服务处于 第 2.5 阶段 — swiss-geodata-mcp 整合 (阶段状态以 docs/roadmap.md 为唯一依据)。

属性状态
读取工具24 个,均为 readOnlyHint: true / destructiveHint: false
写入工具无 — 第 3 阶段,暂无计划
传输方式stdio(默认)和 Streamable-HTTP
ISDS 分类docs/isds-dsg.md — 低保护要求
DSG 处理记录未维护,原因见 docs/isds-dsg.md §5
最近一次审计audits/2026-07-27T162602-Z-swisstopo-mcp/

推进阶段的条件:完成该阶段路线图中的所有事项,重新运行审计且没有未解决的 critical 问题,并添加注明新阶段的 CHANGELOG 条目。第 3 阶段(写入工具)还要求重新进行 Lethal-Trifecta 评估,并在开始任何实现前进行安全审查。

工具预算与聚合

当前有 20 个工具,低于自行设定的 25 个预算。检查理想值为 ≤12,因此仍需给出理由,而不能只报数字。按工具组划分:

五个 api3 工具已合并(0.4.1,破坏性变更)。search_layers、 layer_info、identify_features、find_features 和 get_feature 现在是 swisstopo_map_query 上的 operation 值。它们原本是检查所指的典型 “一工具对应一个 REST 端点”映射,现在已不再是独立工具。

之前的版本在这里提出过相反的论点。这个论点值得保留下来,因为它并非 错误,而是被其他考量压过了:合并会把选择从挑选工具转移到浏览架构中, 而模型得到的帮助会更少,因为模型实际读取的是工具描述。以下三点是直接 解决这个问题,而不是寄希望于它无关紧要:

  • 操作名称描述的是问题,而非端点。 identify 和 find 是 ESRI 术语——它们表示调用哪个 MapServer 路由,而不是用户想问什么;没有 ArcGIS 经验的人无法分辨二者。仅凭操作列表就能正确选择 features_at_point 和 features_by_attribute。
  • 参数放错会报错,而非被静默丢弃。 使用五个工具时,架构本身就不 可能出现错误配对。使用一个工具时则不然,因此需要通过验证来确保: 每个操作都会声明其接受的字段,其他字段会被拒绝,并显示可选项。
  • ARCH-003 中的 note 提示仍会指出下一步,只是现在指向操作而非 工具名称——属性搜索结果为空时,会提示使用 operation='layer_info' 查看有效字段名称,其他情况也同理。

可观测性是另一个代价,但我们并未付出:每个操作都保留各自的日志和跟踪 标签(swisstopo_map_query:features_at_point),因此合并后仍可按操作 统计耗时和错误率。

保持独立的两组工具,均由审计点名:

  • geocode + reverse_geocode 确实调用同一个 SearchServer 端点,因此在 API 维度上,这两者都是 1:1 映射。它们在影响工具选择的维度上保持独立: “地址 → 坐标”和“坐标 → 地址”是不同的问题,输入类型也不同;若将它们 合并为一个带模式的工具,模型就必须选择一种变体,而非一个工具。共享 端点只是上游实现细节。
  • search_geodata → get_collection 是一组真正的 STAC 搜索 → 详情工具; 见下文。

命名歧义已解决。审计没有提出这一点,但本节上一版记录了要在“下一次 破坏性版本”中解决——现在就是这个版本。swisstopo_search_layers 和 swisstopo_list_available_layers 都提到了“图层”,但实际对应的是不同的 目录。前者现在是 swisstopo_map_query,并使用 operation='search_layers',这样国家目录就体现在工具名称中,而 list_available_layers 则明确表示整合后的统一接口。 搜索 → 详情配对。 search_geodata → get_collection 是一组真正的配对:STAC 集合元数据很大,而调用方通常只想查看众多搜索结果中的某一个。get_egrid → get_oereb_extract 也属于这种模式,但现已合并:swisstopo_oereb_at 一次调用即可回答实际问题,并在内部解析 EGRID,因为 EGRID 是上游标识符,而非调用方主动查询的内容。需要直接获取地块 ID 的调用方仍可使用 get_egrid。

已有真正的聚合。 query_geodata 通过一个工具统一对接三个来源;zoning_at 和 municipality_at 则各自合并了此前需要两次调用的发现链。

添加下一个来源时,要么增加预算,要么进行合并。合并了五个 api3 工具后,显然已没有可合并且能腾出空间的对象,因此下一次扩展接口时,必须认真讨论上限,而不能再将清理工作推迟。tests/test_tool_namespace.py::TestToolBudget 会强制进行这项讨论:提高预算意味着要修改该处的数值,以及两份 README 中的数值。

数据来源和许可

每条响应都包含 source 和 license。ARE 与 swisstopo 属于不同的联邦机构,因此其许可会单独声明,而非沿用 swisstopo 的许可。

来源提供方许可
swisstopo / geo.admin.ch大多数工具瑞士 OGD(opendata.swiss)
swisstopo REFRAME(geodesy.geo.admin.ch)swisstopo_convert_coordinates瑞士 OGD(opendata.swiss)
swissBOUNDARIES3D(swisstopo)swisstopo_municipality_at瑞士 OGD(opendata.swiss)
ch.are.bauzonen(ARE)swisstopo_zoning_at瑞士 OGD — Bundesamt für Raumentwicklung ARE
州级 ÖREB 地籍swisstopo_get_egrid、swisstopo_get_oereb_extract、swisstopo_oereb_at、swisstopo_query_geodata州级 ÖREB 条款
geodienste.ch(各州)swisstopo_query_geodata可免费使用 — 需注明出处
OpenStreetMap(Overpass)swisstopo_query_osm_featuresODbL — © OpenStreetMap 贡献者
OpenPLZ(BFS + swisstopo)swisstopo_lookup_postal_code、swisstopo_find_commune、swisstopo_search_address可免费使用 — 需注明出处

ch.are.bauzonen 是联邦层面的综合数据,用于跨州比较,不具有法律约束力——只有州级或市镇级的 Nutzungsplanung 才具有约束力。每条 swisstopo_zoning_at 结果记录都会注明这一点。

项目结构

工具模块直接平铺在 src/swisstopo_mcp/ 下,而不放在 tools/ 子包中。每个模块都对应一个上游 API 系列——rest_api.py → api3 MapServer、stac.py → STAC、oereb.py → 州级 ÖREB、openplz.py → OpenPLZ、overpass.py → OSM、coords.py → REFRAME——这正是本服务代码实际存在差异的维度。增加 tools/ 这一层只会多一个目录,不会带来新的区分。

server.py 只包含工具注册;每个工具的实现都位于其领域模块中。是否进一步拆分,是可读性问题,而非结构问题。

Lethal Trifecta 评估
能力状态理由
访问私有数据❌ 否仅提供公共开放数据(联邦/州级地理数据)
接触不可信内容⚠️ 有限仅从固定的可信 geo.admin / OEREB 主机允许列表中读取数据
外部通信(写入/发送)❌ 否只读;没有邮件/webhook/写入工具

三重威胁评分:最多 1 项(共 3 项)——设计上安全。

出站流量

出站请求仅限于代码层明确列出的允许列表,并且已禁用重定向——参见 docs/network-egress.md。

容器部署

对于容器化 HTTP 部署,项目提供了加固的 Dockerfile 和 Kubernetes 清单(非 root 用户、只读根文件系统、移除功能权限、出站 NetworkPolicy)——参见 docs/deployment.md。

MCP 协议版本

服务原生支持 2026-07-28,并且仍可使用所有较早的版本。使用哪个版本取决于连接方式,而非配置——同一个进程可同时服务这两个时代。

现代时代握手时代
版本2026-07-282024-11-05 … 2025-11-25
初始交互无——每个请求都独立处理initialize + notifications/initialized
协议版本所在位置params._meta 和 MCP-Protocol-Version 标头initialize 参数
会话无Mcp-Session-Id,通过 DELETE /mcp 关闭
目录server/discoverinitialize 结果
服务身份每个响应 _meta 中的 io.modelcontextprotocol/serverInfo 标记initialize 结果中的 serverInfo
此版本中已移除initialize、ping、logging/setLevel、resources/subscribe—

2026-07-28 请求是一个独立的 POST 请求,发送到 /mcp:

POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: swisstopo_map_url
Content-Type: application/json
Accept: 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":{}
  }}}

MCP-Protocol-Version 标头负责将请求路由到现代路径;如果没有此标头,相同的请求正文会进入握手传输路径。Mcp-Method 和 Mcp-Name 必须与请求正文一致,否则请求会以 -32020 被拒绝。这三个标头都在 CORS 允许列表中,因此浏览器客户端的预检请求可以通过。

实际影响如下:

  • 现有客户端不受影响。 请求 2025-06-18 的客户端仍会获得 2025-06-18;通过 initialize 请求 2026-07-28 的客户端会获得握手时代的最高版本 2025-11-25。此结论经过实际验证,而非假设——tests/test_protocol_version.py。
  • 两个时代都提供相同的 20 个工具。 不会根据版本限制任何工具(tests/test_modern_protocol.py)。
  • 只有握手时代需要粘性会话。 现代请求不携带会话,因此可由任意副本处理——参见 docs/deployment.md。

两个版本上限都无法由项目作者设置:SDK 会协商握手时代的版本并路由现代时代的请求,而 pyproject.toml 将 mcp 固定在 2.x 主版本,防止更新时悄然改变任一上限。tests/test_protocol_version.py 会固定这两个上限;如果 Dependabot 更新导致其中任一个发生变化,测试就会失败。

更新策略

  • SDK 更新会先在功能分支上进行测试,再合并。
  • 任一协议版本上限发生变化时,都会在 CHANGELOG.md 的 ### Changed 下记录旧版本和新版本。
  • 会破坏现有客户端的协议变更会触发主版本发布。
会话与身份验证

此服务在设计上不进行身份验证——它只提供公开的开放数据。

只有握手时代存在会话。在该时代,会话 ID 完全由 MCP SDK 管理;由于没有按用户区分的状态,因此无需将会话绑定到特定用户。空闲会话会在 SWISSTOPO_SESSION_IDLE_TIMEOUT 秒后清理(默认值为 1800),这样客户端断开连接却未发送 DELETE /mcp 时,不会让会话一直泄漏到进程生命周期结束。2026-07-28 请求不会创建会话,因此不适用超时或清理机制。

如果将来引入经过身份验证的部署,会话 ID 必须绑定到已验证的用户身份(审计发现 SEC-009)。

错误处理
  • 执行错误(上游故障、无效值)会作为 ToolResponse 返回,其中 is_error: true,并附带面向用户的 summary;意外异常文本会被屏蔽,并改为记录到 stderr。不会转发任何上游响应正文或内部配置:Overpass 错误页面会根据固定的特征表进行分类,出站请求遭拒时会返回固定消息,而非允许列表(OBS-002)。
  • 协议错误(未知工具、参数格式错误或无效)会由 SDK 作为工具结果返回,并设置协议 isError 标志——而不是 JSON-RPC 错误对象。已通过运行时探测对照 mcp 1.28.1 验证;本节早期版本曾声称错误码为 -32602,这是错误的。输入验证发生在 Pydantic 边界处(SEC-018)。
  • 两个标志保持一致。 已处理的执行错误会同时设置协议 isError 标志和负载字段 is_error,因此客户端可以依据其中任一标志进行分支处理。错误路径会保留整个封装,包括 source 和 license(OBS-001)。

MCP 基元

工具是主要接口,几乎也是全部接口:每个结果都是实时的、带参数的 API 查询,而非静态的可寻址文档。

一个资源——swisstopo://catalogue/layers——提供门面图层目录。这是此处唯一表现得像文档的内容:结果确定、幂等,且已通过 provenance: "cached" 提供。swisstopo_list_available_layers 仍可用于筛选查询;而资源则适用于希望直接访问整个目录的客户端。

两个提示词编码了下方的工作流,包括点位问题的优先级规则。该规则也写在工具描述和服务端指令中,但提示词是模型以指导内容而非 24 条描述之一的形式读取该规则的唯一位置(审计 ARCH-007/ARCH-008):

提示词参数
swisstopo_feature_lookuport、was
swisstopo_geodata_downloadthema
工具工作流

大多数工具一次调用即可返回信息完整的结果。两个领域采用简短且有文档说明的发现链(每个工具的描述都会说明下一步):

  • 要素查询: 四个步骤都通过 swisstopo_map_query 完成,只是 operation 不同:search_layers(查找图层 ID)→ layer_info(查看可查询字段)→ features_at_point / features_by_attribute → feature_by_id(获取完整详情)。
  • 地籍: swisstopo_geocode → swisstopo_oereb_at(一次调用:坐标 → EGRID → 提取内容)。仅当需要地块 ID 本身时,才使用 swisstopo_get_egrid → swisstopo_get_oereb_extract。
  • 下载: swisstopo_search_geodata → swisstopo_get_collection。

响应格式

每个工具都会返回结构化的 ToolResponse(FastMCP 会将其作为带有输出架构的结构化内容,以及一个 JSON 文本块发出):

字段含义
summary便于阅读的 Markdown 摘要
results机器可读的结构化记录
countresults 的数量
match_typeexact / fuzzy / none(搜索类工具)
source / license数据归属信息(OGD-CH、CC/OGD 条款)
provenance / retrieved_at数据的获取方式和时间
is_error已处理错误时为 true

已知限制

  • OEREB 工具需要提供州参数;并非所有州都公开相同格式的 API
  • STAC 目录使用 Swisstopo 的 v0.9 端点;部分集合可能缺少完整元数据
  • 地理编码仅覆盖瑞士地址(不包括列支敦士登)
  • 速率限制由 Swisstopo 执行;高频使用可能会受到限流
已知发现 — OpenPLZ 实时探测(2026-07-20)

在实现前对 OpenPLZ 端点进行了实时探测。工具中纳入了以下发现:

端点 / 行为结果处理方式
/Cantons200,26 条记录,key = BFS 州编号(ZH = 1)从此列表解析州缩写
/Cantons/{key}/Districts|Communes200路径参数是数字 key
/Cantons/ZH/Districts(缩写)200 + [] ——不是错误服务端将 ZH→1;空结果会附上说明
/Localities?postalCode=8001200,commune.key = 261(BFS 苏黎世)在顶层提供 bfs_commune_number
/Localities?postalCode=9999(未知)200 + []作为说明报告——空结果 ≠ 不存在
列表端点分页默认 pageSize=10,硬性上限为 50(100 → HTTP 400)工具通过 x-total-count 遍历各页
查询中的原始变音符(?name=Zürich)HTTP 400httpx 会自动对 URL 参数编码
historicalCode 字段对市镇而言 ≠ key(历史目录 ID)不使用;关联键是当前的 key
批量数据下载OpenPLZ 未提供(只有 /swagger)架构 A(仅实时 API)——足以满足查询连接器的需求

**一句话说明缩写与 key 的陷阱:**OpenPLZ 返回空列表,几乎从不能证明某项内容不存在——通常意味着路径参数错误(需要数字 key,却传入了缩写)。工具会在服务端解析缩写,并为每个空结果附上说明。


测试

# Unit tests (no network required)
pytest tests/ -m "not live"

# Integration tests (live API calls)
pytest tests/ -m "live"

更新日志

参见 CHANGELOG.md


贡献

参见 CONTRIBUTING.md


安全

只读,无需身份验证,仅使用公开地理数据。有关安全状况以及如何报告漏洞,请参阅 SECURITY.md (Deutsch)。


许可证

MIT License——参见 LICENSE

数据由 swisstopo 根据 Open Government Data 条款提供。


作者

Hayal Oezkan · malkreide


致谢与相关项目

安装

通过 uv 的 uvx 运行——无需克隆或手动安装。添加到 MCP 客户端配置中(Claude Desktop、Cursor 和 Windsurf 使用 mcpServers;VS Code 在 .vscode/mcp.json 中使用顶层 servers 键):

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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。