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 pointUse it for
@trycua/cuaembedded(), connect(), every generated object, and the helpers on this page
@trycua/cua/spaces, /spaces/transport, /spaces/hostSpaces threads, approvals and transports
@trycua/cua/browserA 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#

PropertyType
tier?ImageTier
version?string

McpEndpointConfig#

Where an MCP endpoint is: Sandbox.mcpConfig() returns one.

Properties#

PropertyType
headersobject[] | Record<string, string>
urlstring

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#

NameTypeDescription
fromRegistry()(reference) => string-
linux()(version?, tier?) => string-
macos()(version?, tier?) => string-
omarchy()(channel?) => stringOmarchy (Arch Linux, Hyprland) with cua-spacesd: an amd64 VM.
resolve()(reference, backend, arch?) => ResolvedImage-
windows()(version?, tier?) => string-

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#

NameTypeDescription
awsEcr()(region?) => RegistrySecretA private Amazon ECR image: a login token from the AWS CLI.
basic()(username, password, registry?) => RegistrySecretA user name and password (or token).
fromEnv()(usernameVar, passwordVar, registry?) => RegistrySecretRead from environment variables at create time.

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#

ParameterType
errorunknown

Returns#

string[]


connect()#

function connect(address?, token?): CuaLike;

A client of a running cua daemon (socket path or loopback URL).

Parameters#

ParameterType
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#

ParameterType
configMcpEndpointConfig
clientInfo{ name: string; version: string; }
clientInfo.namestring
clientInfo.versionstring

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#

ParameterType
errorunknown

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#

ParameterType
configPartial<CuaConfig>

Returns#

CuaLike


http()#

function http(service, path?): ReadinessProbe;

Ready once GET path on the declared service returns 2xx.

Parameters#

ParameterTypeDefault value
servicestringundefined
pathstring"/"

Returns#

ReadinessProbe


mcpHeaders()#

function mcpHeaders(config): Record<string, string>;

The headers as a plain object.

Parameters#

ParameterType
configMcpEndpointConfig

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#

ParameterType
optsPartial<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#

ParameterType
imagestring
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#

ParameterType
servicestring

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 id matches the request's, and not some other reply that happened to arrive first;
  • resolve null for 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
ParameterType
optionsMcpHttpOptions
Returns

McpHttpTransport

Properties#

PropertyModifierTypeDefault valueDescription
kindreadonly"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

Transport.close

send()
send(message, options?): Promise<RpcResponse | null>;

Send one message; resolve its reply, or null for a notification.

Parameters
ParameterType
messageRpcOutgoing
optionsSendOptions
Returns

Promise<RpcResponse | null>

Implementation of

Transport.send


McpSession#

Constructors#

Constructor
new McpSession(transport, options?): McpSession;
Parameters
ParameterType
transportTransport
optionsSessionOptions
Returns

McpSession

Properties#

PropertyModifierType
transportreadonlyTransport

Methods#

call()
call(
   name,
   args?,
   options?
): Promise<ContentPart[]>;

Call a tool, throwing ToolError if it failed.

Parameters
ParameterType
namestring
argsRecord<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 ParameterDefault type
TJson
Parameters
ParameterType
namestring
argsRecord<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
ParameterType
namestring
argsRecord<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
ParameterType
namestring
argsRecord<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
ParameterType
options?SendOptions
Returns

Promise<ServerInfo | undefined>

listTools()
listTools(options?): Promise<ToolDescriptor[]>;

Every tool the server serves, with its input schema.

Parameters
ParameterType
options?SendOptions
Returns

Promise<ToolDescriptor[]>


TauriTransport#

The one interface a host has to implement.

Implementations must:

  • resolve the reply whose id matches the request's, and not some other reply that happened to arrive first;
  • resolve null for 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
ParameterType
optionsTauriOptions
Returns

TauriTransport

Properties#

PropertyModifierTypeDefault valueDescription
kindreadonly"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

Transport.close

send()
send(message, options?): Promise<RpcResponse | null>;

Send one message; resolve its reply, or null for a notification.

Parameters
ParameterType
messageRpcOutgoing
optionsSendOptions
Returns

Promise<RpcResponse | null>

Implementation of

Transport.send


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
ParameterType
toolstring
contentContentPart[]
structured?Json
Returns

ToolError

Overrides
Error.constructor

Properties#

