QGIS Desktop as an MCP Server — Full GUI via noVNC, PyQGIS scripting, 1000+ Processing algorithms, smart data pipeline with 30+ French national datasets.

QgisRemoteMCP — QGIS Desktop as an MCP Server

Give any AI assistant a complete GIS workstation — data loading, spatial analysis, cartography, and multi-format export — all running in a live QGIS Desktop instance.

QgisRemoteMCP is an MCP server that exposes a full QGIS Desktop through Docker. The AI loads data, runs analysis, produces maps — while users interact with the same instance in their browser via noVNC.

MCP Streamable HTTPQGIS 3.40+Python 3.12License: MITStatus: ProductionMulti-user: Beta

Project status: Production-ready for single-user local deployments. Multi-user mode (per-user isolated containers with GPU passthrough) is functional but in beta. Developed at Cerema Méditerranée — published to share the approach and invite contributions.

What it does

You say to the AI What happens
"Analyse le risque inondation à Nîmes" Loads flood zones + buildings, computes exposure, exports interactive HTML with water height slider
"Carte de densité du bâti à Montpellier" Downloads 10,000 buildings as local GPKG, creates hex grid, graduated symbology, PDF A3 export
"Prépare un relevé terrain pour Sète" Loads data, styles layers, exports QField-ready ZIP with editable Observations layer (camera, dropdowns)
"Analyse l'évolution foncière sur la côte" DVF transactions 2020-2024, coastal distance bands, temporal web map with animated playback

Everything runs inside Docker — the AI calls MCP tools, QGIS does the work, the user sees results live in their browser. No local QGIS installation needed.

Quick start

1. Run

git clone https://github.com/nic01asFr/QgisRemoteMCP.git
cd QgisRemoteMCP
cp .env.example .env
docker compose up -d --build

2. Verify

Service URL
MCP Server http://localhost:8100/mcp
noVNC (QGIS in browser) http://localhost:6080
REST API http://localhost:8081
MJPEG stream http://localhost:8082/stream
curl http://localhost:8100/health
# → {"status":"ok","bridge":true,"server":"QgisRemoteMCP","multi_user":false}

3. Connect Claude Desktop

Edit claude_desktop_config.json:

{
  "mcpServers": {
    "qgis": {
      "type": "http",
      "url": "http://localhost:8100/mcp"
    }
  }
}

4. Connect Claude Code

The repo includes a .mcp.json — Claude Code picks it up automatically when you open the project directory.

Smart data loading

The core innovation: a structured pipeline that replaces unreliable live WFS connections with fast local GeoPackage files.

Problem Live WFS Smart Loading
Pagination IGN silently truncates at 5000 ogr2ogr handles all pages automatically
Spatial index None (in-memory) R-tree in GeoPackage
Processing speed 60-250x slower Fast local file
Network during analysis HTTP per feature Zero network
CRS confusion Mixed 4326/3857/2154 Standardized EPSG:2154
1. set_study_zone("Montpellier")         → geocode, store bbox, zoom
2. smart_load("osm_xyz")                 → basemap (streaming)
3. smart_load("bdtopo_batiments")        → 10,000 buildings as local GPKG
4. run_processing / execute_python       → analyse (fast, no network)
5. export_pdf / export_web_map           → deliver

Downloads are cached in /data/cache/ with bbox hash — same area = instant reload for 24h.

Performance (Montpellier, ~10 km bbox)

Operation Features Time
Download buildings 10,000 ~30s
Download roads 5,000 6.8s
Cache reload (2nd call) 10,000 instant
Buffer 50m 10,000 1.8s
Density grid 500m 440 cells 0.3s

Available data sources

All sources are free (IGN open data since July 2021). No API key needed. 47 pre-configured in datasources.json.

Vector (WFS → local GPKG)

