Spaces transport (TypeScript)
MCP-over-HTTP and Tauri transports for Spaces in webviews and browsers.
MCP-over-HTTP and Tauri transports for Spaces in webviews and browsers.
Import from @trycua/cua/spaces/transport. This page covers the hand-written TypeScript layer; the generated native binding (sandboxes, guest, fleet, spaces objects) is documented per object in the Cua SDK reference.
The one interface a host has to implement.
Implementations must:
id matches the request's, and not some other
reply that happened to arrive first;null for a notification;close(), either by restarting or by
rejecting clearly.new McpHttpTransport(options): McpHttpTransport;| Parameter | Type |
|---|---|
options | McpHttpOptions |
| Property | Modifier | Type | Default value | Description |
|---|---|---|---|---|
kind | readonly | "http" | "http" | A short name for diagnostics: "stdio", "tauri". |
get sessionId(): string | undefined;The session id the daemon minted, once initialized.
string | undefined
close(): Promise<void>;Ends the MCP session (DELETE /mcp). Never throws.
Promise<void>
send(message, options?): Promise<RpcResponse | null>;Send one message; resolve its reply, or null for a notification.
| Parameter | Type |
|---|---|
message | RpcOutgoing |
options | SendOptions |
Promise<RpcResponse | null>
new McpSession(transport, options?): McpSession;| Parameter | Type |
|---|---|
transport | Transport |
options | SessionOptions |
| Property | Modifier | Type |
|---|---|---|
transport | readonly | Transport |
call(
name,
args?,
options?
): Promise<ContentPart[]>;Call a tool, throwing ToolError if it failed.
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Promise<ContentPart[]>
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 Parameter | Default type |
|---|---|
T | Json |
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Promise<T>
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.
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Promise<ToolResult>
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.
| Parameter | Type |
|---|---|
name | string |
args | Record<string, Json> |
options? | SendOptions |
Promise<string>
close(): Promise<void>;Promise<void>
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.
| Parameter | Type |
|---|---|
options? | SendOptions |
Promise<ServerInfo | undefined>
listTools(options?): Promise<ToolDescriptor[]>;Every tool the server serves, with its input schema.
| Parameter | Type |
|---|---|
options? | SendOptions |
Promise<ToolDescriptor[]>
The one interface a host has to implement.
Implementations must:
id matches the request's, and not some other
reply that happened to arrive first;null for a notification;close(), either by restarting or by
rejecting clearly.new TauriTransport(options): TauriTransport;| Parameter | Type |
|---|---|
options | TauriOptions |
| Property | Modifier | Type | Default value | Description |
|---|---|---|---|---|
kind | readonly | "tauri" | "tauri" | A short name for diagnostics: "stdio", "tauri". |
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.
Promise<void>
send(message, options?): Promise<RpcResponse | null>;Send one message; resolve its reply, or null for a notification.
| Parameter | Type |
|---|---|
message | RpcOutgoing |
options | SendOptions |
Promise<RpcResponse | null>
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.
Errornew ToolError(
tool,
content,
structured?
): ToolError;| Parameter | Type |
|---|---|
tool | string |
content | ContentPart[] |
structured? | Json |
Error.constructor| 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 | - |
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.
Errornew TransportError(message, code?): TransportError;| Parameter | Type |
|---|---|
message | string |
code? | number |
Error.constructor| Property | Modifier | Type | Default value |
|---|---|---|---|
code | readonly | number | undefined | undefined |
tag | readonly | "TransportError" | "TransportError" |
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.
[key: string]: Json | undefined| 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. |
@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.
| Property | Type |
|---|---|
code | number |
data? | Json |
message | string |
A message with no id. It gets no reply, and a transport must resolve
null for it rather than inventing one.
| Property | Type |
|---|---|
jsonrpc | "2.0" |
method | string |
params? | Json |
@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.
@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.
@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.
| Property | Type |
|---|---|
clientInfo? | ClientInfo |
| 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). |
| Property | Type |
|---|---|
description? | string |
inputSchema? | Json |
name | string |
| 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). |
The one interface a host has to implement.
Implementations must:
id matches the request's, and not some other
reply that happened to arrive first;null for a notification;close(), either by restarting or by
rejecting clearly.close(): Promise<void>;Release the peer. Idempotent.
Promise<void>
send(message, options?): Promise<RpcResponse | null>;Send one message; resolve its reply, or null for a notification.
| Parameter | Type |
|---|---|
message | RpcOutgoing |
options? | SendOptions |
Promise<RpcResponse | null>
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).
| 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 |
Promise<{
headers: {
get: string | null;
};
ok: boolean;
status: number;
text: Promise<string>;
}>
type InvokeFn = <T>(command, args?) => Promise<T>;The shape of Tauri's invoke, narrowed to what this needs.
| Type Parameter |
|---|
T |
| Parameter | Type |
|---|---|
command | string |
args? | Record<string, unknown> |
Promise<T>
type Json =
| null
| boolean
| number
| string
| Json[]
| {
[key: string]: Json;
};A JSON value, as it crosses the wire.
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.
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.
const DEFAULT_REQUEST_COMMAND: "spaces_mcp_request" = "spaces_mcp_request";const DEFAULT_SHUTDOWN_COMMAND: "spaces_mcp_shutdown" = "spaces_mcp_shutdown";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.
const SESSION_HEADER: "mcp-session-id" = "mcp-session-id";function textOf(content): string;Concatenate the text parts, which is what almost every caller wants.
| Parameter | Type |
|---|---|
content | ContentPart[] |
string