Imported from nkarasiak/qgis-mcp (
qgis_mcp_plugin/AGENTS.md). Install upstream withnpx skills add nkarasiak/qgis-mcp --skill qgis_mcp_plugin. Copyright stays with the author.
AGENTS.md — qgis_mcp_plugin/
Plugin-side details. See the repository-root AGENTS.md for the architecture
overview, the socket protocol and the shared conventions.
Key Details
- Raster styling:
set_raster_stylecoverssingleband_pseudocolor,singleband_gray,multiband_colorandhillshade;set_layer_stylestays vector-only. Unsetmin_value/max_valuefall back tobandStatistics. - Message log capture: Plugin connects to
QgsApplication.messageLog().messageReceivedon start, stores up to 1000 entries in adeque. Disconnects on stop. - Processing outputs are verified:
processing.run()returns an algorithm's declared outputs whether or not the run succeeded. The GDAL providers shell out and report a non-zero exit code only through the feedback object, so a command that wrote nothing still came back as{"OUTPUT": "/path/never/written"}(issue #40). Everyprocessing.run()in the plugin goes throughProcessingHandlers._run_alg, which enforces the deadline, then raisesCommandErrorwhen a caller supplied output path is not on disk afterwards (_missing_outputs, skippingTEMPORARY_OUTPUT,memory:and provider uris). A file that already existed must also have changed (mtime/size, snapshotted by_output_files_before), since a failed GDAL run leaves an earlier run's file in place. The check is "did the file appear or change", never "was anything reported": GDAL writes non-fatal warnings to stderr on runs that succeed, and_ResponsiveFeedback.reportErrorcollects those intoexecute_processing'swarningskey instead of failing the call.execute_processing(load_results=True)swapsprocessing.runforprocessing.runAndLoadResults, which rewrites the destination entries of the parameters dict in place intoQgsProcessingOutputLayerDefinitionobjects - so_run_algverifies againstdeclared, a snapshot taken before the run, or the check would find no string paths left and silently pass everything (#46). Which layers were loaded is answered by diffingQgsProject.instance().mapLayers()around the run: only feature sink, vector and raster destinations are loaded (never file or folder ones), and the name QGIS gives a loaded layer is not the caller's output value. - Plugin error model: handlers raise
CommandError(or itsLayerNotFound/WrongLayerTypesubclasses) fromerrors.pyfor anything the caller can act on - bad parameters, missing layer, unsupported option._dispatchreturns those as{"status": "error", "message": ...}and logs them at WARNING. Any other exception is treated as a plugin defect: the traceback is logged at CRITICAL and the response carries"internal": True. Neverraise Exception(...)in a handler - it becomes indistinguishable from a bug._dispatchalso validates the caller's parameters against the handler signature (inspect.signature(...).bind, cached per command) before calling, so a missing or misspelled argument comes back as an ordinary error instead of a CRITICAL traceback in the user's QGIS log. - Table-driven handlers: options with a fixed set of values are class-level dicts, not if/elif chains (
_LAYER_PROPERTIES,_LAYOUT_EXPORTS,_WEB_SERVICES,_INPUT_CLASSES/_INPUT_BUILDERS,_VECTOR_STYLES,_RASTER_STYLES,_SHADER_*,_CONTRAST_ALGORITHMS)._pick(mapping, key, label)does the lookup and raisesUnknown <label>: <key>. Use one of [...]generated from the table's keys, so the accepted-values list in the error can never drift from the code. Values that need attribute lookup on the running QGIS (settings classes, setters, builder methods) are stored as names and resolved withgetattr- also because astaticmethodobject is not callable on Python 3.9. - QGIS 3.x/4.x compat:
qgis_mcp_plugin/compat.pyresolves deprecated enum forms at import time via try/except (e.g.QgsMapLayer.VectorLayer→Qgis.LayerType.Vector). The plugin imports constants likeLAYER_VECTOR,MSG_WARNING,AGG_COUNTfromcompatinstead of using raw enum values. When adding new enum usages, add the compat constant tocompat.pyfirst.