QgisStreamMCP
Allows loading Esri basemaps (e.g., World Topographic) and satellite imagery (e.g., World Imagery) for use as background layers in spatial analysis.
Provides access to French open geospatial data from IGN, including BD TOPO vectors (buildings, roads, hydrography), orthophotos, cadastre, DEM, and administrative boundaries, via WFS, WMS, and WMTS.
Allows loading OpenStreetMap basemap (osm_xyz) as a streaming tile layer for context in QGIS projects.
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., "@QgisStreamMCPRun flood risk analysis for Nimes."
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.
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.
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.
Related MCP server: QGIS MCP
Quick start
1. Run
git clone https://github.com/nic01asFr/QgisRemoteMCP.git
cd QgisRemoteMCP
cp .env.example .env
docker compose up -d --build2. Verify
Service | URL |
MCP Server | |
noVNC (QGIS in browser) | |
REST API | |
MJPEG 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 → deliverDownloads 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 |
| Buildings | nature, usage, height, floors, materials |
| Roads | nature, importance, width, lanes, speed |
| Rivers | name, class, width |
| Communes | name, INSEE code, population |
| Communes (Admin Express) | name, code, population |
| Agricultural parcels | crop type, area |
+ 10 more | BD TOPO vegetation, railways, POI, activity zones... |
Basemaps & imagery (streaming)
ID | Name |
| OpenStreetMap |
| Plan IGN v2 |
| IGN orthophotos |
| Cadastral parcels |
| Land cover 2018 |
| Hillshade from high-resolution DEM (display) |
| 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 |
| Building density | Hex grid + graduated symbology + PDF |
| Urban overview | Buildings, roads, vegetation + categorized styles |
| Flood risk | Flood zones + building exposure + interactive web map |
| Land cover | Corine Land Cover + categorized symbology |
| 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 |
| Print-ready layout (A3 landscape, A4 portrait) with title, legend, scalebar | |
Web map |
| Leaflet HTML with embedded GeoJSON, layer toggle, popups |
Flood map |
| Interactive HTML — water height slider, building exposure stats, animation |
Temporal map |
| Interactive HTML — year slider, trend arrows, animated playback |
QField |
| Portable ZIP for mobile (.qgz + GPKGs + editable Observations layer) |
Grist |
| Collaborative document with typed columns, map widget, form pages |
Layer |
| GPKG, GeoJSON, Shapefile, or CSV |
Grist export — universal HTML converter
export_grist has two modes:
From QGIS project — exports loaded layers as Grist tables with map widget
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 |
| Define study area (commune, address, bbox). Geocodes, stores bbox, zooms. |
| Get current zone (name, bbox in 4326 + 2154). |
| Load data by catalog ID. WFS → local GPKG. Rasters stream. |
| Browse data catalog (filter by category or search). |
| Add source by catalog ID. |
Core
Tool | Description |
| Run PyQGIS code with |
| Capture QGIS canvas (JPEG ≤1MB). Auto-included after modifying tools. |
| Project state: layers, CRS, layouts, extents. |
| Execute any of ~730 Processing algorithms (native, qgis, gdal, grass, 3d). |
| Find algorithms by keyword. |
| 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, an
extractbylocation over 50 000 features, a wide smart_load — should be
submitted rather than awaited. The bridge streams heartbeats, so a frozen
Qt main thread is told apart from a dead bridge without waiting for a
timeout to expire.
Tool | Description |
| Submit a bridge action for background execution, returns a |
| Job status, |
| Best-effort cancellation. |
Data & layers
Tool | Description |
| Add vector/raster/WFS/WMS by URI. Point clouds too — pass |
| Remove a layer. |
| Query features with attribute/spatial filters. |
| Navigate to extent, layer, or point. |
Styling & layout
Tool | Description |
| Single color, categorized, or graduated symbology. |
| Show/hide layers. |
| Apply print layout template (A3/A4). |
| List available templates. |
Recipes
Tool | Description |
| Browse workflow recipes. |
| Get recipe with parameter substitution. |
| Execute all steps automatically. |
Export
Tool | Description |
| Print layout → PDF. |
| Visible layers → Leaflet HTML. |
| Flood analysis → interactive HTML. |
| Time series → interactive HTML. |
| QField-ready ZIP (.qgz + GPKGs + Observations). |
| Grist document from project or HTML. |
| Layer → GPKG/GeoJSON/Shapefile/CSV. |
| Publish a deliverable (storymap, dataset, recipe, PDF) to S3 through the hub, returns a stable public URL. |
Files
Tool | Description |
| Upload via multipart POST (any size), URL fetch, or base64. |
| Download from /data/ (URL for user, inline for small files). |
| List files in /data/. |
| Delete file from /data/. |
| Save as .qgz. |
GUI interaction
Tool | Description |
| Open interactive QGIS view in conversation (MCP App). |
| Direct GUI interaction. |
Project management
Tool | Description |
| Project lifecycle. |
MCP resources & prompts
Resources (skill documents)
URI | Content |
| Smart loading pipeline, CRS handling, caching |
| PyQGIS scripting patterns & API usage |
| Processing algorithms guide (native, GDAL, GRASS) |
| Symbology, labels, layouts, PDF export |
| Python helpers (geocode, add_wfs, zoom_to, overpass_query...) |
| French national datasets reference |
| Workflow recipes reference |
| Vision services integration |
| Solar cadastre pipeline (DSM, r.sun, observatory) |
| bridge_put / bridge_get file transfer — design, pending implementation |
| Live QGIS instance status |
Prompts
Prompt | Description |
| Territory analysis template (zone + question) |
| 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=30Uncomment 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 containerContainers 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 |
|
| Register user, get API key |
|
| Login, get token |
|
| Current user's container info |
|
| 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:
NVIDIA GPU
No configuration needed — auto-detection with graceful fallback to CPU
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) → browserWorkflow 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 askill://my-domainURIAdd to the
_SKILL_MAPdict
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 | Title, subtitle, legend, scalebar, north arrow, data sources |
| 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 |
| General map | Layer toggle, popups, legend, basemap selector |
| Flood analysis | Water height slider, building exposure stats, animation |
| 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:
Add domain skills —
skills/urbanisme.md,skills/hydrologie.md, etc.Add domain data sources — extend
datasources.jsonwith relevant WFS/WMSCreate domain recipes — automated workflows for common analyses
Design layout templates — branded
.qptwith your organization's logo and styleCustomize 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 # MITDevelopment
Source files are mounted as volumes in dev — edit locally, restart to apply:
docker compose restart qgisremotemcp
docker compose logs -f qgisremotemcpQGIS 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
helpersmodule (geocode, overpass, smart loading)~730 Processing algorithms (native, qgis, GDAL, GRASS, 3d) — SAGA n'est pas 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 simplicityIn-memory sessions in multi-user mode — no persistence across gateway restarts
No automated tests yet
French-focused data sources (IGN, BD TOPO) — extend
datasources.jsonfor other countries
Compatible MCP clients
Tested with:
Claude Desktop (Windows, macOS)
Claude Code (CLI, VS Code, JetBrains)
Any MCP client supporting Streamable HTTP transport
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.jsonfor non-French datasetsNew 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
Fork the repo
Create a feature branch (
git checkout -b feat/my-feature)Test with a real QGIS container (
docker compose up -d)Submit a merge request
See CONTRIBUTING.md for detailed guidelines.
External vision services (optional)
Service | Default URL | Purpose |
Moondream | Image captioning, VQA | |
SAMGeo3 | Geospatial segmentation | |
DepthPro | Monocular depth estimation |
Configure via MOONDREAM_URL, SAMGEO3_URL, DEPTHPRO_URL in .env.
Credits
QGIS — https://qgis.org
noVNC — https://novnc.com
IGN Geoplateforme — https://data.geopf.fr
GDAL/OGR — https://gdal.org
License
MIT — Nicolas LAVAL, Cerema Méditerranée
This server cannot be deployed
Maintenance
Related MCP Connectors
Geospatial AI MCP server — satellite imagery, embeddings, weather, GNS governance
MCP server for Japan geodata: cadastral lot numbers (chiban) and reverse geocoding, for AI agents.
Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.
MCP server giving AI agents one-connection access to supply-chain & logistics data: AIS vessel track
Related MCP Servers
- AlicenseBqualityDmaintenanceA Model Context Protocol server that connects Claude AI to QGIS, enabling direct interaction with the GIS software for project creation, layer manipulation, code execution, and processing algorithms through natural language prompts.1510Creative Commons Zero v1.0 Universal
- AlicenseBqualityAmaintenanceAn MCP server that enables AI assistants to directly control QGIS for tasks like layer management, feature editing, and map rendering. It provides a suite of 50 tools to execute processing algorithms and manage GIS projects through natural language commands.118281GPL 2.0
- FlicenseBqualityDmaintenanceQGISMCP connects QGIS to Claude AI through the Model Context Protocol (MCP), allowing Claude to directly interact with and control QGIS for project creation, layer loading, code execution, and more.151-
- 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