Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
Your own AI agent, running 24/7 with QwikClaw logoYour own AI agent, running 24/7 with QwikClaw

QwikClaw sets up and runs an always-on OpenClaw agent for you. One click, no config files, no server setup.

Deploy now
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 47,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Deploy QGIS in Docker as an MCP server, enabling AI assistants to perform spatial analysis, data loading, cartography, and multi-format export without local installation.

README.md

QgisStreamMCP

QGIS as a service for your AI assistant — Deploy once, use QGIS from anywhere. No local installation required, no GIS expertise needed.

Run a full QGIS Desktop inside Docker and expose it as an MCP server. Your AI assistant gets 42 tools covering data loading, spatial analysis, cartography and multi-format export. You don't need QGIS installed — the AI handles it entirely on your behalf, from any device, at any time.

Administrators deploy the container once (on a server, a NAS, or locally). Users connect their AI client and immediately have QGIS capabilities available in their conversations.

<p align="center"> <img src="docs/architecture.png" alt="Architecture" width="700"> </p>

Highlights

  • No QGIS Required — The full QGIS Desktop runs in Docker. Your AI assistant uses it on your behalf — no local installation, no GIS expertise needed.
  • Available Anywhere — Deploy on a server or locally. Connect from any MCP-compatible AI client (Claude Desktop, Cursor, Continue...).
  • Smart Data Pipelineset_study_zone("Montpellier") then smart_load("bdtopo_batiments"). Downloads WFS as local GeoPackage with automatic pagination, R-tree spatial index, CRS transform. Processing runs 60-250x faster than live WFS.
  • 30+ Pre-configured French Datasets — BD TOPO, Admin Express, RPG, IGN orthophotos, cadastre, DEM, Corine Land Cover, OSM, Esri. No API key needed.
  • Full QGIS Desktop — Live GUI via noVNC. AI and user work on the same instance simultaneously.
  • 1000+ Processing Algorithms — Native, GDAL, GRASS, SAGA. All accessible via MCP tools.
  • Automated Recipes — Pre-built workflows (density analysis, flood risk, land cover, coastal pressure). One command: run_recipe("risque_inondation", zone="Nimes").
  • Multi-format Export — PDF layouts, interactive Leaflet maps (standard, flood analysis, temporal), QField mobile packages, Grist collaborative documents.
  • MCP App — Interactive QGIS view embedded directly in the conversation (VNC viewer, file upload, keyboard/mouse forwarding).

Quick Start

git clone https://github.com/nic01asFr/QgisStreamMCP.git
cd QgisStreamMCP
docker compose up -d --build

Endpoints

| Port | Service | URL | |------|---------|-----| | 6080 | noVNC (QGIS in browser) | http://localhost:6080 | | 8100 | MCP Server (Streamable HTTP) | http://localhost:8100/mcp | | 8080 | REST API | http://localhost:8080/docs | | 8081 | MJPEG stream | http://localhost:8081/stream |

Connect to Claude Desktop

Add to claude_desktop_config.json:

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

Smart Data Loading

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

Why?

| 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 (network + no index) | Fast local file | | Network during analysis | HTTP requests per feature | Zero network | | CRS confusion | Mixed 4326/3857/2154 | Standardized EPSG:2154 |

Pipeline

1. set_study_zone(target="Montpellier")    # Geocode, store bbox, zoom
2. smart_load(id="osm_xyz")                # Basemap (streaming)
3. smart_load(id="bdtopo_batiments")       # Buildings (download GPKG)
4. smart_load(id="bdtopo_routes")          # Roads (download GPKG)
5. get_screenshot                           # Verify
6. run_processing / execute_python          # Analyze
7. export_pdf / export_web_map / ...        # Deliver (PDF, HTML, QField, Grist)

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 | | Dissolve by usage | 10,000 | 2.2s | | Density grid 500m | 440 cells | 0.3s |

Caching

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

Available Data Sources

All sources are free (IGN open data since July 2021). No API key needed.

