rendermaptool: the Mapbox map visualization primitive

September 3, 2026 · View on GitHub

render_map_tool displays a live, interactive Mapbox GL JS map inside any MCP client that supports MCP Apps. It is the single visualization primitive for this server — every other Mapbox tool that produces geospatial output hands its result to this one tool to actually draw it, and you can call it directly with your own data too, without any other Mapbox tool involved at all.

Table of Contents

Overview

render_map_tool takes a small JSON payload describing what to draw — layers, markers, a legend, an optional camera position — and renders it as a live Mapbox GL JS map in a self-contained HTML panel (via the MCP Apps protocol). It's intentionally the only tool in this server that declares an MCP Apps UI resource. Two reasons for that:

  1. Chain-position limitation: several MCP App hosts (Claude Desktop among them) only fully render the interactive panel for the last tool call in a sequence. Funneling every visualization through one terminal tool means the map always renders, regardless of how many other tools ran first.
  2. Token efficiency: geometry (a route polyline, a set of isochrone contours, a polygon boundary) can be tens of thousands of coordinate pairs. Passing it through the model as tool-call arguments is slow and expensive. The other Mapbox tools in this server avoid that by stashing their result behind a short reference string (payload_refs) instead of inlining the geometry — but this is an optimization, not a requirement. You're always free to pass geometry inline instead.

Base map style: the underlying base map defaults to Mapbox Standard; pass baseStyle: "standard-satellite" to switch to Mapbox Standard Satellite instead (satellite imagery with the same dynamic labels/roads on top). Beyond drawing your own layers/markers on top of either, you can restyle the base map itself (colors, lighting, label visibility) via baseMapConfig — see the Payload reference below.

Two ways to use it

ModeWhen to use itWhat you pass
ChainedVisualizing a result from another Mapbox tool in this server (directions, isochrone, search, etc.)payload_refs: one or more reference strings from that tool's structuredContent.mapboxRender.ref
StandaloneVisualizing your own GeoJSON — data from your own database, a third-party API, a file, anythinglayers / markers / legend / camera, composed directly from your data

Both modes use the exact same tool, and can be combined in one call (e.g. inline markers layered on top of a chained route). Nothing about the standalone path is a fallback or a lesser-supported mode — it's the same code path other Mapbox tools use internally.

Standalone usage: visualize your own data

This is the case most third-party integrators care about: you have your own geospatial data (a delivery zone, a set of store locations, a GPS trace, anything expressible as GeoJSON) and want to show it on a live Mapbox map inside an MCP conversation, without calling any other tool in this server first.

Call render_map_tool directly with layers and/or markers:

{
  "summary": "Downtown delivery zone",
  "layers": [
    {
      "id": "delivery-zone",
      "type": "fill",
      "data": {
        "type": "Feature",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [-122.4194, 37.7749],
              [-122.4094, 37.7749],
              [-122.4094, 37.7849],
              [-122.4194, 37.7849],
              [-122.4194, 37.7749]
            ]
          ]
        },
        "properties": {}
      },
      "paint": {
        "fill-color": "#3b82f6",
        "fill-opacity": 0.25,
        "fill-outline-color": "#1d4ed8"
      }
    }
  ],
  "markers": [
    {
      "coordinates": [-122.4144, 37.7799],
      "style": "pin",
      "color": "#ef4444",
      "popup": "Warehouse"
    }
  ],
  "legend": [{ "label": "Delivery zone", "color": "#3b82f6", "opacity": 0.25 }]
}

No payload_refs, no dependency on directions_tool/isochrone_tool/etc. — the map renders exactly this polygon and marker. This is the whole request; there's nothing else to configure.

As a natural-language prompt, this looks like: "Using the Mapbox map render tool, show a fill polygon over these four coordinates: [...], with a red pin at [...] labeled 'Warehouse'." An LLM with access to this tool can compose the JSON payload itself from a plain-language description of your data — you don't need to hand it pre-built GeoJSON if the model already has (or can derive) the coordinates.

Multiple layers and markers in one call are merged onto the same map and the camera auto-fits to the union of everything drawn, unless you provide an explicit camera.

Chained usage: rendering another tool's result

When another tool in this server returns geospatial data, its structuredContent includes a mapboxRender.ref field:

{
  "structuredContent": {
    "routes": [
      /* ... */
    ],
    "mapboxRender": { "ref": "mapbox://selffetch/directions?data=..." }
  }
}

Pass that ref straight through:

{ "payload_refs": ["mapbox://selffetch/directions?data=..."] }

Pass multiple refs to merge several tool results onto one map — for example, an isochrone plus a route:

{
  "payload_refs": [
    "mapbox://selffetch/isochrone?data=...",
    "mapbox://selffetch/directions?data=..."
  ]
}

An LLM using this server is instructed (via each tool's own output) to call render_map_tool as the final step whenever a mapboxRender field is present — you generally don't need to prompt for this explicitly.

Payload reference

All fields are optional; provide whichever combination fits what you're drawing.

FieldTypeDescription
payload_refsstring[]Reference strings from other tools' mapboxRender.ref. Merges with any inline fields below.
summarystringShort header chip shown top-left on the map (e.g. "Route: 12.4 mi, 23 min").
layersarrayInline GL JS layers — see below.
markersarrayInline point markers — see below.
legendarrayInline legend rows — see below.
cameraobjectInitial camera position. If omitted, the map auto-fits to everything drawn.
baseMapConfigobjectRestyles the Standard base map itself (colors, theme, lighting, label visibility) — see below.
baseStyle"standard" | "standard-satellite"Which Mapbox-owned base style to load; defaults to "standard" — see below.

layers[] — one entry per Mapbox GL JS source+layer pair:

FieldTypeDescription
idstringUnique id within the payload (used as both source id and layer id).
type"fill" | "line" | "circle" | "symbol"Mapbox GL layer type.
dataGeoJSON Feature or FeatureCollectionGeometry must be Point, LineString, Polygon, or MultiPolygon. Coordinates are [longitude, latitude].
paintobjectMapbox Style Spec paint object, passed through to addLayer as-is (e.g. { "line-color": "#3b82f6", "line-width": 5 }).
layoutobjectStyle Spec layout object (e.g. { "line-join": "round", "line-cap": "round" }).
slot"bottom" | "middle" | "top"Placement relative to the base map's own layers. "bottom": below all basemap layers. "middle": above land/water but below roads, buildings, and labels — the usual choice for data-viz layers (isochrones, choropleths). "top": above all basemap layers except labels — the usual choice for routes and highlighted features. Omit to render above everything, including labels (prior default behavior).

markers[] — one entry per point marker:

FieldTypeDescription
coordinates[number, number][longitude, latitude].
style"pin" | "numbered" | "start" | "end"pin is the default Mapbox marker. numbered is a circular badge containing label (e.g. visit order). start/end are green/red badges for route endpoints.
labelstringRequired when style is "numbered".
colorstringCSS color override; defaults are style-derived.
popupstringText shown when the marker is clicked.

legend[] — one entry per legend row:

FieldTypeDescription
labelstringRow label.
colorstringSwatch CSS color.
opacitynumber (0-1)Swatch opacity.

camera:

FieldTypeDescription
center[number, number][longitude, latitude].
zoomnumberZoom level.
bounds[[number, number], [number, number]][[minLng, minLat], [maxLng, maxLat]]. Takes precedence over center/zoom and over auto-fit if set.

baseMapConfig — restyles the Mapbox Standard base map itself via its config-property system (map.setConfigProperty('basemap', ...)), rather than adding a new layer on top of it. For example, { "colorWater": "#ff0000" } turns the water red:

A San Francisco map with the water rendered bright red via baseMapConfig's colorWater property, with a route line and marker drawn on top

FieldTypeDescription
theme"default" | "faded" | "monochrome" | "custom"Overall color treatment.
lightPreset"day" | "dawn" | "dusk" | "night"Lighting/shadow preset.
colorWater, colorLand, colorGreenspace, colorMotorways, colorTrunks, colorRoads, colorBuildings, colorAdminBoundaries, colorCommercial, colorEducation, colorMedical, colorIndustrial, colorPlaceLabels, colorRoadLabels, colorPointOfInterestLabelsstringCSS color override for the named base-map feature.
showPointOfInterestLabels, showTransitLabels, showPlaceLabels, showRoadLabels, showAdminBoundaries, showIndoor, showIndoorLabels, showLandmarkIconLabels, show3dObjects, show3dTrees, show3dFacadesbooleanToggles visibility of the named base-map feature.
densityPointOfInterestLabelsnumberPOI label density.

Only the properties above are typed in the schema, but unrecognized keys still pass through — useful if Mapbox ships a new Standard config property before this list is updated.

Important: like every other field, baseMapConfig participates in render_map_tool's "fresh render" model — a call doesn't accumulate on top of a previous one. If you want to restyle a map that already has data on it, re-pass the same payload_refs/layers alongside baseMapConfig in the same call, rather than sending baseMapConfig alone (a baseMapConfig-only call is valid, but renders a blank map in the new style with no data on it).

baseStyle — which Mapbox-owned base style to load. Defaults to "standard"; set to "standard-satellite" to load Mapbox Standard Satellite instead — the same Standard style family rendered over global satellite imagery, with the same dynamic POI/road/place labels and the same bottom/middle/top slots for custom layers:

A satellite view of San Francisco via baseStyle: "standard-satellite", with a yellow route line drawn on top and Standard's dynamic place/POI labels rendering over the imagery

baseMapConfig still works under standard-satellite, but it supports a smaller set of properties than standard does — no theme and no flat-color overrides like colorWater/colorBuildings, since there's no vector land/water surface to recolor under the imagery. See the Standard Satellite reference for its exact supported property list.

Like baseMapConfig, baseStyle participates in the "fresh render" model described above — re-pass payload_refs/layers alongside it if you want to restyle a map that already has data on it.

The payload format is intentionally a thin pass-through to the Mapbox Style Spec rather than its own DSL — anything expressible as a GL JS paint/layout object is expressible here, so you're not limited to a fixed set of pre-baked styles.

Compatible clients

render_map_tool renders via MCP Apps (@modelcontextprotocol/ext-apps). Hosts that support it show a live, interactive map with a Fullscreen toggle:

  • Claude Desktop
  • VS Code with GitHub Copilot
  • Claude Code
  • Goose

In a client without MCP Apps support, render_map_tool's text/JSON output (the resolved payload) is still returned as the tool result — you lose the live interactive panel, but the call doesn't fail. If you need a guaranteed visual image regardless of client capability, use static_map_image_tool instead, which returns a base64-encoded PNG/JPEG that every client can display.

Notes for third-party integrators

  • You don't need any other Mapbox tool. render_map_tool accepts arbitrary standalone GeoJSON through layers/markers. Nothing about payload_refs is required.
  • You don't need a Mapbox access token to reason about the payload shape — token handling for rendering (fetching map tiles) happens entirely inside the host's iframe, using this server's own public-token resolution. Your layers/markers are plain GeoJSON/CSS values with no credentials embedded.
  • Geometry size: very large inline payloads (tens of thousands of coordinates) are still passed through the model as tool arguments in standalone mode, unlike the internal payload_refs optimization other tools use. For most use cases (a handful of markers, a modest polygon or route) this is a non-issue; if you're regularly rendering very large geometries, consider simplifying them first (see simplify_tool) before passing them to render_map_tool.
  • Multiple calls: each call to render_map_tool performs a fresh render — it does not accumulate state across calls. Merge everything you want on one map into a single call's layers/markers/payload_refs.

Troubleshooting

"I'm not seeing an interactive map, just text/JSON"

Check that your client supports MCP Apps (see Compatible clients). Clients without support still receive the resolved payload as a tool result, just not the rendered panel.

"My inline layer isn't drawing anything"

Confirm data is a valid GeoJSON Feature or FeatureCollection with Point, LineString, Polygon, or MultiPolygon geometry, and that type (fill/line/circle/symbol) matches the geometry — for example, a Polygon needs type: "fill" or "line", not "symbol".

"I want a guaranteed image even in clients without MCP Apps support"

Use static_map_image_tool — it always returns a base64-encoded PNG/JPEG, with no dependency on MCP Apps support, so every client gets a usable result.


For questions or issues, please open an issue on GitHub.