ID Name Key attributes
bdtopo_batiments Buildings nature, usage, height, floors, materials
bdtopo_routes Roads nature, importance, width, lanes, speed
bdtopo_hydrographie Rivers name, class, width
bdtopo_communes Communes name, INSEE code, population
admin_express_communes Communes (Admin Express) name, code, population
rpg Agricultural parcels crop type, area
+ 10 more BD TOPO vegetation, railways, POI, activity zones...

Basemaps & imagery (streaming)

ID Name
osm_xyz OpenStreetMap
ign_planign Plan IGN v2
ign_ortho_wmts IGN orthophotos
ign_cadastre Cadastral parcels
corine_land_cover Land cover 2018
ign_dem Hillshade from high-resolution DEM (display)
ign_dem_altitudes Raw elevation values, 32-bit TIFF (compute only, not renderable)
+ 10 more Esri, Stamen, CartoDB, infrared, SCAN 25...

APIs

ban_geocode (address search), geo_api_communes (commune info), dvf_api (property transactions), panoramax (street imagery), ign_altimetrie (elevation).

Recipes

Pre-built workflow templates — from data loading to styled map export in one command.

ID Name Output
densite_bati Building density Hex grid + graduated symbology + PDF
urbanisme_general Urban overview Buildings, roads, vegetation + categorized styles
risque_inondation Flood risk Flood zones + building exposure + interactive web map
occupation_sol Land cover Corine Land Cover + categorized symbology
pression_fonciere_cotiere Coastal land pressure DVF 2020-2024 + coastal bands + temporal web map
# Automated — all steps in one shot
run_recipe(id="risque_inondation", zone="Nimes")

# Manual — follow steps one by one
get_recipe(id="densite_bati", zone="Montpellier")

Export formats

Format Tool Output
PDF export_pdf Print-ready layout (A3 landscape, A4 portrait) with title, legend, scalebar
Web map export_web_map Leaflet HTML with embedded GeoJSON, layer toggle, popups
Flood map export_flood_map Interactive HTML — water height slider, building exposure stats, animation
Temporal map export_temporal_map Interactive HTML — year slider, trend arrows, animated playback
QField export_qfield Portable ZIP for mobile (.qgz + GPKGs + editable Observations layer)
Grist export_grist Collaborative document with typed columns, map widget, form pages
Layer export_layer GPKG, GeoJSON, Shapefile, or CSV

Grist export — universal HTML converter

export_grist has two modes:

  1. From QGIS project — exports loaded layers as Grist tables with map widget
  2. From any HTML file — takes any Leaflet HTML containing GeoJSON (flood maps, temporal maps, qgis2web) and creates a Grist document with data in tables and the original interactive map as a custom widget

Auto-detected column types: Choice (colored dropdowns), Date (epoch timestamps), Ref (cross-table references). Form-like tables get a Grist Form page.

MCP tools (46)

Smart loading

Tool Description
set_study_zone Define study area (commune, address, bbox). Geocodes, stores bbox, zooms.
get_study_zone Get current zone (name, bbox in 4326 + 2154).
smart_load Load data by catalog ID. WFS → local GPKG. Rasters stream.
list_datasources Browse data catalog (filter by category or search).
add_from_catalog Add source by catalog ID.

Core

Tool Description
execute_python Run PyQGIS code with helpers module, iface, project, processing.
get_screenshot Capture QGIS canvas (JPEG ≤1MB). Auto-included after modifying tools.
get_project_info Project state: layers, CRS, layouts, extents.
run_processing Execute any of ~730 Processing algorithms (native, qgis, gdal, grass, 3d).
search_algorithms Find algorithms by keyword.
restart_qgis_engine Respawn the QGIS process when the bridge is deadlocked. Last resort — 5-15 s of unavailability.

Async execution

Anything expected to run past 30 s — a heavy native:difference, anextractbylocation over 50 000 features, a wide smart_load — should besubmitted rather than awaited. The bridge streams heartbeats, so a frozenQt main thread is told apart from a dead bridge without waiting for atimeout to expire.