Vector Data (WFS, downloaded as 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_vegetation | Vegetation zones | nature | | bdtopo_voie_ferree | Railways | nature, nb_voies | | bdtopo_hydro_surfaces | Water bodies | nature, name | | bdtopo_communes | Communes (BD TOPO) | name, code INSEE, population | | admin_express_communes | Communes (Admin Express) | name, code, population | | admin_express_departements | Departments | name, code | | admin_express_regions | Regions | name, code | | rpg | Agricultural parcels | crop type, area | | bdtopo_poi | Points of interest | nature | | bdtopo_lieu_dit | Place names | name | | bdtopo_surface_activite | Activity zones | nature | | bdtopo_equipement_transport | Transport facilities | nature |

Basemaps (streaming)

| ID | Name | |----|------| | osm_xyz | OpenStreetMap | | ign_planign | Plan IGN v2 | | ign_scan25 | Cartes topo IGN 1:25000 | | cartodb_positron | Light basemap | | cartodb_dark | Dark basemap | | esri_world_topo | Esri World Topographic | | stamen_terrain | Stamen Terrain (relief) |

Imagery (streaming)

| ID | Name | |----|------| | ign_ortho_wmts | IGN orthophotos (WMTS, fast) | | ign_ortho_wms | IGN orthophotos (WMS) | | ign_ortho_irc | IGN infrared photos | | esri_world_imagery | Esri satellite |

Other (WMS/API)

| ID | Name | |----|------| | ign_cadastre | Cadastral parcels | | ign_dem | High-resolution DEM | | corine_land_cover | Land cover 2018 | | ban_geocode | Address geocoding API | | geo_api_communes | Commune info API | | dvf_api | Property transactions API | | panoramax | Street-level imagery API | | ign_altimetrie | Elevation API |

Recipes

Pre-built workflow templates that automate complete analyses — from data loading to styled map export.

| ID | Name | Description | |----|------|-------------| | densite_bati | Building Density | Hex grid density analysis with graduated symbology | | urbanisme_general | Urban Overview | Buildings, roads, vegetation, hydrology with categorized styles | | risque_inondation | Flood Risk | Flood zones, building exposure, buffer analysis + interactive web map | | occupation_sol | Land Cover | Corine Land Cover with categorized symbology | | pression_fonciere_cotiere | Coastal Land Pressure | DVF transactions 2020-2024, coastal bands + temporal web map |

Usage

# 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")
→ Returns step-by-step instructions to execute individually

Export Formats

PDF Layout

Print-ready PDF via QGIS print layouts with pre-built templates (A3 landscape, A4 portrait). Includes title, legend, scalebar, north arrow, and data sources.

apply_layout_template(template="a3_landscape", title="Flood Risk — Nimes")
export_pdf(layout="a3_landscape")

Interactive Web Map

Leaflet HTML files with embedded GeoJSON data. Three specialized templates:

| Template | Use case | Features | |----------|----------|----------| | Standard | General map | Layer toggle, popup, legend, basemap selector | | Flood | Flood risk analysis | Water height slider, building exposure stats, animation | | Temporal | Time series | Year slider, per-band statistics, trend arrows, animated playback |

QField Mobile Package

Portable ZIP ready for QField mobile data collection:

  • .qgz project with relative GPKG sources
  • All vector layers materialized as individual GeoPackages
  • Editable Observations layer with form widgets (dropdowns, date picker, camera, free text)
export_qfield(project_name="terrain_survey")
→ /data/terrain_survey_qfield.zip

Grist Document

Converts QGIS project layers or any HTML map into a Grist collaborative document (.grist):

  • From project — Exports visible vector layers as Grist tables with a custom map widget
  • From HTML — Universal converter: takes any HTML file containing GeoJSON (flood maps, temporal maps, qgis2web exports) and creates a Grist document with data in tables and the original interactive map as a Grist custom widget

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

# From QGIS project
export_grist(title="Urban Analysis")

# From any HTML with GeoJSON
export_grist(html_path="/data/flood_map_nimes.html")

MCP Tools

Smart Loading

| Tool | Description | |------|-------------| | set_study_zone | Define study area (commune, address, bbox). Geocodes, stores bbox, zooms canvas. | | get_study_zone | Get current study zone (name, bbox in 4326 + 2154). | | smart_load | Load data by catalog ID. WFS → local GPKG with spatial index. Rasters stream. |

Core

| Tool | Description | |------|-------------| | execute_python | Run PyQGIS code with full access to iface, project, processing, helpers module. | | get_screenshot | Capture QGIS canvas as PNG. Auto-included after modifying tools. | | get_project_info | Current project state (layers, CRS, layouts, extents). | | run_processing | Execute any of 1000+ Processing algorithms. | | search_algorithms | Find Processing algorithms by keyword. | | zoom_to | Navigate to extent, layer, or point. |

Data & Layers

| Tool | Description | |------|-------------| | add_layer | Add vector/raster/WFS/WMS by URI. | | remove_layer | Remove a layer. | | get_features | Query features with attribute/spatial filters. | | list_datasources | Browse the pre-configured data catalog. | | add_from_catalog | Add a source by catalog ID. |

Styling

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

Recipes

| Tool | Description | |------|-------------| | list_recipes | Browse available workflow recipes. | | get_recipe | Get recipe details with parameter substitution. | | run_recipe | Execute a complete recipe automatically (all steps in one shot). |

Export

| Tool | Description | |------|-------------| | export_pdf | Export print layout to PDF. | | export_web_map | Export visible layers as interactive Leaflet HTML. | | export_flood_map | Interactive flood analysis HTML (water height slider, building exposure). | | export_temporal_map | Interactive temporal analysis HTML (year slider, animated playback). | | export_qfield | QField-ready ZIP package (.qgz + GPKGs + editable Observations layer). | | export_grist | Grist document from project layers or from any HTML with GeoJSON. | | export_layer | Export vector layer to GPKG, GeoJSON, Shapefile, CSV. |

Files

| Tool | Description | |------|-------------| | upload_file | Upload file (shapefile, GeoJSON, GPKG, CSV, TIFF, project). | | download_file | Download file from /data/. | | list_files | List files in /data/. | | delete_file | Delete file from /data/. | | download_project | Save project as .qgz. |

GUI Interaction

| Tool | Description | |------|-------------| | qgis_desktop_ui | Open interactive QGIS MCP App in conversation. | | mouse_click / mouse_scroll / mouse_drag / key_press | Direct GUI interaction via xdotool. |

Projects

| Tool | Description | |------|-------------| | new_project | Create empty project. | | open_project | Open a .qgz project. | | save_project | Save current project. |

MCP Skills (Resources)

Reference documents that guide the AI assistant's expertise:

| Resource URI | Content | |-------------|---------| | skill://smart-loading | Smart Loading Pipeline — set_study_zone + smart_load, CRS handling, caching | | skill://pyqgis | PyQGIS scripting patterns & API usage | | skill://processing | Processing algorithms guide (native, GDAL, GRASS) | | skill://cartography | Symbology, labels, print layouts, PDF export | | skill://helpers | Ready-made Python helpers (geocode, add_wfs, zoom_to, create_point_layer...) | | skill://data-sources | French national datasets reference | | skill://recipes | Workflow recipes reference | | skill://external-services | Calling external HTTP services from PyQGIS scripts | | skill://qgis-status | Live QGIS instance status |

MCP Prompts

| Prompt | Description | |--------|-------------| | analyse_territoire | Template for territory analysis (zone + question). | | workflow_donnees | Guided workflow — theme-based loading (urbanisme, environnement, transport, agriculture, risques) with step-by-step instructions. |

MCP App

An interactive QGIS view embedded directly in the Claude conversation:

  • Live VNC viewer — See and interact with QGIS in-conversation
  • File upload — Drag & drop files directly into the QGIS container
  • Keyboard/mouse forwarding — Full interaction without leaving the chat
  • MJPEG fallback — Lightweight stream for quick visual feedback

Example Workflows

Manual: Urban Analysis

User: "Analyse l'urbanisation autour de Montpellier"

AI: [set_study_zone("Montpellier")]
    → Geocodes, stores bbox, zooms canvas

    [smart_load("osm_xyz")]              → OpenStreetMap basemap
    [smart_load("bdtopo_batiments")]     → 10,000 buildings as GPKG
    [smart_load("bdtopo_routes")]        → 5,000 road segments

    [execute_python → density grid]      → 500m hex grid, graduated symbology
    [apply_layout_template("a3_landscape", title="Densité bâtie — Montpellier")]
    [export_pdf]                         → /data/densite_montpellier.pdf

Automated: Flood Risk with Recipe

User: "Analyse le risque inondation à Nîmes"

AI: [run_recipe("risque_inondation", zone="Nimes")]
    → Executes all steps automatically:
      1. set_study_zone("Nimes")
      2. smart_load basemap + buildings + flood zones
      3. Buffer analysis (50m, 100m, 200m from flood zones)
      4. Building exposure classification
      5. Graduated symbology
    → Returns screenshot + statistics

    [export_flood_map(include_fields=["nature","usage","height"])]
    → Interactive HTML with water height slider

    [export_grist(html_path="/data/flood_map_nimes.html")]
    → Grist document with editable tables + embedded map widget

Field Survey: QField Export

User: "Prépare un relevé terrain pour la commune de Sète"

AI: [set_study_zone("Sète")]
    [smart_load("bdtopo_batiments")]
    [smart_load("bdtopo_routes")]
    [set_layer_style("Batiments", type="categorized", field="usage")]

    [export_qfield(project_name="releve_sete")]
    → ZIP with .qgz + GPKGs + Observations layer (camera, dropdowns, date picker)
    → Ready to load on QField mobile app

Extending with External Services

PyQGIS scripts (execute_python) can call any HTTP service accessible from the container — local or remote. This covers ML inference APIs, custom backends, satellite processors, elevation services, or any internal API.

Pass service URLs via environment variables in .env:

MY_SERVICE_URL=http://host.docker.internal:8001

Then use them in scripts:

import os, urllib.request, json
url = os.environ.get("MY_SERVICE_URL")
# call your service...

host.docker.internal resolves to the host machine, allowing access to services running outside the container.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    QgisStreamMCP 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)                     │
│  /data/cache/    (smart_load GPKG cache, 24h)               │
│  /app/skills/    (MCP skill documents)                      │
│  /app/datasources.json  (30+ pre-configured sources)        │
└──────────┬──────────────────────────────────────────────────┘
           │ HTTP (optional)
           ▼
    External services     (any HTTP API reachable from the container)

