MapSmith
Shaded relief of Mount St. Helens with six drainage basins tinted over it, radiating from the crater.
Mount St. Helens, USGS 3DEP · EPSG:32610 · flow accumulation, then watersheds from its six principal outlets. Rendered from the GeoTIFFs MapSmith wrote, by the build that published this page. Not an illustration.

Geoprocessing for AI agents, with provenance you can check.

MapSmith is an open-source MCP server that gives an agent real GIS analysis — buffers, overlays, reprojection, zonal statistics, terrain, hydrology — run by GeoPandas, DuckDB Spatial, exactextract and Whitebox. Never written by the model.

Ask for the result. The agent picks the tools.
You can check the work afterwards.

Every dataset it writes lands on disk beside a lineage manifest — the one below came with the image on the left. It is the whole argument, so it is the first thing on this page.

basins.tif.provenance.json

What came with the picture

Not a log. A record complete enough that someone who was not there can re-run the analysis and disagree with it — which is a different and much higher bar than being able to read what happened.

  • inputs[].sha256

    The bytes, not the filename. A manifest whose input has been edited since stops matching, and says so.

  • crs_decisions.reason

    Why this coordinate system, in words. Silent reprojection is the most expensive habit in the field.

  • engine.version

    Which build produced the numbers. Terrain algorithms change between releases, and so do their outputs.

  • verification[]

    Deterministic checks, recorded whether they passed or failed. Written before a critical failure raises, so the audit trail survives the error.

  • started_at / finished_at

    UTC, from the process that ran it — not from whoever is reading it later.

{ "operation": "watershed", "parameters": { "method": "d8", "preprocessing": "fill_depressions", "n_pour_points": 6 }, "inputs": [ { "path": "dem.tif", "sha256": "166b1e4e7bc975d81ba049311c5713365e090dd1e8944a944ca241acf22633fa", "crs": "EPSG:32610" }, { "path": "outlets.gpkg", "sha256": "4889c3650743256fa18eea0cce901574b79c0869439c68a4fa1118281db35186", "crs": "EPSG:32610" } ], "crs_decisions": { "analysis_crs": "EPSG:32610", "reason": "pour points and DEM share the same CRS" }, "engine": { "name": "whitebox-workflows", "version": "2.0.6" }, "verification": [ { "name": "crs_present", "passed": true, "detail": "EPSG:32610", "critical": true, "hint": null, "argument": null }, { "name": "crs_matches", "passed": true, "detail": "expected EPSG:32610, got EPSG:32610", "critical": true, "hint": null, "argument": null }, { "name": "dimensions_match_input", "passed": true, "detail": "expected (520, 520), got (520, 520)", "critical": true, "hint": null, "argument": null }, { "name": "has_valid_cells", "passed": true, "detail": "81367/270400 valid cells", "critical": true, "hint": null, "argument": null }, { "name": "values_in_expected_range", "passed": true, "detail": "valid cells in [1, 6], expected [1, 6]", "critical": true, "hint": null, "argument": null } ], "repairs": [], "notes": [ "input GeoTIFF is stored with TIFF predictor 2; the engine was given a copy with identical values and no predictor, because whitebox_workflows 2.x does not undo the TIFF predictor when decompressing (see _needs_plain_copy for the measurements)" ], "mapsmith_version": "0.2.2", "started_at": "2026-08-23T13:31:08Z", "finished_at": "2026-08-23T13:31:09Z", "parameters_redacted": false }

Install

One entry in your MCP client

Works with Claude Desktop, Claude Code, Cursor and VS Code over stdio. Docker is the other supported path; both are documented in the README.

{
  "mcpServers": {
    "mapsmith": {
      "command": "uvx",
      "args": ["mapsmith"]
    }
  }
}

Point MAPSMITH_WORKSPACE at a directory and every path argument is confined to it. Paths come from a language model; they are treated as untrusted input.

Operations

16 tools, not a catalogue

Tool-selection accuracy degrades past a few dozen exposed tools, so capability lives behind a searchable catalogue rather than in the tool list. What is exposed is what an agent has to choose between.

buffer_layer · clip_layer · overlay_layersVector geometry, with the metric-on-geographic trap handled explicitly rather than silently
reproject_layerCoordinate systems, with the decision and its reason recorded
zonal_statisticsRaster statistics per polygon, via exactextract — partial pixels weighted, not counted or dropped
hillshade · slope · flow_accumulation · watershedTerrain and hydrology on Whitebox Workflows
run_sqlSpatial SQL on DuckDB, sandboxed to the workspace with external access off
validate_plan · run_planA typed DAG checked before anything executes: stable error codes, simulated CRS flow
preview_mapAn in-chat map panel that shows the verification status of every layer it draws

Evidence

The headline of our own benchmark is a null result

375TASK-RUNS, 5 ARMS
242TESTS, CLOSED-FORM

Published with the analysis that took our own positive number apart, and with a conclusion we had already published and had to correct rather than delete.

Enforcing a validated plan buys reproducibility, not accuracy. The flow-aware gate we expected to help moved nothing measurable. The one effect outside the noise came from telling the planner the argument rules up front.

Saying so is the point. A geospatial agent can pick the right tools in the right order, write a file, and hand you a number that is wrong — and every trajectory benchmark in circulation scores that run full marks.

Read the benchmark →

Invariants

What it will not do

Invent a number

Geometry and statistics come from engine executions. If a model produced it, it is not an output.

Guess a coordinate system

An input without a CRS is refused with a message that says what to fix. A declaration MapSmith will not resolve is quoted back, not swallowed.

Write without a manifest

Every writer emits one. A dataset with no lineage beside it did not come from here.

Hide a failed check

Verification is recorded before a critical failure raises, so the diagnosis outlives the error.

Reach the network quietly

Remote paths are opt-in, enforced at the GDAL driver level — a local file that makes GDAL fetch a URL is the same hole with a different name.

Keep a credential

SQL that configures one is refused rather than redacted, because redaction is a text scan and an audit walked past it four ways.