@llui/agent-bridge

The MCP bridge that lets Claude Desktop (and other MCP clients) drive an LLui app. Companion to @llui/agent — the bridge speaks MCP on one side and LAP on the other.

npm install -g llui-agent

The package ships a single CLI, llui-agent, which runs a stdio MCP server. You don't import this package — you configure Claude Desktop to spawn it.

Claude Desktop configuration

Add the bridge to your MCP config (on macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "llui": { "command": "llui-agent" }
  }
}

Restart Claude Desktop. The LLui tools appear in the tool picker.

Binding a conversation

Each conversation targets at most one LLui app. Before any LLui tool call, bind the session:

  1. In your app, mint a token (your app's agent connect slice produces the mint request).
  2. The app displays /llui-connect <lap-url> <token> — copy it.
  3. Paste it into Claude. Claude calls connect_session; the bridge caches the URL + token for this session and validates by pinging /describe.

From then on, every MCP tool Claude calls routes through the bound LAP endpoint. disconnect_session clears the binding.

Tools

Meta tools

Tool Purpose
connect_session Bind this conversation to a LAP URL + bearer token.
disconnect_session Clear the binding.

Recommended path

Tool Purpose
observe One call returns {state, actions, description, context} — replaces the legacy bootstrap trio.
send_message Dispatch a Msg; by default waits for the message queue to drain (http/delay/debounce round-trips).

send_message controls:

  • waitFor: 'drained' | 'idle' | 'none''drained' (default) waits for quiescence; 'idle' flushes the synchronous cycle only; 'none' is fire-and-forget.
  • drainQuietMs — no-commit window size. Default 100ms.
  • timeoutMs — hard cap. Default 5000ms. Partial results return with drain.timedOut: true.

The response envelope includes the new stateAfter, fresh actions, and a drain block with effectsObserved, durationMs, timedOut, and any effect-thrown errors captured during the window.

Legacy / specialized

Tool Purpose
describe_app Static app metadata. Cached after the first call.
get_state Full state or a JSON-pointer slice. Prefer observe; use this for scoped reads.
list_actions Currently-affordable actions. Subsumed by observe.
get_confirm_result Poll a pending-confirmation by confirmId.
wait_for_change Long-poll for an external state change (e.g. WebSocket push). send_message waits already for LLM-initiated changes.
query_dom Read elements tagged with data-agent="<name>".
describe_visible_content Structured outline of the visible data-agent-tagged subtrees.
describe_context Current agentContext(state) output.

See also

  • @llui/agent — full adoption guide: annotations, component metadata, DOM tagging, production server setup, security.

Functions

createBridgeServer()

Builds the bridge's MCP server using the high-level McpServer registrars. Each tool's Zod schema (declared once in tools.ts) drives both runtime input validation and the JSON Schema published to tools/list — eliminating the hand-written-schema-vs-handler drift that the low-level setRequestHandler pattern is prone to. Forwarded tools (kind: 'forward') share a generic forwarder that looks up the binding, dispatches to LAP, and caches description payloads where applicable. The two meta tools (connect_session, disconnect_session) carry custom handlers that mutate the BindingMap directly.

function createBridgeServer(deps: BridgeDeps): McpServer

Types

ToolDescriptor

export type ToolDescriptor = McpForwardedToolDescriptor | McpMetaToolDescriptor

BridgeDeps

export type BridgeDeps = {
  /** Injectable for tests. */
  fetch?: typeof fetch
  /** MCP session ID for this client. In stdio mode there's one session; derive from the Server instance. */
  sessionId: string
  /** Shared binding map (one BindingMap per process). */
  bindings: BindingMap
  /** Package version — set from package.json at boot. */
  version: string
}

Constants

TOOL_DESCRIPTORS

const TOOL_DESCRIPTORS: ToolDescriptor[]