qgis-mcp
README.md
# qgis-mcp
**A headless MCP server for QGIS 4.x: load layers, run Processing algorithms, style and export maps from Claude,
without opening QGIS Desktop.**
Built on PyQGIS standalone and the [Model Context Protocol](https://modelcontextprotocol.io). It is in daily use with
Claude Code against **QGIS 4.2.1** for spatial overlay work in environmental and mining projects (project area vs.
restriction layers, buffers, zonal statistics, map figures).
> 📄 Notes in Spanish: [README.es.md](README.es.md)
## Why headless, not a Desktop plugin
A headless server doesn't depend on an open QGIS Desktop session with a plugin running, so it's sturdier for long-lived
agent use. It covers Processing, field calculation, vector overlay and static image export, which is nearly the whole
overlay use case. A future "desktop" mode (plugin + socket) could reuse the exact same tool interface; the
implementation underneath would change, but what Claude sees would not.
## Tools
| Module | Tools |
|---|---|
| `tools_project.py` | `qgis_ping`, `load_project`, `save_project`, `list_layers`, `get_layer_info`, `add_vector_layer`, `add_raster_layer`, `remove_layer`, `zoom_to_layer`, `set_extent`, `set_layer_style` |
| `tools_attributes.py` | `calculate_field`, `get_attribute_table` |
| `tools_processing.py` | `list_algorithms`, `get_algorithm_help`, `run_processing_algorithm` |
| `tools_visual.py` | `export_map_image`, `create_print_layout`, `export_layout_pdf` |
| `tools_escape.py` | `execute_pyqgis` (escape hatch) |
## Design decisions
- **Every write tool goes through `resolve_layer()`.** It accepts a real `layer_id` or a name, and fails explicitly on
ambiguity or a missing layer; it never guesses.
- **`run_processing_algorithm` auto-resolves** any string parameter that matches a loaded layer id or name, so the
agent only passes ids it already saw in `list_layers()`.
- **The `QgsProcessingFeedback` log is captured and returned**, so failures can be diagnosed instead of retried blindly.
- **Server instructions encode verification rules for the agent:** confirm CRS before any geometric operation between
two layers, and verify the output layer after a Processing run, because an algorithm can finish without error and
still produce 0 features.
- **QGIS 4 / Qt6 migration handled:** PyQt6 requires scoped enums (`QgsMapLayer.LayerType.VectorLayer`,
`QMetaType.Type.Int`, `QgsLayoutExporter.ExportResult.Success`…) where QGIS 3.x accepted flat ones.
- **`execute_pyqgis` is deliberately unsandboxed** and documented as such, rather than wrapped in a layer of fake
"security". The server is meant for local stdio use only, with no network surface.
- **Nothing overwrites files by default.** `save_project` without a path reuses the loaded path and never creates
silent copies.
## Setup
PyQGIS is **not** installable with pip; use a Python interpreter that already ships with it.
```bash
# Option A: the Python bundled with QGIS (Windows)
"C:\Program Files\QGIS 4.2.1\bin\python-qgis.bat" -m pip install -e .
# Option B: conda-forge
conda create -n qgis-mcp -c conda-forge python=3.11 qgis
conda activate qgis-mcp
pip install -e .
```
Smoke test:
```bash
python -c "from qgis_mcp.qgis_backend import backend; backend.start(); print('OK', backend.qgs_app.processingRegistry().providers())"
```
Register with Claude Code:
```json
{
"mcpServers": {
"qgis": {
"command": "cmd.exe",
"args": ["/c", "C:\\Program Files\\QGIS 4.2.1\\bin\\python-qgis.bat", "-m", "qgis_mcp.server"]
}
}
}
```
## Roadmap
- Automated test suite (today it's validated through live use, not a test bench).
- Optional desktop mode (plugin + socket) reusing the same tool schema.
- Authentication and permissions if it ever stops being local-only.
---
Built by **Oscar Daniel Montero Miranda**. Independent project, not affiliated with the QGIS project. Code comments are in
Spanish; docstrings mention internal design notes (`00-mcp-tools-y-modo-agente.md`) that are not part of this repository.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues