MCP server¶
GeoLibre ships an MCP server that authors
GeoLibre projects. Point an MCP client (Claude Desktop, Claude Code, or any
other) at it and you can ask for a map in words: the server writes a real
.geolibre.json project you open in the desktop app, the web app, or the
geolibre Jupyter widget, and can export it as a standalone HTML page.
The server is headless. It needs no browser, no running GeoLibre instance, and no bundled web build. It builds project files with the same project builders the Python package uses, so a project it writes is byte-for-byte the kind the app already loads.
Install¶
The MCP SDK is an optional extra:
pip install "geolibre[mcp]"
Run it¶
geolibre-mcp --root ~/maps
The server speaks MCP over stdio, which is what desktop clients spawn. The
--root flag is repeatable, and GEOLIBRE_MCP_ROOTS (:-separated, ; on
Windows) does the same job from the environment. With neither set, the workspace
is the current directory.
Client configuration¶
Claude Desktop (claude_desktop_config.json) and most other clients take the
same shape:
{
"mcpServers": {
"geolibre": {
"command": "geolibre-mcp",
"args": ["--root", "/Users/you/maps"]
}
}
}
For Claude Code:
claude mcp add geolibre -- geolibre-mcp --root ~/maps
If geolibre-mcp is not on the client's PATH (common when it was installed
into a virtualenv), give the interpreter instead:
{
"mcpServers": {
"geolibre": {
"command": "/path/to/venv/bin/python",
"args": ["-m", "geolibre.mcp", "--root", "/Users/you/maps"]
}
}
}
The workspace¶
Every path in every tool call is resolved against the allowed roots before the
server touches it, mirroring GEOLIBRE_CONVERSION_ROOTS in the
sidecar. Paths outside them are refused, and so is a symlink
inside a root that points out of it. Relative paths resolve against the first
root, so a client can say city.geolibre.json without knowing the host layout.
Three more guards on writes: the server only writes files ending in .json
(projects) or .html (exports) — a bare .json with no name is refused too —
it refuses to replace an existing file unless the call passes overwrite, and
a tool that edits an existing project first checks the file actually is one, so
an unrelated package.json sitting inside a root cannot be rewritten as a map.
Give it a directory meant for maps, not your home directory.
Tools¶
Project lifecycle¶
| Tool | What it does |
|---|---|
create_project |
Write a new, empty project with a name, center, zoom, and basemap. |
describe_project |
Summarize the camera, basemap, layers, and map controls. Inlined feature data is reported as a count, never echoed back. |
list_catalog |
List the named basemaps, color ramps, and legend presets, plus the active workspace roots. |
Adding layers¶
| Tool | For |
|---|---|
add_geojson_layer |
Vector data inlined into the project, from a URL, a workspace file, or literal GeoJSON. Self-contained, and the only kind classify_layer can style. |
add_vector_layer |
A large remote FlatGeobuf / GeoParquet / GeoJSON read in place. |
add_raster_layer |
A Cloud Optimized GeoTIFF, with band, colormap, and rescale options. |
add_tile_layer |
A raster XYZ tile template. |
add_tiles_layer |
PMTiles archives and vector tile services. |
add_ogc_layer |
WMS and WMTS endpoints. |
add_3d_tiles_layer |
OGC 3D Tiles tilesets. |
Editing¶
| Tool | What it does |
|---|---|
update_layer |
Rename, show/hide, set opacity, or reorder. |
remove_layer |
Drop a layer. |
style_layer |
Merge style keys (fillColor, strokeWidth, circleRadius, …). |
classify_layer |
Build a graduated choropleth from a numeric column. |
list_layer_properties |
List a layer's feature properties with sample values. |
Layers are addressed by id or by display name, so a client can work from
what describe_project showed it without tracking UUIDs.
Framing and decoration¶
| Tool | What it does |
|---|---|
set_view |
Set center, zoom, bearing, and pitch, or pass a bbox to frame an area. |
set_basemap |
Switch the background style. |
add_legend |
Add a legend from a preset, a {label: color} map, or paired lists. |
add_colorbar |
Add a colorbar for continuous data. |
add_swipe |
Configure the split-map comparison slider. |
Export¶
export_html writes a standalone page that embeds the hosted GeoLibre viewer
and injects the project into it, so the recipient needs no install. Credentials
are stripped from the project on the way out. Layers pointing at local files
will not load for anyone else, so use hosted URLs for a shareable export.
app_url is a trust boundary
export_html's optional app_url names the viewer the exported page
embeds, and the page posts the project to exactly that origin (it must be
an http/https URL). It exists so you can pin a self-hosted deployment.
Treat it as a destination, not a cosmetic setting: whoever opens the exported file hands the project's contents — inlined GeoJSON, layer URLs, the camera — to that origin. Credentials are already stripped, so this is not a key leak, but the rest of the project still travels.
This matters because the caller here is a model, which may be acting on
content it has read. If an exported page points somewhere you did not
choose, that is worth a second look. Only accept an app_url you
intended.
Notes and limits¶
set_viewwith abboxis approximate. A saved project stores a center and zoom, and the app applies those verbatim on load rather than fitting a stored bbox. The server therefore resolves the box to a camera itself, using an assumed map-pane size, and lands within roughly half a zoom level of what the app's own "zoom to layer" would pick. Passcenterandzoomwhen you need exact framing.- Inlined GeoJSON is capped at 50 MB, and a project file the server reads at
256 MB. Past those, use
add_vector_layeror a tiled source. - Remote fetches are checked: a URL whose host resolves to a private, loopback, or link-local address is refused, on every redirect hop as well as the first request, so a crafted URL cannot reach a cloud metadata endpoint.
- The server authors projects; it does not drive a live map. Interactive control of a running GeoLibre instance goes through the scripting bridge that backs the Python widget and the embed API.
Under the hood¶
The tools are thin wrappers over geolibre.authoring, a widget-free module of
operations on project dicts (add/remove/restyle a layer, move the camera,
compose the map controls). geolibre.Map delegates to the same module, so the
notebook widget and the MCP server cannot drift apart in how they build a
project.