MCP adapter

Scope and ownership

tools/mcp/ contains a small standalone Rust server built with the official rmcp SDK. It is an external tools adapter, not a Perastage application module. It must not link to or import Perastage Core, GUI, scene models, MVR/GDTF readers, or local-IPC implementation. Instead, it launches the experimental perastage-cli executable directly with an argv vector and consumes its JSON, exit status, and diagnostic stream. No request is passed through a shell.

The adapter exposes MCP tools only. It has no prompts, resources, AI behavior, authentication, network listener, HTTP transport, or general command tool. tests/check_mcp_adapter_boundary.sh protects these constraints. Perastage continues to own inspection, validation, queries, mutation transactions, Undo, dirty-state handling, and GUI refresh behavior.

Tool surface

MCP tool Effect Perastage CLI mapping
discover_capabilities Read-only capabilities --json
inspect_mvr Read-only inspect PATH --json, restricted to .mvr
inspect_gdtf Read-only inspect PATH --json, restricted to .gdtf
live_scene_summary Read-only live query scene.summary
live_current_selection Read-only live query scene.selection.get
live_objects_list Read-only live query scene.objects.list
live_object_get Read-only live query scene.object.get --args '{"kind":"fixture","uuid":"…"}'
live_layers_list Read-only live query scene.layers.list
live_groups_list Read-only live query scene.groups.list
live_selection_update Selection-only live execute scene.selection.update --args '<typed arguments>'
live_selection_clear Mutating live command clear
live_position_transform Mutating Typed single-axis millimetre values translated to pos syntax
live_rotation_transform Mutating Typed single-axis degree values translated to rot syntax
live_batch_transform Mutating live execute scene.transform.batch --args '<aligned typed lists>'

Selection transforms require an axis, value, relative/absolute choice, world/local space, and group choice. Batch transforms require explicit typed targets and ordered components. The optional port is the only endpoint setting. The adapter does not supply an implicit selection, infer a transform, accept free-form Console text, or expose destructive conversion/grouping operations. Mutation requests therefore continue through the running application's normal semantic command validation and mutation publication path.

live_batch_transform takes targets, an ordered non-empty array of {kind, uuid, components} entries. Each target's non-empty components array contains {kind, axis, value, mode, space} entries. Target kinds are fixture, truss, support, scene_object, and group; component kinds are position and rotation. Position values are millimetres and rotation values are degrees. Every component explicitly chooses an x, y, or z axis, an absolute or relative mode, and world or local space. Local axes affect relative operations; absolute components retain the existing transform semantics. Repeated targets and components retain their input order.

The adapter checks non-empty target/component lists and UUIDs, typed enum tokens, and finite values. It flattens target components in order into target_kinds, target_uuids, component_kinds, axes, values, modes, and spaces, then passes that JSON as a single argv value to the allowlisted scene.transform.batch operation. It performs no scene lookup, mutation, or layout calculations. Object existence, kind matches, preview execution, and atomic publication remain owned by Perastage. The existing selection transform tools retain their original mappings and behavior.

An explicit child target is never promoted to its parent or root group. An explicit group target uses the existing descendant synchronization behavior. The batch preserves the current selection. A rejected batch changes nothing; a successful scene change creates one Undo entry and publishes once. A semantic no-op creates no Undo entry or dirty state. Clients calculate any desired layout themselves and submit the resulting coordinates.

Object lookup accepts all Query kinds, including group. Typed selection update accepts fixture, truss, support, and scene_object. Its required input is target_kind, preserve_existing, and ordered operations, each containing an add or remove kind and explicit {kind, uuid} objects. Every object must belong to the target category and exist in the scene. preserve_existing: false replaces only that category before applying the operations; true adds/removes from its current selection. Empty operations with false clear that category. Other categories retain their selection.

The adapter flattens operations in order to the Command Core argument lists operation_kinds, object_kinds, and object_uuids, alongside target_kind and preserve_existing, then invokes live execute with --args as one argv value. This uses the generic versioned local-live machine contract and the existing semantic selection Command. Selection update creates no scene change, Undo entry, or dirty state; normal application publication synchronizes tables, 2D/3D highlights, and Layout Viewer selection. Legacy live_selection_clear retains its existing Console-compatible behavior.

Object discovery returns Query Core descriptors, including stored fixture IDs and patch addresses when applicable. Clients filter those values themselves; MCP implements no object lookup, classification, or filtering logic.

Each call returns a structured object containing success, exit_code, the CLI result, and stderr. Structured Perastage results and diagnostics are preserved even when the CLI returns a non-zero status. An unavailable CLI or live endpoint is reported as an MCP tool error with structured adapter or CLI diagnostics rather than being mistaken for an empty result.

Batch transform example

Discover UUIDs with live_objects_list or live_groups_list, then replace the example UUIDs with explicit targets in the current scene. Positions are in millimetres and rotations in degrees. Submit this payload to live_batch_transform:

{
  "targets": [
    {
      "kind": "fixture",
      "uuid": "fixture-a-uuid",
      "components": [
        {"kind": "position", "axis": "x", "value": -2000, "mode": "absolute", "space": "world"},
        {"kind": "position", "axis": "y", "value": 1500, "mode": "absolute", "space": "world"}
      ]
    },
    {
      "kind": "fixture",
      "uuid": "fixture-b-uuid",
      "components": [
        {"kind": "position", "axis": "x", "value": 2000, "mode": "absolute", "space": "world"},
        {"kind": "rotation", "axis": "z", "value": 45, "mode": "relative", "space": "local"}
      ]
    }
  ]
}

Build and stdio startup

Build and test independently from the native application:

cd tools/mcp
cargo build --locked
cargo test --locked

Start the server on standard input/output with the CLI available on PATH:

tools/mcp/target/debug/perastage-mcp

Alternatively, set PERASTAGE_CLI to the exact executable path. MCP protocol messages use stdin/stdout; the child CLI's streams are captured internally and never corrupt the MCP transport. For live tools, start Perastage normally so its loopback-only live endpoint is available. A client may provide a specific live port, otherwise the existing CLI default is used.

The pinned Cargo.lock resolves rmcp 3.x, whose stable protocol support includes MCP 2026-07-28. Dependency upgrades should retain stdio-only features and rerun the adapter contract and architecture-boundary checks. Hosted Debug CI runs cargo fmt --manifest-path tools/mcp/Cargo.toml --check and cargo test --manifest-path tools/mcp/Cargo.toml --locked; the workflow architecture guard protects that coverage.

Back to top