cdmx-mcp
# cdmx-mcp
> **Model Context Protocol** para los datos abiertos de la Ciudad de México.
> Dale a Claude acceso directo a **crimen, 911, calidad del aire, ECOBICI y DENUE** — sin escribir una sola línea de código de ingesta.
[](https://github.com/devcsar/cdmx-mcp/actions/workflows/ci.yml)
[](https://www.python.org/)
[](./LICENSE)
[](https://modelcontextprotocol.io/)
```
"¿Cuáles son las 10 colonias con más delitos en Cuauhtémoc en 2025?"
↓ Claude llama crime_hotspots(year=2025, alcaldia="CUAUHTEMOC", top_n=10)
↓ cdmx-mcp traduce a SQL contra CKAN
↓ Claude te contesta con la tabla + análisis, en segundos
```
---
## 📋 Prerrequisitos
Necesitás **Python 3.10+** y **[uv](https://docs.astral.sh/uv/)** (gestor de paquetes moderno para Python — reemplaza a `pip + venv + pyenv` en un solo binario).
### Instalar `uv`
<details>
<summary><b>macOS / Linux</b></summary>
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
O con Homebrew (macOS):
```bash
brew install uv
```
</details>
<details>
<summary><b>Windows</b></summary>
En **PowerShell**:
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
O con **winget**:
```powershell
winget install --id=astral-sh.uv -e
```
O con **Scoop**:
```powershell
scoop install uv
```
</details>
Verificá que quedó instalado:
```bash
uv --version # → uv 0.5.x o similar
```
### Instalar Python (si no lo tenés)
`uv` puede descargar e instalar Python por vos — **no hace falta que lo instales manualmente**:
```bash
uv python install 3.12
```
Igual, si preferís instalarlo a mano:
- **macOS**: `brew install python@3.12` o desde <https://www.python.org/downloads/>
- **Windows**: `winget install Python.Python.3.12` o desde la Microsoft Store
- **Linux (Ubuntu/Debian)**: `sudo apt install python3.12 python3.12-venv`
- **Linux (Arch)**: `sudo pacman -S python`
Verificá:
```bash
python3 --version # → Python 3.10.x o superior
```
> 💡 Con `uv` no necesitás activar venvs ni pelear con versiones — `uv sync` lee `pyproject.toml` + `uv.lock` y arma todo solo.
---
## ⚡ Quickstart — 30 segundos
```bash
git clone https://github.com/devcsar/cdmx-mcp.git
cd cdmx-mcp
uv sync # instala Python + dependencias
uv run python tests/smoke_test.py # verifica: debe decir "smoke: OK"
```
Ya está instalado. Ahora conectalo a tu cliente preferido:
### Claude Code
```bash
claude mcp add cdmx -- uv --directory "$(pwd)" run cdmx-mcp
claude # dentro de la sesión: /mcp
```
O simplemente abrí este directorio con `claude` y detectará el [`.mcp.json`](./.mcp.json) automáticamente.
### Claude Desktop
Editá `claude_desktop_config.json` (Settings → Developer → Edit Config) y pegá:
```json
{
"mcpServers": {
"cdmx": {
"command": "uv",
"args": ["--directory", "/ruta/absoluta/a/cdmx-mcp", "run", "cdmx-mcp"]
}
}
}
```
Reiniciá Claude Desktop. Detalle completo por SO → [`QUICKSTART.md`](./QUICKSTART.md).
### Cowork
Pegá el mismo JSON en el editor de MCPs de Cowork, o apuntá al [`.mcp.json`](./.mcp.json) del repo.
---
## 🎯 Primer prompt de prueba
Copiá y pegá en Claude:
> Lista los 10 tipos de delito más frecuentes en la alcaldía Cuauhtémoc durante 2025. Después hacé una tabla markdown con emojis de barra.
Claude elige `crime_hotspots` automáticamente y te contesta con datos reales. Más prompts en **[`PROMPTS.md`](./PROMPTS.md)**.
---
## 📊 Qué cubre
| Fuente | Cubierta vía | Frescura |
|---|---|---|
| **FGJ** — Carpetas de investigación (delitos) | `datos.cdmx.gob.mx` (CKAN API) | mensual |
| **911 / LOCATEL** — llamadas de emergencia | `datos.cdmx.gob.mx` (CKAN API) | mensual |
| **SIMAT** — Calidad del aire | `datos.cdmx.gob.mx` (CKAN API) | horaria |
| **ECOBICI** — bicis/docks en tiempo real | GBFS (feed estándar) | 🟢 **en vivo** |
| **DENUE** (INEGI) — unidades económicas | API pública INEGI | trimestral |
> **Nota post-migración**: en abril 2026 el portal CDMX migró de OpenDataSoft a **CKAN 2.10**. Las consultas ahora pasan por `/api/3/action/datastore_search_sql` (PostgreSQL real). Los *slugs* de dataset se conservaron.
---
## 🛠 Tools expuestas (9 total)
**Genéricas** — funcionan con cualquier dataset del portal:
| Tool | Qué hace |
|---|---|
| `list_datasets(search?, limit)` | Buscar en el catálogo completo |
| `describe_dataset(dataset_id)` | Schema real (columnas, tipos, total de filas) |
| `query_records(dataset_id, where?, select?, order_by?, limit, offset)` | Consulta SQL vía `datastore_search_sql` |
| `aggregate(dataset_id, group_by, metric?, where?, limit)` | GROUP BY server-side |
**Recetas** — atajos para el top 5:
| Tool | Qué hace |
|---|---|
| `crime_hotspots(year, alcaldia?, category?, top_n)` | Top colonias/alcaldías por crimen |
| `ecobici_status(station_id? · near_lat+near_lng+radius_m)` | Bicis/docks libres en tiempo real |
| `air_quality_now(zone?, limit)` | Índice SIMAT más reciente |
| `denue_near(lat, lng, radius_m, keyword)` | Negocios cerca de un punto |
Más `cache_stats()` y 2 recursos (`cdmx://guide/fgj`, `cdmx://guide/top5`).
Shortcuts soportados como `dataset_id`: **`fgj`** · **`911`** · **`ids`** · **`aire`**.
---
## 🧪 Verificar que funciona
```bash
# Test offline (no golpea el portal)
uv run python tests/smoke_test.py
# → smoke: OK
# Test live (opcional — consulta real a datos.cdmx.gob.mx)
CDMX_MCP_LIVE=1 uv run python tests/live_test.py
# → live: OK
```
El CI corre el smoke test en **Python 3.10, 3.11 y 3.12** en cada push.
---
## 🔐 Token opcional para DENUE
`denue_near` requiere un token gratuito de INEGI:
1. Registrate en <https://www.inegi.org.mx/servicios/api_denue.html> (toma 2 min)
2. Exportalo antes de iniciar Claude Desktop/Code:
```bash
export INEGI_TOKEN=tu_token
```
O agregalo al `env` de tu config JSON — ver [`config/claude_desktop_config.example.json`](./config/claude_desktop_config.example.json).
---
## 🏗 Arquitectura
```
Claude (Desktop / Cowork / Code)
│ stdio · JSON-RPC
▼
cdmx_mcp.server (FastMCP)
│
┌──────┴──────┬───────────┬──────────┐
▼ ▼ ▼ ▼
ckan gbfs denue cache
(datos.cdmx) (ECOBICI) (INEGI) (TTL + LRU)
```
- **CKAN** cubre 4 de las 5 fuentes (FGJ, 911, aire, IDS) con la misma API (`/api/3/action/*`).
- `describe_dataset` usa `package_show` + `datastore_search` para exponer el schema real.
- `query_records` y `aggregate` delegan en `datastore_search_sql` (PostgreSQL real: identificadores con `"`, literales con `'`).
- **ECOBICI** se consume por GBFS (feed estándar; también sobre-escribible vía `ECOBICI_GBFS_URL`).
- **Cache** en memoria con TTL por tool: 15 s para tiempo real, 10 min para queries, 1 h para catálogo.
- Sin tokens excepto DENUE (opcional).
---
## 📚 Aprende más
- **[`PROMPTS.md`](./PROMPTS.md)** — biblioteca de prompts listos para copiar/pegar agrupados por dominio (crimen, movilidad, aire, negocios, combos).
- **[`QUICKSTART.md`](./QUICKSTART.md)** — instalación paso a paso por sistema operativo + troubleshooting.
- **[`examples/`](./examples/)** — demos específicas por canal:
- [`demo-cowork.md`](./examples/demo-cowork.md) · prompts para Cowork (stakeholders no técnicos).
- [`demo-claude-desktop.md`](./examples/demo-claude-desktop.md) · flujo guiado en chat.
- [`demo-claude-code.md`](./examples/demo-claude-code.md) · ejercicios avanzados en terminal con pandas/matplotlib.
---
## 🤝 Contribuir
El servidor está diseñado para extenderse fácilmente.
**Para añadir una nueva fuente:**
1. Crear `src/cdmx_mcp/adapters/<fuente>.py` con funciones que devuelvan dicts con la forma `{"results": [...], "total_count": N}`.
2. Envolverlas con `cache.cached(...)` con un TTL apropiado.
3. Exponerlas en `server.py` con `@mcp.tool()` y una docstring clara (Claude la lee).
4. Agregar el nombre al set `expected` en `tests/smoke_test.py`.
**Correr CI local:**
```bash
uv run python tests/smoke_test.py
CDMX_MCP_LIVE=1 uv run python tests/live_test.py # requiere internet
```
Issues y PRs son bienvenidos. El workflow de `.github/workflows/ci.yml` valida que todo compile en Python 3.10 / 3.11 / 3.12.
---
## 📄 Licencia
[MIT](./LICENSE) · built at **Impact Lab CDMX 01** (abril 2026) por [rohan.mx](https://rohan.mx) · [csar.dev](https://csar.dev).
## Extender
Para añadir una fuente nueva:
1. Crear `src/cdmx_mcp/adapters/<fuente>.py` con funciones que devuelvan dicts.
2. Envolver con `cache.cached(...)` para TTL.
3. Exponer en `server.py` con `@mcp.tool()`.
## Licencia
MIT.
TDQS
Scored across 9 tools
Most tools have distinct purposes targeting different CDMX data domains (crime, air quality, business, bikes, datasets), but some overlap exists between 'aggregate' and 'query_records' as both query datasets with SQL-like capabilities. The descriptions help differentiate them, with 'aggregate' focused on server-side GROUP BY operations and 'query_records' on general record retrieval.
Naming conventions are mixed but generally readable. Most tools use snake_case (e.g., 'air_quality_now', 'crime_hotspots'), but there are deviations like 'denue_near' (abbreviation) and 'ecobici_status' (brand name). Verb styles vary from descriptive nouns ('cache_stats') to action-oriented phrases ('list_datasets'), lacking a uniform pattern.
With 9 tools, the count is well-scoped for a city data server covering multiple domains (crime, environment, transportation, business). Each tool serves a clear purpose, such as querying datasets, retrieving specific data types, or listing metadata, making the set comprehensive without being overwhelming.
The toolset provides strong coverage for accessing and querying CDMX open data, including listing, describing, and querying datasets, along with specialized tools for crime, air quality, bikes, and businesses. Minor gaps exist, such as no explicit update or delete operations (reasonable for read-only public data) and limited filtering options in some tools, but core workflows are well-supported.