kepler-mcp-app
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@kepler-mcp-appLoad the sample CSV and add a point layer to the map"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
kepler-mcp-app
A demo of building an MCP App for kepler.gl — the new MCP Apps extension where a server returns an interactive HTML interface that the host (Claude Desktop / Claude.ai) renders in a sandboxed iframe inside the chat.
This is distinct from:
WebMCP (native
document.modelContextregistration), andthe existing kepler-mcp-demo (
~/github/kepler-mcp-demo), where the map renders on the kepler.gl website and a node daemon routes MCP tool calls to the page over a WebSocket bridge.
Here, the kepler.gl map renders inside the MCP App iframe, and the map.*
commands from @kepler.gl/mcp
execute inside that iframe against the iframe's own kepler.gl redux store.
No WebSocket bridge, no external website.
Architecture
Claude Desktop / Claude.ai (host)
│ MCP (Streamable HTTP, e.g. via cloudflared tunnel)
▼
kepler-mcp-app server (node, port 3001)
│ • serves ui://kepler-map/mcp-app.html (bundled kepler.gl app, single HTML file)
│ • registers kepler_show_map tool with _meta.ui.resourceUri → the UI resource
▼
MCP App iframe (sandboxed, rendered by host inside the chat)
├── kepler.gl map (React 19 + redux store, @kepler.gl/components KeplerGl)
├── App instance (@modelcontextprotocol/ext-apps)
└── registers every map.* command from @kepler.gl/mcp as an app tool
(map_load_data, map_add_layer, map_set_basemap, …) + kepler_get_map_skillFlow: the agent calls kepler_show_map → the host fetches the UI resource
and renders the kepler.gl app in a sandboxed iframe → the iframe boots
kepler.gl, builds a KeplerContext from its own redux store, and registers the
map.* commands via App.registerTool() → the agent can then call
map_load_data / map_add_layer / … and the host routes each call to the
iframe, which executes it against its redux store and returns the result. The
app also calls app.updateModelContext() to tell the model the map is ready.
This is the same pattern as the official map-server example (CesiumJS globe)
in modelcontextprotocol/ext-apps: the server registers the launch tool + UI
resource, and the app registers the interactive tools.
Related MCP server: map-mcp
Repo structure
kepler-mcp-app/
├── package.json # deps: @kepler.gl/*, @modelcontextprotocol/ext-apps, react, redux, vite
├── tsconfig.json # app + server typecheck
├── vite.config.ts # vite + vite-plugin-singlefile → single mcp-app.html
├── server.ts # MCP server: kepler_show_map tool + ui:// resource (with CSP)
├── main.ts # Streamable HTTP + stdio entry
├── mcp-app.html # kepler.gl app HTML entry
├── src/
│ ├── mcp-app.tsx # embeds <KeplerGl>, creates App, registers map.* tools
│ ├── kepler-store.ts # redux store (keplerGlReducer + thunk)
│ ├── kepler-context.ts # buildKeplerContext(store) — live accessors into the iframe's store
│ └── tool-utils.ts # toMcpToolName, resultToText, toCallToolResult, skill tool
├── vendor/@kepler.gl/mcp/ # vendored @kepler.gl/mcp source (from PR #3678) + package.json
├── files/ # sample data (random_points.csv) served at /files/*
└── README.mdRun
npm install
npm run build # vite → dist/mcp-app.html; esbuild → dist/index.js
npm start # node dist/index.js → http://localhost:3001/mcpnpm run dev rebuilds the app on change and restarts the server.
Test
Server smoke test:
curl -X POST http://localhost:3001/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'tools/list should return kepler_show_map with _meta.ui.resourceUri, and
resources/read of ui://kepler-map/mcp-app.html should return the bundled
HTML.
basic-host (from modelcontextprotocol/ext-apps):
SERVERS='["http://localhost:3001/mcp"]' npm start # in examples/basic-hostCall kepler_show_map, confirm the kepler.gl map renders in the sandboxed
iframe, then call map_load_data (data URL) + map_add_layer and confirm the
map updates.
Claude Desktop:
npx cloudflared tunnel --url http://localhost:3001Add the tunnel URL as a custom connector, then ask it to "show me a map of
these points" and confirm the map renders in-chat and the map.* tools drive
it.
Loading data
The sandboxed iframe cannot fetch localhost, so load local files as data
URLs (≤2 MB):
data:text/csv;base64,<base64>See files/README.md for a worked example. The server also
serves files/ at /files/* with CORS for hosts whose iframe can reach it.
Notes
Basemap: kepler.gl 3.3.0-alpha.10 defaults to MapLibre with CARTO styles (
dark-matter/positron/voyager) — no Mapbox token needed. The UI resource's CSP allowsbasemaps.cartocdn.comand*.basemaps.cartocdn.com. The wildcard matters: the CARTO vector TileJSON advertises its tiles on sharded hostnames (tiles-a…d.basemaps.cartocdn.com), so allowing onlytiles.basemaps.cartocdn.comsilently blocks the tile fetches and the map stays black (thebackgroundlayer still paints, which looks like a working dark map).data:is also allowed inconnect-srcsomap_load_datacan fetch data URLs.KeplerGl sizing: the
<KeplerGl>component'swidth/heightprops must be pixel numbers — the root div gets inlinewidth: ${width}px. Passing"100%"produces invalid CSS, the div collapses to height 0, and kepler's ResizeObserver then reports a zero size forever.src/mcp-app.tsxtracks the iframe viewport (window.innerWidth/Height+ aresizelistener) instead.DuckDB commands:
map.create-table,map.add-column, andmap.save-dataneed a DuckDB connector and are filtered out of the catalog. The skill tool (kepler_get_map_skill) notes this.@kepler.gl/mcpis not on npm yet; it is vendored from PR #3678 intovendor/@kepler.gl/mcp/and depended on viafile:.
This server cannot be deployed
Maintenance
Related MCP Connectors
Agent communication platform for agent to agent messaging via MCP. Messages, channels, skills.
Agent-to-agent messaging: directory, public lobby, DMs, channels, search. Stateless MCP + REST.
Track agent ROI and enrich companies via MCP. Remote, OAuth 2.1, no install or keys.
Surface customer & prospect context from Slack, email, transcripts and tickets in any MCP client.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables to interact with Felt maps programmatically through an MCP server. Supports listing, retrieving details, and creating maps with natural language.4MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to perceive and control a live MapLibre GL map over MCP, allowing querying rendered features, reading popups, navigating, and toggling layers.1MIT
- FlicenseAqualityBmaintenanceMCP server that lets agents create, display, and export rich UI widgets (cards, dashboards, charts, forms) inline in conversations, with interactive iframe support in MCP Apps hosts and PNG image fallback for other clients.3-
- FlicenseNot gradedqualityBmaintenanceMCP server that enables LLMs to create and control interactive MapLibre maps by placing markers, drawing paths, and rendering polygons directly in Claude Desktop.-