TypeScript additions
The hand-written helpers of @trycua/cua (embedded, connect, Image, probes, sidecars, specs, MCP), and the Spaces transport and host modules.
Import from @trycua/cua. This page covers the hand-written TypeScript layer; the generated native binding (sandboxes, guest, spaces objects) is documented per module in the Cua SDK reference.
npm install @trycua/cua installs the Node package; native code ships as per-platform optional packages (@trycua/cua-darwin-arm64, @trycua/cua-linux-x64-gnu, ...).
| Entry point | Use it for |
|---|---|
@trycua/cua | embedded(), connect(), every generated object, and the helpers on this page |
@trycua/cua/spaces, /spaces/transport, /spaces/host | Spaces threads, approvals and transports |
@trycua/cua/browser | A WebAssembly subset: cua-spacesd over gRPC-Web with a static bearer. No sandboxes, local runtimes, Spaces, daemon, file transfer or media. Give it a short-lived token from your backend, never client secrets |
cua does not implement MCP: connectMcp(await sb.mcpConfig(service, undefined)) hands the endpoint to the official SDK (npm install @modelcontextprotocol/client). Cloud bearers are short-lived, so fetch a config per connection.
Interfaces#
ImageOptions#
Options for Image.linux() / windows() / macos().
Properties#
| Property | Type |
|---|---|
tier? | ImageTier |
version? | string |
McpEndpointConfig#
Where an MCP endpoint is: Sandbox.mcpConfig() returns one.
Properties#
Type Aliases#
ImageTier#
type ImageTier = "slim" | "full" | "docker" | "xcode" | `xcode-${string}`;An image tier: slim, full (the default), on macOS xcode / xcode-<X.Y>,
or on Linux docker (slim plus the Docker engine, a VM: -docker-disk).
Variables#
Image#
const Image: object;Canonical images: Image.linux() is ghcr.io/trycua/linux:24.04
(CUA_IMAGE_LINUX overrides it), Image.windows()
ghcr.io/trycua/windows:2022, Image.macos() ghcr.io/trycua/macos:26.
These are the full tier (dev tooling); { tier: "slim" } is the minimal
image CI runs, on macOS { tier: "xcode" } adds a pinned Xcode and, on Linux,
{ tier: "docker" } is a VM with dockerd up at boot (24.04-docker-disk).
Image.omarchy() is ghcr.io/trycua/omarchy:edge, an amd64 VM. Images CI
has not published yet throw ImageNotPublished; pass the reference to
fromRegistry to use one anyway.
Image.resolve(ref, backend) is the one resolver: the digest-pinned
variant a backend runs (rootfs, the -disk containerDisk, Lume).
Type Declaration#
registrySecret#
const registrySecret: object;Credentials for a private registry image (registrySecret on
SandboxCreateOptions). Locally they authenticate the pull; in the cloud
the SDK stores them as the sandbox's registry pull secret. Never logged.
Type Declaration#
Functions#
ambiguousCandidates()#
function ambiguousCandidates(error): string[];The qualified refs (local:box, cloud:box) a CuaError.AmbiguousSandbox
lists: a bare sandbox name that matches sandboxes in more than one
location. Empty for any other error.
Parameters#
| Parameter | Type |
|---|---|
error | unknown |
Returns#
string[]
connect()#
function connect(address?, token?): CuaLike;A client of a running cua daemon (socket path or loopback URL).
Parameters#
| Parameter | Type |
|---|---|
address? | string |
token? | string |
Returns#
CuaLike
connectMcp()#
function connectMcp(config, clientInfo?): Promise<any>;Connects the official SDK's Client to config over streamable HTTP.
A provider's headers may be short-lived: fetch a fresh config per connection.
Parameters#
| Parameter | Type |
|---|---|
config | McpEndpointConfig |
clientInfo | { name: string; version: string; } |
clientInfo.name | string |
clientInfo.version | string |
Returns#
Promise<any>
cuaErrorDocUrl()#
function cuaErrorDocUrl(error): string | undefined;The link to a CuaError's entry (cause and fix) on the errors
reference; undefined for any other value. Every CuaError also carries
it as docUrl.
Parameters#
| Parameter | Type |
|---|---|
error | unknown |
Returns#
string | undefined
embedded()#
function embedded(config?): CuaLike;An SDK runtime in this process (no I/O until the first call). Pass
accountFromSession: true to use the cua auth login session for the
relay account the runtime lists "My machines" with.
Parameters#
| Parameter | Type |
|---|---|
config | Partial<CuaConfig> |
Returns#
CuaLike
http()#
function http(service, path?): ReadinessProbe;Ready once GET path on the declared service returns 2xx.
Parameters#
| Parameter | Type | Default value |
|---|---|---|
service | string | undefined |
path | string | "/" |
Returns#
ReadinessProbe
mcpHeaders()#
function mcpHeaders(config): Record<string, string>;The headers as a plain object.
Parameters#
| Parameter | Type |
|---|---|
config | McpEndpointConfig |
Returns#
Record<string, string>
poolCreateOptions()#
function poolCreateOptions(opts?): PoolCreateOptions;Options for cua.sandboxes().createPool(image, options): sandboxes of
one shape the cua daemon keeps ready on this machine, min at all times
(one starts again as soon as one is created from the pool), up to max
while creates come faster than sandboxes start, and none after
idleSeconds without a create. env and services take plain objects
too. A create with pool: <name> takes one of its ready sandboxes.
const c = connect() // pools are kept by the cua daemon
const pool = await c.sandboxes().createPool("ghcr.io/trycua/linux:24.04-slim",
poolCreateOptions({ name: "agents", min: 2, max: 8, idleSeconds: 600 }))
const sb = await c.sandboxes().create(SandboxCreateOptions.create({
on: "local", image: pool.image, pool: pool.name }))Parameters#
| Parameter | Type |
|---|---|
opts | Partial<Omit<PoolCreateOptions, "env" | "services" | "idleMs">> & object |
Returns#
PoolCreateOptions
sidecar()#
function sidecar(image, opts?): Container;A sidecar container, addressed by name on every runtime: the sandbox
reaches it at its name (and on localhost where they share a network
namespace), it reaches the sandbox at main, and services may name its
ports. Local containers (with runtime: "runc") and every cloud sandbox;
local VM sandboxes refuse sidecars. With sidecars the service names
main, sidecars and sc are reserved.
SandboxCreateOptions.create({ image: "python:3.12-slim",
sidecars: [sidecar("redis:7-alpine", { ports: [6379] })],
services: new Map([["db", 6379]]) })Parameters#
| Parameter | Type |
|---|---|
image | string |
opts | { command?: string[]; env?: Record<string, string>; name?: string; ports?: number[]; } |
opts.command? | string[] |
opts.env? | Record<string, string> |
opts.name? | string |
opts.ports? | number[] |
Returns#
Container
tcp()#
function tcp(service): ReadinessProbe;Ready once a TCP connect to the declared service succeeds.
Parameters#
| Parameter | Type |
|---|---|
service | string |
Returns#
ReadinessProbe
@trycua/cua/spaces/transport#
Import from @trycua/cua/spaces/transport. MCP-over-HTTP and Tauri transports for Spaces in webviews and browsers.
Classes#
McpHttpTransport#
The one interface a host has to implement.
Implementations must:
- resolve the reply whose
idmatches the request's, and not some other reply that happened to arrive first; - resolve
nullfor a notification; - reject every in-flight call if the peer dies, rather than leaving callers hanging on a reply that is never coming;
- be safe to call again after
close(), either by restarting or by rejecting clearly.
Implements#
Constructors#
Constructor
new McpHttpTransport(options): McpHttpTransport;Parameters
| Parameter | Type |
|---|---|
options | McpHttpOptions |
Returns
Properties#
| Property | Modifier | Type | Default value | Description |
|---|---|---|---|---|
kind | readonly | "http" | "http" | A short name for diagnostics: "stdio", "tauri". |
Accessors#
sessionId
Get Signature
get sessionId(): string | undefined;The session id the daemon minted, once initialized.
Returns
string | undefined
Methods#
close()
close(): Promise<void>;Ends the MCP session (DELETE /mcp). Never throws.
Returns
Promise<void>
Implementation of
send()
send(message, options?): Promise<RpcResponse | null>;Send one message; resolve its reply, or null for a notification.
Parameters
| Parameter | Type |
|---|---|
message | RpcOutgoing |
options | SendOptions |
Returns
Promise<RpcResponse | null>
Implementation of
McpSession#
Constructors#
Constructor
new McpSession(transport, options?): McpSession;Parameters
| Parameter | Type |
|---|---|
transport | Transport |
options | SessionOptions |
Returns
Properties#
| Property | Modifier | Type |
|---|---|---|
transport | readonly | Transport |
Methods#
call()
call(
name,
args?,
options?
): Promise<ContentPart[]>;Call a tool, throwing ToolError if it failed.
Parameters
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Returns
Promise<ContentPart[]>
callJson()
callJson<T>(
name,
args?,
options?
): Promise<T>;Call a tool whose text part is JSON, and parse it.
Worth its own method because the parse failure needs to say which tool
produced the unparsable text: without that, a server-side format change
surfaces as a bare SyntaxError with no attribution.
Type Parameters
| Type Parameter | Default type |
|---|---|
T | Json |
Parameters
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Returns
Promise<T>
callRaw()
callRaw(
name,
args?,
options?
): Promise<ToolResult>;Call a tool and return its envelope, including a failure.
Use this when a caller wants to inspect isError itself: for instance a
probe that treats "not available" as an answer rather than an exception.
Most callers want call.
Parameters
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Returns
Promise<ToolResult>
callText()
callText(
name,
args?,
options?
): Promise<string>;Call a tool and return its text parts joined: the common case, since most of these tools answer with one text part.
Parameters
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Returns
Promise<string>
close()
close(): Promise<void>;Returns
Promise<void>
initialize()
initialize(options?): Promise<ServerInfo | undefined>;Perform the MCP handshake, at most once per session.
The server does not require this: it builds its dispatch table before the
read loop starts, so tools/call works cold. We do it anyway, and we
memoize it: it is what makes this session usable against a conforming
MCP server rather than only against this one, and the cost is a single
round trip.
The result is cached as a promise, not a value, so concurrent first calls share one handshake instead of racing two.
Parameters
| Parameter | Type |
|---|---|
options? | SendOptions |
Returns
Promise<ServerInfo | undefined>
listTools()
listTools(options?): Promise<ToolDescriptor[]>;Every tool the server serves, with its input schema.
Parameters
| Parameter | Type |
|---|---|
options? | SendOptions |
Returns
Promise<ToolDescriptor[]>
TauriTransport#
The one interface a host has to implement.
Implementations must:
- resolve the reply whose
idmatches the request's, and not some other reply that happened to arrive first; - resolve
nullfor a notification; - reject every in-flight call if the peer dies, rather than leaving callers hanging on a reply that is never coming;
- be safe to call again after
close(), either by restarting or by rejecting clearly.
Implements#
Constructors#
Constructor
new TauriTransport(options): TauriTransport;Parameters
| Parameter | Type |
|---|---|
options | TauriOptions |
Returns
Properties#
| Property | Modifier | Type | Default value | Description |
|---|---|---|---|---|
kind | readonly | "tauri" | "tauri" | A short name for diagnostics: "stdio", "tauri". |
Methods#
close()
close(): Promise<void>;Ask the bridge to close the server's stdin.
A failure here is swallowed: the transport is being torn down, the app may already be quitting, and turning "could not reach the bridge while closing" into an exception makes shutdown paths fragile for no gain.
Returns
Promise<void>
Implementation of
send()
send(message, options?): Promise<RpcResponse | null>;Send one message; resolve its reply, or null for a notification.
Parameters
| Parameter | Type |
|---|---|
message | RpcOutgoing |
options | SendOptions |
Returns
Promise<RpcResponse | null>
Implementation of
ToolError#
A tool that ran and failed.
This is a separate type from TransportError on purpose. The server
reports a failing tool as a successful JSON-RPC response carrying
isError: true; the only JSON-RPC error objects it ever sends are
-32601 for an unknown method or tool. Collapsing the two would make
"the sandbox refused this command" indistinguishable from "the control
plane is not running", and those need different handling by every caller.
Extends#
Error
Constructors#
Constructor
new ToolError(
tool,
content,
structured?
): ToolError;Parameters
| Parameter | Type |
|---|---|
tool | string |
content | ContentPart[] |
structured? | Json |
Returns
Overrides
Error.constructorProperties#
| Property | Modifier | Type | Default value | Description |
|---|---|---|---|---|
content | readonly | ContentPart[] | undefined | - |
kind | readonly | string | undefined | undefined | The server's stable error kind (capability_missing, teleport_refused, not_found, ...), when it sent one. |
tag | readonly | "ToolError" | "ToolError" | - |
tool | readonly | string | undefined | - |
TransportError#
Thrown for anything that goes wrong beneath the tool call: a dead peer, a
timeout, a JSON-RPC error object. A failing tool is not this: see
ToolError, because this server reports tool failure as a successful
response carrying isError: true.
Extends#
Error
Constructors#
Constructor
new TransportError(message, code?): TransportError;Parameters
| Parameter | Type |
|---|---|
message | string |
code? | number |
Returns
Overrides
Error.constructorProperties#
| Property | Modifier | Type | Default value |
|---|---|---|---|
code | readonly | number | undefined | undefined |
tag | readonly | "TransportError" | "TransportError" |
Interfaces#
ClientInfo#
Properties#
ContentPart#
One part of a tool result. call_tool passes through image,
audio and resource parts from an in-space tool unchanged, so this is not
only ever text.
Indexable#
[key: string]: Json | undefinedProperties#
McpHttpOptions#
Properties#
| Property | Type | Description |
|---|---|---|
fetch? | FetchFn | Inject a fetch (tests, a Tauri HTTP plugin). Defaults to globalThis.fetch. |
token? | string | The daemon's loopback bearer token. |
url | string | Daemon loopback base URL (http://127.0.0.1:<port>) or the full /mcp URL. |
RpcError#
@trycua/cua/spaces/transport: the Spaces control plane for hosts that
cannot load the native binding (a Tauri webview, a browser).
Dependency-free TypeScript: McpSession speaks MCP to the Rust Spaces
server over any Transport:
McpHttpTransport: thecua daemonloopback/mcp(streamable HTTP, bearer = the daemon's loopback token);TauriTransport:invoke()into the Tauri app's Rust side.
Node code should use @trycua/cua/spaces (the typed SDK) instead. See
types.ts for why the seam exchanges whole JSON-RPC messages rather than
bytes.
Properties#
| Property | Type |
|---|---|
code | number |
data? | Json |
message | string |
RpcNotification#
A message with no id. It gets no reply, and a transport must resolve
null for it rather than inventing one.
Properties#
| Property | Type |
|---|---|
jsonrpc | "2.0" |
method | string |
params? | Json |
RpcRequest#
@trycua/cua/spaces/transport: the Spaces control plane for hosts that
cannot load the native binding (a Tauri webview, a browser).
Dependency-free TypeScript: McpSession speaks MCP to the Rust Spaces
server over any Transport:
McpHttpTransport: thecua daemonloopback/mcp(streamable HTTP, bearer = the daemon's loopback token);TauriTransport:invoke()into the Tauri app's Rust side.
Node code should use @trycua/cua/spaces (the typed SDK) instead. See
types.ts for why the seam exchanges whole JSON-RPC messages rather than
bytes.
Properties#
RpcResponse#
@trycua/cua/spaces/transport: the Spaces control plane for hosts that
cannot load the native binding (a Tauri webview, a browser).
Dependency-free TypeScript: McpSession speaks MCP to the Rust Spaces
server over any Transport:
McpHttpTransport: thecua daemonloopback/mcp(streamable HTTP, bearer = the daemon's loopback token);TauriTransport:invoke()into the Tauri app's Rust side.
Node code should use @trycua/cua/spaces (the typed SDK) instead. See
types.ts for why the seam exchanges whole JSON-RPC messages rather than
bytes.
Properties#
SendOptions#
@trycua/cua/spaces/transport: the Spaces control plane for hosts that
cannot load the native binding (a Tauri webview, a browser).
Dependency-free TypeScript: McpSession speaks MCP to the Rust Spaces
server over any Transport:
McpHttpTransport: thecua daemonloopback/mcp(streamable HTTP, bearer = the daemon's loopback token);TauriTransport:invoke()into the Tauri app's Rust side.
Node code should use @trycua/cua/spaces (the typed SDK) instead. See
types.ts for why the seam exchanges whole JSON-RPC messages rather than
bytes.
Properties#
ServerInfo#
Properties#
SessionOptions#
Properties#
| Property | Type |
|---|---|
clientInfo? | ClientInfo |
TauriOptions#
Properties#
| Property | Type | Description |
|---|---|---|
invoke | InvokeFn | Usually (await import("@tauri-apps/api/core")).invoke. |
requestCommand? | string | The Rust command name (default spaces_mcp_request). |
shutdownCommand? | string | The Rust command that ends the session (default spaces_mcp_shutdown). |
ToolDescriptor#
Properties#
| Property | Type |
|---|---|
description? | string |
inputSchema? | Json |
name | string |
ToolResult#
Properties#
| Property | Type | Description |
|---|---|---|
content | ContentPart[] | - |
isError | boolean | - |
structured? | Json | structuredContent, when the tool returned one (tool errors carry {error: {kind, message}} with a stable kind). |
Transport#
The one interface a host has to implement.
Implementations must:
- resolve the reply whose
idmatches the request's, and not some other reply that happened to arrive first; - resolve
nullfor a notification; - reject every in-flight call if the peer dies, rather than leaving callers hanging on a reply that is never coming;
- be safe to call again after
close(), either by restarting or by rejecting clearly.
Properties#
Methods#
close()
close(): Promise<void>;Release the peer. Idempotent.
Returns
Promise<void>
send()
send(message, options?): Promise<RpcResponse | null>;Send one message; resolve its reply, or null for a notification.
Parameters
| Parameter | Type |
|---|---|
message | RpcOutgoing |
options? | SendOptions |
Returns
Promise<RpcResponse | null>
Type Aliases#
FetchFn#
type FetchFn = (input, init) => Promise<{
headers: {
get: string | null;
};
ok: boolean;
status: number;
text: Promise<string>;
}>;The fetch shape this needs (the global one, or an injected fake).
Parameters#
| Parameter | Type |
|---|---|
input | string |
init | { body?: string; headers: Record<string, string>; method: string; signal?: AbortSignal; } |
init.body? | string |
init.headers | Record<string, string> |
init.method | string |
init.signal? | AbortSignal |
Returns#
Promise<{
headers: {
get: string | null;
};
ok: boolean;
status: number;
text: Promise<string>;
}>
InvokeFn#
type InvokeFn = <T>(command, args?) => Promise<T>;The shape of Tauri's invoke, narrowed to what this needs.
Type Parameters#
| Type Parameter |
|---|
T |
Parameters#
| Parameter | Type |
|---|---|
command | string |
args? | Record<string, unknown> |
Returns#
Promise<T>
Json#
type Json =
| null
| boolean
| number
| string
| Json[]
| {
[key: string]: Json;
};A JSON value, as it crosses the wire.
RpcId#
type RpcId = number | string;JSON-RPC 2.0 ids: this SDK only ever sends numbers, but a reply echoes whatever it was given, and a conforming peer may use a string.
RpcOutgoing#
type RpcOutgoing = RpcRequest | RpcNotification;@trycua/cua/spaces/transport: the Spaces control plane for hosts that
cannot load the native binding (a Tauri webview, a browser).
Dependency-free TypeScript: McpSession speaks MCP to the Rust Spaces
server over any Transport:
McpHttpTransport: thecua daemonloopback/mcp(streamable HTTP, bearer = the daemon's loopback token);TauriTransport:invoke()into the Tauri app's Rust side.
Node code should use @trycua/cua/spaces (the typed SDK) instead. See
types.ts for why the seam exchanges whole JSON-RPC messages rather than
bytes.
Variables#
DEFAULT_REQUEST_COMMAND#
const DEFAULT_REQUEST_COMMAND: "spaces_mcp_request" = "spaces_mcp_request";DEFAULT_SHUTDOWN_COMMAND#
const DEFAULT_SHUTDOWN_COMMAND: "spaces_mcp_shutdown" = "spaces_mcp_shutdown";PROTOCOL_VERSION#
const PROTOCOL_VERSION: "2025-06-18" = "2025-06-18";The MCP revision offered. The Rust server accepts 2025-06-18, 2025-03-26 and 2024-11-05, and answers with the one it picked.
SESSION_HEADER#
const SESSION_HEADER: "mcp-session-id" = "mcp-session-id";Functions#
textOf()#
function textOf(content): string;Concatenate the text parts, which is what almost every caller wants.
Parameters#
| Parameter | Type |
|---|---|
content | ContentPart[] |
Returns#
string
@trycua/cua/spaces/host#
Import from @trycua/cua/spaces/host. The @trycua/cua/spaces/host helpers for hosting a Space from Node.
Classes#
HostPresentation#
Constructors#
Constructor
new HostPresentation(options?): HostPresentation;Parameters
| Parameter | Type |
|---|---|
options | HostPresentationOptions |
Returns
Methods#
available()
available(): Promise<boolean>;Is the desktop app reachable right now?
Returns
Promise<boolean>
openViewer()
openViewer(spaceId): Promise<void>;Open the full viewer window for a Local Space.
Parameters
| Parameter | Type |
|---|---|
spaceId | string |
Returns
Promise<void>
pin()
pin(spaceId): Promise<void>;Pin a Local Space as picture-in-picture on this Mac's desktop.
Parameters
| Parameter | Type |
|---|---|
spaceId | string |
Returns
Promise<void>
streamWindow()
streamWindow(input): Promise<void>;Stream one window of a Local Space back to a bidirectional window here.
rcdpToken must be read fresh: a Local Space's rcdp token rotates on every
rcdpd restart, so a cached one silently stops working.
replica: true opens an additional stream for the same target rather
than focusing the existing one. Note the real limit: only one page per app
can hardware-decode H.264, so a second concurrent stream falls back to an
uncompressed codec and is visibly heavier.
Parameters
| Parameter | Type |
|---|---|
input | { appName?: string; rcdpToken: string; replica?: boolean; spaceId: string; title?: string; windowId: string; } |
input.appName? | string |
input.rcdpToken | string |
input.replica? | boolean |
input.spaceId | string |
input.title? | string |
input.windowId | string |
Returns
Promise<void>
unpin()
unpin(spaceId): Promise<void>;Parameters
| Parameter | Type |
|---|---|
spaceId | string |
Returns
Promise<void>
Interfaces#
ControlEndpoint#
Properties#
HostPresentationOptions#
Properties#
| Property | Type | Description |
|---|---|---|
controlFile? | string | Override the discovery file. Mostly for tests. |
fetch? | (input, init?) => Promise<Response> | Inject a fetch (tests, or a different agent). |
Functions#
cuaHome()#
function cuaHome(home, env?): string;$CUA_HOME when set and non-empty, else ~/.cua (the Rust core's rule).
Parameters#
| Parameter | Type |
|---|---|
home | string |
env | Record<string, string | undefined> |
Returns#
string
readControlEndpoint()#
function readControlEndpoint(controlFile?): Promise<ControlEndpoint>;Read {port, token} from the app's control file.
Parameters#
| Parameter | Type |
|---|---|
controlFile? | string |
Returns#
Promise<ControlEndpoint>