Skip to main content
@luxxon/mcp is the canonical agent integration path. It runs as a Model Context Protocol server over stdio and exposes Luxxon as agent-callable tools — your agent calls them like any other function, no WebRTC stack required.
npx -y --package=@luxxon/mcp luxxon-mcp

Install

The server is published on npm and runs on demand via npx. No global install needed. It’s also listed in the major MCP catalogs:

Smithery

Glama

Or wire it manually:
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
  "mcpServers": {
    "luxxon": {
      "command": "npx",
      "args": ["-y", "--package=@luxxon/mcp", "luxxon-mcp"],
      "env": {
        "LUXXON_API_KEY": "lxxn_live_..."
      }
    }
  }
}
Restart Claude Desktop. The twelve Luxxon tools appear under the tools icon in the message composer.

Tools

Twelve tools, authenticated via the LUXXON_API_KEY env var. The headline (request_live_view) takes a lat/lng pair and a duration; everything else is a thin wrapper around a single API endpoint.
ToolReturnsWhen to use
request_live_view{ sessionId, state, whepUrl?, hint }Start here. Open + dispatch + wait-for-LIVE in one call. sessionId is always returned, even if the operator doesn’t reach LIVE in time.
wait_for_live{ sessionId, state, whepUrl?, hint }Keep waiting on a known sessionId that hasn’t gone LIVE yet (e.g. recovery after a request_live_view timeout).
list_sessions{ count, sessions }Recover a sessionId you lost, or audit recent workspace sessions. Filter client-side by state.
get_sessionSession JSON (state, meter, settlement)Read where the session is in its lifecycle
get_frameJPEG (inline image content block)Hand directly to a vision model
get_stream_urlWHEP playback URLAgent has its own WebRTC stack
end_sessionUpdated session JSONClose a LIVE session; triggers on-chain settle
cancel_sessionUpdated session JSONAbort a pre-LIVE session (REQUESTED / ASSIGNED)
get_pricing_quote{ quoteId, ratePerSecondMicroUsdc, estimatedTotalMicroUsdc, … }Estimate cost before opening; returns a quoteId to lock the rate on request_live_view
get_coverageAnonymized list of operator coverage circlesPre-flight check: is Luxxon available here?
get_wallet{ trackedBalance, lastSyncBlock, … }Decide whether to top up before a session
get_settlement{ state, txHash, confirmedAt, payload }Read on-chain tx info after end_session

request_live_view — the headline

The one tool call from prompt to live frame:
agent: "look at lat 4.71, lng -74.05 for 30 seconds and tell me what's happening"
  → tool call: request_live_view({ lat: 4.71, lng: -74.05, maxDurationSeconds: 30 })
  → response: { sessionId: "a1b2c3...", whepUrl: "https://...", state: "LIVE", hint: "call get_frame next" }
agent: → tool call: get_frame({ sessionId: "a1b2c3..." })
  → response: <image: 640x480 JPEG, 32KB>
agent: "looks like a busy intersection at dusk; ~12 cars visible..."
  → tool call: end_session({ sessionId: "a1b2c3..." })
  → response: { state: "ENDED", cleanSeconds: 28, chargedMicroUsdc: "28000" }
The operator’s pool is debited per second at session end via LuxxonSettlement.settleFromPool — no wallet popup, no per-call signature. Throws NO_COVERAGE (503) if no operator is in range; get_coverage is your pre-flight check.

When the operator doesn’t go LIVE in time

request_live_view does not throw on a wait-for-LIVE timeout — it returns the sessionId plus the current state (usually ASSIGNED) so the agent can decide what to do next:
→ tool call: request_live_view({ lat, lng, maxDurationSeconds: 30 })
→ response: {
    sessionId: "a1b2c3...",
    state: "ASSIGNED",
    waitOutcome: "TIMEOUT",
    hint: "Operator hasn't gone LIVE. Call wait_for_live('a1b2c3...') to keep waiting, or cancel_session('a1b2c3...') to give up."
  }
→ tool call: wait_for_live({ sessionId: "a1b2c3...", timeoutMs: 30000 })
→ response: { sessionId: "a1b2c3...", state: "LIVE", whepUrl: "...", hint: "Call get_frame for a JPEG." }
If you lost a sessionId (e.g. a previous tool run crashed before returning), call list_sessions to find it — sessions are returned newest first, with their current state.

get_frame

For LIVE sessions only. Returns the latest decoded video frame as a JPEG content block. MCP-aware clients pass it straight to the LLM as image input — no base64 plumbing on your side. get_frame is reliable the moment state === "LIVE": the API only flips the state once the first JPEG is buffered server-side, so the first call after request_live_view / wait_for_live resolves always returns bytes. Calling during ASSIGNED (pre-first-frame) returns FRAME_NOT_AVAILABLE — that’s where wait_for_live is meant to do the waiting for you.

Auth

Pass your workspace API key via LUXXON_API_KEY. Use lxxn_test_* keys today; lxxn_live_* will work once the mainnet contract ships (until then, LIVE keys are rejected at the API layer). The pool model means no wallet popups, ever — the consumer workspace pre-deposits USDC to LuxxonSettlement once via the console, and every session debits from that pool at end-time. The MCP holds nothing more than the API key.

Not yet wired (v0.3+)

Most of the original strategy is shipped. The next batch needs new API-side primitives:
  • get_observation — single-frame SKU (per-frame pricing endpoint)
  • Bounty tools (create_bounty, get_bounty_status) — pay-on-fulfillment for unsupported locations
Track progress in the luxxon-sdk repo.

Source + issues

  • npm i @luxxon/mcp — npm package
  • github.com/luxxon-dev/luxxon-sdk — source, including the TypeScript SDK the server is built on
  • Issues + feature requests via the repo