Communication Flow

Claude Desktop / MCP Client
  │ JSON-RPC over Streamable HTTP (:8100)
  ▼
main_mcp.py (MCP Server)
  │ UNIX socket /tmp/qgis_bridge.sock
  ▼
qgis_bridge.py (runs inside QGIS, main thread)
  │ PyQGIS API (iface, QgsProject, processing)
  ▼
QGIS Desktop (Xvfb display :99)
  │ X11
  ▼
x11vnc → websockify → noVNC (:6080)
  │ WebSocket
  ▼
User's browser

Development

# Source files are mounted as volumes — edit locally
# Restart to apply changes:
docker compose restart qgisstreammcp

# View logs
docker compose logs -f qgisstreammcp

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

# Test smart loading
curl -X POST http://localhost:8080/api/execute \
  -H "Content-Type: application/json" \
  -d '{"code": "result.update(helpers.set_study_zone(\"Montpellier\"))", "timeout": 30}'

Project Structure

QgisStreamMCP/
├── main_mcp.py             # MCP Server (40 tools, 10 resources, 3 prompts)
├── datasources.json        # 30+ pre-configured data sources catalog
├── qgis_app.html           # MCP App (interactive QGIS in conversation)
├── src/
│   ├── qgis_bridge.py      # Runs inside QGIS (UNIX socket bridge, 45 actions)
│   ├── qgis_helpers.py     # Python helpers (geocode, smart loading, etc.)
│   ├── api_server.py       # FastAPI REST API
│   └── stream_server.py    # MJPEG stream
├── skills/                 # MCP Resources (AI skill documents)
│   ├── smart_loading.md
│   ├── pyqgis.md
│   ├── processing.md
│   ├── cartography.md
│   ├── helpers.md
│   ├── data_sources.md
│   └── external_services.md
├── recipes/                # Workflow recipes (JSON)
│   ├── densite_bati.json
│   ├── urbanisme_general.json
│   ├── risque_inondation.json
│   ├── occupation_sol.json
│   └── pression_fonciere_cotiere.json
├── templates/              # Print layout templates (.qpt)
│   ├── a3_landscape.qpt
│   ├── a4_portrait.qpt
│   └── web/                # Leaflet HTML templates
│       ├── leaflet_template.html
│       ├── leaflet_flood_template.html
│       └── leaflet_temporal_template.html
├── projects/               # QGIS project files (persisted)
├── docs/                   # Architecture diagrams
├── Dockerfile
├── docker-compose.yml
├── supervisord.conf
├── entrypoint.sh
├── requirements.txt
└── CLAUDE.md

License

MIT

Credits

  • QGIS — https://qgis.org
  • noVNC — https://novnc.com
  • MCP — https://modelcontextprotocol.io
  • IGN Geoplateforme — https://data.geopf.fr (free French national geodata)
  • GDAL/OGR — https://gdal.org (data conversion & download engine)

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Cloud & DevOps servers.