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.
Discovery
Section titled “Discovery”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.
Voicing: the speak MCP server
Section titled “Voicing: the speak MCP server”The easiest integration is the same one the CLI installs. Run:
node ~/.unclaw/bin/unclaw-speak.mjs --mcpas 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.
Viewing: the stream
Section titled “Viewing: the stream”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:
- Open
ws://127.0.0.1:<signalling_player>/?viewer=<yourtag>. - You are the offerer: create an
RTCPeerConnection, addrecvonlyvideo and audio transceivers, create a data channel namedcirrus, then send{ "type": "offer", "sdp": ... }. HandleanswerandiceCandidatemessages; onconfig, use itspeerConnectionOptions.iceServers. - 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. - 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.
Stability, in honest terms
Section titled “Stability, in honest terms”| 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.