Tool Description
execute_async Submit a bridge action for background execution, returns a job_id immediately.
poll_job Job status, heartbeat_age_s, qt_lag_ms, probably_frozen, and the result once done.
cancel_job Best-effort cancellation.

Data & layers

Tool Description
add_layer Add vector/raster/WFS/WMS by URI. Point clouds too — pass provider=copc|ept|vpc and the layer is built as a point cloud, with point_count returned.
remove_layer Remove a layer.
get_features Query features with attribute/spatial filters.
zoom_to Navigate to extent, layer, or point.

Styling & layout

Tool Description
set_layer_style Single color, categorized, or graduated symbology.
set_layer_visibility Show/hide layers.
apply_layout_template Apply print layout template (A3/A4).
list_layout_templates List available templates.

Recipes

Tool Description
list_recipes Browse workflow recipes.
get_recipe Get recipe with parameter substitution.
run_recipe Execute all steps automatically.

Export

Tool Description
export_pdf Print layout → PDF.
export_web_map Visible layers → Leaflet HTML.
export_flood_map Flood analysis → interactive HTML.
export_temporal_map Time series → interactive HTML.
export_qfield QField-ready ZIP (.qgz + GPKGs + Observations).
export_grist Grist document from project or HTML.
export_layer Layer → GPKG/GeoJSON/Shapefile/CSV.
publish_artifact Publish a deliverable (storymap, dataset, recipe, PDF) to S3 through the hub, returns a stable public URL.

Files

Tool Description
upload_file Upload via multipart POST (any size), URL fetch, or base64.
download_file Download from /data/ (URL for user, inline for small files).
list_files List files in /data/.
delete_file Delete file from /data/.
download_project Save as .qgz.

GUI interaction

Tool Description
qgis_desktop_ui Open interactive QGIS view in conversation (MCP App).
mouse_click / mouse_scroll / mouse_drag / key_press Direct GUI interaction.

Project management

Tool Description
new_project / open_project / save_project Project lifecycle.

MCP resources & prompts

Resources (skill documents)

URI Content
skill://smart-loading Smart loading pipeline, CRS handling, caching
skill://pyqgis PyQGIS scripting patterns & API usage
skill://processing Processing algorithms guide (native, GDAL, GRASS)
skill://cartography Symbology, labels, layouts, PDF export
skill://helpers Python helpers (geocode, add_wfs, zoom_to, overpass_query...)
skill://data-sources French national datasets reference
skill://recipes Workflow recipes reference
skill://external-services Vision services integration
skill://solar Solar cadastre pipeline (DSM, r.sun, observatory)
skill://file-exchange bridge_put / bridge_get file transfer — design, pending implementation
skill://qgis-status Live QGIS instance status

Prompts

Prompt Description
analyse_territoire Territory analysis template (zone + question)
workflow_donnees Guided theme-based workflow (urbanisme, environnement, transport, agriculture, risques)

Multi-user mode (beta)

Each authenticated user gets an isolated QGIS container with its own project, data, and noVNC session.

Enable

# .env
MULTI_USER_MODE=true
JWT_SECRET=your-secret-key
IDLE_TIMEOUT_MINUTES=30

Uncomment the Docker socket volume in docker-compose.yml.

How it works

Agent A (Bearer: qgis_xxx)  →  Gateway (:8100)  →  Container-A (172.22.0.3:8080)
Agent B (Bearer: qgis_yyy)  →  Gateway (:8100)  →  Container-B (172.22.0.4:8080)
  • POST /api/auth/register → get API key (qgis_...)
  • All MCP calls with Authorization: Bearer qgis_xxx → routed to user's container
  • Containers auto-start on first call, auto-stop after idle timeout
  • Per-user data isolation (/data/users/<user_id>/)
  • GPU automatically passed to workers when available (NVIDIA Container Toolkit)

Endpoints (multi-user)

Method Route Purpose
POST /api/auth/register Register user, get API key
POST /api/auth/login Login, get token
GET /api/session Current user's container info
GET /api/sessions List all active sessions

GPU support

