Use mapcv with AI agents
mapcv ships an MCP server and an agent skill. Together they let a coding agent go through the same journey you would, inspect the labels → write a config → validate → plan → generate → check, without guessing at config keys: the agent reads the schema from the real models and gets mapcv’s own error messages.
Install
Section titled “Install”The server is an optional extra, so the base install stays small:
pip install "mapcv[mcp]"mapcv mcp --helpWithout the extra, mapcv mcp prints the install command and exits. The examples below use uvx, which runs the server without installing anything into your project; replace it with mapcv mcp if you installed mapcv yourself.
Every client starts the server the same way:
uvx --from "mapcv[mcp]" mapcv mcp # read-only, in the current folderuvx --from "mapcv[mcp]" mapcv mcp --root ~/datasets # the only folder it may touchuvx --from "mapcv[mcp]" mapcv mcp --root ~/datasets --allow-writeThe environment variable MAPCV_MCP_ALLOW_WRITE=true does the same as --allow-write, for clients that configure servers through the environment (the Claude Code plugin uses it).
The plugin installs the skill and registers the server in one step:
/plugin marketplace add tahamukhtar20/mapcv/plugin install mapcv@mapcvThe plugin asks once whether the agent may write datasets (off by default). The server works in the folder Claude Code was started in.
To register only the server, without the skill:
claude mcp add mapcv -- uvx --from "mapcv[mcp]" mapcv mcpclaude mcp add --scope project mapcv-write -- uvx --from "mapcv[mcp]" mapcv mcp --allow-writeOpen Settings → Developer → Edit Config and add the server to claude_desktop_config.json. Desktop does not start servers in your project folder, so always give an absolute --root:
{ "mcpServers": { "mapcv": { "command": "uvx", "args": ["--from", "mapcv[mcp]", "mapcv", "mcp", "--root", "/Users/you/datasets"] } }}Add "--allow-write" to args to let it generate datasets. Restart Claude Desktop afterwards.
Add the server to .cursor/mcp.json in your project, or to ~/.cursor/mcp.json for every project:
{ "mcpServers": { "mapcv": { "type": "stdio", "command": "uvx", "args": ["--from", "mapcv[mcp]", "mapcv", "mcp"] } }}Cursor starts it in the project folder. Add "--allow-write" to args to allow writing.
Add the server to .vscode/mcp.json (note the top-level key is servers):
{ "servers": { "mapcv": { "type": "stdio", "command": "uvx", "args": ["--from", "mapcv[mcp]", "mapcv", "mcp"] } }}Or run MCP: Add Server from the Command Palette. Add "--allow-write" to args to allow writing.
Any other MCP client works too: the server speaks MCP over stdio.
Safety model
Section titled “Safety model”An agent can run long jobs, download imagery and write files, so the server is built to be hard to misuse. These rules are enforced by the server, not left to the agent’s judgement.
| Rule | What it means |
|---|---|
| Read-only by default | Without --allow-write the tools write_config, generate and split do not exist: the server does not list them, so there is nothing to call or refuse. The server’s instructions tell the agent which mode it is in, and that it must ask you to restart with --allow-write to write. The agent can still inspect labels, validate and plan, read a finished dataset, and compute stats and verify (which write stats.json or SHA256SUMS only with --allow-write). |
| One folder | --root (default: the current folder) is the only place the server reads or writes. Every path an agent passes, and every path a config resolves to (labels.path, a local imagery.path, writer.staging_dir), must be inside it after symbolic links are resolved. .., absolute paths elsewhere and links that point out are refused with a message that names the root. A Shapefile’s sidecar files (.prj, .dbf, …) count as paths too, and so does a local file a STAC catalog names. In an existing dataset folder, a symbolic link that points outside and a hard link (a file with a second name, which may be outside) are refused before anything is written. |
| Plan first, confirm big jobs | generate plans before it starts and refuses a job that plan marks large (over 20,000 tiles, about 5 GB of download plus output, or 1,000,000 patches) unless the call passes confirm_large=true. The refusal carries the plan, so the agent can show it to you and ask. |
| Credentials stay hidden | Whatever url_template carries (keys in the query, tokens in the path, user information) is removed from every tool result and error, including the SDK’s own errors for arguments of the wrong type, the way the CLI shows only scheme://host/.... With --allow-local-urls, a url_template on a link-local or cloud metadata address (such as 169.254.169.254) gets a warning from validate_config and plan. Do not paste a key into the chat: write the config with a placeholder and put the real key into the file yourself. |
| Public addresses only | The tools connect to public addresses only, so an agent (or a label file that talks it into a URL) cannot use the server to reach this machine or your private network. A URL that names localhost, a loopback, private, link-local or carrier-grade NAT address is an error in validate_config and plan; a host name that resolves to one, and a redirect to one, are refused when connecting. The refusal reads the same whether something listens there or not. To use a tile or GeoTIFF server on your own machine or network, start the server with --allow-local-urls. |
| Config files only | write_config writes only .yaml or .yml files, and only after the content validates as a mapcv config. |
| Interruptible | generate reports progress per chunk and stops when the call is cancelled. The chunk being written is finished first (minutes for a big region on one connection), then the run stops; finished chunks are kept and calling it again resumes. plan, info and the other read tools keep answering meanwhile. |
What the server does not do: it does not limit which public hosts a config’s url_template or remote GeoTIFF may contact, and a config can name any public https:// imagery. Review the config the agent wrote, as you would any file it produces. Each imagery source’s license and credit line are listed under Providers & licensing.
Two things work differently from the CLI, because both would write outside --root: generate does not use the on-disk tile cache, and plan and generate refuse labels.osm, whose answer is cached there (run mapcv plan / mapcv generate in a terminal, or point labels.path at an OSM extract). validate_config and write_config accept labels.osm but return a warning saying so, so the agent learns it before it plans.
To get an OSM extract over MCP, have the agent fetch it with curl and convert it with jq; the skill has the commands. Overpass answers HTTP 406 to a request without a User-Agent, and can answer HTTP 200 with an HTML error page when it is busy (retry later, use a smaller box or a mirror such as overpass.kumi.systems).
| Tool | Needs --allow-write |
What it does |
|---|---|---|
describe_config_schema |
no | Every config model’s fields with type and default, generated from the pydantic models, plus the rules between fields (which tasks need labels, which image formats each imagery type accepts, the exact message of each invalid combination). Found by validating real configs, so it cannot go stale. About 3,000 tokens; section (for example labels) narrows it to one key, full_schema: true adds the raw JSON schema. |
validate_config |
no | The checks and messages of mapcv validate, for a file or for text, and more. Misspelt keys are reported together with the other independent errors, each with the closest valid key (“Did you mean ‘source’?”). The label and imagery files must exist, a labels.label_field must be an attribute of the label file (with a suggestion), and writer.stack_sources needs sources with matching bands: each is an error (valid: false), because plan or generate would fail. warnings carry what this server cannot do (labels.osm). Imagery is not read. |
inspect_labels |
no | Fields of a vector label file (GeoJSON, KML, GeoPackage, Shapefile or GeoParquet) with distinct values and counts, feature and geometry counts, the extent in lon/lat (usable as region) and which fields can be labels.label_field. |
plan |
no | Tiles, patches, sizes, memory and warnings, like mapcv plan, plus large and the reason. Downloads nothing. It does not estimate what it does not download: download_bytes is null for files and catalogs, and the Sentinel-2 scene is chosen at generate; notes say so. blocking lists problems that make generate stop (such as sources that cannot be stacked, or a grid of more than 3,000,000 patches, which plan counts without building). It runs while a generate runs. |
info |
no | A dataset’s task, every imagery source (sources; source is the first), class balance, split sizes and complete: false for a dataset whose generate was interrupted (call generate again to resume), null for one written by an older mapcv. |
stats |
no (save needs it) |
Per-band mean and std, class balance and median-frequency class weights, like mapcv stats, for train (default), val, test or all. Writes stats.json only with save: true. warnings say when generate did not finish the dataset, since the numbers then cover only the patches written so far. |
verify |
no (write_sums needs it) |
Whether every file of a dataset is present and intact (and matches SHA256SUMS, when there is one), like mapcv verify; deep decodes every image. Writes SHA256SUMS only with write_sums: true. |
write_config |
yes | Validates, then saves a config inside the root. Refuses to overwrite unless asked. A config whose files do not exist yet is saved as a draft, and the result lists the problems. |
generate |
yes | Plans, then builds or resumes the dataset, like mapcv generate. Output is byte-identical to the CLI’s. |
split |
yes | Re-splits a dataset from its manifest, like mapcv split. |
Each result is structured JSON (also sent as text, for clients that only show text) with a one-line summary first. Errors are results marked as errors, with the message mapcv would print and no traceback.
A few things worth knowing when you read the results:
- Naming the class. Without
labels.label_field, every polygon is class 1, shown aslabeled(segmentation) orobject(detection, instance, classification). Name it by listing the file underlabels.fileswith aclass:labels: {files: [{path: buildings.geojson, class: building}]}. - Object counts. For detection and instance datasets
infocounts an object once in every patch that shows it, so a feature cut by a patch edge counts twice;plan.objectscounts the features in the region. The two differ. - Instance id masks. With
instance.id_mask: true, a mask value is the patch’s first, second, … instance, a per-patch number; it is not the COCO annotation id. - 0/255 change masks with
change.beforeandchange.after: setignore_index: nullon both label sets andchange.change_value: 255. - Labels are local files.
file://andhttps://work forimagery.path;labels.pathmust be a file in the project folder.
The agent skill
Section titled “The agent skill”agent/skills/mapcv/SKILL.md in the repository teaches an agent the journey, config recipes per task (segmentation, detection, instance, classification, change, regression), imagery (XYZ, Earth Engine, Sentinel-2 as EOPF Zarr or STAC COGs, GeoTIFF/COG and mosaics) and labels (several files, OpenStreetMap, label rasters), where each source’s license and credit line are listed, and what to do about the real error messages. The Claude Code plugin installs it. For other agents that read SKILL.md skill folders, copy the agent/skills/mapcv/ folder into their skills folder.
A session
Section titled “A session”Start the server with --allow-write in a folder that holds buildings.geojson, then ask:
Make a segmentation dataset of the buildings in
buildings.geojsonfrom Esri imagery at about 0.4 m per pixel, 256 px patches. Show me the plan before you download anything.
The agent calls inspect_labels (the file has a class field with two values and an extent), writes mapcv.yaml with write_config, runs validate_config and plan, and shows you the tile count, patch count and output size. After you agree it calls generate, then info. If a plan is marked large, it has to ask you first.
For language models
Section titled “For language models”This site publishes plain-text versions for models: https://tahamukhtar20.github.io/mapcv/llms.txt (an index), llms-full.txt (every page) and llms-small.txt (a shorter version), all generated from these pages at build time.
See also
Section titled “See also”mapcv mcpin the CLI reference- Configuration for every key
- Troubleshooting for the messages an agent may relay