Skip to content

Wire protocol

Frames are JSONL: one JSON-RPC 2.0 object per \n-terminated line (in-process framing is per-message, no newline). Requests carry an id and get exactly one response; notifications don't. The engine drains the whole inbound queue in order, once per frame, on the main thread.

json
→ {"jsonrpc":"2.0","id":7,"method":"set","params":{"oid":"123","property":"text","value":"hi"}}
← {"jsonrpc":"2.0","id":7,"result":{}}
← {"jsonrpc":"2.0","id":7,"error":{"code":-32001,"message":"no such property: txet"}}

Values

Godot Variants map to JSON with $-tagged wrappers for non-JSON types: {"$v2":[x,y]}, {"$v3":[x,y,z]}, {"$col":[r,g,b,a]}, {"$rect":[x,y,w,h]}, {"$oid":"…"} (object reference), {"$bytes":"base64…"}, {"$sn":"name"} (StringName), {"$np":"path"} (NodePath). Plain strings/numbers/bools/arrays/ dicts pass through.

Object handles (oid) are decimal strings — 64-bit ObjectIDs exceed JSON consumers' safe-integer range (RefCounted handles set bit 63). Treat them as opaque.

Ops

MethodParamsResultNotes
handshakeclientInfo {name, version}, token?protocolVersion, engineVersion, apiHash, buildFlavor, rootId, features[]must be first when --bridge-token is set
instantiateclassName{oid}you own it until addChild or free
freeoid{}queue_free if in tree; broadcasts objectFreed
setoid, property, value{}
getoid, property{value}
calloid, method, args?[]{result}return value under result (objects as {"$oid":…}); RefCounted returns are pinned in the registry until freed
connectoid, signal, once?, throttleMs?{subId}emissions arrive as signal notifications
disconnectsubId{}
addChild / removeChild / reparentparent/newParent, child{}
root{oid}SceneTree root Window (also in handshake)
singletonname{oid}e.g. "Input", "DisplayServer"
loadpath{oid}load a Resource — res:// from a mounted pack, or an absolute filesystem path
instantiateScenepath or oid{oid}PackedScene → instance; oid reuses a loaded scene
queryTreeoid, depth?{tree}recursive {oid, class, name, children}
batchops[{method, params}]{results[]}send many fire-and-forget ops in one message
sync{}ordering barrier: everything sent before it has been applied
loadPackpath, replace?{ok}mount a .pck at runtime (absolute path — not resolved against the client's cwd)
classescontains?, instantiableOnly?{classes[]}what exists in this build
classInfoclassName, ownOnly?{parent, instantiable, properties[], signals[], methods[]}ClassDB reflection
quitcode?{}asks the engine to exit

Notifications (engine → client)

MethodParamsWhen
signalsubId, args[]a subscribed signal fired (after throttleMs conflation)
objectFreedoidan object you tracked was freed
quitcodeengine is shutting down — invalidate all proxies

SDKs surface window-close as a convenience (onWindowCloseRequested) by subscribing to the root Window's close_requested signal at connect time.

Errors

JSON-RPC standard codes plus: -32001 bad property/call, -32002 unknown or freed oid, -32003 invalid or missing token (auth), -32602 bad params / unknown class.

Feature detection

handshake.features lists optional capabilities of the runtime you actually connected to: "classes", "classInfo", "inproc", "stderrLogs". Check it rather than parsing versions; condensed builds differ.