GPU is automatically detected at startup and passed through to all containers for compute workloads (GDAL CUDA, PyTorch, heavy raster processing). QGIS rendering stays on CPU (Xvfb limitation).

Requirements:

Architecture

┌──────────────────────────────────────────────────────────────┐
│                 QgisRemoteMCP Container                      │
│                                                              │
│  supervisord                                                 │
│  ├── Xvfb :99              (virtual display 1920x1080)      │
│  ├── fluxbox               (window manager)                 │
│  ├── QGIS Desktop ◄──────────────────┐                     │
│  │   └── qgis_bridge.py  (startup)   │ UNIX socket         │
│  ├── x11vnc → noVNC       (:6080)    │                     │
│  ├── api_server.py         (:8080) ──┘                     │
│  ├── main_mcp.py           (:8100)                          │
│  └── stream_server.py      (:8081)                          │
│                                                              │
│  /data/          user files, projects, exports              │
│  /data/cache/    smart_load GPKG cache (24h)                │
│  /app/skills/    MCP resource documents                     │
└──────────────────────────────────────────────────────────────┘

Communication flow

MCP Client (Claude Desktop, Claude Code, any MCP client)
  │ JSON-RPC over Streamable HTTP (:8100)
  ▼
main_mcp.py (MCP Server, 46 tools)
  │ HTTP → api_server.py (:8080) → UNIX socket
  ▼
qgis_bridge.py (runs inside QGIS, Qt main thread)
  │ PyQGIS API (iface, QgsProject, processing)
  ▼
QGIS Desktop (Xvfb :99) → x11vnc → noVNC (:6080) → browser

Workflow context

Every mutating tool response includes a _context with:

  • phase: setup / analysis / cartography / export (auto-detected)
  • study_zone: current zone name
  • layers: loaded layers with feature counts
  • hint: suggested next action

This guides the AI through structured workflows without hard restrictions.

Customization — skills, templates, recipes, data sources

QgisRemoteMCP is designed to be specialized for specific domains. All customization is file-based — no code changes needed.

Skills (MCP Resources)

Skills are Markdown documents in skills/ that teach the AI how to work. The LLM reads them on demand via skill:// URIs.

skills/
├── smart_loading.md     # Data loading pipeline, CRS, caching
├── pyqgis.md            # PyQGIS scripting patterns
├── processing.md        # Processing algorithms (native, GDAL, GRASS)
├── cartography.md       # Symbology, labels, layouts, PDF export
├── helpers.md           # Ready-made Python helpers reference
├── data_sources.md      # French national datasets
├── recipes.md           # Workflow recipes reference
└── external_services.md # Vision services (Moondream, SAMGeo3, DepthPro)

To add a skill: create skills/my_domain.md, then register it in main_mcp.py:

  • Add to RESOURCES[] with a skill://my-domain URI
  • Add to the _SKILL_MAP dict

Example: a skills/environnement.md skill could document environmental analysis patterns (species habitats, noise mapping, air quality), making the AI an environmental GIS specialist.

Print layout templates (.qpt)

QGIS print layout templates in templates/:

Template Format Content
a3_landscape.qpt A3 landscape Title, subtitle, legend, scalebar, north arrow, data sources
a4_portrait.qpt A4 portrait Same elements, portrait layout

Templates use QGIS expressions for dynamic labels: [% @title %], [% @subtitle %], [% @data_sources %]. Variables are set by apply_layout_template().

To add a template: create a .qpt file in QGIS Layout Manager → Export as Template. Place it in templates/. It's automatically available via list_layout_templates and apply_layout_template.

Web map templates (Leaflet HTML)

Three specialized Leaflet templates in templates/web/:

Template Use case Key features
leaflet_template.html General map Layer toggle, popups, legend, basemap selector
leaflet_flood_template.html Flood analysis Water height slider, building exposure stats, animation
leaflet_temporal_template.html Time series Year slider, per-band stats, trend arrows, playback

