Skip to content

Spectator API Reference

Complete parameter schemas for all 9 Spectator MCP tools.

spatial_snapshot

Get an instant snapshot of all tracked nodes.

typescript
{
  detail?: "summary" | "standard" | "full"  // default: "summary"
  token_budget?: number                      // default: 2000
  focal_node?: string                        // node name or path
  class_filter?: string[]                    // Godot class names to include
  include_properties?: string[]              // for detail="full"
}

Response:

typescript
{
  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 a specific frame.

typescript
{
  since_frame: number                      // required
  token_budget?: number                    // default: 1000
  class_filter?: string[]
  min_distance_change?: number             // default: 0.01 (meters)
  min_velocity_change?: number             // default: 0.1
}

Response:

typescript
{
  from_frame: number
  to_frame: number
  elapsed_ms: number
  changed_node_count: number
  unchanged_node_count: number
  nodes: {
    [name: string]: {
      // Only fields that changed since from_frame
      global_position?: [number, number, number]
      velocity?: [number, number, number]
      // ...any other changed properties
    }
  }
}

Errors:

  • since_frame older than ring buffer depth → "Frame out of buffer range"

spatial_query

Run geometric queries against the scene.

typescript
{
  query_type: "nearest" | "radius" | "area" | "raycast" | "path_distance" | "relationship"

  // For query_type="nearest":
  from: string | [number, number, number]
  k?: number                               // max results, default: 10
  class_filter?: string[]

  // For query_type="radius":
  from: string | [number, number, number]
  radius: number
  k?: number
  class_filter?: string[]

  // For query_type="area":
  min: [number, number, number]
  max: [number, number, number]
  class_filter?: string[]

  // For query_type="raycast":
  from: string | [number, number, number]
  direction: [number, number, number]      // normalized
  max_distance?: number                    // default: 100.0
  collision_mask?: number                  // default: 0xFFFFFFFF

  // For query_type="path_distance":
  from: string | [number, number, number]
  to: string | [number, number, number]

  // For query_type="relationship":
  from: string
  to: string
}

Response (nearest/radius):

typescript
{
  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):

typescript
{
  result: {
    bounds: { min: [number, number, number], max: [number, number, number] }
    results: Array<{
      node: string
      class: string
      global_position: [number, number, number]
    }>
  }
}

Response (raycast):

typescript
{
  result: {
    hit: boolean
    node?: string
    class?: string
    global_position?: [number, number, number]
    normal?: [number, number, number]
    distance?: number
  }
}

Response (path_distance):

typescript
{
  result: {
    from: [number, number, number]
    to: [number, number, number]
    distance: number
    reachable: boolean
    waypoints: Array<[number, number, number]>
  }
}

Response (relationship):

typescript
{
  result: {
    from: string
    to: string
    distance: number
    bearing_deg: number
    relative: [number, number, number]
    occluded: boolean
    in_fov: boolean
  }
}

spatial_inspect

Deep inspection of a single node.

typescript
{
  node: string                             // node name or scene path; required
  include?: Array<"properties" | "signals" | "children" | "spatial_context">
  // default: ["properties", "spatial_context"]
}

Response:

typescript
{
  node: string
  path: string
  class: string
  frame: number
  properties?: {
    global_position: [number, number, number]
    // ...all tracked properties for this class
  }
  signals?: Array<{
    signal: string
    connected_to: string
    method: string
    flags: number
  }>
  children?: Array<{
    name: string
    class: string
    relative_position: [number, number, number]
  }>
  spatial_context?: {
    parent: {
      name: string
      class: string
      relative_position: [number, number, number]
    }
    nearby: Array<{
      node: string
      class: string
      distance: number
    }>
  }
}

spatial_watch

Monitor nodes for continuous change tracking.

typescript
// Create
{
  action: "create"
  node: string
  track?: string[]                         // default: ["position", "velocity"]
}

// List
{
  action: "list"
}

// Delete
{
  action: "delete"
  watch_id: string
}

// Clear all
{
  action: "clear"
}

Create response:

typescript
{
  watch_id: string
  node: string
  track: string[]
  active_watches: number
}

List response:

typescript
{
  watches: Array<{
    watch_id: string
    node: string
    track: string[]
    created_frame: number
  }>
}

Delete response:

typescript
{
  watch_id: string
  result: "ok"
}

spatial_config

Configure collection behavior.

typescript
{
  tick_rate?: number                       // 1-120, default: 60
  capture_radius?: number                  // meters, default: 200.0
  capture_center?: string | null           // node to follow, default: null (origin)
  tracked_types?: string[] | null          // null = restore defaults
  extra_tracked_types?: string[]
  buffer_depth_frames?: number             // default: 600
  default_token_budget?: number            // default: 2000
  default_detail?: "summary" | "standard" | "full"
  record_path?: string                     // directory for clip files
}

No parameters → returns current config:

typescript
{
  tick_rate: number
  capture_radius: number
  capture_center: string | null
  buffer_depth_frames: number
  buffer_depth_seconds: number
  default_token_budget: number
  default_detail: string
  record_path: string
  tracked_types: string[]
  extra_tracked_types: string[]
}

spatial_action

Set properties, call methods, or emit signals on running game nodes.

typescript
// Set property
{
  node: string
  action: "set_property"
  property: string
  value: any
}

// Call method
{
  node: string
  action: "call_method"
  method: string
  args?: any[]
}

// Emit signal
{
  node: string
  action: "emit_signal"
  signal: string
  signal_args?: any[]
}

Response:

typescript
{
  node: string
  action: string
  result: "ok" | "error"
  return_value?: any                       // for call_method with return value
  error?: string                           // on failure
}

scene_tree

Get scene tree structure.

typescript
{
  root?: string                            // default: "/" (full tree)
  max_depth?: number                       // default: 5
  find_by?: string                         // filter by property name
  find_value?: any                         // match value for find_by
  show_properties?: string[]               // inline properties to include
}

Response:

typescript
{
  root: string
  frame: number
  node_count: number
  tree: {
    name: string
    class: string
    // inline properties if show_properties set
    children: Array</* recursive */>
  }
}

clips

Record and query spatial gameplay clips.

typescript
// Start recording
{
  action: "start"
  clip_id?: string                         // auto-generated if omitted
}

// Stop recording
{
  action: "stop"
}

// Mark a frame
{
  action: "mark"
  label?: string
}

// List clips
{
  action: "list"
}

// Query single frame
{
  action: "query_frame"
  clip_id: string
  frame: number
  nodes?: string[]
  detail?: "summary" | "full"
}

// Query frame range
{
  action: "query_range"
  clip_id: string
  start_frame: number
  end_frame: number
  nodes?: string[]
  detail?: "summary" | "full"
  stride?: number                          // default: 1
  condition?: {
    type: "proximity" | "velocity_above" | "property_equals"
    // proximity:
    nodes?: [string, string]
    max_distance?: number
    // velocity_above:
    node?: string
    threshold?: number
    // property_equals:
    node?: string
    property?: string
    value?: any
  }
}

// Delete clip
{
  action: "delete"
  clip_id: string
}

List response:

typescript
{
  clips: Array<{
    clip_id: string
    frame_count: number
    duration_ms: number
    created_at: string               // ISO 8601
    markers: Array<{
      frame: number
      label: string
    }>
  }>
}

query_range response:

typescript
{
  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
      }
    }
  }>
}

Open source under the MIT License.