Stage API Reference
Stage tool parameters and response examples. The MCP tool schemas are the current parameter authority.
project_select
Switch the active project within a persistent MCP session. It discards watches, baselines, spatial indexes, session overrides and the cached clip location; nothing is restored on switching back or reselecting the same project. Take a fresh spatial_snapshot before deltas and recreate watches afterward.
| Parameter | Type | Required | Description |
|---|---|---|---|
port | number | optional | |
project_path | string | required | Absolute Godot project directory containing project.godot. Even selecting the same project again resets watches, baselines and session overrides. |
The response distinguishes selected project_path/port from verified runtime identity, connected and ready. An unavailable target stays selected while Stage retries it. Invalid parameters leave the previous selection intact. Selection does not stop games or delete recordings. Director still uses project_path per call, and client feedback-hook environments remain independent.
spatial_snapshot
Get an instant picture of every tracked node in the running game.
{
perspective?: "camera" | "node" | "point" // default: "camera"
focal_node?: string // node name or path; anchors perspective for "node"
focal_point?: [number, number, number] // world-space point; used for "point" perspective
radius?: number // default: 50.0
detail?: "summary" | "standard" | "full" // default: "standard"
groups?: string[] // filter to nodes belonging to these groups
class_filter?: string[] // Godot class names to include
include_offscreen?: boolean // default: false
token_budget?: number // default: 1500 (standard tier)
expand?: string // node path to expand with extra detail
}Response:
{
frame: number
timestamp_ms: number
node_count: number
included_nodes: number
truncated: boolean
nodes: {
[name: string]: {
class: string
path: string
global_position: [number, number, number]
velocity?: [number, number, number]
rotation_deg?: [number, number, number]
scale?: [number, number, number]
visible?: boolean
collision_layer?: number
collision_mask?: number
on_floor?: boolean
on_wall?: boolean
// ...additional properties for detail="full"
}
}
}spatial_delta
Get only what changed since the last spatial_snapshot baseline.
{
perspective?: "camera" | "node" | "point" // default: "camera"
radius?: number // default: 50.0
groups?: string[] // filter to nodes in these groups
class_filter?: string[]
token_budget?: number
}Delta computes changes against the baseline established by the most recent spatial_snapshot call. There is no since_frame parameter — the baseline is stored automatically when you call spatial_snapshot.
Response:
{
frame: number
baseline_frame: number
elapsed_ms: number
changed_node_count: number
nodes: {
[name: string]: {
// Only fields that changed since the baseline snapshot
global_position?: [number, number, number]
velocity?: [number, number, number]
// ...any other changed properties
}
}
}spatial_query
Run geometric queries against the current game state.
{
query_type: "nearest" | "radius" | "area" | "raycast" | "path_distance" | "relationship"
from: string | [number, number, number] // node name/path or [x, y, z] coordinate
to?: string | [number, number, number] // used by path_distance and relationship
k?: number // max results for nearest, default: 5
radius?: number // search radius for radius query, default: 20.0
groups?: string[]
class_filter?: string[]
token_budget?: number
}Response (nearest/radius):
{
result: {
origin: [number, number, number]
radius?: number // radius queries only
results: Array<{
node: string
class: string
global_position: [number, number, number]
distance: number
}>
}
}Response (area):
{
result: {
bounds: { min: [number, number, number], max: [number, number, number] }
results: Array<{
node: string
class: string
global_position: [number, number, number]
}>
}
}Response (raycast):
{
result: {
hit: boolean
node?: string
class?: string
global_position?: [number, number, number]
normal?: [number, number, number]
distance?: number
}
}Response (path_distance):
{
result: {
from: [number, number, number]
to: [number, number, number]
distance: number
reachable: boolean
waypoints: Array<[number, number, number]>
}
}Response (relationship):
{
result: {
from: string
to: string
distance: number
bearing_from_a: number // horizontal bearing from A to B (degrees)
bearing_from_b: number // horizontal bearing from B to A (degrees)
relative: [number, number, number] // offset from A to B in world space
occluded: boolean
}
}spatial_inspect
Deep inspection of a single node.
{
node: string // node name or scene path; required
include?: Array<
"transform" | "physics" | "state" | "children" |
"signals" | "script" | "spatial_context" | "resources"
>
// default: ["transform", "physics", "state", "children", "signals", "script", "spatial_context"]
}Response:
{
node: string
path: string
class: string
frame: number
transform?: {
global_position: [number, number, number]
rotation_deg: [number, number, number]
scale: [number, number, number]
}
physics?: {
velocity?: [number, number, number]
collision_layer?: number
collision_mask?: number
on_floor?: boolean
on_wall?: boolean
}
state?: {
visible: boolean
// ...node-class-specific state properties
}
children?: Array<{
name: string
class: string
relative_position: [number, number, number]
}>
signals?: Array<{
signal: string
connected_to: string
method: string
}>
script?: {
path: string
// ...exported script properties
}
spatial_context?: {
parent: {
name: string
class: string
relative_position: [number, number, number]
}
nearby: Array<{
node: string
class: string
distance: number
}>
}
resources?: {
// ...resource references attached to this node
}
}spatial_watch
Monitor nodes continuously for changes.
// Add a watch
{
action: "add"
watch: {
node: string
conditions?: Array<{
property: string
operator: "lt" | "gt" | "eq" | "changed"
value?: any
}> // default: []
track?: Array<"position" | "state" | "signals" | "physics" | "all">
// default: ["All"]
}
}
// Remove a watch
{
action: "remove"
watch_id: string
}
// List active watches
{
action: "list"
}
// Remove all watches
{
action: "clear"
}Add response:
{
watch_id: string
node: string
track: string[]
}List response:
{
watches: Array<{
watch_id: string
node: string
track: string[]
}>
}Remove response:
{
watch_id: string
result: "ok"
}spatial_config
Configure MCP behavior (clustering, bearing format, token limits, static patterns).
{
static_patterns?: string[] // node name patterns treated as static (not tracked)
state_properties?: { [class: string]: string[] } // extra properties to capture per class
cluster_by?: "group" | "class" | "proximity" | "none"
bearing_format?: "cardinal" | "degrees" | "both"
expose_internals?: boolean // include internal/hidden nodes
poll_interval?: number // polling interval in ms
token_hard_cap?: number // hard cap on response tokens
}No parameters → returns current config:
{
static_patterns: string[]
state_properties: { [class: string]: string[] }
cluster_by: string
bearing_format: string
expose_internals: boolean
poll_interval: number
token_hard_cap: number
}When parameters are set, the response echoes the new values with "result": "ok".
spatial_action
Interact with the running game: control execution, modify state, inject input, spawn/remove nodes.
// Pause or unpause the game
{ action: "pause", paused: boolean }
// Step N physics frames (game must be paused)
{ action: "advance_frames", frames: number }
// Advance by N seconds (game must be paused)
{ action: "advance_time", seconds: number }
// Move a node to a position
{
action: "teleport"
node: string
position: [number, number, number]
rotation_deg?: number
}
// Set a property on a node
{
action: "set_property"
node: string
property: string
value: any
}
// Call a method on a node
{
action: "call_method"
node: string
method: string
args?: any[]
}
// Emit a signal from a node
{
action: "emit_signal"
node: string
signal: string
args?: any[]
}
// Instantiate a scene as a child node
{
action: "spawn_node"
scene_path: string
parent: string
name?: string
}
// Remove a node from the scene tree
{
action: "remove_node"
node: string
}
// Simulate input action press
{
action: "action_press"
input_action: string
strength?: number
}
// Simulate input action release
{
action: "action_release"
input_action: string
}
// Simulate keyboard input
{
action: "inject_key"
keycode: string
pressed?: boolean
echo?: boolean
}
// Simulate mouse button click
{
action: "inject_mouse_button"
button: string
pressed?: boolean
position?: [number, number]
}All actions accept an optional return_delta: boolean (default: false) that appends a spatial_delta to the response.
Response:
{
action: string
result: "ok" | "error"
return_value?: any // for call_method with return value
delta?: object // if return_delta: true
error?: string // on failure
}scene_tree
Get scene tree structure without spatial data.
{
action: "roots" | "children" | "subtree" | "ancestors" | "find"
node?: string // required for children/subtree/ancestors
depth?: number // default: 3
find_by?: "name" | "class" | "group" | "script"
find_value?: string // search term for find action
include?: Array<"class" | "groups" | "script" | "visible" | "process_mode">
// default: ["Class", "Groups"]
token_budget?: number
}Response:
{
root: string
frame: number
node_count: number
tree: {
name: string
class: string
groups?: string[]
children: Array</* recursive */>
}
}clips
Manage dashcam clips and analyze recorded gameplay frame by frame.
// Mark the current moment and trigger clip capture
{
action: "add_marker"
marker_label?: string
marker_frame?: number // defaults to current frame
}
// Force-save dashcam buffer as clip
{
action: "save"
}
// Dashcam buffer state and config
{
action: "status"
}
// List saved clips
{
action: "list"
}
// Remove a clip
{
action: "delete"
clip_id: string
}
// List markers in a clip
{
action: "markers"
clip_id: string
}
// Spatial state at a frame
{
action: "snapshot_at"
clip_id?: string // defaults to most recent
at_frame?: number
at_time_ms?: number // alternative: find nearest frame
detail?: "summary" | "standard" | "full"
}
// Position/property timeseries
{
action: "trajectory"
clip_id?: string
node: string
from_frame?: number
to_frame?: number
properties?: string[] // default: ["position"]
sample_interval?: number // default: 1
}
// Search frames for spatial conditions
{
action: "query_range"
clip_id?: string
from_frame?: number
to_frame?: number
node?: string
condition?: {
type: "moved" | "proximity" | "velocity_spike" | "property_change"
| "state_transition" | "signal_emitted" | "entered_area" | "collision"
// + type-specific fields (target, threshold, property, signal)
}
token_budget?: number
}
// Compare two frames
{
action: "diff_frames"
clip_id?: string
frame_a: number
frame_b: number
}
// Search events
{
action: "find_event"
clip_id?: string
event_type?: string
event_filter?: string
from_frame?: number
to_frame?: number
}
// Viewport screenshot at a frame
{
action: "screenshot_at"
clip_id?: string
at_frame?: number
at_time_ms?: number
}
// List screenshot metadata
{
action: "screenshots"
clip_id?: string
}List response:
{
clips: Array<{
clip_id: string
frame_count: number
duration_ms: number
created_at: string // ISO 8601
markers: Array<{
marker_id: string
frame: number
marker_label: string
}>
}>
}query_range response:
{
clip_id: string
start_frame: number
end_frame: number
frame_count: number
frames: Array<{
frame: number
timestamp_ms: number
nodes: {
[name: string]: {
class: string
global_position?: [number, number, number]
velocity?: [number, number, number]
// ...other properties per detail level
}
}
}>
}