Templates are plain HTML/JS with placeholder markers (__LAYERS_DATA__, __TITLE__) replaced at export time by the bridge.

To create a custom template: copy an existing one, modify the HTML/JS, and register it in qgis_bridge.py as a new export action.

Recipes (JSON workflows)

Recipes are step-by-step GIS workflows in recipes/:

{
  "id": "densite_bati",
  "name": "Carte de densité du bâti",
  "description": "Hex grid density analysis...",
  "tags": ["urbanisme", "densité"],
  "parameters": {
    "zone": {"type": "string", "required": true}
  },
  "steps": [
    {"action": "set_study_zone", "params": {"target": "{zone}"}},
    {"action": "smart_load", "params": {"id": "bdtopo_batiments"}},
    {"action": "run_processing", "params": {"algorithm": "native:creategrid", ...}},
    {"action": "set_layer_style", "params": {...}}
  ]
}

To add a recipe: create a JSON file in recipes/. It's automatically available via list_recipes, get_recipe, and run_recipe. Parameters like {zone} are substituted at runtime.

Data sources catalog

datasources.json contains 47 pre-configured French data sources. Each entry:

{
  "id": "bdtopo_batiments",
  "name": "Bâtiments (BD TOPO)",
  "category": "topography",
  "type": "wfs",
  "url": "https://data.geopf.fr/wfs/ows",
  "typename": "BDTOPO_V3:batiment",
  "description": "Buildings with usage, height, materials"
}

To add a source: add an entry to datasources.json. Supported types: wfs (downloaded as GPKG via smart_load), wms, wmts, xyz (streaming), api (custom).

Specialization patterns

To create a domain-specific QGIS MCP server:

  1. Add domain skillsskills/urbanisme.md, skills/hydrologie.md, etc.
  2. Add domain data sources — extend datasources.json with relevant WFS/WMS
  3. Create domain recipes — automated workflows for common analyses
  4. Design layout templates — branded .qpt with your organization's logo and style
  5. Customize web templates — adapt Leaflet HTML templates for your visualization needs

The same Docker image serves all specializations — everything is configured through mounted files.

Project structure

QgisRemoteMCP/
├── main_mcp.py              # MCP Server (46 tools, 12 resources, 3 prompts)
├── qgis_app.html            # MCP App (interactive QGIS in conversation)
├── datasources.json         # 47 pre-configured French data sources
├── src/
│   ├── qgis_bridge.py       # Runs inside QGIS (47 actions, Qt main thread)
│   ├── qgis_helpers.py      # Python helpers (geocode, smart loading, etc.)
│   ├── api_server.py        # FastAPI REST API (file upload/download, commands)
│   ├── stream_server.py     # MJPEG stream
│   ├── auth.py              # Authentication (multi-user mode)
│   └── container_manager.py # Per-user Docker containers (multi-user mode)
├── skills/                  # MCP Resources (AI skill documents)
├── recipes/                 # Workflow recipes (JSON)
├── templates/               # Print layouts (.qpt) + web map templates (Leaflet)
├── Dockerfile
├── docker-compose.yml
├── supervisord.conf
├── entrypoint.sh
├── requirements.txt
├── .env.example
├── CLAUDE.md                # AI assistant instructions
├── CONTRIBUTING.md
└── LICENSE                  # MIT

Development

Source files are mounted as volumes in dev — edit locally, restart to apply:

docker compose restart qgisremotemcp
docker compose logs -f qgisremotemcp

QGIS bridge changes require a full restart (loaded at QGIS startup via PYQGIS_STARTUP).

Testing

# Health
curl http://localhost:8100/health

# Execute Python
curl -X POST http://localhost:8081/api/execute \
  -H "Content-Type: application/json" \
  -d '{"code": "result[\"version\"] = Qgis.version()"}'

# Run a recipe
curl -X POST http://localhost:8100/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"run_recipe","arguments":{"id":"risque_inondation","zone":"Nimes"}}}'

What works, what doesn't

