Skip to main content
Glama
geodaai
by geodaai

kepler-mcp-demo

Drive the kepler.gl demo map from Claude / Codex through the @kepler.gl/mcp map surface — mapping-only, no analysis engine, no API key.

claude/codex ──Streamable HTTP MCP──▶ kepler-mcp-demo daemon (node)
  (or stdio spawn)                        │  /mcp   JSON-RPC for agents
                                          │  /ws    ▲ outbound (reverse connect)
                                          │  /files data for map.load-data
        https://kepler.gl/demo  ──────────┘

The kepler.gl demo page owns the map. It registers the map.* commands from @kepler.gl/mcp (getKeplerCommands(ctx)) against its own redux store and serves them over a WebSocket bridge. This node process is deliberately map-agnostic: the tool catalog comes from the page's connection, so the map surface is wholly owned by @kepler.gl/mcp.

What you get

The map.* commands the demo page serves (the DuckDB-free subset):

Tool (MCP name)

Command

Needs

map_get_boundary

map.get-boundary

current view bbox

map_load_data

map.load-data

load a CSV/GeoJSON/... from a URL (see files/)

map_add_layer

map.add-layer

add point/h3/hexagon/... layer

map_update_layer_color

map.update-layer-color

recolor a layer

map_set_basemap

map.set-basemap

change the basemap style

map_get_dataset_context, map_add_time_filter, map_toggle_time_filter, map_split_view

more map.* commands

harness_status

(built-in)

is a page connected?

map.create-table / map.add-column / map.save-data need a DuckDB connector and are intentionally not exposed by the mapping-only bridge.

Related MCP server: Felt MCP

First install and build (and rebuild after any source change — a long-running daemon serves stale dist/):

pnpm install
pnpm build

Start the server

Foreground:

node dist/index.js --port 8765 --token mytoken --demo-url https://kepler.gl/demo

Detached (survives the terminal closing; log lands in /tmp/kepler-mcp-demo.log):

cd /path/to/kepler-mcp-demo && nohup node dist/index.js --port 8765 --token mytoken \
  --demo-url https://kepler.gl/demo > /tmp/kepler-mcp-demo.log 2>&1 &

Flags:

Flag

Default

Purpose

--port

8765

port for /mcp, /ws and /files

--token

random

shared secret — the page and MCP clients must present it. Pick a fixed one (--token mytoken) so the page URL and claude mcp add header stay stable across restarts.

--demo-url

https://kepler.gl/demo

the page the daemon tells agents/users to open — e.g. --demo-url https://deploy-preview-3678--keplergl.netlify.app/demo when testing a deploy preview

Check it's up:

curl -s http://localhost:8765/          # status page
lsof -nP -i :8765 -sTCP:LISTEN          # or check the listener
tail -f /tmp/kepler-mcp-demo.log        # see the banner + page connect/disconnect

Stop it:

lsof -ti :8765 | xargs kill

Start-once lifecycle

The daemon must be started once — not per session. Sessions (and claude mcp list health checks) just connect to it; the page stays connected across session restarts. You only need to run the start command again after a reboot, after killing the process, or after pnpm build.

  1. Register the HTTP MCP server (once; pick the token so the page can use it):

    claude mcp add --transport http kepler-demo http://127.0.0.1:8765/mcp \
      --header "x-kepler-demo-token: <yours>"
  2. Connect the page (once): open https://kepler.gl/demo (or the local demo-app) with ?mcp=<token>&mcpPort=8765, or toggle Connect to map harness and paste the token. It stays connected across session restarts — nothing to redo per session.

  3. Start sessions freely. Each session initially sees only harness_status; the page's catalog arrives via a live tools/list_changed. Because the daemon owns the port, parallel sessions and health checks (claude mcp list) no longer collide.

Tip: register with -s user (claude mcp add -s user --transport http …) so the server is visible from any directory, not just the one you ran claude mcp add in.

Optional: auto-start with launchd (macOS)

To never think about starting the daemon again — it comes back after every reboot — create ~/Library/LaunchAgents/com.kepler-mcp-demo.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.kepler-mcp-demo</string>
  <key>ProgramArguments</key><array>
    <string>/usr/local/bin/node</string>       <!-- or `which node` -->
    <string>/path/to/kepler-mcp-demo/dist/index.js</string>
    <string>--port</string><string>8765</string>
    <string>--token</string><string>mytoken</string>
  </array>
  <key>KeepAlive</key><true/>
  <key>RunAtLoad</key><true/>
  <key>StandardOutPath</key><string>/tmp/kepler-mcp-demo.log</string>
  <key>StandardErrorPath</key><string>/tmp/kepler-mcp-demo.log</string>
</dict></plist>

Then: launchctl load ~/Library/LaunchAgents/com.kepler-mcp-demo.plist.

Run (stdio — per-session spawn, also supported)

claude mcp add kepler-demo -- node /abs/path/to/kepler-mcp-demo/dist/index.js --port 8765 --token <yours>

Pass --port/--token so the page can connect to the instance the client spawns. The session starts first and the page connects second; the bridge pushes tools/list_changed so the map tools appear without a restart. Note the single-instance rule: while one such session is open, port 8765 is taken (the server exits with a legible message if not).

Security: the WS bridge requires the per-launch token; the static file server only serves the files/ directory (path-traversal blocked); the page connects out to your localhost, so a remote Claude cannot reach your browser.

Examples

Verify headlessly

pnpm smoke

Spawns the server, connects a fake page over WS, and exercises the MCP tools/list / tools/call round trip through the bridge.

Available Tools

1 tool
harness_statusA
Read-onlyIdempotent

Check the bridge status. Returns whether a kepler.gl demo page is currently connected to this bridge, and which map.* commands the page is serving.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is established. The description adds meaningful behavioral context by disclosing what the return value tells the caller: current connection status and the active command surface. This is especially useful given no output schema is present.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two short sentences with no redundant words. The core purpose is front-loaded, and the additional return-value detail is provided in the second sentence without padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, read-only status tool with no output schema and no siblings, the description fully covers what an agent needs: the action, the resource, and the nature of the returned information. Nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes no parameters and the input schema is empty, so there is nothing for the description to add. Per the baseline for zero-parameter tools, this is adequate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ('Check'), a clear resource ('bridge status'), and states exactly what the answer will contain: connection state and the map.* commands being served. It is unambiguous and fully distinguishes the tool from any potential alternative, even with no sibling tools present.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use is clear: call this tool when you need to know whether a kepler.gl demo page is connected to the bridge and what map.* commands are available. There are no sibling tools to contrast against, so explicit exclusions are not necessary; the context is sufficiently clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A4.1/5.0
Disambiguation5/5

With only one tool, there is no possibility of confusion or overlap. The single tool has a clear, specific purpose.

Naming Consistency5/5

The single tool name 'harness_status' is clear and there are no other names to compare against, so no inconsistency exists.

Tool Count2/5

One tool is too few for the apparent scope of a kepler.gl bridge that should serve multiple map.* commands. The minimal count severely limits the server's utility.

Completeness1/5

The server only provides a status check, while the tool description implies a broader set of map.* commands that are not exposed. Agents cannot actually interact with the kepler.gl demo page beyond checking its status.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables to interact with Felt maps programmatically through an MCP server. Supports listing, retrieving details, and creating maps with natural language.
    4
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to perceive and control a live MapLibre GL map over MCP, allowing querying rendered features, reading popups, navigating, and toggling layers.
    1
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/geodaai/kepler-mcp-demo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server