Zurich Open Data MCP Server
Permite a Claude, ChatGPT y otros asistentes de IA compatibles con MCP consultar directamente más de 900 conjuntos de datos, geodatos, actas parlamentarias, datos turísticos, datos enlazados e información ambiental y de movilidad en tiempo real de la Ciudad de Zúrich. 20 herramientas, 6 recursos, 6 API.
Documentación
🇨🇭 Parte del Portafolio Suizo de MCP de Datos Públicos
🏙️ Servidor MCP de Datos Abiertos de Zúrich
🌐 Inglés | Deutsch
Un servidor MCP (Protocolo de Contexto de Modelo) que proporciona acceso impulsado por IA a Datos Abiertos de la Ciudad de Zúrich, Suiza.
Permite a Claude, ChatGPT y otros asistentes de IA compatibles con MCP consultar directamente más de 900 conjuntos de datos, geodatos, actas parlamentarias, resoluciones del concejo, datos turísticos, datos enlazados e información ambiental y de movilidad en tiempo real de la Ciudad de Zúrich. 23 herramientas (+3 alias obsoletos), 5 recursos, 6 API.
Demostración
✨ Características
Datos Abiertos CKAN (data.stadt-zuerich.ch)
zurich_search_datasets– Búsqueda de texto completo con sintaxis Solr en más de 900 conjuntos de datoszurich_get_dataset– Metadatos completos y URL de descarga para un conjunto de datoszurich_datastore_query– Consulta directa de datos tabulares (filtros, ordenación)zurich_datastore_sql– Consultas SQL en el DataStorezurich_list_categories– Explorar 19 categorías temáticaszurich_list_tags– Búsqueda temática basada en etiquetas
Datos Ambientales en Tiempo Real
zurich_weather_live– 🌤️ Clima actual (temperatura, humedad, presión, lluvia) de 4 estaciones UGZzurich_air_quality– 🌬️ Calidad del aire en vivo (NO₂, O₃, PM10, PM2.5) con umbrales de la OMSzurich_water_weather– 🌊 Datos del Lago de Zúrich (temperatura del agua, nivel, viento) cada 10 minutos
Datos de Movilidad en Tiempo Real
zurich_pedestrian_traffic– 🚶 Conteo de peatones en Bahnhofstrasse (3 ubicaciones, cada hora)zurich_vbz_passengers– 🚊 Afluencia de pasajeros del transporte público VBZ (más de 800,000 registros, todas las líneas/paradas)zurich_parking_live– 🅿️ Ocupación en tiempo real de 36 estacionamientos (ParkenDD)
Geoportal (Geodatos WFS)
zurich_geo_layers– 📍 Listar 14 capas de geodatos disponibleszurich_geo_features– 📍 Obtener características GeoJSON (escuelas, distritos, parques infantiles, datos climáticos, etc.)
Parlamento de la Ciudad (API Paris)
zurich_parliament_search– 🏛️ Buscar actas parlamentarias (interpelaciones, mociones, postulados)zurich_parliament_members– 🏛️ Buscar miembros del concejo (partido, comisiones, mandatos)
Turismo de Zúrich
zurich_tourism– 🏨 Atracciones, restaurantes, hoteles, eventos (datos Schema.org, 4 idiomas)
Datos Enlazados (SPARQL)
zurich_sparql– 📊 Consultas SPARQL en el endpoint de datos estadísticos enlazados (el endpoint aún no está productivo — la herramienta no está registrada por defecto; actívela con la variable de entornoZURICH_OPENDATA_ENABLE_SPARQL=1)
Stadtratsbeschlüsse (Resoluciones del Concejo)
zurich_strb_search– 📜 Búsqueda de texto completo de resoluciones públicas del concejo (título, departamento, rango de fechas)zurich_strb_by_department– 📜 Listar todas las resoluciones de un departamento (p. ej.SSD,FD,PRD)zurich_strb_detail– 📜 Resolución individual por númeroNNNN/YYYY
(Los nombres anteriores search_stadtratsbeschluesse, get_beschluesse_by_departement y get_stadtratsbeschluss_detail siguen disponibles como alias obsoletos hasta la próxima versión principal.)
Herramientas de Análisis
zurich_analyze_datasets– Análisis integral: relevancia, actualidad, estructura de datoszurich_catalog_stats– Resumen del catálogo con estadísticaszurich_find_school_data– Búsqueda curada para conjuntos de datos relacionados con educación
Recursos MCP
zurich://dataset/{name}– Metadatos de conjuntos de datoszurich://category/{group_id}– Detalles de categoríaszurich://parking– Datos actuales de estacionamientozurich://geo/{layer_id}– Geodatos GeoJSON (14 capas)zurich://tourism/categories– Categorías de turismo
🚀 Instalación
Requisitos previos
- Python 3.11+
- pip o uv
mcp[cli]2.x — se instala automáticamente; el servidor usa la API 2.x (mcp.server.mcpserver) y no puede ejecutarse enmcp1.x
Use 0.6.0 o más reciente. La versión
0.5.1declarómcp[cli]>=1.28.1sin límite superior. Una vez quemcp2.0.0 eliminómcp.server.fastmcp, cada instalación nueva de0.5.1se resolvió a 2.0.0 y falló en la importación conModuleNotFoundError.0.6.0se ejecuta en la API 2.x y fija>=2.0.0,<3. Si está fijado a0.5.1, actualice — no queda ninguna configuración funcional de esa versión.
Instalar
# Clone
git clone https://github.com/malkreide/zurich-opendata-mcp.git
cd zurich-opendata-mcp
# Install
pip install -e .
# Or with uv
uv pip install -e .
⚙️ Configuración
Claude Desktop
Edite ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"zurich-opendata": {
"command": "python",
"args": ["-m", "zurich_opendata_mcp.server"],
"env": {}
}
}
}
Alternativamente, usando el comando instalado:
{
"mcpServers": {
"zurich-opendata": {
"command": "zurich-opendata-mcp"
}
}
}
Claude Code (CLI)
claude mcp add zurich-opendata -- python -m zurich_opendata_mcp.server
Cursor / VS Code
Agregue a .vscode/settings.json:
{
"mcpServers": {
"zurich-opendata": {
"command": "python",
"args": ["-m", "zurich_opendata_mcp.server"]
}
}
}
💬 Consultas de Ejemplo
Una vez configurado, puede preguntarle a Claude:
Datos Abiertos
- "¿Qué conjuntos de datos hay disponibles sobre escuelas en Zúrich?"
- "Muéstrame las vacaciones escolares para escuelas públicas"
- "Analiza los geodatos disponibles"
Datos en Tiempo Real
- "¿Cuál es la temperatura actual en Zúrich?" →
zurich_weather_live - "¿Cómo está la calidad del aire hoy?" →
zurich_air_quality - "¿Cuál es la temperatura del agua en el Lago de Zúrich?" →
zurich_water_weather - "¿Cuántos espacios de estacionamiento están libres ahora mismo?" →
zurich_parking_live - "¿Cuántas personas hay en Bahnhofstrasse ahora mismo?" →
zurich_pedestrian_traffic
Geodatos
- "Muéstrame todas las instalaciones escolares en Zúrich como GeoJSON" →
zurich_geo_features - "¿Qué capas de geodatos están disponibles?" →
zurich_geo_layers - "¿Dónde están los parques infantiles en Zúrich?"
Parlamento de la Ciudad
- "¿Qué mociones parlamentarias sobre escuelas se presentaron?" →
zurich_parliament_search - "¿Qué miembros del concejo pertenecen al partido SP?" →
zurich_parliament_members
Resoluciones del Concejo (Stadtratsbeschlüsse)
- "Encuentra resoluciones del concejo sobre Volksschule de 2025" →
zurich_strb_search - "Lista todas las resoluciones SSD en 2025" →
zurich_strb_by_department - "Muestra la resolución del concejo 1203/2025" →
zurich_strb_detail
Turismo
- "¿Qué restaurantes recomienda Turismo de Zúrich?" →
zurich_tourism
🔗 Fuentes de Datos
| API | Endpoint | Datos |
|---|---|---|
| CKAN | data.stadt-zuerich.ch/api/3/ | Más de 900 conjuntos de datos abiertos |
| Geoportal WFS | ogd.stadt-zuerich.ch/wfs/geoportal | 14 capas de geodatos (GeoJSON) |
| API Paris | gemeinderat-zuerich.ch/api | Actas parlamentarias y miembros |
| Turismo de Zúrich | zuerich.com/en/api/v2/data | Atracciones, restaurantes, hoteles |
| SPARQL | ld.stadt-zuerich.ch/query | Datos abiertos enlazados / estadísticas |
| ParkenDD | api.parkendd.de/Zuerich | Ocupación de estacionamientos en tiempo real |
📊 Categorías de Datos Disponibles
| Categoría | ID |
|---|---|
| Empleo | arbeit-und-erwerb |
| Mapas Base | basiskarten |
| Construcción y Vivienda | bauen-und-wohnen |
| Población | bevolkerung |
| Educación | bildung |
| Energía | energie |
| Finanzas | finanzen |
| Ocio | freizeit |
| Salud | gesundheit |
| Delincuencia | kriminalitat |
| Cultura | kultur |
| Movilidad | mobilitat |
| Política | politik |
| Precios | preise |
| Asuntos Sociales | soziales |
| Turismo | tourismus |
| Medio Ambiente | umwelt |
| Administración | verwaltung |
| Economía | volkswirtschaft |
📍 Capas Geo Disponibles
Fuente de verdad: GEOPORTAL_LAYERS en src/zurich_opendata_mcp/config.py.
| ID de Capa | Descripción |
|---|---|
schulanlagen | Instalaciones escolares (jardines de infancia, escuelas, cuidado extraescolar) |
schulkreise | Límites de distritos escolares (polígonos) |
schulwege | Cruces de rutas escolares y puntos de peligro |
stadtkreise | Límites de distritos de la ciudad (polígonos) |
spielplaetze | Parques infantiles públicos |
kreisbuero | Oficinas de distrito de la ciudad |
sammelstelle | Puntos de recolección de residuos |
sport | Instalaciones deportivas |
klimadaten | Datos climáticos (raster, temperaturas, islas de calor) |
lehrpfade | Senderos educativos |
stimmlokale | Centros de votación |
sozialzentrum | Centros sociales |
velopruefstrecken | Rutas de examen de bicicleta para escuelas |
familienberatung | Puntos de encuentro de asesoramiento familiar |
🏗️ Estructura del Proyecto
zurich-opendata-mcp/
├── src/zurich_opendata_mcp/
│ ├── __init__.py
│ ├── app.py # Shared FastMCP instance
│ ├── server.py # Console entry + back-compat re-exports
│ ├── config.py # Endpoints, layer maps, resource IDs
│ ├── http_client.py # Shared httpx client + CKAN wrapper
│ ├── formatters.py # CKAN→model mapping + Markdown rendering
│ ├── models.py # Pydantic structured-output models
│ ├── clients/ # API clients: paris, sparql, tourism, wfs
│ └── tools/ # @mcp.tool implementations:
│ # catalog, datastore, geo, parliament,
│ # realtime, sparql, strb, tourism,
│ # resources (zurich:// URIs)
├── tests/ # respx round-trip, unit and live-marked tests
├── audits/ # Code-audit reports
├── .github/workflows/ # ci.yml + publish.yml (Trusted Publisher)
├── pyproject.toml
├── README.md / README.de.md
├── CONTRIBUTING.md / .de.md
├── SECURITY.md / .de.md
├── CHANGELOG.md
├── CLAUDE.md # Project conventions for Claude
├── LICENSE
└── claude_desktop_config.json
🧪 Desarrollo
# Install dev dependencies
pip install -e ".[dev]"
# Unit + validation tests (no network)
pytest tests/ -m "not live"
# Live integration tests (against live APIs — opt-in)
pytest tests/ -m live
# Linting
ruff check src/ tests/
🌐 Transporte HTTP
Por defecto, el servidor habla MCP sobre stdio. --http sirve Streamable HTTP
en su lugar:
zurich-opendata-mcp --http --port 8000 # binds 127.0.0.1 (default)
zurich-opendata-mcp --http --host 0.0.0.0 --port 8000
| Opción | Significado | Predeterminado |
|---|---|---|
--http | Servir Streamable HTTP en lugar de stdio | (desactivado → stdio) |
--host | Dirección de enlace | 127.0.0.1 |
--port | Puerto de enlace (1–65535) | 8000 |
MCP_ALLOWED_HOSTS | Nombres separados por comas bajo los cuales este servidor es accesible, incluido el puerto (p. ej. zurich.example.ch:8000). Las solicitudes bajo cualquier otro Host reciben 421; el bucle local permanece permitido para que las comprobaciones de salud del contenedor sigan funcionando. | (sin establecer) |
El valor predeterminado de bucle local es deliberado. Enlazar 0.0.0.0 expone el servidor en
cada interfaz, a cualquiera que pueda alcanzar la máquina — no hay
autenticación delante de él.
Establezca MCP_ALLOWED_HOSTS siempre que enlace más allá del bucle local. Protege contra
el rebinding de DNS: una página en su red resuelve su propio nombre de host a la dirección de este
servidor y luego habla con él desde el navegador. Desde el punto de vista del navegador,
esa solicitud es de mismo origen, por lo que ninguna regla de origen la detiene — solo la
verificación de Host lo hace.
Si se deja sin establecer en un enlace que no sea de bucle local, la verificación permanece desactivada y se registra una
advertencia. Ese es el valor predeterminado correcto solo cuando algo delante del servidor
valida Host. Deliberadamente no se adivina: en 0.0.0.0 el nombre alcanzable
es incognoscible dentro del proceso, y una suposición incorrecta respondería al mismo
despliegue que se pretende proteger con 421 en cada solicitud.
Versión del Protocolo MCP
Este servidor habla dos eras de protocolo sobre el mismo endpoint. La primera solicitud del cliente en una conexión decide cuál se aplica; una reclamación posterior de la otra era es rechazada.
| Era | Revisión | Quién la alcanza |
|---|---|---|
Protocolo de enlace initialize | 2024-11-05 … 2025-11-25 | Lo que hablan los clientes actuales. El servidor responde con la revisión solicitada, o con el techo 2025-11-25 cuando la solicitud pide algo más nuevo. |
| Envoltura por solicitud | 2026-07-28 | Una solicitud que lleva la envoltura 2026-07-28 _meta abre una conexión moderna. |
Ambas revisiones están fijadas en
tests/test_protocol_version.py, y la
puerta las verifica dos veces: mide la revisión que un
mcp.Client en proceso realmente negocia contra este servidor (ambas eras, sobre un transporte
de memoria — sin ASGI, sin red), y lee las constantes del SDK para que un
aumento de Dependabot de mcp no pueda mover ninguna de las dos en silencio. La mitad medida es la
que soporta la carga: falla incluso cuando una era deja de servirse mientras su
constante permanece en su lugar.
Tenga en cuenta que el LATEST_PROTOCOL_VERSION del SDK es un alias para la era moderna,
no para la era del protocolo de enlace — fijarse solo contra él dejaría la era
que los clientes actuales realmente negocian libre de derivar.
Lo que este servidor hace de forma nativa en 2026-07-28
La revisión no es solo un número que el SDK alcanza. Tres de sus cambios piden algo al propio servidor, y este responde a los tres:
| Cambio de especificación | Qué hace este servidor |
|---|---|
SEP-2549 — ttlMs / cacheScope en los métodos de listado | tools/list, resources/list, resources/templates/list y server/discover llevan ttlMs 300000, cacheScope public. Sin ellos, el SDK responde "already stale, never share" para directorios fijados en la importación. resources/read deliberadamente no lleva ninguna pista: eso sería una promesa sobre el contenido, no sobre un directorio. |
SEP-2575 — sin initialize, por lo que la identidad viaja por resultado | El _meta de cada resultado lleva io.modelcontextprotocol/serverInfo con nombre, título, versión, descripción y websiteUrl. Un MCPServer construido sin version= sella una cadena vacía en cada respuesta, y el SDK nunca sustituye la suya propia. |
SEP-2575 — server/discover es el único lugar restante para instrucciones | El servidor incluye instructions; un llamador sin estado que nunca envía initialize aún aprende el orden catálogo → UUID de recurso → DataStore y cuánto tiempo es válida una lectura en tiempo real. |
El cambio menor #3 (tools/list DEBERÍA estar ordenado de forma determinista) se cumple
mediante el orden de inserción del gestor de herramientas, que sigue el orden de importación fijado en
server.py; una prueba compara el orden en el cable contra él.
Una propiedad es del SDK, no de este servidor: como se sirve subscriptions/listen,
el bloque de capacidades anuncia listChanged y
resources.subscribe. Los directorios de este servidor se fijan en la importación y
nunca emite una notificación de cambio. Eso se mide y se deja como está — la
capacidad dice que el método se sirve, lo cual es cierto.
Política de actualización. Cuando la compuerta falle, no edites la constante a ciegas: lee
el registro de cambios de la especificación entre las dos revisiones, verifica que el servidor aún se comporta correctamente,
luego mueve la constante, esta sección, README.de.md y
CHANGELOG.md juntos.
Seguridad y Límites
- Solo lectura: Todas las herramientas realizan únicamente solicitudes HTTP GET — no se escribe, modifica ni elimina ningún dato.
- Sin datos personales: Las APIs devuelven conjuntos de datos cívicos abiertos (ocupación de estacionamientos, lecturas meteorológicas, actas parlamentarias). Ninguna información de identificación personal (PII) es procesada o almacenada por este servidor.
- Límites de tasa: La búsqueda CKAN Solr y ParkenDD son APIs públicas sin límites de tasa documentados; usa los parámetros
rowsylimitde forma conservadora. El servidor aplica un tiempo de espera de 30s por solicitud; los errores transitorios del proveedor (fallos de conexión, HTTP 502/503/504) se reintentan una vez con una breve espera. - Frescura de los datos: Las herramientas en tiempo real (estacionamiento, clima, calidad del aire) reflejan la fuente original en el momento de la consulta. Los datos de medición nunca se almacenan en caché; solo la búsqueda del ID de recurso UGZ actual por año (clima/calidad del aire) se almacena en caché en proceso durante 24h.
- Términos de servicio: Los datos están sujetos a los ToS de cada fuente — data.stadt-zuerich.ch, ParkenDD, gemeinderat-zuerich.ch. Todos los datos de la Ciudad de Zúrich se publican bajo CC0 (Open by Default desde 2021).
- Sin garantías: Este servidor es un proyecto comunitario, no afiliado con la Ciudad de Zúrich ni con ninguno de los proveedores de APIs. La disponibilidad depende de las APIs originales.
🤝 Contribuciones
Las contribuciones son bienvenidas — consulta CONTRIBUTING.md (Deutsch).
🔒 Seguridad
Solo lectura, sin PII, sin autenticación, un conjunto fijo de endpoints de datos públicos. Consulta SECURITY.md (Deutsch) para la postura de seguridad completa y las decisiones de riesgo aceptado.
📜 Licencia
Licencia MIT — consulta LICENSE. Todos los datos utilizados se publican bajo licencias abiertas (CC0 / Open by Default desde 2021).
👤 Autor
Hayal Oezkan · malkreide
Impulsado por Model Context Protocol • 6 APIs • 23 Herramientas • 5 Recursos
Instalación
Ejecuta a través del uvx de uv — sin clonar ni instalación manual. Añádelo a la configuración de tu cliente MCP (mcpServers para Claude Desktop, Cursor y Windsurf; usa una clave servers de nivel superior para VS Code en .vscode/mcp.json):
{
"mcpServers": {
"zurich-opendata-mcp": {
"command": "uvx",
"args": [
"zurich-opendata-mcp"
]
}
}
}