Stable (single-user, Docker)

  • Full MCP server: 46 tools, 11 resources, 3 prompts, SSE streaming
  • Smart data loading pipeline: WFS → GPKG with pagination, R-tree, caching
  • All export formats: PDF, Leaflet (standard/flood/temporal), QField, Grist
  • PyQGIS scripting with helpers module (geocode, overpass, smart loading)
  • ~730 Processing algorithms (native, qgis, GDAL, GRASS, 3d) — SAGA n'estpas installe, et QGIS a retire son provider du coeur depuis la 3.30
  • MCP App: interactive QGIS in conversation (noVNC + file management)
  • Workflow recipes: 5 pre-built analysis templates
  • Print layouts: A3/A4 with dynamic labels

Beta — functional, needs hardening

  • Multi-user mode: per-user Docker containers with auth, session isolation, and idle cleanup. Tested with 2 concurrent users. Not yet tested at scale or in production.
  • GPU passthrough: auto-detected and passed to workers for CUDA/compute. Works with NVIDIA Container Toolkit. Xvfb rendering stays CPU.
  • Grist HTML→Grist converter: works with all tested Leaflet HTML files but edge cases may exist with unusual GeoJSON structures.

Known limitations

  • QGIS rendering uses Mesa llvmpipe (CPU) — Xvfb cannot use GPU for OpenGL
  • Single-file MCP server (main_mcp.py, ~2300 lines) — intentional for deployment simplicity
  • In-memory sessions in multi-user mode — no persistence across gateway restarts
  • No automated tests yet
  • French-focused data sources (IGN, BD TOPO) — extend datasources.json for other countries

Compatible MCP clients

Tested with:

Tech stack

  • QGIS 3.40+ · PyQGIS · Processing · GDAL/OGR 3.8
  • Python 3.12 · FastAPI · uvicorn · httpx · MCP SDK
  • Docker · supervisord · Xvfb · fluxbox · x11vnc · noVNC
  • MCP transport: Streamable HTTP (spec 2025-03-26)
  • No database: all state in QGIS project + filesystem

Contributing

We welcome contributions — bug reports, feature ideas, or merge requests.

Particularly interested in

  • Additional data sources — extend datasources.json for non-French datasets
  • New recipes — workflow templates for common GIS analyses
  • Testing — automated tests, CI/CD pipeline
  • Multi-user hardening — HTTPS, rate limiting, session persistence
  • Documentation — usage guides, video demos, tutorials

How to contribute

  1. Fork the repo
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Test with a real QGIS container (docker compose up -d)
  4. Submit a merge request

See CONTRIBUTING.md for detailed guidelines.

External vision services (optional)

Service Default URL Purpose
Moondream http://localhost:8001 Image captioning, VQA
SAMGeo3 http://localhost:8002 Geospatial segmentation
DepthPro http://localhost:8003 Monocular depth estimation

Configure via MOONDREAM_URL, SAMGEO3_URL, DEPTHPRO_URL in .env.

Credits

License

MIT — Nicolas LAVAL, Cerema Méditerranée

MCP Server · Populars

MCP Server · New

    LeulAria

    Aria Icons

    MCP server for 340k SVG icons

    Community LeulAria
    knowall-ai

    Reverie — graph memory that dreams

    Memory management MCP server for AI agents using Neo4j knowledge graphs

    Community knowall-ai
    keploy

    Key Highlights

    Open-source platform for creating safe, isolated production sandboxes for API, integration, and E2E testing.

    Community keploy
    hermes-labs-ai

    Fidelis Memory

    Zero-LLM agent memory for Claude Code and AI agents: local-first BM25, dense-vector, and reciprocal-rank-fusion retrieval. Returns original passages verbatim by default. Available on PyPI as fidelis-memory. MIT.

    Community hermes-labs-ai
    n24q02m

    Better Code Review Graph

    Knowledge graph for token-efficient code reviews -- semantic search and call-graph resolution across your codebase.

    Community n24q02m