DuckDB H3 Local MCP Server
Allows querying local H3-indexed Parquet files using DuckDB SQL.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@DuckDB H3 Local MCP ServerWhat is the percentage of natural cover in each department?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Servidor MCP local (DuckDB + H3)
En este repositorio se creó el servidor MCP, el cual funciona como intermediario para hacer que un modelo LLM pueda establecer conexión y hacer consultas a una base de datos SQL.
Además de este servidor, también se tienen los scripts que convierten los datos geoespaciales en una base de datos relacional en formato H3.
tif_to_h3_parquet.py— convierte un raster (COG local, remoto, o resuelto desde un item STAC) en un.parquetindexado en H3: una fila por hexágono, con la clase mayoritaria (datos categóricos) o el promedio (datos continuos).vector_to_h3_parquet.py— convierte un vector de polígonos (GeoJSON, Shapefile, GeoPackage) en un.parquetindexado en H3: una fila por cada combinación de feature y hexágono que esa geometría cubre — útil para límites administrativos, páramos, cuencas y cualquier capa que luego se quiera cruzar (porJOIN) con datos raster ya indexados en H3.server.py— un servidor MCP que sirve esos.parquetlocales con la herramientaqueryde SQL DuckDB.
Arquitectura
Vista general del ecosistema
Este servidor es una pieza dentro de un ecosistema de tres repositorios: la app web (geo-agent-LIB), la librería del mapa/agente que esa app carga desde CDN (geo-agent), y este servidor de datos local.
flowchart LR
User(("Persona<br/>usando el chat"))
LIB["geo-agent-LIB<br/>app: datasets, branding,<br/>layers-input.json"]
LibCore["geo-agent<br/>librería: mapa, chat, agente LLM<br/>(cargada desde CDN)"]
MCP["local-mcp-server<br/>(este repo)<br/>SQL sobre datos H3 locales"]
STAC["Catálogos STAC<br/>local + público"]
LLM[["LLM externo<br/>OpenRouter / Bedrock (BYOK)"]]
User <-->|"pregunta →<br/>← mapa + respuesta"| LIB
LIB <-->|carga módulos / eventos UI| LibCore
LibCore -->|metadata + capas visuales| STAC
LibCore <-->|Chat Completions API| LLM
LibCore <-->|MCP · Streamable HTTP| MCPgeo-agent-LIB es la app configurada (datasets propios, direcciones propias); los módulos que la hacen funcionar (mapa, chat, agente LLM) vienen de la librería geo-agent, cargada desde CDN. Esa librería habla con tres sistemas externos: el catálogo STAC (metadata + capas visuales), un LLM externo (razonamiento del chat), y este servidor (SQL sobre datos H3 propios).
Este repositorio en detalle
flowchart TD
Client[["Cliente MCP<br/>(geo-agent, en el navegador)"]]
Map[["Mapa<br/>(MapLibre GL, en el navegador)"]]
subgraph Repo["local-mcp-server"]
direction TB
Tools["Tools MCP (server.py)<br/>list_local_datasets · get_local_schema · query<br/>get_stac_details · compute_area<br/>summarize_by_category · join_datasets_by_h3<br/>register_hex_tiles · get_hex_tile_status"]
Engine[("DuckDB<br/>+ extensiones h3, spatial")]
Data[("data/*.parquet<br/>H3-indexado")]
Tiles[("tiles/*.geojson")]
Conv["Herramientas de conversión a H3<br/>tif_to_h3_parquet.py · vector_to_h3_parquet.py"]
Tools --> Engine --> Data
Tools --> Tiles
Conv --> Data
end
Raster[("Raster COG /<br/>GeoTIFF")] --> Conv
Vector[("Vector GeoJSON /<br/>SHP / GPKG")] --> Conv
Client <-->|"MCP · Streamable HTTP<br/>localhost:8001/mcp"| Tools
Tiles -.->|"GET /tiles/<hash>.geojson"| Mapserver.py expone los tools MCP; todos leen/escriben a través de DuckDB (con las extensiones h3 y spatial cargadas). Los .parquet de data/ se generan una sola vez, offline, con los dos scripts de conversión — nunca en tiempo de consulta. register_hex_tiles es la única excepción que escribe en caliente: genera un .geojson en tiles/ y lo sirve por HTTP directo al mapa, sin pasar por el cliente MCP.
Se recomienda tener un ambiente conda o un ambiente virtual para ejecutar todo lo siguente.
Related MCP server: Spatial Lakehouse MCP
1. Instalar
Clonar este repositorio
cd local-mcp-server
pip install -r requirements.txtAdemás se necesitan los binarios de GDAL en el PATH (gdalinfo,
gdal_translate):
sudo apt install gdal-bin # Ubuntu/Debian
# o
conda install -c conda-forge gdal2. Convertir un dataset a H3 (ejemplo: colección "Coberturas")
El catálogo STAC expone la colección
Coberturas (grado de conservación/transformación de ecosistemas —
1=Natural, 2=Secundaria, 3=Transformada) como un COG categórico. Para una
primera prueba rápida usar una resolución H3 gruesa (h6, ~36 km²/celda):
python tif_to_h3_parquet.py \
--stac-item http://172.191.168.255:8082/collections/Coberturas/items/2020 \
--output data/coberturas_h6.parquet \
--resolution 6 \
--labels "1=Secundaria,2=Natural,3=Transformada"Si se quiere una resolución "estándar" (h8, ~0.7 km²/celda, la misma que usa el servidor de boettiger-lab), correr:
python tif_to_h3_parquet.py \
--stac-item http://172.191.168.255:8082/collections/Coberturas/items/2020 \
--output data/coberturas_h8.parquet \
--resolution 8 --parent-resolutions "6,0" \
--labels "1=Natural,2=Secundaria,3=Transformada"Nota de tamaño/tiempo: el raster de Coberturas es 48657×66749 píxeles (~27m/píxel) cubriendo todo Colombia — del orden de mil millones de píxeles válidos. A resolución h8 puede tardar bastante. Para pruebas o iterar rápido es suficiente con un h6 o h7 El h8 solo cuando sea algo más definitivo y preciso
El mismo comando sirve para la colección Humedales (humedal binario,
clase única "humedal") — solo cambiar --stac-item y --labels, ejemplo:
python tif_to_h3_parquet.py \
--stac-item http://172.191.168.255:8082/collections/Humedales/items/Humedales30 \
--output data/humedales_h8.parquet \
--resolution 8 --parent-resolutions "6,0" \
--labels "1=Humedal"Y para datos continuos (elevación, HHContinua, etc. — no categóricos) se usa --agg avg en vez de --agg mode (que es el default), y omiten --labels.
Desde un archivo local (sin STAC)
--source no está limitado a datos del catálogo STAC — también acepta una
ruta local directa a cualquier COG/GeoTIFF en disco:
python tif_to_h3_parquet.py \
--source /ruta/local/mi_raster.tif \
--output data/mi_raster_h8.parquet \
--resolution 8 --parent-resolutions "6,0" \
--labels "1=Clase A,2=Clase B"--stac-item y --source son mutuamente excluyentes: usa --stac-item
cuando sea un item del stac; usar --source con ruta local.
2b. Indexar límites vectoriales (departamentos, páramos, cuencas...)
vector_to_h3_parquet.py hace lo mismo que el conversor de raster pero para
polígonos: convierte cada .geojson/.shp/.gpkg en un parquet con una
fila por cada (feature, hexágono) que esa geometría cubre — el modelo de
"tiling" que usa el resto del ecosistema para límites administrativos, no
el de "un valor agregado por celda" del raster.
Nota: Para estas pruebas poner los archivos geojson en la carpeta ./data_previa
python vector_to_h3_parquet.py \
--source data_previa/departamentos.geojson \
--output data/departamentos_h8.parquet \
--resolution 8 --parent-resolutions "6,0" \
--keep-columns dpto_cnmbr,dpto_ccdgoMismo patrón para los otros archivos en data_previa/:
python vector_to_h3_parquet.py --source data_previa/paramos.geojson \
--output data/paramos_h8.parquet --resolution 8 --parent-resolutions "6,0" \
--keep-columns NM_UA,COD_CMPLJ,Area_Ha
python vector_to_h3_parquet.py --source data_previa/jurisdicciones-ambientales.geojson \
--output data/jurisdicciones_h8.parquet --resolution 8 --parent-resolutions "6,0" \
--keep-columns car,nombre
python vector_to_h3_parquet.py --source data_previa/subzonas-hidrograficas.geojson \
--output data/subzonas_h8.parquet --resolution 8 --parent-resolutions "6,0" \
--keep-columns nom_szh,COD_SZH,nom_zh,nom_ah--keep-columns es opcional pero recomendado: sin él se conservan todas las
columnas de atributos
A diferencia del raster, esto corre en segundos incluso a resolución h8: los
33 departamentos de Colombia tilados a h8 son ~1.5M filas, generadas en
menos de un minuto, todo en local con DuckDB (no necesita GDAL como binario
aparte — ST_Read de la extensión spatial lee GeoJSON directo).
Ejemplo real: "cobertura natural en Antioquia"
Con departamentos_h8.parquet y las Coberturas
(coberturas_h8.parquet, generado igual que en el paso 2) se usa
un SEMI JOIN por h8 — nunca con una intersección geométrica
sobre el polígono:
WITH antioquia_hex AS (
SELECT DISTINCT h8
FROM read_parquet('data/departamentos_h8.parquet')
WHERE dpto_cnmbr = 'ANTIOQUIA'
),
cobertura_en_antioquia AS (
SELECT c.h8, c.class_label
FROM read_parquet('data/coberturash8.parquet') c
SEMI JOIN antioquia_hex a USING (h8)
)
SELECT class_label,
COUNT(*) AS celdas,
SUM(h3_cell_area(h8, 'km^2')) AS area_km2,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) AS pct
FROM cobertura_en_antioquia
GROUP BY class_label
ORDER BY area_km2 DESCResultado real (recorte de Coberturas a la extensión de Antioquia):
class_label | celdas | area_km2 | % |
Transformada | 48,950 | 36,932.7 | 58.7% |
Secundaria | 26,686 | 20,190.3 | 32.0% |
Natural | 7,806 | 5,887.4 | 9.4% |
El área total ≈ 63,020 km², consistente con el área oficial de Antioquia —
Este es exactamente el patrón que el LLM del chat de geo-agent generará solo
cuando se le pregunta "¿cuánta cobertura natural hay en Antioquia?": llama
list_datasets → get_schema en ambos datasets → arma el SEMI JOIN de
arriba.
3. Levantar el servidor MCP
python server.pyDATA_DIR: /ruta/a/local-mcp-server/data
Datasets encontrados: 2 → ['coberturas_h8', 'humedales_h8']
Sirviendo MCP en http://127.0.0.1:8001/mcp4. Conectar geo-agent a este servidor local
En el archivo de configuración de geo-agent-LIB app/layers-input.json:
{
"mcp_url": "http://localhost:8001/mcp"
}Al pregúntar al chat algo como "¿qué porcentaje del área tiene cobertura
natural?" — el agente llamará list_datasets → get_schema → query
This server cannot be deployed
Maintenance
Related MCP Connectors
Query, join, profile, clean and convert CSV/JSON/Parquet with server-side DuckDB over MCP.
Geospatial AI MCP server — satellite imagery, embeddings, weather, GNS governance
- earthOAuthcom.mireye
MCP server for Mireye Earth — federal-source-cited geospatial data for any MCP-aware agent.
Generate and run high performance queries on open and private spatial data at-scale in the cloud
Related MCP Servers
- AlicenseAqualityAmaintenanceAn MCP server for MotherDuck and local DuckDB.5522MIT
- FlicenseNot gradedqualityNot gradedmaintenanceAn Iceberg-native geospatial MCP server powered by DuckDB that provides tools for spatial SQL queries, catalog discovery, and data management. It enables LLM agents to interact with Apache Iceberg lakehouses to perform complex spatial analysis, joins, and aggregations.1-
- AlicenseAqualityDmaintenanceAn MCP server for discovering, downloading, querying, and analyzing datasets from Ontario's open data portals, allowing natural language questions and high-performance analytics via DuckDB.231MIT
- AlicenseNot gradedqualityAmaintenanceAn MCP server that connects AI agents to cloud-native geospatial data via STAC metadata and DuckDB with H3 spatial indexing, enabling zero-configuration SQL queries on terabyte-scale datasets over S3.23BSD 3-Clause