GIS Cloud
Create maps and GIS analysis
- Category
- Data & Analytics
- Primary Subcategory
- Geospatial & Spatial Analytics Data
Integration details
Description
GIS Cloud is a cloud geospatial platform used by governments, utilities, and field teams in 60+ countries. This app lets you work with your GIS Cloud account in plain language, no GIS expertise or clicking through menus required. Ask ChatGPT to build and edit maps, create and style layers, add or query features, run spatial analysis, design mobile data collection forms for field teams, import datasets, and generate reports. It covers the full GIS workflow, from field capture to mapping, analysis, and reporting. What you can do: - Create, update, and organize maps and layers - Add, edit, and query features and attribute data - Run spatial analysis and compute statistics on your data - Build and manage Mobile Data Collection forms for field teams - Import files and manage datasets - Generate reports from your geospatial data You stay in control. GIS Cloud uses a propose-and-confirm model: changes are proposed before anything is written to your account, and destructive actions are clearly flagged. Access is scoped to your account through secure OAuth, with read-only operations separated from writes. Works with your existing GIS Cloud account. New to GIS Cloud? Sign up free at giscloud.com.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- Geospatial & Spatial Analytics Data
- Secondary Subcategories
- None listed
- Brand
- GIS Cloud
- Access
- Account required
- First tracked
- 2026-08-17
- Tool count
- 55
- Geography
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
ChatGPT Plugin Discovery Score
ChatGPT Plugin discovery is coming soon
ChatGPT can surface a Plugin when it matches a user's request.Your Plugin Discovery Score measures how often yours appears.
No spam. Unsubscribe any time.
What discovery looks like

