Skip to content

Build on Unclaw

Unclaw runs entirely on the user’s machine, and its integration surface is local too: plain HTTP and WebSocket endpoints on loopback. This page documents the surfaces we consider stable enough to build against, so you can wire your own tool, editor plugin, or agent into the companion without waiting for us.

This page is also written to be pasted into an AI agent’s context. A condensed machine-friendly version lives at docs.unclaw.io/llms.txt.

Unclaw’s engine (“soul”) binds its ports dynamically. The one fixed address is the discovery endpoint:

GET http://127.0.0.1:8973/
→ { "app": "unclaw-soul", "http": <port>, "signalling_player": <port> }

It answers only while the app is running, sends Access-Control-Allow-Origin: * so browser contexts can call it, and is the intended way to find everything else. Poll it to learn when Unclaw comes and goes; do not hardcode the other ports, they change every launch.

The easiest integration is the same one the CLI installs. Run:

node ~/.unclaw/bin/unclaw-speak.mjs --mcp

as a stdio MCP server from any MCP-capable client and you get three tools:

Tool Arguments What it does
speak text (required), mood?, action? Says the line aloud through the companion with lipsync and expression. Launches Unclaw on first use if needed.
launch_unclaw - Starts the app in passthrough mode without speaking
unclaw_status - Whether the app is up and ready to speak

Ground rules that make integrations feel good: keep spoken lines short and conversational, never speak code, logs, or file paths, and honor the verbosity / muted hints echoed back on each speak response (the user controls those in the app).

If your platform is not MCP-capable, the runtime is a readable single file; the underlying HTTP call it makes is a POST /speak to soul’s http port with the text and optional mood, and it renders TTS, lipsync, and expression in one shot.

The companion’s live video is served over standard WebRTC through a Pixel Streaming-compatible signalling socket at the signalling_player port from discovery. The contract, briefly:

  1. Open ws://127.0.0.1:<signalling_player>/?viewer=<yourtag>.
  2. You are the offerer: create an RTCPeerConnection, add recvonly video and audio transceivers, create a data channel named cirrus, then send { "type": "offer", "sdp": ... }. Handle answer and iceCandidate messages; on config, use its peerConnectionOptions.iceServers.
  3. Tagging ?viewer= takes the render lease: the desktop app freezes behind a “playing elsewhere” overlay while your surface shows the live character, and reclaims automatically when your socket closes. An untagged connection does not take the lease and only receives frames when no local viewer holds the surface.
  4. Send your surface’s size over the data channel and the engine renders at your resolution. While you hold the lease, resolution is yours.

Exactly one viewer renders at a time, by design: the desktop app, your surface, or the phone. Whoever holds the lease gets the frames.

Surface Stability
Discovery on :8973 Stable, build on it
speak MCP tools Stable, build on it
/unclaw skill convention (SKILL.md) Stable
Player signalling + lease behavior Works today, may grow fields; pin your expectations to the steps above
Anything else you find in soul’s HTTP surface Internal, changes without notice

If you build something on this and need a surface promoted to stable, tell us: aryan@fotonlabs.com. The fastest way to get an endpoint documented is to ship something that uses it.