Skip to main content
Glama

cdmx-mcp

Model Context Protocol for Mexico City's open data. Give Claude direct access to crime, 911, air quality, ECOBICI, and DENUE — without writing a single line of ingestion code.

CI Python 3.10+ License: MIT MCP

"¿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

📋 Prerequisites

You need Python 3.10+ and uv (a modern Python package manager — replaces pip + venv + pyenv in a single binary).

Install uv

curl -LsSf https://astral.sh/uv/install.sh | sh

Or with Homebrew (macOS):

brew install uv

In PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Or with winget:

winget install --id=astral-sh.uv -e

Or with Scoop:

scoop install uv

Verify that it is installed:

uv --version   # → uv 0.5.x o similar

Install Python (if you don't have it)

uv can download and install Python for you — you don't need to install it manually:

uv python install 3.12

However, if you prefer to install it manually:

  • macOS: brew install python@3.12 or from https://www.python.org/downloads/

  • Windows: winget install Python.Python.3.12 or from the Microsoft Store

  • Linux (Ubuntu/Debian): sudo apt install python3.12 python3.12-venv

  • Linux (Arch): sudo pacman -S python

Verify:

python3 --version   # → Python 3.10.x o superior

💡 With uv you don't need to activate venvs or fight with versions — uv sync reads pyproject.toml + uv.lock and sets everything up on its own.


Related MCP server: MobusMCP

⚡ Quickstart — 30 seconds

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"

It is already installed. Now connect it to your preferred client:

Claude Code

claude mcp add cdmx -- uv --directory "$(pwd)" run cdmx-mcp
claude                                    # dentro de la sesión: /mcp

Or simply open this directory with claude and it will automatically detect the .mcp.json.

Claude Desktop

Edit claude_desktop_config.json (Settings → Developer → Edit Config) and paste:

{
  "mcpServers": {
    "cdmx": {
      "command": "uv",
      "args": ["--directory", "/ruta/absoluta/a/cdmx-mcp", "run", "cdmx-mcp"]
    }
  }
}

Restart Claude Desktop. Full details by OS → QUICKSTART.md.

Cowork

Paste the same JSON into the Cowork MCP editor, or point to the .mcp.json in the repo.


🎯 First test prompt

Copy and paste into Claude:

List the 10 most frequent types of crime in the Cuauhtémoc borough during 2025. Then create a markdown table with bar emojis.

Claude automatically chooses crime_hotspots and answers with real data. More prompts in PROMPTS.md.


📊 What it covers

Source

Covered via

Freshness

FGJ — Investigation files (crimes)

datos.cdmx.gob.mx (CKAN API)

monthly

911 / LOCATEL — emergency calls

datos.cdmx.gob.mx (CKAN API)

monthly

SIMAT — Air quality

datos.cdmx.gob.mx (CKAN API)

hourly

ECOBICI — bikes/docks in real time

GBFS (standard feed)

🟢 live

DENUE (INEGI) — economic units

INEGI public API

quarterly

Post-migration note: in April 2026 the CDMX portal migrated from OpenDataSoft to CKAN 2.10. Queries now go through /api/3/action/datastore_search_sql (real PostgreSQL). Dataset slugs were preserved.


🛠 Exposed Tools (9 total)

Generic — work with any dataset on the portal:

Tool

What it does

list_datasets(search?, limit)

Search the full catalog

describe_dataset(dataset_id)

Real schema (columns, types, total rows)

query_records(dataset_id, where?, select?, order_by?, limit, offset)

SQL query via datastore_search_sql

aggregate(dataset_id, group_by, metric?, where?, limit)

GROUP BY server-side

Recipes — shortcuts for the top 5:

Tool

What it does

crime_hotspots(year, alcaldia?, category?, top_n)

Top neighborhoods/boroughs by crime

ecobici_status(station_id? · near_lat+near_lng+radius_m)

Real-time free bikes/docks

air_quality_now(zone?, limit)

Most recent SIMAT index

denue_near(lat, lng, radius_m, keyword)

Businesses near a point

Plus cache_stats() and 2 resources (cdmx://guide/fgj, cdmx://guide/top5).

Shortcuts supported as dataset_id: fgj · 911 · ids · aire.


🧪 Verify it works

# 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

The CI runs the smoke test on Python 3.10, 3.11, and 3.12 on every push.


🔐 Optional token for DENUE

denue_near requires a free INEGI token:

  1. Register at https://www.inegi.org.mx/servicios/api_denue.html (takes 2 min)

  2. Export it before starting Claude Desktop/Code:

export INEGI_TOKEN=tu_token

Or add it to the env of your JSON config — see config/claude_desktop_config.example.json.


🏗 Architecture

Claude (Desktop / Cowork / Code)
          │  stdio · JSON-RPC
          ▼
   cdmx_mcp.server (FastMCP)
          │
   ┌──────┴──────┬───────────┬──────────┐
   ▼             ▼           ▼          ▼
   ckan        gbfs        denue      cache
(datos.cdmx) (ECOBICI)   (INEGI)   (TTL + LRU)
  • CKAN covers 4 of the 5 sources (FGJ, 911, air, IDS) with the same API (/api/3/action/*).

  • describe_dataset uses package_show + datastore_search to expose the real schema.

  • query_records and aggregate delegate to datastore_search_sql (real PostgreSQL: identifiers with ", literals with ').

  • ECOBICI is consumed via GBFS (standard feed; also overridable via ECOBICI_GBFS_URL).

  • Cache in memory with TTL per tool: 15 s for real-time, 10 min for queries, 1 h for catalog.

  • No tokens except DENUE (optional).


📚 Learn more


🤝 Contributing

The server is designed to be easily extended.

To add a new source:

  1. Create src/cdmx_mcp/adapters/<source>.py with functions that return dicts in the form {"results": [...], "total_count": N}.

  2. Wrap them with cache.cached(...) with an appropriate TTL.

  3. Expose them in server.py with @mcp.tool() and a clear docstring (Claude reads it).

  4. Add the name to the expected set in tests/smoke_test.py.

Run local CI:

uv run python tests/smoke_test.py
CDMX_MCP_LIVE=1 uv run python tests/live_test.py   # requiere internet

Issues and PRs are welcome. The .github/workflows/ci.yml workflow validates that everything compiles in Python 3.10 / 3.11 / 3.12.


📄 License

MIT · built at Impact Lab CDMX 01 (April 2026) by rohan.mx · csar.dev.

Extend

To add a new source:

  1. Create src/cdmx_mcp/adapters/<source>.py with functions that return dicts.

  2. Wrap with cache.cached(...) for TTL.

  3. Expose in server.py with @mcp.tool().

License

MIT.

Available Tools

9 tools
aggregateA

Server-side aggregation (GROUP BY + metric) via CKAN datastore SQL.

Example: aggregate("fgj", group_by="alcaldia_hecho", metric='count(*) as delitos', where="anio_hecho=2025").

Inputs: dataset_id (required), group_by (required), metric, where, +1 more. Returns: result

air_quality_nowC

Most recent air-quality index rows from SIMAT (via datos.cdmx).

Args: zone: optional zone filter (e.g. "NOROESTE", "CENTRO", "SURESTE").

Inputs: zone, limit. Returns: result

cache_statsB

Return cdmx-mcp cache stats (for demos — shows how much we saved).

Inputs: no named parameters. Returns: result

crime_hotspotsB

Top colonies/alcaldías by crime count (FGJ carpetas).

Args: year: filter by anio_hecho (defaults to 2025). alcaldia: optional alcaldía filter (e.g. "CUAUHTEMOC"). UPPERCASE. category: optional crime category filter (e.g. "ROBO DE VEHICULO"). top_n: how many rows to return.

Inputs: year, alcaldia, category, top_n. Returns: result

denue_nearC

Economic units (businesses) near a point (INEGI DENUE).

Requires env INEGI_TOKEN. Free registration: https://www.inegi.org.mx/servicios/api_denue.html

Inputs: lat (required), lng (required), radius_m, keyword, +1 more. Returns: result

describe_datasetA

Return schema and metadata for a dataset on datos.cdmx.gob.mx.

Use shortcut ids: "fgj", "911", "ids", "aire".

Inputs: dataset_id (required). Returns: result

ecobici_statusA

Live bike/dock availability from ECOBICI (GBFS).

Pass station_id for one station, or near_lat/near_lng + radius_m to find the closest ones. Omit all to get the full snapshot (throttled).

Inputs: station_id, near_lat, near_lng, radius_m, +1 more. Returns: result

list_datasetsB

List datasets on datos.cdmx.gob.mx. Optional free-text search.

Args: search: free-text query (searches title and description). limit: max datasets to return (1-100).

Inputs: search, limit. Returns: result

query_recordsA

Query records from a CDMX dataset (CKAN datastore, PostgreSQL).

Args: dataset_id: id on datos.cdmx.gob.mx (or shortcut: fgj, 911, ids, aire). where: SQL-ish WHERE, e.g. anio_hecho=2025 AND alcaldia_hecho="BENITO JUAREZ". String literals can use single or double quotes; identifiers are quoted automatically. select: comma-separated columns (for fewer tokens). order_by: e.g. "fecha_hecho desc". limit: 1-100. Defaults to 50. offset: for pagination.

Inputs: dataset_id (required), where, select, order_by, +2 more. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 9 tools

    Disambiguation4/5

    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 Consistency3/5

    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.

    Tool Count5/5

    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.

    Completeness4/5

    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.

    Maintenance

    ActivityInactive
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables Claude to connect to and interact with SQLite, SQL Server, PostgreSQL, and MySQL databases through natural language. Supports executing queries, managing tables, exporting data, and storing business insights with authentication options including AWS IAM.
      782 npm
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Search, preview, and analyze datasets from 20+ platforms and millions of datasets via a single MCP connector. Works with Claude instantly
      7 npm
      29
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      An MCP server that gives Claude deep access to US public data -- demographics, economics, crime, employment, weather, housing, transit, schools, budgets, and more across 30+ cities for government intelligence workflows.
      28
      MIT