Competing in ChatGPT Geospatial & Spatial Analytics Data
View Category55 tools agents can invoke
Add one or more columns to an EXISTING table (ALTER TABLE ADD COLUMN). This is how you back new form fields with real storage before binding a form — it NEVER creates a new table or layer. To add columns to a layer, add them to the layer's backing table (get_layer → source/query_table gives the table name). Pass `columns` as a list of {name, type}. Types use the create_table vocabulary: "text", "integer", "number", "decimal", "datetime". Names are alphanumeric + underscore. For an external-database table pass connection_id (and schema). Additive only; existing data is untouched. Adds only NEW columns: if any requested column already exists (matched case-insensitively) the WHOLE call is rejected with an error naming the conflicts — drop or rename them and retry. Use get_table to see the current schema first. Example: {"name":"boston_potholes","columns":[{"name":"severity","type":"text"},{"name":"depth_cm","type":"decimal"}]}.
add_table_columns
Bind an existing form to a layer for feature collection in the GIS Cloud apps — but ALWAYS preview the field→column mapping and have the user confirm it first. A form bound with no mapping still validates and returns 200 yet stores NOTHING, so binding is a deliberate two-step: preview, confirm, commit. Step 1 — preview (default): call WITHOUT confirm (or preview:true); nothing is written. The result classifies every form field and returns a `mapping_table` (one row per field) — render it to the user as a table. The user asked to connect this form, so do your best to map it onto THIS layer's columns; do NOT refuse or redirect to a different layer just because names differ. `implicit` = a column of the same name, auto-connects; `suggested` = a normalized-name guess (e.g. "Location Description" ↔ `location_description`) that you must HIGHLIGHT and have the user confirm; `unmapped` = no column — ask the user which column to map it to (or to add the column to the layer first). Step 2 — commit: after the user confirms, call again with confirm:true and `mappings` = the full confirmed {form_field: column} set (include the accepted suggestions and the user's choices for unmapped fields). Only `implicit` fields plus your explicit `mappings` are written; a suggested field you don't pass back stays unstored. Binding does NOT create columns — if a form field has no matching column, add it to the layer's backing table first with add_table_columns, then re-preview to map it. Two-side write: it sets the form on the layer and stores the field→column mappings on the form, preserving any other layers' mappings. To unbind: update_layer with form=null.
bind_form_to_layer
Apply the SAME attribute changes to many features in one transaction (e.g. status="approved" on 50 features). Identify rows with EITHER "feature_ids" (int array, max 1000) OR "feature_ids_encoded" (base64 delta-varint blob) — exactly one. Geometry CANNOT be updated this way; for per-feature different values loop update_feature. Pass "map_id" when known and surface returned editor_urls as clickable markdown links. Form-bound layers validate "attributes" STRICTLY against the form (inspect via get_layer; see create_feature). Use this when every targeted feature should get the same value(s) — e.g. setting status="approved" on 50 features, or zeroing out a flag column. Each provided field replaces the current value; fields not in "attributes" are left unchanged. The whole batch runs in a single transaction. Geometry CANNOT be updated this way (use update_feature for per-row geometry edits). For per-feature different values, call update_feature in a loop. Identify the rows EITHER with "feature_ids" (an explicit integer array, max 1000) OR with "feature_ids_encoded" (a base64 delta-varint blob for large sets such as an on-map selection) — pass exactly one. ALWAYS pass "map_id" when you know it — for the feature_ids path the response includes "editor_urls" you should surface as clickable markdown links (the encoded path omits them). FORM-BOUND LAYERS: if the layer has a bound form, "attributes" are validated STRICTLY against it — inspect the form first via get_layer (returned inline under `form`) and use each field's exact `type` and, for option/select fields, the stored option CODE exactly (case-sensitive), not a display label. See create_feature for the full rule. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
bulk_update_features
Create a new bookmark (saved map view) on a map. `state` needs `zoom`, `lat`, `lng` in WGS84 degrees REGARDLESS of the map projection — do NOT pre-convert to metres; the server transforms them into the map's native projection. Example state: {"zoom":7,"lat":3.71,"lng":7.77}. The server-side transform makes the bookmark match editor-created ones. Pass either an object (auto-JSON-encoded) or an already-encoded JSON string.
create_bookmark
Create one or many features in a PG-backed layer. SINGLE: pass "attributes" (+ optional WKT "geometry"). BULK: pass "features" as an array of {attributes, geometry?, srid?} — one atomic transaction. Pass "map_id" when known and show returned editor_url(s) as clickable markdown links. WKT is read in the parent MAP's SRID (often EPSG:3857, NOT WGS84) — for lon/lat also pass srid=4326. FORM-BOUND LAYERS (get_layer's expanded `form` object carries an `id`): writes validate STRICTLY against the form — call get_layer first and conform to `definition_decoded` (exact types, every `required` field, option CODEs case-sensitive). PHOTO/FILE/SIGNATURE fields: upload_binary_file first, then set the column to {"rid": <int>, "path": "<pathname>"} — never a bare path. SINGLE feature: pass "attributes" (and optionally "geometry"); the response includes the created feature and, if "map_id" was supplied, an "editor_url" deep link (https://<editor>/map/<map_id>/layer/<layer_id>/<feature_id>) you MUST surface as a clickable markdown link. BULK: pass "features" as an array of {attributes, geometry?, srid?} objects — the whole batch is inserted in a single transaction and rolled back as one if any item fails; the response is {"ids": [...], "editor_urls": [...]} in submission order. Always pass "map_id" when you know it. COORDINATES: by default WKT is interpreted in the parent MAP's SRID (often Web Mercator EPSG:3857, NOT WGS84) — if you are passing user-friendly lon/lat (e.g. POINT(15.97 45.81) for Zagreb), you MUST also pass srid=4326 (top-level applies to all bulk items unless overridden per item) so the server reprojects. Call get_map(map_id) to confirm the map's projection. FORM-BOUND LAYERS: if the target layer has a bound form, every write is validated STRICTLY against that form. Detect a bound form from get_layer: its expanded `form` object carries an `id` (and `mappings`). A vector layer with NO bound form STILL returns a `form` object — a synthesized attribute form derived from the columns — but WITHOUT an `id`/`mappings`; that one is NOT strictly validated (only the DB column types are enforced), so do not treat the mere presence of `form` as "form-bound". Before writing you MUST inspect the form definition first — call get_layer(map_id, layer_id), which returns it inline under `form` (see `definition_decoded`) — and build "attributes" to conform: use each field's exact `type` (datetime values must be ISO 8601, e.g. "2026-05-18T07:30:00"), supply every `required` field, and for select/radio (single-choice) fields send the stored option CODE exactly, case-sensitive (e.g. "zebra", never "Zebra"; "white", never "White") — not a display label; a checkbox (multi-select) field takes an ARRAY of codes (e.g. ["a","b"]) — a single selection may be a bare code, but never a comma-joined string like "a, b" (note: a multi-select value is STORED and read back — via get_feature / list_features — as the comma-joined codes "a,b", so to interpret an existing value split it on comma; a WRITE must still send the array). The layer's own DB columns are all plain strings and do NOT tell you these rules; only the form does. PHOTO / FILE / SIGNATURE fields: these hold an uploaded file, and they CAN be set on create — including `required` ones. First upload each file with upload_binary_file (it returns an "rid"), then set the photo column in "attributes" to an attachment spec: {"rid": <int>, "path": "<pathname from upload>"} for one file, or an array of those objects for several. The MCP server turns that into the storage reference giscloud needs and resolves the new feature id internally; a photo column set to a bare path string renders broken — always use the {rid, path} spec. The same spec works in update_feature. A `required` photo/file field MUST be attached — omitting it or sending it empty fails strict validation. This is certain from the form definition; do not spend a create call probing it. If you have no real file (e.g. seeding sample data), either generate a placeholder image and upload it, or ask the user whether to relax the field's `required` flag — do not silently skip it.
create_feature
Create a new Mobile Data Collection form. Pass `definition` as a structured object — the MCP server JSON-encodes it for storage. Convention: end every field-collection form with a `{"type":"photos", "name":"<contextual_name>", "title":"Photos"}` item (e.g. `name:"site_photos"`, `"damage_photos"`, or just `"photos"` — never `"__photos"`; the `__` prefix is reserved for system columns like `__created`/`__modified`/`__owner`). Forms can be standalone or bound to a layer afterward via `bind_form_to_layer`. DEFINITION SHAPE { "name": "<machine_id>", // form_name; required; not translatable "title": "<label>", // form title shown on mobile; translatable "description": "<help>", // form description; translatable "geometryType": "point" | "line" | "polygon",// what geometry this form captures; required for layer-bound forms "mdcp": true, // expose to MDC Portal "noLocation": "edit", // disables GPS capture during edit (omit for normal location capture) "crowdsource": true, // allow public crowdsourced submissions "defaultLanguage": "en", // see TRANSLATIONS "translatedLanguages": ["en","de"], // see TRANSLATIONS "automationRules": [...], // see AUTOMATION RULES (form-level) "items": [ <field items> ] // form fields, recursive via type="group" } FIELD ITEM TYPES (the `type` value on each entry in items[]): - Standard: text, numeric, datetime, select, radio, checkbox, signature, hidden, group - Media: photos, videos, recording, qr - Auto-captured (invisible to collector, populated automatically on submission — include only when you want who/when/where stored): mdcUsername, mdcDeviceId, mdcDeviceModel, mdcDeviceVersion, mdcDevicePlatform, mdcAppVersion, mdcLocationLat, mdcLocationLon, mdcLocationAlt, mdcLocationSpeed, mdcLocationHeading, mdcCompassHeading, mdcLocationAcc, mdcLocationAltAcc, mdcLocationTime COMMON ITEM PROPERTIES (any type): - name: DB column name the value writes to (required) - title: label shown on mobile (translatable) - description: help text shown to collector (translatable) - type: one of the types above (required) - required (bool), readOnly (bool, camelCase), persistent (bool — retains last value across submissions), multiEditDisabled (bool) - value: default value (also the stored value for `hidden`) - active: conditional visibility predicate — see DEPENDENCIES below - automationRules: per-item auto-fill rules — see AUTOMATION RULES below - valid: validation rules — see VALIDATION below TYPE-SPECIFIC PROPERTIES: - text: `multiline` (bool); `autocomplete: {data:[{title,value}]}` (offline-capable suggestion list); `autofill: {query:"nearest", source:{layer:"auto"|<id>}, result:"<field>"}` (online-only — copies from nearest existing feature) - datetime: `mode: "datetime" | "date" | "time"` - select / radio / checkbox: `options: [{title, value}, ...]` — each option may also carry its own `active` predicate (cascading dropdowns) - signature: `agreement` text shown on the signing screen - hidden: `value` is the hidden stored value - group: `items: [<nested form items>]` — collapsible section; may have its own `active` DEPENDENCIES — per-item `active` predicate (conditional visibility) Shape: `{<satisfaction>: [ {"item":{"name":"<other_field>", ...predicate}}, ... ]}` - `<satisfaction>`: "all" | "any" | "one" | "none" - Each entry MUST be wrapped in `{"item":{...}}` (NOT `{"field":...}` — that wrapper is used by automationRules, see below) - Two predicate shapes inside the `item` object: - any-value-present: `{"name":"<field>","hasValue":true}` - value comparison: `{"name":"<field>","value":{"<op>":<literal>}}` where <op> is one of: `equals`, `contains`, `lessThan`, `lessThanOrEqual`, `moreThan`, `moreThanOrEqual` (NO shorthand like `eq`/`gt`/`lt` — exact keys only) - Optional `preserveOnHide: true` to retain stored value when the field becomes invisible - Example: `{"all":[{"item":{"name":"is_hazard","value":{"equals":"yes"}}}]}` - The first item in a form/group cannot have `active`. The referenced parent field must appear earlier than the dependent field. AUTOMATION RULES — auto-fill another field from a condition Place `automationRules` ON the target item (the array lives on the field it fills) — NOT at the form root; a root-level `automationRules` is never ref-resolved or evaluated. Shape: `[{"condition":{"<satisfaction>":[ {"field":"<other_field>","value":<literal>}, ... ]}, "action":{"type":"<predefined|field_value|today_date>", "value"?:<literal>, "sourceField"?:"<other_field>"}}]` - The rule lives on the TARGET/destination field (the item it is attached to). `condition.field` and `action.sourceField` MUST reference a DIFFERENT field — never the target itself (a self-copy is a no-op) — and that referenced field MUST appear EARLIER in the form than the target (automation resolves in a single top-down pass; a source placed after its target silently fires nothing). - Note the asymmetry with `active`: condition entries are FLAT `{"field":"<name>","value":<literal>}` and support SCALAR EQUALITY ONLY — there is NO `hasValue` and NO `{"item":{...}}` wrapper here (those belong to `active`). Mixing these shapes is a common bug. - `<satisfaction>`: "all" | "any" | "one" | "none" - To copy a value WHENEVER the source field is filled (any non-empty value), invert an emptiness check: `{"none":[{"field":"<source_field>","value":""}]}`. - Action types: - `{"type":"predefined","value":<literal>}` — sets the target field to <literal> - `{"type":"field_value","sourceField":"<other_field>"}` — copies value from another field - `{"type":"today_date"}` — sets the target to today (for `datetime` fields) - Evaluation: rules run top-down; the FIRST satisfied rule on a field wins; if none match, the field reverts to the collector's manual value. - Supported on: text, numeric, datetime, select, radio, checkbox, qr - Mutually exclusive with `persistent: true` or a default `value` on the same target. VALIDATION — `valid` on an item (uniqueness checks) - `{"all":[{"value":{"type":"unique"}}]}` — value must be unique within this form's submissions - `{"all":[{"value":{"type":"unique-among","among":["<uuid_of_other_field>"],"scope":["form","datasource"]}}]}` — value must be unique across this and other named fields within the chosen scope TRANSLATIONS - Set form root `defaultLanguage` (e.g. "en") and `translatedLanguages` (e.g. ["en","de"] — the default MUST be in the list). - Replace every translatable property value with a language-keyed object: `{"default":"<text>", "<code1>":"<text>", "<code2>":"<text>", ...}` — `default` is mandatory and is what the runtime falls back to. - Translatable: form `title`, form `description`, item `title`, item `description`, `options[].title`, `signature` `agreement`. - NOT translatable (must remain plain): form `name`, item `name`, `type`, `value`, `required`, `persistent`, `readOnly`, `mode`, `multiline`, `active`, `automationRules`, `options[].value`. EXAMPLE — minimal point form with conditional visibility + auto-fill: { "name":"hazard_report", "title":"Hazard Report", "geometryType":"point", "mdcp":true, "items":[ {"type":"text","name":"reporter","title":"Reporter","required":true,"persistent":true}, {"type":"radio","name":"is_hazard","title":"Is this a hazard?","required":true,"options":[{"title":"Yes","value":"yes"},{"title":"No","value":"no"}]}, {"type":"text","name":"hazard_description","title":"Describe the hazard","multiline":true,"active":{"all":[{"item":{"name":"is_hazard","value":{"equals":"yes"}}}]}}, {"type":"datetime","name":"reported_at","title":"Reported At","mode":"datetime","automationRules":[{"condition":{"all":[{"field":"is_hazard","value":"yes"}]},"action":{"type":"today_date"}}]}, {"type":"photos","name":"hazard_photos","title":"Photos"} ] }
create_form
Create a new layer in a map. Pick `type` and supply a matching `source`: vector (point/line/polygon) — OMIT `source` to auto-create a spatial PG table (via table_name/columns), or pass a pg source to attach an EXISTING table; tile — basemap, `source` is the basemap name string (see list_basemaps); file — uploaded vector file from user storage, `source` is an object {src, name?}; folder — group container (no data, can hold children via `parent`). WMS/WFS/WMTS/TMS service layers are NOT supported here yet — see the detail for what to tell the user. WMS/WFS/WMTS/TMS: GIS Cloud supports these, but they cannot be added through this tool yet. Don't fake it with a pg/file/tile source — tell the user to add the service by hand in the Map Editor and link the manual verbatim: https://manual.giscloud.com/knowledge-base/how-to-add-wfs-wms-wmts-and-tms-on-your-map/ LEGEND TIP: with multiple style classes using `expression` for thematic splits, set `showlabel: true` and a human-readable `label` per class — that label is what users see in the legend panel.
create_layer
Create a new map. To start the map with a basemap, pass the `basemap` argument — `"default"` lets the server pick the best basemap this account has (osm.streets → mapbox.streets → osm), or pass a specific name (e.g. "osm"; see list_basemaps). This adds it as the map's initial tile layer in this one call, so no separate create_layer is needed. Response includes an "editor_url" deep link — when confirming the creation, make the map's NAME a clickable markdown link to it. Link format example: `[City Parks](https://<editor>/map/20)`.
create_map
Create a new table. Omit "geometry" for a non-spatial table; set it to POINT, LINESTRING, or POLYGON (or a MULTI* variant) for a spatial table. A primary key is guaranteed: declare a column of type "serial"/"key" to choose it, else a non-spatial table gets a serial "id" key and a spatial table gets "ogc_fid". The guaranteed key keeps rows addressable by update_table_row / delete_table_row. An existing "id" column on a non-spatial table is promoted to the serial key.
create_table
Insert a new row into a table. Attribute columns go in "data" (NOT geometry, NOT wkb_geometry); for spatial tables supply a separate top-level WKT "geometry". WKT is parsed in the TABLE's storage SRID by default — pass srid=4326 for lon/lat. Without srid your coords must already match the table's storage SRID (no reprojection); with srid=4326 the server reprojects. Call get_table to confirm the storage SRID.
create_table_row
Delete a bookmark by ID. Irreversible. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_bookmark
Delete a feature from a layer. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_feature
Delete a file from user storage, or remove an EMPTY directory. Removing a directory is non-recursive — a non-empty directory is refused (delete its contents first); this never mass-deletes a folder's files. Irreversible. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_file
Delete a form by ID. Irreversible. Does NOT automatically unbind it from layers — call update_layer with `form: null` on bound layers first if you want the binding cleared too. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_form
Delete a layer from a map. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_layer
Delete a map by ID. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_map
Delete a table and all its rows. Irreversible. cascade=true drops dependent DB views along with the table — but NOT map layers: a vector layer whose source is this table (source.type=pg) is left orphaned, not removed or warned about. Remove such layers separately with delete_layer. The delete succeeds (204) even while map layers still reference the table; cascade is a Postgres DROP … CASCADE that reaches DB views only, never gc_layer rows. The orphaned layer stays registered on its map with its source/query still pointing at the now-deleted table, so its feature reads fail. Find dependents with list_layers and drop them with delete_layer. Note: a failed delete on a table that appears orphaned may surface only as a generic "FATAL" error; the actionable detail is in the server-side log. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_table
Delete a single row from a table by its row ID. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
delete_table_row
Compute a per-column statistic on a layer. `action` is REQUIRED and selects what to compute. Per-column actions ("distinct", "min", "max", "minmax") need a `column`; "info" returns layer-wide column metadata and ignores `column`. For an average (or any other aggregate), use query_read with AVG()/SUM()/etc.
get_attribute_stats
Get a specific bookmark by ID, including its saved viewport (`state`). Read-back `state` is `{position:{lon,lat}, zoom, bearing, pitch}` with `position` in the map's projection (often Web Mercator metres) — this differs from the create_bookmark/update_bookmark input shape `{zoom, lat, lng}` in WGS84 degrees.
get_bookmark
Get the currently authenticated user profile.
get_current_user
Get details of a specific datasource.
get_datasource
Get a specific feature by ID.
get_feature
Get a single form by ID. The response includes `definition` (raw JSON string as stored) and a convenience `definition_decoded` object — read fields from the decoded object, never re-parse the string yourself.
get_form
Get detailed information about a specific layer. PG-backed layers include `query_table` (schema-qualified table name) — pass it verbatim as the `table` value in a query_read/query_write AST. The default expand is "columns,form,options", so a form-bound layer already carries its full form definition under `form` — ALWAYS inspect that form before writing features to the layer (see create_feature). The `form` map is keyed by layer id and includes `definition_decoded` with each field's type, `required` flag and option list. IS THIS LAYER FORM-BOUND? A real bound form's `form` object carries an `id` (and `mappings`); strict feature-write validation applies only then. A vector layer with NO bound form still returns a `form` object — a synthesized attribute form derived from its columns — but WITHOUT an `id`/`mappings`, and writes to it are validated only by DB column type, not by the form. So key the decision on `form.id`, not on whether `form` is present. Pass an explicit `expand` to override the default set.
get_layer
Get the column (attribute) definitions for a layer. Returns {"columns": [{"name", "type"}, ...]}. `type` uses this tool's own vocabulary (string/int/real/timestamp) and excludes the primary key — it differs from get_table (string=text, int|real=number, timestamp=datetime).
get_layer_columns
Get detailed information about a specific map by ID. Response includes an "editor_url" deep link. When reporting the map to the user, make the map's NAME a clickable markdown link to editor_url (e.g. `[City Parks](https://<editor>/map/20)`).
get_map
Get a single table by name, including its column definitions and geometry type. Pass `connection_id` to read a table on an external database connection (see list_dbconnections). `columns[].type` is text/number/datetime — "number" merges integer, decimal and the key (use `primary_key`); it maps to get_layer_columns as text=string, number=int|real, datetime=timestamp. A column made with the create_table "!<raw>" escape reports its native PG type instead ("boolean", or "unknown" for anything unrecognised), so the type vocabulary is not limited to text/number/datetime. KEY VISIBILITY: a non-spatial table's serial/declared key (e.g. "id") appears in `columns` as a "number"; a spatial table's auto key "ogc_fid" and its geometry column do NOT appear in `columns` (they are reported via `primary_key`/`geometry`), so there `columns` lists user attribute columns only.
get_table
Import a file from user storage into a PostgreSQL table. Tabular (.csv/.xls/.xlsx): point geometry from lat/lon columns via geometry_column_x/_y, or a WKT column via geometry_column + geometry_format=WKT; if_exists supports fail/rename/overwrite/append/replace. Spatial vector files (.shp/.geojson/.kml/.gpkg/…) import with their native geometry; if_exists fail/rename/overwrite only. The file must already exist in user storage (upload_file / upload_binary_file first; a .zip must be extracted with unzip_file before importing the dataset inside). A .shp needs its companion .dbf/.shx/(.prj) files alongside it in storage. Rasters (.tif, …) cannot be imported into a table — add them to a map with create_layer(type:"file") instead. For spatial vector files, importing is worthwhile when the data must be EDITABLE (file layers are read-only) or needs full PostGIS spatial SQL (file layers query through a limited SQLite dialect); to simply show the file on a map, add it directly with create_layer(type:"file") — no import needed. Tabular-only parameters (separator, geometry_column*, geometry_srs, create_datasource) are rejected for spatial files — they already carry geometry. For spatial files, epsg declares the source SRID and geometry_type forces the layer geometry (useful for mixed-geometry GeoJSON). After import, render the table on a map with create_layer (type point/line/polygon, source {"type":"pg","src":"<tablename>"}). TABULAR (.csv) TYPES: CSV columns import as TEXT — there is no type inference, so a numeric/date column lands as text. If you need it typed (for numeric filters/sorts or date math), cast it in query_read/query_write (e.g. col::numeric) or add a typed column and copy the cast values over. Runs synchronously — large files may take time. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
import_file
List the basemaps ACTUALLY available on THIS account (OSM plus whatever the account has access to, e.g. Mapbox/HERE). Call this to answer "what/which basemaps do I have / are available" — the set is account-specific and is NOT the generic web list, so report ONLY the names it returns and NEVER answer from general knowledge or assume Google/Bing/Satellite etc. are present (they usually are not). Each item has a `name` (pass as a tile layer source in create_map/create_layer) and a human-readable `title`.
list_basemaps
List bookmarks (saved map views) for a map. Each bookmark has a `state` object capturing a viewport. NOTE the read-back shape differs from the write shape: stored state reads back as `{position:{lon,lat}, zoom, bearing, pitch}` with `position` in the MAP's projection (often Web Mercator metres), whereas create_bookmark/update_bookmark take `{zoom, lat, lng}` in WGS84 degrees — so a read-back state cannot be fed straight back into an update without reprojecting `position` to lon/lat.
list_bookmarks
List available datasources (databases, files, WMS/WFS services). PAGINATED: one page per call (default 100) — the envelope's `total` is the real count, so when it exceeds the number of rows you received you have only a PAGE; fetch the rest with `page`/`perpage` rather than assuming the list is complete or that no further datasources exist.
list_datasources
List the user's external database connections (e.g. an external PostgreSQL server such as AWS RDS). Use an entry's `id` as `connection_id` in list_tables / get_table, and as `connid` in a create_layer pg source. This is how you answer "list my external databases" and how you obtain a connection id to reach tables that are NOT in the user's own GIS Cloud schema. Each entry has an `id` and a `name`.
list_dbconnections
List features (rows) in a layer. Supports attribute/spatial filtering and pagination. Use this to read or filter rows from ONE layer. For anything that spans multiple layers, joins, aggregations (COUNT/SUM/GROUP BY), or richer PostGIS predicates, use query_read instead.
list_features
List or search files in user storage. To FIND files by name OR by extension/type, pass `search` — one recursive, case-insensitive substring sweep across ALL directories (an extension is a substring: search ".tif" finds every TIFF anywhere in storage) — do NOT walk directories one by one. Browse mode: pass `path` for one directory's direct contents, or omit it for the storage root. Results are PAGINATED — read `total` in the response envelope and fetch further pages with `page` (search defaults to 100 per page, browse to 1000). Each entry is {name, path, type:"file"|"directory", extension, size (bytes), modified (unix epoch), id, url}; size and modified are populated in BOTH modes (id and url are null for storage-backed listings). When the user was NOT specific about where to look, omit `path` — searching everywhere is the right default. When the user named a specific place, pass it as `path` (must be a directory): the search then checks just that directory's direct contents; set `recursive` to sweep its whole subtree (when the user asks for everything UNDER a folder). Passing a file path as `path` in browse mode returns that file's metadata (size, mtime, extension). Both modes return the same {type:"files", total, page, data:[...]} shape.
list_files
List Mobile Data Collection forms (owned by or shared with the caller). Match a user-given form name against BOTH `name` and `title` (case-insensitive) — the title is usually what the user means. Each item is lean: `id`, `name` (often a slug), human `title` (from the definition, may be null), `owner`, and `bound_layers` (layer ids the form is already attached to). Fetch a form's full fields/definition with get_form.
list_forms
List all layers in a map. PG-backed layers include a `query_table` field (schema-qualified table name, e.g. "usrsch3.parcels") — use this verbatim as the `table` value in query_read/query_write. By default the response is expanded with `columns` and `form`. The default expansion means any form-bound vector layer already carries its form definition inline under `form` (keyed by layer id) — pass an explicit `expand` to override the default set.
list_layers
List maps for the authenticated user, including Mobile Data Collection (MDC) projects — data collection projects are maps whose name starts with the "MDC:" prefix. By DEFAULT this returns only the user's own maps and maps shared with them (visibility "private,shared") — public maps are excluded; pass visibility="public" for public maps and ALWAYS paginate them. Each map includes an "editor_url" deep link — when presenting maps to the user, you MUST make each map's NAME a clickable markdown link to its editor_url. There can be thousands of public maps system-wide, so with visibility="public" set "perpage", fetch one page at a time, read the "total" in the response envelope to gauge how many pages exist, and never assume a single call returned them all. Link format example: `[City Parks](https://<editor>/map/20)` — do not just show plain text names, and do not drop the link to save space. This applies in any format (table, bulleted list, prose). When you render the list as a table, use just two columns — ID and Name (the Name being the clickable link); do NOT add a leading row-number/ordinal "#" column, it is redundant noise.
list_maps
List rows of a table by table name. For layer-scoped access (with layer styling/permissions) use list_features instead.
list_table_rows
List tables readable by the current user — the caller's own GIS Cloud schema by default, or an external DB connection via `connection_id` (see list_dbconnections). PAGINATED: one page per call — compare the received count against the envelope `total` and fetch further pages rather than infer missing names. For the caller's OWN tables `num_rows` is an AUTHORITATIVE count (maintained feature_count, same as list_datasources) — never a planner estimate; for external-connection tables it is ABSENT. expand="columns" adds column definitions (or use get_table for one table). Pass `query` to filter by name (case-insensitive substring) and narrow the listing server-side instead of paging through everything. A table is the underlying spatial or non-spatial PostgreSQL store that backs a vector layer (or stands alone). By default lists tables in the caller's own GIS Cloud schema; pass `connection_id` to instead list tables on an external database connection (see list_dbconnections). Use list_layers if you want layers in a specific map instead. PAGINATED: the response is ONE page (default 100 tables) and the envelope reports the true `total` — a connection can hold far more tables than one page. Always compare the number of tables you received against `total`: if `total` is larger, you have only a PAGE, so say the list is partial and fetch the rest with `page`/`perpage` (or narrow with only_spatial) — NEVER infer, extrapolate, or pattern-fill the table names you did not receive. By default each table carries NO column definitions (just name, geometry, srid, num_rows, primary_key, is_view) to keep the listing small; pass expand="columns" to include them, or call get_table for one table's full columns. COUNTS: for the caller's own tables `num_rows` is the table's maintained feature_count (same source as list_datasources), with a live COUNT fallback for tables that have no datasource record; it is never a raw planner estimate, so it is reliable for "is this empty / roughly how big". A backend monitor keeps it current rather than bumping it on every write, so it can briefly LAG just after rows change (it self-corrects) — when you need an EXACT count run a query_read COUNT(*). For external-connection tables (connection_id) no count is available and `num_rows` is omitted; query_read/query_write only reach your OWN GIS Cloud schema (usrsch*) and layers — they cannot address an external table by name — so to size an external table add it to a map as a layer first and run query_read COUNT(*) against that layer_id.
list_tables
Create a directory (and any missing parents) in user storage.
make_directory
Run a structured SQL-AST read — a SELECT, or a UNION of SELECTs — against one or more PG-backed GIS Cloud layers: filters, aggregations, joins, subqueries, PostGIS spatial predicates. Returns attribute rows. This tool is READ-ONLY and never modifies data — for update/delete/insert use the query_write tool. Top-level node is one of: - {"type":"query", "from":{relation}, "select":[items], "joins":[], "where":null, "group_by":[], "having":null, "order_by":[], "limit":null, "offset":0} — SELECT - {"type":"union","operator":"UNION ALL","queries":[...]} — UNION of SELECTs Node types: column {table,column}, literal {value}, call {name,args}, star {} (the COUNT(*) wildcard — ONLY valid as count's single argument), binary_op {operator,left,right} (comparisons =, !=, >, >=, <, <=, IN, LIKE; arithmetic +, -, *, /, %; string concat ||; integer division truncates — cast a side to numeric for a fractional result), logical_op {operator,conditions} (AND, OR), unary_op {operator,operand} (NOT, IS NULL, IS NOT NULL), cast {expression,as}, case {whens:[{condition,then}],else}, scalar_subquery {query}. Relation: table {table,alias} or relation_subquery {query,alias}. Join: {join_type,source,on} (INNER/LEFT/RIGHT/FULL). select_item {expression,as}. order_by item {expression, direction} — `direction` is REQUIRED, "asc" or "desc"; bare expression objects in order_by are rejected. Example: "order_by":[{"expression":{"type":"call","name":"COUNT","args":[{"type":"column","table":"e","column":"ogc_fid"}]},"direction":"desc"}]. Rules: every table needs an alias, every column reference uses the alias as {type:"column",table:"alias",column:"name"}. Use the `query_table` field from get_layer / list_layers as the `table` value (it's already schema-qualified, e.g. "usrsch3.parcels") — do not guess or strip the schema. PG layers always have wkb_geometry (geometry) and ogc_fid (integer pk) — use ogc_fid for GROUP BY/JOIN. NOTE: a bare ogc_fid in the SELECT list comes back under the key "__id" (the same row-id key get_feature/list_features use) — the engine reserves the literal name "ogc_fid" for the feature id and would otherwise drop it from the row; alias it yourself (ogc_fid AS something) to pick a different output name. To count rows use COUNT(*), written as a call to count with a single star arg: {"type":"call","name":"count","args":[{"type":"star"}]} (or COUNT(ogc_fid) for non-null pk counts). PostGIS functions (ST_Contains, ST_Within, ST_Intersects, ST_DWithin, ST_Transform, etc.) work as call nodes. For cross-SRID spatial predicates, wrap one side with ST_Transform(geom, srid). IN lists: an `IN` right operand may be (a) a scalar_subquery, (b) {"type":"in_list","values":[1,2,3]} for an explicit set of ids/values you can write out (the ONLY correct way to write a literal list — do NOT use a `literal` whose value is an array, that is rejected), or (c) {"type":"in_varints","encoded":"<base64 delta-varint>"} to match a compressed integer id set WITHOUT expanding it (only pass a blob produced by the server, never fabricate one). Each is valid ONLY as the right side of an IN, e.g. ogc_fid IN {"type":"in_list","values":[3,4]}. Datasets: pass `datasets` as a list of items. Each item is either {"layer_id": N} (for items from list_layers / get_layer) or {"table": "name"} (for items from list_tables — bare name is auto-promoted to the caller's usrsch schema; use `usrschN.foo` for tables owned by another user that are shared via a datasource). Joinability: items are joinable iff they share the same conn_id. Per-layer datasource_id is irrelevant. Errors name the conflicting conn_ids. Permissions: reads require READ on every referenced layer/datasource. Permission denials surface as 403 FORBIDDEN.
query_read
Run a structured SQL-AST write — an UPDATE, DELETE or INSERT — against one or more PG-backed GIS Cloud layers, with conditional logic and joins. Returns ONLY the number of affected rows (rows_affected); the per-row RETURNING values/ids are NOT returned to you (a write can touch tens of thousands of rows — there is no point streaming them back). If you need specific updated/inserted values (e.g. a new row's ogc_fid), run a query_read afterwards. For SELECT/UNION reads use the query_read tool. Top-level node is one of: - {"type":"update","target":{table-relation},"set":[set_items],"from":[opt],"joins":[opt],"where":expr,"returning":[items]} — UPDATE - {"type":"delete","target":{table-relation},"using":[opt],"joins":[opt],"where":expr,"returning":[items]} — DELETE - {"type":"insert","target":{table-relation},"columns":["c1","c2"],"values":[[expr,expr],[expr,expr]],"returning":[items]} — INSERT (multi-row VALUES form) Node types: column {table,column}, literal {value}, call {name,args}, star {} (the COUNT(*) wildcard — ONLY valid as count's single argument), binary_op {operator,left,right} (comparisons =, !=, >, >=, <, <=, IN, LIKE; arithmetic +, -, *, /, %; string concat ||; integer division truncates — cast a side to numeric for a fractional result), logical_op {operator,conditions} (AND, OR), unary_op {operator,operand} (NOT, IS NULL, IS NOT NULL), cast {expression,as}, case {whens:[{condition,then}],else}, scalar_subquery {query}. Relation: table {table,alias}. select_item {expression,as} (for `returning`). set_item {column:"<bare name>",value:expr}. Rules: every table needs an alias, every column reference uses the alias as {type:"column",table:"alias",column:"name"}. Use the `query_table` field from get_layer / list_layers as the `table` value (it's already schema-qualified, e.g. "usrsch3.parcels") — do not guess or strip the schema. PG layers always have wkb_geometry (geometry) and ogc_fid (integer pk). THIS TOOL USES A TWO-CALL PREVIEW/CONFIRM FLOW. Called WITHOUT `confirmed` the server DOES NOT MUTATE DATA: it runs validation, generates the SQL, computes a row-count estimate (the values-array length for a VALUES insert, a server-side COUNT(*) over the SELECT for an INSERT … SELECT, and a COUNT(DISTINCT target.ctid) WHERE … for update/delete), and returns {preview_sql, rows_affected_estimate, confirmation_required, expires_at}. You must then: (1) summarize the change for the user in PLAIN LANGUAGE — name the layer/table, what will change (or that rows will be deleted), the filter in human terms (e.g. "rows where status is 'archived'"), and the rows_affected_estimate. DO NOT paste the preview_sql into the user-facing message by default — most users don't read SQL. Treat preview_sql as an internal/debug field; only show it if the user explicitly asks "show me the SQL" or similar; (2) wait for explicit "yes, do it" / "go ahead" — a NEW user message sent after the preview was shown; the request that triggered the preview is not approval; (3) call query_write AGAIN with the SAME datasets + ast PLUS `confirmed: true` to actually execute the write. If the user says no or wants changes, abandon the action or call query_write again (without confirming) to preview the new shape. Confirmations have a 5-minute TTL and are bound to the calling user, the AST, and the datasets — any byte change invalidates them. Do NOT send the confirming call in the same turn as the preview; the user needs a chance to read the preview first. Write rails: - update/delete: `where` is REQUIRED and must not be null (prevents accidental whole-table writes). - insert: `columns` non-empty, `values` non-empty, each `values[i]` row length matches `columns` length, no duplicate column names. - All writes: `returning` REQUIRED and non-empty (the backend uses it internally for the audit trail and form automation). Its VALUES are not returned to you, so keep it minimal — just `[ogc_fid]`; do not add extra columns expecting to read them back. - Validator rejects writes to system columns __id, ogc_fid, and wkb_geometry — geometry edits go through update_feature/create_feature, which handle SRID reprojection. - FORM-BOUND LAYERS: if a referenced layer has a bound form, the write is validated STRICTLY against that form — read-only fields, required fields, types, and allowed option CODEs — exactly as create_feature/update_feature/bulk_update_features are. query_write does NOT bypass form validation when the layer is addressed by {"layer_id"}: setting a read-only field (or violating any form rule) through raw SQL fails the same way it would through the feature tools. SCOPE — this form check is keyed on the {"layer_id": N} dataset reference, because the form is a layer-scoped resource. Referencing the SAME backing store as a raw table — {"table": "usrschN.foo"} — writes straight to the table and SKIPS form validation entirely (option CODEs, required/read-only fields, and form types are NOT enforced). DEFAULT RULE: when data is governed by a bound form, address it by {"layer_id"} so the form is enforced. Do NOT switch to a {"table"} reference in order to get around a form — not even when the caller owns the table or has direct table access; owning the store is not license to bypass its form, and silently routing around form rules can write values the form would reject. A {"table"} write to form-governed data is permitted ONLY when the user has EXPLICITLY asked for a raw / form-free write (e.g. "write straight to the table", "skip the form", a deliberate one-off seed). When unsure whether the user wants to bypass the form, use {"layer_id"} or ask — never bypass on your own initiative. - update/delete/insert targets must be {type:"table",...} (subqueries cannot be write targets). update example: {"type":"update","target":{"type":"table","table":"usrsch3.parcels","alias":"p"},"set":[{"type":"set_item","column":"status","value":{"type":"literal","value":"archived"}}],"where":{"type":"binary_op","operator":"=","left":{"type":"column","table":"p","column":"zone"},"right":{"type":"literal","value":"X"}},"returning":[{"type":"select_item","expression":{"type":"column","table":"p","column":"ogc_fid"},"as":null}]}. delete example: replace the "type"/"set" with {"type":"delete"} and drop "set". insert example: {"type":"insert","target":{"type":"table","table":"usrsch3.parcels","alias":"p"},"columns":["name","status"],"values":[[{"type":"literal","value":"Foo"},{"type":"literal","value":"active"}]],"returning":[{"type":"select_item","expression":{"type":"column","table":"p","column":"ogc_fid"},"as":null}]}. IN lists: an `IN` right operand may be (a) a scalar_subquery, (b) {"type":"in_list","values":[1,2,3]} for an explicit set of ids/values (the ONLY correct way to write a literal list — do NOT use a `literal` whose value is an array, that is rejected), or (c) {"type":"in_varints","encoded":"<base64 delta-varint>"} to match a compressed id set without expanding it (e.g. updating/deleting exactly a set of ogc_fid; only pass a server-produced blob, never fabricate one). Each is valid ONLY as the right side of an IN, e.g. ogc_fid IN {"type":"in_list","values":[3,4]}. Datasets: pass `datasets` as a list of items, each {"layer_id": N} or {"table": "name"} (bare table name is auto-promoted to the caller's usrsch schema; use `usrschN.foo` for another owner's shared table). Joinability: items are joinable iff they share the same conn_id. Permissions: writes require EDIT or LEGACY_WRITE on every referenced layer/datasource (the same set update_feature/bulk_update_features enforce). Permission denials surface as 403 FORBIDDEN.
query_write
Read the content of a text file in user storage. Subject to a server-side 2 MB size cap — files larger than that return FILE_TOO_BIG. Use list_files first to check size.
read_file
Re-mint the short-lived access token for the inline map viewer (see render_map). The embedded viewer calls this ITSELF to keep a PRIVATE map loading on long sessions and after a page reload (where the original token, replayed from chat history, has expired) — you do NOT need to call it. Returns a fresh "accessToken" for the given map. No-op for public maps.
refresh_access_token
Show a map as an INTERACTIVE viewer inline in the chat — the real GIS Cloud map, pannable and zoomable, not a static image. Use this whenever the user wants to SEE, LOOK AT, or EXPLORE a map (open it, view it, preview it, browse it, or check it after edits/adds/style changes). It renders the live GIS Cloud viewer (api.js) right in the conversation, so the user can pan and zoom without leaving the chat. Pass `bounds` to frame a specific extent (e.g. a feature bbox); omit it to fit the map's default extent. Requires a host that supports inline interactive components (MCP Apps); on other hosts the map's name + editor link is returned instead. This is read-only — it never changes the map.
render_map
Extract a ZIP archive that is still sitting un-extracted in user storage, into the same directory (useful for shapefile bundles .shp/.shx/.dbf/.prj before layer creation). IMPORTANT: do NOT call this after upload_binary_file — that tool AUTO-EXTRACTS ZIPs on upload and removes the original archive, so no .zip remains and unzip_file would fail with "Resource not found". The extracted files are already in the directory after the upload (use list_files to see them). unzip_file is only for a .zip that reached storage some other way and is genuinely still archived. Files are extracted into the same directory as the ZIP. The trap to avoid: a ZIP put through upload_binary_file (or a multipart web-UI upload) is expanded into its contents immediately and the .zip itself is removed, so a follow-up unzip_file on that path returns NOTFOUND — the upload already did the extraction. Only reach for unzip_file when a .zip genuinely persists in storage un-extracted. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
unzip_file
Update an existing bookmark. Only the fields you pass are changed. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
update_bookmark
Update an existing feature's "attributes" and/or WKT "geometry"; omitted fields stay unchanged. ALWAYS pass "map_id" when known and show the returned editor_url as a clickable markdown link. WKT is read in the parent MAP's SRID by default — pass srid=4326 for lon/lat. Form-bound layers validate changed attributes STRICTLY against the form (inspect via get_layer; exact types, option CODEs case-sensitive). To attach photos/files: upload_binary_file first, then set the column to {"rid": <int>, "path": "<pathname>"} — never a bare path. ALWAYS pass "map_id" when you know it — the response will include an "editor_url" deep link that you MUST show the user as a clickable markdown link. Pass changed attributes under "attributes" and/or new WKT geometry under "geometry". Omitted fields are left unchanged. COORDINATES: by default the WKT is interpreted in the parent MAP's SRID — if you are passing user-friendly lon/lat, pass srid=4326 so the server reprojects. FORM-BOUND LAYERS: if the layer has a bound form, the changed "attributes" are validated STRICTLY against it — inspect the form first via get_layer (returned inline under `form`) and use each field's exact `type` and, for option/select fields, the stored option CODE exactly (case-sensitive), not a display label. See create_feature for the full rule. PHOTO / FILE / SIGNATURE fields: this is the tool that attaches an uploaded file to a feature. First upload the file with upload_binary_file (it returns "pathname" and "rid"), then set the photo column in "attributes" to an attachment spec — {"rid": <int>, "path": "<pathname>"} for one file, or an array of those objects for several. The MCP server expands that into the "___json" storage reference giscloud requires and fills in the layer/feature ids itself; a column set to a bare path (no rid) renders broken. Optional per-file keys: "title" (display name, defaults to the filename) and "panorama" (boolean). create_feature accepts the same attachment spec — it resolves the new feature id for you. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
update_feature
Update an existing form. Two modes — pick ONE: REPLACE MODE — pass a full new `definition` (and/or `name`, `mappings`). The supplied definition fully replaces the stored one. Use this when you have the complete new form in hand. See create_form for the definition shape. GRANULAR MODE — pass an `edits` array of typed operations. The MCP server fetches the current form, applies edits in order, and PUTs the result. This is the preferred mode for incremental changes (add a field, rename, translate, set a property). Group items are recursed: name-based ops find nested items inside `group` containers automatically. Edit operations: - `{"op":"add","item":<itemDef>, "after"?:"<name>"}` — add an item; positioned after `<name>` at top level, or appended. - `{"op":"remove","name":"<name>"}` — remove the named item (searches nested groups too). - `{"op":"rename","old_name":"<old>","new_name":"<new>","new_title"?:"<title>"}` — rename a field (and optionally retitle). - `{"op":"reorder","order":["<name1>","<name2>", ...]}` — reorder top-level items. List EVERY top-level field name; missing names are appended in original order. - `{"op":"change_property","name":"<field>","property":"<prop>","value":<v>}` — set ANY item property (required, description, persistent, readOnly, value, active, multiline, mode, options, agreement, autocomplete, autofill, automationRules, title, ...). See create_form for the full property list and value shapes (`active` predicate, `automationRules`, `options[]`, translations). To attach an auto-fill rule to a field, use this op with `property:"automationRules"` and the per-item rule array as `value`. - `{"op":"change_type","name":"<field>","new_type":"<type>"}` — change a field's type (text/numeric/select/etc.). - `{"op":"set_form_property","property":"<prop>","value":<v>}` — set a property at the form ROOT (not on an item). Valid `property`: `title`, `description`, `defaultLanguage`, `translatedLanguages`, `noLocation`, `crowdsource`, `mdcp`. Use this (NOT `change_property`) for form-level changes. (`automationRules` is per-item — set it with `change_property`, never here.) Translation note: to add languages, emit one `set_form_property` for `defaultLanguage`, one for `translatedLanguages`, one each for form-root `title`/`description` with language-keyed objects, and one `change_property` per item for `title` + `description` (and `options` on select/radio/checkbox, `agreement` on signature). Every translatable property gets `{default, <lang1>, <lang2>, ...}`. You can also pass `name` and/or `mappings` alongside `edits` — they're forwarded to the API in the same PUT. Confirmation handshake: a destructive call without `confirmed` makes NO change — it returns a plain-language `summary` (5-minute TTL). Show the summary and STOP: approval must be a NEW user message sent after seeing it (the triggering request is NOT approval). Only then repeat the call with identical arguments plus `confirmed: true` to execute.
update_form
How do I improve a ChatGPT Plugin's discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are GIS Cloud alternatives on ChatGPT?
As of 2026-09-13, GIS Cloud competes with Alloy, BigGeo AI, DMAP AI, emem, Farmbit, Parcelle Cadastre, Property Hazard MCP, SkyWatch, Technis in ChatGPT Geospatial & Spatial Analytics Data, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.