PropertyModifierTypeDefault valueDescription
contentreadonlyContentPart[]undefined-
kindreadonlystring | undefinedundefinedThe server's stable error kind (capability_missing, teleport_refused, not_found, ...), when it sent one.
tagreadonly"ToolError""ToolError"-
toolreadonlystringundefined-

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
ParameterType
messagestring
code?number
Returns

TransportError

Overrides
Error.constructor

Properties#

PropertyModifierTypeDefault value
codereadonlynumber | undefinedundefined
tagreadonly"TransportError""TransportError"

Interfaces#

ClientInfo#

Properties#

PropertyType
namestring
versionstring

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 | undefined

Properties#

PropertyType
text?string
typestring

McpHttpOptions#

Properties#

PropertyTypeDescription
fetch?FetchFnInject a fetch (tests, a Tauri HTTP plugin). Defaults to globalThis.fetch.
token?stringThe daemon's loopback bearer token.
urlstringDaemon 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: the cua daemon loopback /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#

PropertyType
codenumber
data?Json
messagestring

RpcNotification#

A message with no id. It gets no reply, and a transport must resolve null for it rather than inventing one.

Properties#

PropertyType
jsonrpc"2.0"
methodstring
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: the cua daemon loopback /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#

PropertyType
idRpcId
jsonrpc"2.0"
methodstring
params?Json

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: the cua daemon loopback /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#

PropertyType
error?RpcError
idRpcId
jsonrpc"2.0"
result?Json

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: the cua daemon loopback /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#

PropertyTypeDescription
signal?AbortSignalAborts the wait. A transport should still expect the server to finish the work: this server has no notifications/cancelled handling, so aborting abandons the reply rather than stopping the call.
timeoutSeconds?numberSeconds to wait for a reply. The default is deliberately long. teleport_app and provisioning run with timeout=900 server-side, and the agent-CLI registration this server ships with sets requestTimeoutMs: 900000. A transport that imposed a short deadline would silently break the app's slowest and most valuable operations, so a conformer must not shorten this on its own initiative.

ServerInfo#

Properties#

PropertyType
namestring
versionstring

SessionOptions#

Properties#

PropertyType
clientInfo?ClientInfo

TauriOptions#

Properties#

PropertyTypeDescription
invokeInvokeFnUsually (await import("@tauri-apps/api/core")).invoke.
requestCommand?stringThe Rust command name (default spaces_mcp_request).
shutdownCommand?stringThe Rust command that ends the session (default spaces_mcp_shutdown).

ToolDescriptor#

Properties#

PropertyType
description?string
inputSchema?Json
namestring

ToolResult#

Properties#

PropertyTypeDescription
contentContentPart[]-
isErrorboolean-
structured?JsonstructuredContent, 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 id matches the request's, and not some other reply that happened to arrive first;
  • resolve null for 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#

PropertyModifierTypeDescription
kindreadonlystringA short name for diagnostics: "stdio", "tauri".

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
ParameterType
messageRpcOutgoing
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#

ParameterType
inputstring
init{ body?: string; headers: Record<string, string>; method: string; signal?: AbortSignal; }
init.body?string
init.headersRecord<string, string>
init.methodstring
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#

ParameterType
commandstring
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: the cua daemon loopback /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#

ParameterType
contentContentPart[]

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
ParameterType
optionsHostPresentationOptions
Returns

HostPresentation

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
ParameterType
spaceIdstring
Returns

Promise<void>

pin()
pin(spaceId): Promise<void>;

Pin a Local Space as picture-in-picture on this Mac's desktop.

Parameters
ParameterType
spaceIdstring
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
ParameterType
input{ appName?: string; rcdpToken: string; replica?: boolean; spaceId: string; title?: string; windowId: string; }
input.appName?string
input.rcdpTokenstring
input.replica?boolean
input.spaceIdstring
input.title?string
input.windowIdstring
Returns

Promise<void>

unpin()
unpin(spaceId): Promise<void>;
Parameters
ParameterType
spaceIdstring
Returns

Promise<void>

Interfaces#

ControlEndpoint#

Properties#

PropertyType
portnumber
tokenstring

HostPresentationOptions#

Properties#

PropertyTypeDescription
controlFile?stringOverride 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#

ParameterType
homestring
envRecord<string, string | undefined>

Returns#

string


readControlEndpoint()#

function readControlEndpoint(controlFile?): Promise<ControlEndpoint>;

Read {port, token} from the app's control file.

Parameters#

ParameterType
controlFile?string

Returns#

Promise<ControlEndpoint>