# Omniverse MCP (pypi · omniverse-mcp)

Let an AI assistant work in NVIDIA Isaac Sim and other Omniverse Kit apps

- Trust score: 68/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-05

## Components

- pypi · `omniverse-mcp`: 68/100 (this document), [markdown](https://verifymcp.io/servers/serionist-omniverse-mcp/omniverse-mcp.md), [page](https://verifymcp.io/servers/serionist-omniverse-mcp/omniverse-mcp)

## Channel facts

- Registry: `pypi`
- Package: `omniverse-mcp`
- Version: `0.5.1`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-05.

- **Supply Chain Security**: 50/100
  - Malware scan not yet available for this package.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs hatchling.build at install time, a recognised build step with no custom scripting around it.
  - 1 of 28 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 100/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to serionist/omniverse-mcp).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 1 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 75/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 14087 tokens (~306/item across 46 items; 44 tools + 2 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 10/100
  - Stability check failed: the tool surface changed between 0.2.2 and 0.5.1: 4 tool removals, 0 breaking changes, 7 additions.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
  - Structured output schemas are declared (98% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 46 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### How do I install the Omniverse MCP server?

Omniverse MCP runs locally as a PyPI package, launched with uvx omniverse-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add serionist-omniverse-mcp -- uvx omniverse-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "serionist-omniverse-mcp": {
      "command": "uvx",
      "args": [
        "omniverse-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "serionist-omniverse-mcp": {
      "command": "uvx",
      "args": [
        "omniverse-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add serionist-omniverse-mcp -- uvx omniverse-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "serionist-omniverse-mcp": {
      "type": "local",
      "command": [
        "uvx",
        "omniverse-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add serionist-omniverse-mcp --command uvx --arg omniverse-mcp
```

### Hermes

```yaml
mcp_servers:
  serionist-omniverse-mcp:
    command: "uvx"
    args: ["omniverse-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "serionist-omniverse-mcp": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "omniverse-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add serionist-omniverse-mcp -t stdio -c uvx -a omniverse-mcp
```

### Other

```json
{
  "mcpServers": {
    "serionist-omniverse-mcp": {
      "command": "uvx",
      "args": [
        "omniverse-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-05 (score 68, −14)

- [security regression] Malware scan: pass → unverified

### 2026-11-04 (score 82, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-11-03 (score 67, 0)

- [security improvement] Malware scan: unverified → pass
- [functional regression] Schema quality: 266 → 306
- [functional] Package version: 0.4.1 → 0.5.1
- [functional] Package version: 0.4.1 → 0.5.0

### 2026-11-02 (score 67, −15)

- [security regression] Malware scan: pass → unverified
- [functional regression] Schema quality: 236 → 266
- [functional regression] Schema quality: 236 → 265
- [functional] Package version: 0.3.0 → 0.4.1
- [functional] Package version: 0.3.0 → 0.4.0

### 2026-11-01 (score 82, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-10-31 (score 67, −1)

- [security regression] Stability: unverified → fail
- [security improvement] Malware scan: unverified → pass
- [functional regression] Schema quality: 120 → 236
- [functional regression] Schema quality: 120 → 212
- [functional improvement] Stability: unverified → 0.03
- [functional] Schema quality: good → excellent
- [functional] Package version: 0.2.2 → 0.3.0
- [functional] Package version: 0.2.2 → 0.2.3

### 2026-10-30 (score 68)

First indexed and scored.

## MCP tools (44)

### `get_scene_tree` (~453 tokens)

List the stage's prims under root as prim blocks: each one's path, type and world position, to max_depth.

Use it to find prim paths before acting on them: it is the lightest look at a scene. A prim at max_depth that
has children says how many, as children = N (truncated), and nothing below it is listed. The prims inside an
instanceable prim are not listed (get_mesh_stats looks inside). With include_properties, filter_types or
property_filter, Kit writes the blocks to mcp_output/scene_dump.txt instead, replacing the last, each with the
prim's rotation, world box and physics flags, and its applied API schemas, variant sets, attribute values and
relationship targets with include_properties or property_filter; the answer gives the file's path, how many
prims it holds and how deep it went: grep it, or read it in parts. Kit writes that file itself, so with Kit on
another machine it lands there. get_prim_properties reads one prim inline.

The positions and rotations follow the simulation, where physics has each rigid body while it plays or after a
pause, so one call with property_filter=["physics:"] gives where every body under root came to rest, whatever
the filter keeps of the rest.

Input parameters:

- `filter_types`: Only prims of these types, e.g. ["Mesh", "Xform"], written to the file; the others' children are still walked
- `include_properties` (boolean): Each prim's rotation, world box, applied API schemas, variant sets, attribute values and relationship targets, written by Kit to mcp_output/scene_dump.txt
- `max_depth`: How many levels below root: 5 when left out, or 15 with properties or a filter; in the list, a prim on the last level says how many children it has
- `property_filter`: Only properties whose names hold one of these, e.g. ["joint"], written to the file; it takes the properties with it. ["variant_sets"] lists every prim's variant sets: a scene's switches
- `root` (string): Prim path to start from

Output parameters:

- `result` (string)

### `get_prim_properties` (~297 tokens)

Get one prim as a prim block: its type, world position and rotation, world bounding box, physics flags, its
applied API schemas, its variant sets, every attribute's value, and each relationship with its targets.

Use it to read one prim; get_prim_bounds when only its size and place matter, get_scene_tree to find paths, and
with include_properties for many prims at once, written to a file. The rotation, rot, is a quaternion w, x, y,
z with any scale taken out; a prim scaled to 0 has none. api_schemas lists the applied API schemas the Kit app
knows, a type's built-in ones included, [] for none: PhysicsCollisionAPI for a collider, MaterialBindingAPI for
a bound material. variant_sets lists each variant set with its selection (none when there is none), its
options, and the layer holding the selection: session (Kit's session layer, which set_variant_selection writes
by default), root, or a file's name; [] for no set, such as state: open [closed, open] (session). A
relationship gives its targets, such as material:binding = /World/Looks/Red; one with none is left out, as an
unset attribute is.

Input parameters:

- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"

Output parameters:

- `result` (string)

### `get_prim_bounds` (~139 tokens)

Get a prim's world-space, axis-aligned bounding box, its descendants included: centre, dimensions, min and
max corners, and diagonal, in the stage's units.

Use it to place things and measure: what rests on what (a bottom on a top along the up axis), gaps, sizes.
get_prim_properties gives the same box among a prim's other values. A prim with no geometry, such as an empty
Xform, gets a 0.1 box at its position rather than an error.

Input parameters:

- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"

Output parameters:

- `result` (string)

### `set_prim_transform` (~324 tokens)

Set a prim's position, rotation or scale relative to its parent; any of the three left out keeps its value.

Use it to move, turn or size a prim; to aim the viewport camera, use set_camera, and to push a rigid body while
physics runs, apply_force. Values are in the parent's space, not world space: get_prim_properties gives a
prim's world position. It writes into the prim's own xform ops: the position into
its translate op, the rotation into its rotate or orient ops in their kind, the scale into its scale op, adding
an op it lacks; other ops, a pivot among them, stay. A part with no op of its own goes into the prim's matrix
(xformOp:transform) when it has one, as imported assets often do. An error when the prim doesn't exist or can't
be transformed, for an animated op, for ops a stronger layer holds, such as a Kit camera's, and for a rotation
or scale on a matrix that can't be factored.

Input parameters:

- `position`: [x, y, z] relative to the parent prim, in the stage's units (get_sim_state's meters_per_unit)
- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"
- `rotation`: Euler angles [rx, ry, rz] in degrees, or a quaternion [w, x, y, z]
- `scale`: A uniform scale, or [sx, sy, sz]

Output parameters:

- `result` (string)

### `create_prim` (~473 tokens)

Create a prim at prim_path: a shape, an Xform, a camera, a light or any USD type, or a reference to a file.

prim_type is a USD type name: Cube, Sphere, Cylinder, Cone, Capsule, Xform, Camera, DistantLight, DomeLight,
SphereLight and the rest. Missing parents are made as typeless prims. A prim already at prim_path keeps its
type: one of another type is refused, one of the same type takes the new transform, written as
set_prim_transform writes it. A transform it can't write makes nothing. A usd_path file whose upAxis,
metersPerUnit or kilogramsPerUnit isn't the stage's is brought to the stage as create_robot brings a robot
file: turned upright, scaled, its physics values converted, and the answer says so; one that authors none is
left as it is. usd_path adds its reference to a prim already there as the file is. A file that can't be
opened is an error. enable_physics puts the RigidBody and Collision APIs on the prim itself, so a shape falls
and collides while the simulation plays; the meshes under an Xform or a referenced file's root get no
Collision API of their own: give each one with set_physics_properties' collision=True, which makes a static
collider without a rigid body. To copy a prim, use clone_prim; for a robot, create_robot.

Input parameters:

- `enable_physics` (boolean): Add the RigidBody and Collision APIs
- `position`: [x, y, z] relative to the parent prim, in the stage's units (get_sim_state's meters_per_unit)
- `prim_path` (string, required): Where to create it, e.g. "/World/MyCube"
- `prim_type` (string): USD type name, e.g. Cube, Sphere, Xform, DistantLight
- `rotation`: Euler angles [rx, ry, rz] in degrees, or a quaternion [w, x, y, z]
- `scale`: A uniform scale, or [sx, sy, sz]
- `usd_path`: A USD file to reference instead of a type: an absolute path on Kit's machine, or a URL

Output parameters:

- `result` (string)

### `delete_prim` (~144 tokens)

Delete a prim and everything under it, by removing what the stage's edit layer holds at that path.

A prim another layer defines too, or one a reference or payload on a prim above brings, such as a part of a
referenced asset, can't be deleted this way: the call is refused, saying why, and nothing changes;
set_visibility hides such a prim, or its instance for a prim inside one. Kit's undo doesn't record the delete,
and the file on disk keeps the prim until save_scene. An error when no prim is there.

Input parameters:

- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"

Output parameters:

- `result` (string)

### `set_material` (~314 tokens)

Colour a prim with an OmniPBR material: make the material, or update the one at its path, set its colour and
surface values, and bind it to the prim in place of its binding.

opacity, roughness and metallic are set on every call, each to its default when left out, so a call without
opacity makes the material opaque. Without material_path the material is /World/Looks/<prim name>_Mat, so prims
of the same name share one and colouring one recolours the rest: give material_path to keep them apart, or to
share one on purpose. The binding reaches the prim's children unless they have bindings of their own. For
friction and bounce, use set_physics_properties; to repoint bindings after materials move,
update_material_paths.

Input parameters:

- `color` (array, required): [r, g, b] diffuse colour: 0-1 floats (e.g. [1, 0, 0] for red) or 0-255 ints
- `material_path` (string): The material prim's path; made under /World/Looks/ when empty
- `metallic` (number): 0 plastic (dielectric) to 1 metal
- `opacity` (number): 0 transparent to 1 opaque
- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"
- `roughness` (number): 0 glossy (mirror) to 1 rough (matte)

Output parameters:

- `result` (string)

### `clone_prim` (~288 tokens)

Copy a prim with everything under it to a new path, once or as a row of count copies.

The first copy sits on the source and each next one moves by offset from the one before, so a single copy is
never moved: move it with set_prim_transform. With offset, copy n moves n times offset from the source in the
parent's space, through its own translate op or matrix, its other ops and placement kept; the first copy, and a
single copy, are left as copied. A source whose position can't be written copies nothing. It copies what the
stage's root layer holds at source_path, its references included, so a prim from a sublayer, or from inside a
referenced asset, can't be copied whole. A target path that already holds a prim is refused before anything is
copied. For a new prim, use create_prim.

Input parameters:

- `count` (integer): How many copies: one goes to target_path, more to target_path_001, _002, ...
- `offset`: [x, y, z] in the parent's space: each copy after the first sits this far from the last
- `source_path` (string, required): The prim to clone, e.g. "/World/MyCube"
- `target_path` (string, required): Where the copy goes, e.g. "/World/MyCube_Copy"

Output parameters:

- `result` (string)

### `set_visibility` (~147 tokens)

Show or hide a prim and everything under it, in the viewport and in captures; the prim stays in the stage.

Use it to see inside or behind something, then show it again; delete_prim removes a prim, and inspect_prim's
isolate hides all but one prim for its captures. Showing a prim under a hidden parent shows the parent and hides
the parent's other children, as USD's MakeVisible does. An error for a prim that has no visibility, such as a
material.

Input parameters:

- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"
- `visible` (boolean): True to show, False to hide

Output parameters:

- `result` (string)

### `set_prim_attribute` (~523 tokens)

Set one attribute of a prim, such as a light's intensity or colour, a camera's focal length, a shape's size
or radius, or a display colour.

Use it for an attribute no other tool sets; set_prim_transform, set_visibility, set_material and
set_physics_properties set theirs. The attribute must exist on the prim: get_prim_properties lists them, a
schema's included, and a name it lacks gets its closest names. The value is converted to the attribute's type,
within the type's range; a token outside its allowed ones is written, with a note naming them. A change that
alters a shape's box updates its authored extent, so get_prim_bounds follows. Refused, with nothing written: a
relationship, an xform op, visibility, an animated attribute, one a stronger layer holds, a size change whose
extent another layer or an animation holds, a prim inside an instance or a prototype, an inactive prim. By
default (layer="auto") it writes where edits go, but Kit's own cameras and a viewport light rig's lights in
Kit's session layer, which save_scene doesn't keep. layer="session" writes there for a check that must leave
every file as it is; no tool takes such a value back, and a later write beneath it, at the edit target, is
refused until the stage is opened again or execute_script removes it from the session layer. A physics value
set while the simulation plays may reach PhysX only once it stops. A DistantLight comes at an intensity of
50,000, UsdLux's sunlight; a few thousand suits a viewport capture. The answer gives the value before and
after, and the layer written.

Input parameters:

- `layer` (string): auto: where edits go, but Kit's own cameras and a light rig's lights in Kit's session layer; session: Kit's session layer, which no save writes, for a check that must leave every file as it is; edit_…
- `name` (string, required): The attribute, as get_prim_properties names it, e.g. "inputs:intensity"
- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"
- `value` (required): Its new value in JSON: a number, true or false, a string or token, or a list for a vector, colour or array, e.g. [1, 0, 0]

Output parameters:

- `result` (string)

### `save_scene` (~264 tokens)

Write the open stage's root layer to a USD file: back to the stage's own file when file_path is empty, or as
a copy at file_path.

Without file_path it overwrites the file the stage was opened from, and fails for a stage never saved, such as
new_scene's. With file_path it overwrites any file there, and the stage stays tied to its own file, or to none;
but another file the open stage has loaded (a sublayer, a referenced or payload file) is an error, since
writing it would replace a file the open scene is made of: name another path. The stage's own file, by any
name, is a save. Only the root layer is written: sublayers and referenced files stay as they are, still
referenced, and Kit's session layer stays out, so the answer names the variant selections it holds, which
set_variant_selection writes there by default. For one self-contained file, use flatten_usd; for one prim's
subtree, export_prim_as_file.

Input parameters:

- `file_path` (string): The USD file to write, on Kit's machine, but never another file the open stage has loaded; empty overwrites the stage's own file

Output parameters:

- `result` (string)

### `open_scene` (~296 tokens)

Open a USD file as the stage, in place of the open one, which goes with its unsaved changes.

Use it to work on a scene from a file; new_scene starts an empty stage instead, and save_scene writes the open
one, so call save_scene first to keep it. Opening the stage's own file reloads it from disk. It stops the
simulation first; a recording that runs stops taking frames, and draw_debug's drawings go. Refused, with the
open stage kept, for a file that isn't there or can't be reached, a type USD doesn't open, a local file that
holds no USD (a Git LFS pointer, say), or while Kit opens or closes a stage. A file that passes those checks
and still fails to load leaves no stage open, and the error says so; an open that takes over 80 s goes on in
Kit after the error. Answers the path opened, the stage it replaced, and the stage's up axis, meters_per_unit,
default prim and prim count, with a note for an axis or units the file leaves to USD's fallbacks.

Input parameters:

- `file_path` (string, required): The USD file: a path on Kit's machine, a relative one read from the folder Kit was started in; a URL (omniverse://, https://, file:///); or a path in Isaac Sim's asset library (/Isaac/...)

Output parameters:

- `result` (string)

### `new_scene` (~267 tokens)

Discard the open stage, unsaved changes included, and open an empty one in the up axis and units asked for,
with a /World Xform as its default prim.

The stage is Z-up in metres when left out: the frame of Isaac Sim's own new stage, its robots, SimReady assets
and most robotics scenes, so scripts, payloads and physics written for them need no conversion. up_axis="Y"
makes a Y-up stage, as USD Composer's are, and meters_per_unit=0.01 one in centimetres. The answer gives the
stage's up_axis and meters_per_unit as read back. Kit places the viewport's camera for the units of its app's
own new stages, so in other units place it with set_camera. Call save_scene first to keep the old one. The new
stage has no file, so save_scene needs a file_path for it. A recording that runs stops taking frames, and
draw_debug's drawings go.

Input parameters:

- `meters_per_unit` (number): The stage's metres per unit: 1.0 for metres, 0.01 for centimetres
- `up_axis` (string): The stage's up axis: Z, as Isaac Sim's robots and stages are, or Y

Output parameters:

- `result` (string)

### `capture_viewport` (~346 tokens)

Render one frame from a camera at width x height and save 3 files to mcp_output/captures/:

1\. **Image** (PNG): what the camera sees.
2\. **Bounding boxes** (TXT): each mesh or shape under /World whose world box lies on the image, its box in
   pixels, world centre and size.
3\. **Instance segmentation** (PNG): each prim in a colour of its own, with a legend naming them.

Use it to see the scene and tell which prim is where: the boxes for places, the segmentation for prims that
overlap. It renders camera_path, or the viewport's camera when empty, in a render of its own, so the viewport
the user sees keeps its camera and size. Aim first with set_camera, at a point or a prim; for one prim from
every side, use inspect_prim. An all-black image, or lighting that lights nothing (mode off, or stage with no
scene lights) though Kit's grid shows, comes with a warning saying what to change with viewport_light.
Without omni.syntheticdata only the image comes: the boxes and segmentation need it.

Input parameters:

- `camera_path` (string): The camera prim to capture from, which must be a camera; the viewport's when empty
- `height` (integer): Image height in pixels, up to 4096; the camera is rendered at this size
- `include_image` (boolean): Also return the image inline, besides the file (uses more context)
- `width` (integer): Image width in pixels, up to 4096; the camera is rendered at this size

### `set_camera` (~336 tokens)

Move the viewport's camera and aim it, at a world point or at a prim.

For a point, position is where the camera stands and target what it looks at, the world origin when left out.
For a prim, prim_path, and the camera goes around the prim's centre at distance, azimuth and elevation, without
position or target. It moves whichever camera the viewport looks through, a scene camera included, and the
user sees the move. inspect_prim captures a prim from up to 14 sides in one call; capture_viewport's
camera_path captures from another camera without moving the viewport. An error when the prim doesn't exist,
when position is the point it would look at, and for a mix of the two forms, saying what to drop.

Input parameters:

- `azimuth`: Degrees around the prim: 0 the front (+Y on a Z-up stage, +Z on a Y-up one), 90 the right (+X), 180 the back, 270 the left; 45 when left out
- `distance`: From the prim's centre in stage units; 2.5 times its largest side when left out
- `elevation`: Degrees above the horizontal; 30 when left out
- `position`: [x, y, z] where the camera stands, in world space: for a point
- `prim_path` (string): A prim to frame instead: the camera aims at the centre of its world box
- `target`: [x, y, z] the world point it looks at, the world origin when left out: for a point

Output parameters:

- `result` (string)

### `inspect_prim` (~445 tokens)

Capture a prim from up to 14 angles around it, each framed so the prim fills the image, and save each as a
PNG in mcp_output/captures/.

Use it to see one object from every side; capture_viewport takes one picture from a camera as it stands. The
angles are the 6 face centres and 8 corners of a cube around the prim, all 14 unless angles names some. The
distance is fitted to the prim's size at width x height, then moved by zoom_offset. It moves whichever camera
the viewport looks through, a scene camera included, and leaves it at the last angle. isolate hides everything
but the prim and the lights while it captures, then shows them again. A call has about 80 s: an angle that
fails says why, and the angles after it say Not captured. An orthographic camera is refused. An all-black angle,
or lighting that lights nothing (mode off, or stage with no scene lights) though Kit's grid shows, comes with a
warning, once a call, saying what to change with viewport_light.

Input parameters:

- `angles`: Angle names, all 14 when omitted. Face centres: "front", "back", "right", "left", "top", "bottom". Cube corners: "top_front_right", "top_front_left", "top_back_right", "top_back_left", "bottom_front_…
- `height` (integer): Image height in pixels, up to 4096; the camera is rendered at this size
- `include_segmentation` (boolean): Also capture instance segmentation at each angle
- `isolate` (boolean): Hide everything else while capturing, then restore it
- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"
- `width` (integer): Image width in pixels, up to 4096; the camera is rendered at this size
- `zoom_offset` (number): Adjusts the fitted distance: negative closer, positive further (-0.3 is 30% closer)

Output parameters:

- `result` (string)

### `viewport_light` (~359 tokens)

Read or set the viewport's lighting mode, which decides how the viewport and its captures are lit, without
changing the scene.

Use it when a capture comes back black, or warns that the lighting lights nothing (mode off, or stage with no
scene lights, though Kit's grid shows), then capture again. "get" (the default) answers the mode, the modes on
offer, and the stage's own lights with their intensity, inside instances too, the first 20 listed; a light
rig's lights are the viewport's, not the stage's, and aren't among them. "set" takes a mode "get" lists:
"stage" (the stage's own lights), "camera" (a light that follows the camera), "off", or a light rig such as
"Default", "Colored Lights" or "Grey Studio"; rigs need Kit's lighting menu, omni.kit.viewport.menubar.lighting,
and a mode not on offer is an error. "set_camera_light" is the older switch, turning the camera light on or off
with enabled. To add a light to the stage itself, use create_prim (DistantLight, DomeLight), then
set_prim_attribute for its intensity: a DistantLight comes at 50,000, which washes a capture out.

Input parameters:

- `action` (string): "get" the mode, "set" one, or the legacy "set_camera_light"
- `enabled` (boolean): For "set_camera_light": turn the camera light on or off
- `mode` (string): For "set": "off", "camera", "stage", or a light rig "get" lists, e.g. "Default"

Output parameters:

- `result` (string)

### `capture_sweep` (~528 tokens)

Capture many shots in one call, each with its own camera pose, lens, variant selections and timeline time,
as a job: for checking a scene's looks and switches from many viewpoints, where one capture_viewport a picture
costs a turn each. Answers the job's id and folder at once.

A shot is a position with yaw and pitch (yaw about the up axis, 0 along +X; pitch -90 looks straight down), a
position with a target, or camera_path, a camera of the stage rendered as it is. Its lens is focal_length and
the apertures in mm, the vertical one fitted to the image when left out; one that doesn't fit the image's
shape is refused. selections switch variant sets for the shot alone, in the session layer, which no save
writes; time sets the timeline. Every shot is checked before anything changes.

Kit writes each shot's PNG and a manifest.json, each shot's pose, lens, selections, time and file, into
mcp_output/sweeps/<name>/. Read it with get_job(id, wait=100): shot 37 of 400 as it goes, then one line naming
the folder and the shots that came out black or failed. A sweep's manifest.json given as shots_file takes the
same shots again, given the first sweep's width and height, on this scene after a change or on another built
from the same spec. Afterwards every
layer, the timeline and the selections are as they were, after cancel_job too, which keeps the shots taken. A
time repeats time-sampled animation; Replicator's walking agents move on each update whatever the time, so
stop the timeline (sim_control) for shots of them that repeat. One size for all the shots; one job runs at a
time.

Input parameters:

- `height` (integer): Every picture's height in pixels, up to 4096
- `name` (string): The sweep's folder in mcp_output/sweeps/: letters, digits, '.', '_', '-' and spaces; the time when empty. A folder that holds a sweep already is refused
- `shots`: The shots, in order, up to 2,000; give this or shots_file
- `shots_file` (string): A JSON file on this server's machine holding a list of shots, or a sweep's manifest.json to take its shots again; give this or shots
- `width` (integer): Every picture's width in pixels, up to 4096

Output parameters:

- `result` (string)

### `create_robot` (~419 tokens)

Add a robot to the stage as a reference: one of Isaac Sim's asset library by name, or a robot's USD file.

Names, which need Isaac Sim: franka, ur10, carter, jetbot, g1, go1, go2, h1, spot, anymal. A USD file as
robot_type is a path on the Kit app's machine, a URL, or a path in Isaac Sim's asset library (/Isaac/...).
Without prim_path, the prim is named after the robot or the file: /World/Franka, or /World/MyBot for MyBot.usd.
A prim already at prim_path, or a file that can't be opened, is an error. A file whose upAxis isn't the
stage's is stood upright on it, as Kit stands a dropped file, and the answer says so: Isaac Sim's robots are
Z-up, so each is turned on a Y-up stage. A file that authors no upAxis is left as it is. A file whose
metersPerUnit or kilogramsPerUnit isn't the stage's is brought to the stage's units, as Kit's metrics
assembler brings a dropped file with its physics rules: scaled, its physics values converted, and the answer
says so; one that authors neither is left as it is. position and rotation stay in the stage's frame. For
anything but a robot, use create_prim; get_robot_info then lists the robot's joints.

Input parameters:

- `position`: [x, y, z] relative to the parent prim, in the stage's units (get_sim_state's meters_per_unit)
- `prim_path` (string): Where to put it; made up from the name when empty
- `robot_type` (string, required): A robot's name (franka, g1, spot, ...) or a USD file's path
- `rotation`: Euler angles [rx, ry, rz] in degrees, or a quaternion [w, x, y, z]

Output parameters:

- `result` (string)

### `get_robot_info` (~282 tokens)

Read a robot: from USD, at any time, whether it is an articulation, its link and DOF counts, and each joint's
name, type, limits and drive; from physics, while the simulation plays or is paused, each joint's position and
velocity.

Use it before driving a robot, and to see where its joints are now. Limits are as USD holds them: degrees for
revolute joints, stage units for prismatic ones. Joint states are in radians for revolute joints and stage
units for prismatic ones, velocities per second; set_joint_targets takes degrees. The Joints list comes in the
order a list of set_joint_targets follows, fixed joints included, and the DOF count leaves fixed joints out;
the joint states come in physics' own order of degrees of freedom. Stopped, or in a Kit app without Isaac Sim,
the answer gives the structure and a line saying why there are no joint states. prim_path is the robot's root:
the joint states are the first articulation's at or under it. An answer too long to give inline keeps the
counts, and the joint states where they fit, and writes the Joints list to a file, its path in the answer.

Input parameters:

- `prim_path` (string, required): The robot's root prim, e.g. "/World/G1"

Output parameters:

- `result` (string)

### `set_joint_targets` (~197 tokens)

Set the position targets of a robot's joint drives, which physics drives the joints toward while the
simulation plays.

A dict sets the joints it names and skips names it doesn't find; a list follows the order of get_robot_info's
Joints list, not of its joint states. A joint without a drive gets one, with stiffness 1000 and damping 100. It
writes USD, so it works stopped too, before physics has started. The answer counts the targets set;
get_robot_info then reads where the joints are, in radians, while the simulation plays or is paused.

Input parameters:

- `prim_path` (string, required): The robot's root prim, e.g. "/World/G1"
- `targets`: Required: {joint_name: value}, or [v0, v1, ...] in get_robot_info's joint order, fixed joints included; degrees for revolute joints, stage units for prismatic ones

Output parameters:

- `result` (string)

### `sim_control` (~123 tokens)

Play, pause, stop or step the simulation's timeline, and return the state it is left in.

play starts or resumes physics; pause holds it where it is; stop rewinds the timeline, and physics puts bodies
back where play found them; step plays for two app updates, then pauses. A robot's first play cooks its
collision meshes, and Kit answers nothing until that ends. get_sim_state reads the state without changing it;
get_robot_info's joint states and apply_force need it playing or paused.

Input parameters:

- `action` (string, required): What to do

Output parameters:

- `result` (string)

### `get_sim_state` (~215 tokens)

Get the simulation's state and the stage's frame of reference: playing, paused or stopped, the timeline's
time, the up axis, meters_per_unit, the prim count; the Kit app, its version, Kit's version and the OS Kit
runs on; and the versions of the Kit extension and this server.

timeline gives the timeline's range and what it does at its end: while it loops, time goes back to the start
there, so a wait for time to pass the end never finishes; a timeline that doesn't loop pauses at its end, and
physics with it. A range of one frame or less gets a note.

fps is the timeline's time codes per second, not a render rate: get_performance measures the frame rate. When
the two versions differ, the answer names the command that installs the matching extension; while a recording
runs whose frames stopped early, it says why; while a job run_script_job started runs, it names it.
sim_control changes the state.

Output parameters:

- `result` (string)

### `get_performance` (~443 tokens)

Measure how fast the Kit app runs the open stage: the app loop's frames per second and frame time (mean,
p50, p95, worst) over `frames` app updates after `warmup`; the GPU's render time a frame; while the simulation
plays, the physics steps a frame, their time and the simulated-to-real ratio; GPU memory; the stage's prim,
mesh and instance counts; the render products (cameras, lidars) rendering every update; and the conditions to
compare two answers by: the GPU, the Kit app, the renderer, the timeline, headless or not, the frame-rate
limiter, async rendering and eco mode.

Use it to check a frame-rate target, such as 15 FPS with a robot's sensors on, and to see what each cut of the
scene gains. Play the simulation first, with the sensors on, so physics and rendering run as they will; a
stopped timeline gives no physics numbers. Measure alone: a capture called meanwhile waits for the measurement,
and any other call's work counts in its frames. It waits for the stage to finish loading, and stops at 90 s,
answering what it measured.

While it measures, Kit's frame-rate limiter is lifted and put back after, so a playing stage with fixed time
steps runs faster than real time for those seconds, and a robot driven from outside falls behind meanwhile.
The render a capture keeps for the next one is released first, so the next capture sets up its view again.
The answer notes when the run's two halves differ by more than 10% (measure again), when eco mode on a still
timeline makes the stage read faster than it plays, and when a recording's camera counts in the numbers. The
numbers are this machine's and GPU's: measure where the target must be met.

Input parameters:

- `frames` (integer): App updates to time, after the warm-up: 120 take 8 s at 15 FPS
- `warmup` (integer): App updates to let pass first, once the stage has loaded, so shaders and caches settle

Output parameters:

- `result` (string)

### `set_physics_properties` (~687 tokens)

Set a prim's mass or density, friction or restitution, or make it a collider or turn its collision off, for
PhysX; a value left out stays as it is.

collision=true makes a static collider, such as a floor something should land on, with no rigid body and no
physics material: only a Cube, Sphere, Capsule, Cylinder, Cone, Mesh or Plane collides, so an Xform is refused,
its error naming the shapes under it. A Mesh that moves with a rigid body, its own or an ancestor's, not
kinematic, gets a convexHull approximation unless one is set, as PhysX can't simulate a triangle mesh there.
collision=false turns physics:collisionEnabled off and keeps the API. Mass or density goes through MassAPI and
makes the prim a rigid body (RigidBodyAPI) if it isn't one, so a static collider starts to fall when the
simulation plays. Friction or restitution adds the Collision API if missing, and sets them on the prim's own
physics material, a child named PhysMat (/World/Cube/PhysMat for /World/Cube), bound to the prim for physics
only; delete_prim removes it with the prim. That material starts from the body's own: the physics material
bound to it before, else the physics scene's default, else PhysX's (friction 0.5, restitution 0), so the one
left out, and anything else the replaced material held, stays. A copy made with clone_prim takes its own with
it. A clone that inherits from its source, as Isaac Sim's cloner makes them, follows the source's material
until a call on the clone, and the answer names the prims a call changes so. An instance or an inactive prim
can hold no material of its own, so friction or restitution on one is refused, as collision is on an inactive
prim or one inside an instance. A prim on an older extension's shared /World/PhysicsMaterials/<prim
name>_PhysMat moves to its own, and the call that moves the last prim off it deletes it. A call with friction
or restitution reads the whole stage, about 1 s per 100,000 prims. The answer gives each value set, collision
on or off,…

Input parameters:

- `collision`: true makes the prim a collider (the Collision API), with no rigid body, such as a floor; false turns its collision off and keeps the API
- `density`: Density in kg/m^3 (applies MassAPI); 0 unsets it; a mass set wins over it
- `friction`: Friction coefficient, set as both static and dynamic friction
- `mass`: Mass in kg (applies MassAPI); 0 unsets it, so density decides
- `prim_path` (string, required): Full USD path of the prim, e.g. "/World/Robot"
- `restitution`: Bounciness: 0 no bounce, 1 full bounce

Output parameters:

- `result` (string)

### `apply_force` (~358 tokens)

Push a rigid body through PhysX with a force or an impulse, so its mass counts. The simulation must be
playing or paused. A push on an articulation's link, such as a robot's, moves the robot through its joints: a
floating robot touching nothing changes speed by the impulse over its whole mass; a fixed robot's link turns
about its joints, and on the ground friction takes some of the push. The answer gives the point pushed at and
the path the push took: method: physx, PhysX's own push, or method: tensor. With /physics/suppressReadback on,
as Isaac Sim's GPU device sets it for PhysX's Direct GPU API, the push goes through PhysX's tensor API. Under
that API a second push on a body before the next physics step replaces the first, so push once per body per
step, combining pushes into one; pushes on different bodies each land. Refused for a prim without RigidBodyAPI,
a disabled or kinematic body, and one a fixed joint holds to the world (a fixed robot's base). To place a body
rather than push it, use set_prim_transform.

Input parameters:

- `force` (array, required): [fx, fy, fz]: an impulse in N s, or a force in N for one physics step
- `impulse` (boolean): True for an impulse in N s, False for a force in N for one physics step. For a sustained push, give an impulse of force times seconds
- `position`: World-space point to push at; the body's centre of mass when omitted
- `prim_path` (string, required): The rigid body's prim path, a robot's link among them

Output parameters:

- `result` (string)

### `raycast` (~214 tokens)

Cast a ray through PhysX and return the closest collider it hits: its prim, the hit point, the surface
normal and the distance in stage units, or "No hit".

Use it to find what lies along a line, such as the ground under a point or what a robot faces; get_prim_bounds
measures a prim you already know. Only colliders (the Collision API, which set_physics_properties'
collision=True or create_prim's enable_physics adds) are hit, not plain meshes. PhysX needs to know the scene:
play or step the simulation once first, or it may find nothing, or fail. The prim named is the rigid body hit,
or a static collider itself.

Input parameters:

- `direction` (array, required): [dx, dy, dz] direction in world space, of any length but zero
- `max_distance` (number): The longest the ray goes, in stage units
- `origin` (array, required): [x, y, z] start point in world space

Output parameters:

- `result` (string)

### `draw_debug` (~306 tokens)

Draw debug lines in the viewport, 2 pixels wide, for `duration` seconds: a line, a polyline, a sphere as
three crossing lines, or a small cross per point.

Use it to mark points, rays or paths, such as a raycast's hit, for a capture or the user to see: on Isaac Sim
6.x the lines show in capture_viewport's image. Nothing is added to the stage. In Isaac Sim, when a drawing
expires, lines other code drew with isaacsim.util.debug_draw go too. A new stage drops the old one's
drawings.

Input parameters:

- `center`: For "sphere": its centre [x, y, z]
- `color`: [r, g, b] colour in 0-1; red when omitted
- `duration` (number): How long it shows, in seconds from the call, whether the simulation plays or not
- `end`: For "line": its end [x, y, z]
- `points`: For "points" and "lines": a list of [x, y, z]
- `radius` (number): For "sphere": its radius
- `shape` (string, required): What to draw: "lines" joins the points in order
- `size` (number): For "points": how far each point's cross reaches, in stage units
- `start`: For "line": its start [x, y, z]

Output parameters:

- `result` (string)

### `start_recording` (~497 tokens)

Start recording simulation frames to disk.

Records camera_path, or the active viewport's camera when empty, at width x height (up to 4096 a side),
through the render capture_viewport and inspect_prim keep when it is that size, else one of its own: the
viewport, its camera and its size stay as they are. The render runs while the recording does and up to 30 s
after, kept for the next capture or recording, and slows the app's updates, the more the larger the frames.

Takes a frame every 1/fps seconds of the recording's own clock, at most one an app update. The clock runs with
the simulation while it plays, so a simulation faster than real time gives more than fps frames a wall second,
and with the wall clock while it is stopped or paused. Play it to record motion. Frames taken while it is
stopped or paused show Kit's grid, as the viewport does; Isaac Sim 6.2 hides the grid while it plays. Frames
stop if the stage changes or the camera is deleted; stop_recording says so. Frames are saved as PNGs in
mcp_output/recordings/, by Kit itself. One recording runs at a time: another start is refused until
stop_recording. Use stop_recording when done, then get_recording_frame to review any frame. Frames go on while
the simulation is stopped, and a stop puts the scene back: pause it, or call stop_recording, before stopping it
to end on the run's last state.

Optionally tracks prim state (transforms, properties) alongside video frames, in state.txt.

Input parameters:

- `camera_path` (string): The camera to record, e.g. /World/RecCam; the active viewport's camera when empty
- `fps` (integer): Frames a second: 5 takes one every 0.2 s, of simulation while it plays and by the clock while not
- `height` (integer): Frame height in pixels, up to 4096
- `property_filter`: Only properties whose names contain one of these, e.g. ["joint", "pos", "vel"]
- `track_prims`: Prims whose state to record, descendants included, e.g. ["/World/G1"]
- `width` (integer): Frame width in pixels, up to 4096

Output parameters:

- `result` (string)

### `stop_recording` (~122 tokens)

Stop the running recording: wait for the frames still being written, write metadata.json with each frame's
simulation time, and hand the render back to the captures, kept up to 30 s.

Returns the frame count, the duration, the seconds of simulation played and the seconds stopped or paused (the
frame count comes out near fps times their sum), the session folder and the state file, with a note when frames
stopped early or the cleanup failed in part. An error when no recording runs. Use get_recording_frame to review
frames.

Output parameters:

- `result` (string)

### `get_recording_frame` (~235 tokens)

Copy one recorded frame to a new PNG in mcp_output/captures/, and return its path and, once the recording
has stopped, the frame's simulation time.

Frames come from start_recording, which Kit writes itself as frame_00000.png and on in the session folder: with
Kit on this machine you can also open them there, and with Kit on another this brings one here. Without
session_dir it reads the last recording started since the Kit extension started, the running one included. A
relative session_dir is read from this server's working folder, as start_recording and stop_recording give it.
A frame Kit failed to capture comes back as an empty file; a frame number past the last is an error. For a
picture of the scene now, use capture_viewport.

Input parameters:

- `frame_index` (integer): The frame's number, from 0
- `session_dir` (string): The session folder as start_recording and stop_recording give it, relative to this server's working folder, or an absolute path on Kit's machine; the last recording when empty

Output parameters:

- `result` (string)

### `get_mesh_stats` (~220 tokens)

Count the faces, vertices and triangles of every mesh at or under a prim: the totals, then the prims under it
to max_depth, each mesh with its counts and each prim above meshes with subtree_faces.

Use it for a geometry budget, what a simplification saved, or the heavy subtrees: a heavy one is the next
call's prim_path. compare_prims puts two prims or variants side by side. Only UsdGeom.Mesh prims count, inside
instances too, which get_scene_tree doesn't list: implicit shapes such as Cube and Sphere have no faces. Prims
below max_depth aren't listed, but count in the totals and in their ancestors' subtree_faces. The totals always
come inline; a tree too long to give with them goes to a file, its path in the answer.

Input parameters:

- `max_depth` (integer): How many levels of the tree below prim_path to list; 0 for prim_path alone
- `prim_path` (string, required): The prim to measure, its descendant meshes included

Output parameters:

- `result` (string)

### `flatten_usd` (~267 tokens)

Write a stage flattened into one USD file: its references, payloads and sublayers resolved into a single
layer.

Use it to hand the whole scene to another tool as one file; save_scene keeps the references and writes only the
root layer, and export_prim_as_file writes one prim's subtree. Leave input_path empty for the open stage, or
name a USD file: one the open stage has loaded, its own file included, is flattened as Kit holds it, unsaved
edits too, and the open stage is left as it is; one USD can't open is an error. output_path is overwritten, but
one the open stage has loaded (its own file, a sublayer, a referenced or payload file) is an error, since
writing it would replace a file the open scene is made of: name another path, or save the open stage with
save_scene. Textures and other files the stage points at are not copied.

Input parameters:

- `input_path` (string): The USD file to flatten, on Kit's machine; the open stage, unsaved edits too, when empty
- `output_path` (string, required): The USD file to write, on Kit's machine; overwritten if there, but never one the open stage has loaded

Output parameters:

- `result` (string)

### `export_prim_as_file` (~230 tokens)

Write one prim's subtree to a USD file of its own, flattened, with the materials it is bound to from outside
the subtree, so the file opens without the scene.

The prim becomes /<its name> and the file's default prim, with the stage's up axis and units. Use it to hand
one asset to another tool or project; flatten_usd writes the whole stage, save_scene the root layer as it is.
output_path is overwritten, but one the open stage has loaded (its own file, a sublayer, a referenced or
payload file) is an error, since writing it would replace a file the open scene is made of: name another path.
An error too when the prim doesn't exist. Textures and other files the subtree points at are not copied.

Input parameters:

- `output_path` (string, required): The file to write on Kit's machine: .usd, .usda or .usdc, which a path without one gets; never one the open stage has loaded
- `prim_path` (string, required): The root of the subtree to export

Output parameters:

- `result` (string)

### `set_variant_selection` (~470 tokens)

Select a variant in a prim's variant set, or clear a layer's selection, and wait until the viewport shows it.

By default the selection goes to Kit's session layer: it shows at once over what the files select, and
save_scene leaves it out of the file, so a check leaves every file as it found it; it lasts until it is
cleared or the stage is opened again. To keep one in the stage's file, use layer="edit_target", where edits
go: the root layer, which save_scene writes, unless Kit's Layer window made a sublayer the edit target, which
save_scene doesn't write; the answer names the layer. It is refused while the session layer holds a selection
for the set, since that would hide it: clear that first with variant_name="". An extension older than the
server writes every switch at the edit target and can't clear, and the answer warns. get_prim_properties'
variant_sets line lists a prim's sets, their options and the layer
holding each selection. A selection on an instance prim gives it a prototype of its own; a prim inside an
instance is refused, naming the instance. An unknown set or variant is refused, listing those there
(create_variant_structure makes them). Switch variants with this rather than execute_script: with Fabric
Scene Delegate on before Kit 110 (Isaac Sim 5.x), it deactivates and reactivates the prim in the session layer
around the switch, some 40 app updates, so the viewport drops the old geometry; otherwise it waits 5.
compare_prims measures two variants and leaves the selection as it was.

Input parameters:

- `layer` (string): session: Kit's session layer, which no save writes, so every file stays as it is; edit_target: where edits go, the stage's root layer, which save_scene writes, unless Kit's Layer window made a sublay…
- `prim_path` (string, required): The prim with the variant set
- `variant_name` (string, required): The variant to select, e.g. "shell"; "" clears the layer's selection, so the one beneath shows again
- `variant_set` (string, required): The variant set's name, e.g. "model"

Output parameters:

- `result` (string)

### `create_variant_structure` (~157 tokens)

Add a variant set of empty variants to a prim and select one, ready to fill and to switch between with
set_variant_selection.

A set or variant that already exists is kept and added to, never replaced. The variants hold nothing until you
author into them, with execute_script. An error when the prim doesn't exist.

Input parameters:

- `default_variant` (string): The variant to select; the first of variant_names when empty or not among them
- `prim_path` (string, required): The prim to add the variant set to
- `variant_names` (array, required): The variants to create, e.g. ["focused", "shell"]
- `variant_set_name` (string, required): The new variant set's name, e.g. "model"

Output parameters:

- `result` (string)

### `compare_prims` (~258 tokens)

Compare two prims, or two variants of one prim, side by side: mesh, face, vertex and triangle counts, world
bounds and bound materials, with B minus A, and the change in faces as a percentage when A has faces.

Two modes:
1\. Two prims: prim_path_a and prim_path_b.
2\. Two variants: prim_path, variant_set, variant_a and variant_b; this mode wins when all four are given.

Variant mode switches to each variant in turn in Kit's session layer, then puts that layer back exactly as it
was, so no file and no selection changes. An unknown variant set or variant is refused, listing those there,
and so is a prim inside an instance, naming the instance. For one prim's counts, use get_mesh_stats.

Input parameters:

- `prim_path` (string): Two variants: the prim that has them
- `prim_path_a` (string): Two prims: the first
- `prim_path_b` (string): Two prims: the second
- `variant_a` (string): Two variants: the first
- `variant_b` (string): Two variants: the second
- `variant_set` (string): Two variants: their variant set

Output parameters:

- `result` (string)

### `update_material_paths` (~218 tokens)

Rewrite every path under a subtree that starts with old_prefix to start with new_prefix instead: relationship
targets, such as material bindings, and asset paths, such as texture files.

Use it after materials or textures move, so bindings and textures point at their new place. The match is a plain
text prefix, on any relationship or asset path: a joint's bodies or a shader's source file change too if they
match, so end the prefix with "/" and keep it specific. Nothing checks that the new paths exist. Kit's undo
doesn't record the change, and a failure part way keeps the edits made before it. The answer counts the paths
changed and names the prims.

Input parameters:

- `new_prefix` (string, required): What to put in its place, e.g. "/World/Materials/"
- `old_prefix` (string, required): The start of the paths to change, matched as text, e.g. "/OldProject/Materials/"
- `prim_path` (string): The subtree to update; the whole stage by default

Output parameters:

- `result` (string)

### `execute_script` (~410 tokens)

Run Python inside the Kit app (Isaac Sim, USD Composer, ...) on its main thread, for what no tool does.

Prefer a tool that does the job: it checks its input and bounds its waits. The script has omni, carb, Usd,
UsdGeom, UsdLux, UsdPhysics, Sdf, Gf and mcp. Assign to 'result' to return JSON-serialisable data (anything else
comes back as its str); stdout and stderr come back too, and an exception as an error with the traceback's last
lines. A script that blocks freezes Kit: the call gives up after 120 s, and the script runs on. For work that
may outlast 120 s, such as settling many objects under physics, use run_script_job: it runs as a job while Kit
renders and other tools answer, and get_job reads its progress and result.

\`mcp` calls the tools' handlers from the script, each returning a dict with "status" and "result". Await them,
which makes the script async: `bounds = await mcp.prim_bounds("/World/Cube")`, then
\`result = bounds["result"]`. Its methods: capture_viewport(width, height, camera_path), set_camera(position,
target), look_at(prim_path, distance, azimuth, elevation), inspect(prim_path, angles, width, height,
zoom_offset=0.0, isolate=False), scene_tree(root, max_depth), prim_properties(prim_path),
prim_bounds(prim_path), set_transform(prim_path, position, rotation, scale), create_prim(prim_path, prim_type,
\**kwargs), delete_prim(prim_path), set_material(prim_path, color, opacity, roughness, metallic),
sim_control(action), sim_state(), get_logs(count, min_level, channel, search).

Input parameters:

- `code` (string, required): Python to run inside the Kit app; assign to result to return data

Output parameters:

- `result` (string)

### `run_script_job` (~360 tokens)

Start a long Python script inside the Kit app as a job, and answer its id at once: for work that may
outlast execute_script's 120 s, such as dropping and settling many objects under physics one after another.

The job runs on Kit's main thread between Kit's updates, so Kit renders and other tools answer while it runs.
It must await once a step: `await omni.kit.app.get_app().next_update_async()` lets one update pass, physics
stepping while the timeline plays, up to its end: get_sim_state's timeline line says where that is and whether
it loops back there. A script that never awaits at its top level is refused, since its loops
would hold Kit from its start to its end; a coroutine the script only defines or schedules doesn't count: await
it at the top level, `await main()`. One that awaits once and then loops, or calls Isaac Sim's world.step(),
holds Kit too, and no tool can read or cancel it until it ends. It has execute_script's names (omni, carb, Usd,
UsdGeom, UsdLux, UsdPhysics, Sdf, Gf, mcp); assign to result to return data. Print a line a step: the lines are
the progress get_job shows. Read it with get_job(id, wait=60), cancel it with cancel_job. One job runs at a
time, and the last one is kept until the next starts.

Input parameters:

- `code` (string): Python to run as the job; give this or file
- `file` (string): A .py file on the Kit app's machine to run as the job, read there; or give code

Output parameters:

- `result` (string)

### `get_job` (~274 tokens)

Read a job run_script_job or capture_sweep started: its state, how long it has run, its last line and the
lines it printed since the last read; once it has ended, its result, or its error and traceback, and what it
printed, each stream's last 200 lines. A sweep's result is one line: its folder, and the shots that came out
black or failed; its message says which shot it is on, and its last line, whatever its end, says what it took.

States: working; cancelling, while its cleanup runs after a cancel; completed; failed; cancelled. With wait,
the call returns when the job ends or changes state, or after that many seconds, so a long job is waited for
without a call every few seconds. With no job_id it reads the running job or the last one, so a job is found
again after the conversation was compacted. Reading changes nothing in Kit.

Input parameters:

- `job_id` (string): The id run_script_job or capture_sweep answered, such as j3; the running or last job when empty
- `wait` (number): Seconds to wait for the job to end or change state, at most 100; new lines don't end it, and other calls through this server wait meanwhile

Output parameters:

- `result` (string)

### `cancel_job` (~136 tokens)

Cancel a job run_script_job or capture_sweep started, at its next await. It reads cancelling until its own
cleanup, its finally blocks, has run, then cancelled: get_job(job_id, wait=10) reads it. A job whose code caught
the cancel, as a bare except does, runs on: call cancel_job again. A sweep puts the stage back and keeps the
shots it took, in its manifest. A job that has ended is left as it is.

Input parameters:

- `job_id` (string, required): The job to cancel, as run_script_job or capture_sweep answered it, such as j3

Output parameters:

- `result` (string)

### `manage_extensions` (~232 tokens)

List the Kit app's extensions, or turn one on or off in the running app.

"list" (the default) only reads: each extension's id, version and whether it is on, the first 50 shown with the
total. "enable" and "disable" need extension_id, last until Kit restarts, and answer whether the extension is on
afterwards: enabled=False after an enable means it didn't take. Use enable when a tool names an extension it
lacks, such as omni.syntheticdata for segmentation. Disabling one that other tools use breaks them, and
disabling ovmcp.bridge cuts this server off from Kit.

Input parameters:

- `action` (string): "list" reads; "enable" and "disable" change the running app
- `enabled_only` (boolean): For "list": only enabled extensions
- `extension_id` (string): For "enable" and "disable", required: the extension, e.g. "omni.syntheticdata"
- `search` (string): For "list": only extensions whose name or id contains this, in any case

Output parameters:

- `result` (string)

### `get_logs` (~239 tokens)

Get the latest entries of the Kit app's log, read from its log file since the bridge extension started, by
level, channel or text.

Each entry shows its channel and the first 200 characters of its message, saying how many more it has. search
matches the whole message: an entry whose match lies past the 200 gets a matched: line with the match in its
line. Kit's file holds Info and above by default; verbose lines need Kit started with both
\--/log/level=verbose and --/log/fileLogLevel=verbose. A script's own exception, print and stderr come back in
execute_script's answer and never reach Kit's log; carb.log_error and the other carb.log_* calls in a script
do.

Input parameters:

- `channel` (string): Only channels containing this, e.g. "omni.physx"
- `count` (integer): How many of the latest entries; the buffer keeps 2000
- `min_level` (string): The lowest severity to return; every level when empty
- `search` (string): Only messages containing this anywhere, in any case

Output parameters:

- `result` (string)

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/serionist-omniverse-mcp/omniverse-mcp#diagnostics

## Score history

- 2026-11-05: 68
- 2026-11-04: 82
- 2026-11-03: 67
- 2026-11-02: 67
- 2026-11-01: 82
- 2026-10-31: 67
- 2026-10-30: 68

## Common questions

### What is the Omniverse MCP server?

Omniverse MCP is listed in the public MCP registry as io.github.serionist/omniverse-mcp. Let an AI assistant work in NVIDIA Isaac Sim and other Omniverse Kit apps. This page covers its PyPI package (omniverse-mcp).

### Is the Omniverse MCP server safe to use?

Omniverse MCP scores 68 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 5 November 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Omniverse MCP server expose?

Omniverse MCP exposes 44 tools: get_scene_tree, get_prim_properties, get_prim_bounds, set_prim_transform, create_prim, and 39 more. Their descriptions and schemas cost roughly 13,512 tokens of context every time the server is loaded.

### Is the Omniverse MCP server still maintained?

Omniverse MCP is still listed as active in the MCP registry. We last reached this channel on 5 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Omniverse MCP server under?

Omniverse MCP declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- PyPI project: https://pypi.org/project/omniverse-mcp/
- Socket report: https://socket.dev/pypi/package/omniverse-mcp
- Repository: https://github.com/serionist/omniverse-mcp
- Changelog RSS feed: https://verifymcp.io/servers/serionist-omniverse-mcp/omniverse-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/serionist-omniverse-mcp/omniverse-mcp.json
- HTML version of this page: https://verifymcp.io/servers/serionist-omniverse-mcp/omniverse-mcp
