editor.inventory

Thirteen methods covering multi-source (Fab / GameWalk / USD / Manual) scan, multi-project reporting, and cross-project asset migration. Shipped in the Asset Inventory Redesign (AIR) milestone. AI agents reach through these methods to answer "where is this asset used" and "move this asset plus its dependency cascade into another project" without scripting Content Browser UI.

← API Reference

Backed by SQLite + v2 schema. Each call opens the local catalog.db at {HomeProject}/FabCatalog/catalog.db, performs its work, and closes the handle. Writes are atomic per call. Two-sided read / read-write coexistence with the Slate Asset Inventory panel is handled by the AIR-FIX-5 auto-close contract; see the Concurrency section below.

Method summary

Method Mutating Tier Purpose
scanProjectyesFreeKick off an async provider scan; returns a scan_id immediately.
getScanStatusnoFreePoll an in-flight or recently-terminal scan by scan_id.
cancelScanyesFreeCooperatively cancel an in-flight scan by scan_id.
listProjectsnoFreeEnumerate every project observed in the inventory.
listAssetsnoFreeBrowse inventory assets, optional filters on project_path + source.
findUsagesnoFreeCross-project memberships for one asset -- which projects use it.
getUsageReportnoFreeAssets ranked by project_count DESC.
syncToNeo4jyesProBulk UNWIND/MERGE the v2 inventory into Neo4j as disjoint :Inventory* labels.
migrateAssetyesFreeCopy an asset + its hard-package dependency cascade into another project.
pruneProjectKeysyesFreeRemove membership rows whose project_path no longer resolves on disk.
deleteProjectyesFreeIntentionally retire a project's memberships.
canonicalizeProjectKeysyesFreeOne-shot rekey of non-canonical membership rows; merge duplicates.
deleteAssetsyesFreeBulk delete by unreal_paths; optional project_path scopes to membership-only removal.

Twelve methods are Free. syncToNeo4j is the sole tier-gated method in the namespace (Pro) because the customer-side Neo4j is a paid-tier resource. See pricing for tier details.

Project-path canonicalization

Methods that accept a project_path canonicalize the value before touching the database. Equivalent inputs collapse to the same key so G:/Home57t, g:\Home57t\, G:/Home57t/, and G:\Home57t all map to the single row G:/Home57t. Rules:

  • Trim surrounding whitespace
  • Resolve to absolute path via FPaths::ConvertRelativePathToFull
  • Backslashes become forward slashes
  • Trailing slash stripped unless the path is a root
  • Drive letter uppercased on Windows

Writes always use the canonical form. scanProject additionally validates that a supplied project_path matches the editor's currently-running project; a mismatch is rejected with -32602 ProjectPathMismatch and no DB changes occur. Typo'd paths cannot create phantom project rows in listProjects.


editor.inventory.scanProject

Start an asynchronous multi-provider scan against the running editor's project. Returns a scan_id within about a second regardless of project size. The scan runs on a TaskGraph worker; clients poll getScanStatus for progress and terminal result. Cooperative cancel via cancelScan.

Idempotent while running: a second scanProject call for the same canonicalized project_path returns the existing scan_id with reused: true instead of starting a second worker. After termination, a subsequent call allocates a fresh scan_id.

Save first. The GameWalk provider reads the Asset Registry's on-disk index. Assets that exist only in memory (newly created or edited but never saved) are not captured by the scan. Save outstanding editor work before invoking this method. Fab and USD providers are unaffected.

Parameters:

Name Type Required Description
project_pathstringNoCanonical path of the UE project. Omit to auto-derive from FPaths::ProjectDir(). If supplied, must equal the editor's running project; mismatch returns -32602 ProjectPathMismatch.
source_overridestringNoOne of fab | usd | gamewalk | manual. Re-tags every discovered asset with this source before DB upsert. Intended for baseline imports of Fab-seeded projects.

Returns:

Name Type Description
scan_idstring (GUID)UUID for subsequent getScanStatus / cancelScan calls.
started_atstring (ISO 8601)Timestamp of scan registration.
reusedbooltrue if an existing in-flight scan was returned; false if a new worker was started.

Example Request:

{
  "jsonrpc": "2.0", "id": 1,
  "method": "editor.inventory.scanProject",
  "params": { "source_override": "fab" }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "scan_id": "8e0c1c1d-8d45-4e12-9c0e-6a3f2b5c1a42",
    "started_at": "2026-04-15T17:42:01.432Z",
    "reused": false
  }
}

editor.inventory.getScanStatus

Read-only poll by scan_id. Scan records are retained for about 60 seconds after terminal state; a 15-second-cadence reaper evicts entries past that window. Polling clients that read slower than 60 seconds miss the terminal state and must re-scan.

Parameters:

Name Type Required Description
scan_idstring (GUID)YesUUID returned by scanProject. Invalid GUID returns -32602. Unknown or evicted scan_id returns -32001 ScanNotFound.

Returns:

Name Type Description
scan_idstringEcho of the GUID.
statestringOne of pending, running, complete, cancelled, failed.
started_atstring (ISO 8601)Timestamp of scanProject invocation.
progressobject{stage, assets_discovered, assets_upserted, elapsed_ms}. Stage is one of starting, enumerating, persisting, pruning, finished, cancelled.
terminal_atstring (ISO 8601)Present on terminal states. Timestamp of transition.
resultobjectPresent when state = complete. Includes project_path, duration_ms, providers_run, assets_upserted, collections_upserted, memberships_added, memberships_pruned, cancelled, provider_errors.
errorobjectPresent when state = failed. {code, message} surfacing the engine error.

Example Request:

{
  "jsonrpc": "2.0", "id": 2,
  "method": "editor.inventory.getScanStatus",
  "params": { "scan_id": "8e0c1c1d-8d45-4e12-9c0e-6a3f2b5c1a42" }
}

Example Response (running):

{
  "jsonrpc": "2.0", "id": 2,
  "result": {
    "scan_id": "8e0c1c1d-8d45-4e12-9c0e-6a3f2b5c1a42",
    "state": "running",
    "started_at": "2026-04-15T17:42:01.432Z",
    "progress": { "stage": "persisting", "assets_discovered": 2041, "assets_upserted": 1987, "elapsed_ms": 8912 }
  }
}

editor.inventory.cancelScan

Sets the engine's cooperative cancel flag for a running scan. Idempotent: calling on an already-terminal scan returns {cancel_requested: false, state: <current>} without touching the engine. The scan transitions to state = cancelled within about two seconds. Partial rows already persisted are retained; the prune pass is skipped on cancel.

Parameters:

NameTypeRequiredDescription
scan_idstring (GUID)YesUUID returned by scanProject.

Returns:

NameTypeDescription
scan_idstringEcho of the GUID.
cancel_requestedbooltrue if the handler set the engine cancel flag; false if the scan was already terminal.
statestringCurrent registry state. Typically still running immediately after cancel; poll getScanStatus to observe the transition to cancelled.

Example Request:

{
  "jsonrpc": "2.0", "id": 3,
  "method": "editor.inventory.cancelScan",
  "params": { "scan_id": "8e0c1c1d-8d45-4e12-9c0e-6a3f2b5c1a42" }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 3,
  "result": {
    "scan_id": "8e0c1c1d-8d45-4e12-9c0e-6a3f2b5c1a42",
    "cancel_requested": true,
    "state": "running"
  }
}

editor.inventory.listProjects

Enumerate every project observed in the inventory, ordered by last_scanned DESC. Empty inventory returns {"projects": []} with no error.

No parameters.

Returns:

NameTypeDescription
projectsarrayOne object per project: {project_path, asset_count, last_scanned}.

Example Request:

{ "jsonrpc": "2.0", "id": 2, "method": "editor.inventory.listProjects" }

Example Response:

{
  "jsonrpc": "2.0", "id": 2,
  "result": {
    "projects": [
      { "project_path": "D:/UnrealProjects/Demo574c",  "asset_count": 412, "last_scanned": "2026-04-13T17:42:18Z" },
      { "project_path": "E:/UnrealProjects/Home57t",   "asset_count":  88, "last_scanned": "2026-04-12T20:14:09Z" }
    ]
  }
}

editor.inventory.listAssets

Browse inventory assets with optional filters. Omit project_path for cross-project listing. Omit source to include all sources. Unknown project_path returns {"assets": [], "total": 0} (no error).

Parameters:

NameTypeRequiredDescription
project_pathstringNoCanonicalized at read-time. Omit for cross-project listing.
sourcestringNoOne of fab | usd | gamewalk | manual.
limitnumberNoDefault 100.
offsetnumberNoDefault 0.

Returns:

NameTypeDescription
assetsarrayPer-asset rows including unreal_path, source, class_name, disk_size_bytes, and class-specific metadata (triangle_count / vertex_count / lod_count / nanite_enabled for StaticMesh, dimensions / sRGB for Texture2D).
totalnumberTotal rows matching the filter.

Example Request:

{
  "jsonrpc": "2.0", "id": 3,
  "method": "editor.inventory.listAssets",
  "params": { "project_path": "D:/UnrealProjects/Demo574c", "source": "fab", "limit": 50, "offset": 0 }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 3,
  "result": {
    "assets": [
      {
        "unreal_path":     "/Game/MyAssets/RusticChair.RusticChair",
        "source":          "fab",
        "collection_id":   "fab:asset_xyz123",
        "name":            "RusticChair",
        "class_name":      "StaticMesh",
        "disk_size_bytes": 4187423,
        "triangle_count":  18432,
        "vertex_count":    9241,
        "lod_count":       4,
        "nanite_enabled":  true,
        "first_seen_at":   "2026-04-09T12:00:00Z",
        "last_seen_at":    "2026-04-13T17:42:18Z"
      }
    ],
    "total": 1
  }
}

editor.inventory.findUsages

Return every project that uses a given asset. Unknown unreal_path returns {"projects": [], "usage_count": 0} (no error). Missing or empty unreal_path returns JSON-RPC error -32602.

Parameters:

NameTypeRequiredDescription
unreal_pathstringYesFull UObject path (e.g. /Game/Shared/CommonMaterial.CommonMaterial).

Returns:

NameTypeDescription
unreal_pathstringEcho of the queried path.
projectsarrayPer-project rows: {project_path, first_seen, last_seen}.
usage_countnumberNumber of projects the asset belongs to.

Example Request:

{
  "jsonrpc": "2.0", "id": 4,
  "method": "editor.inventory.findUsages",
  "params": { "unreal_path": "/Game/Shared/CommonMaterial.CommonMaterial" }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 4,
  "result": {
    "unreal_path": "/Game/Shared/CommonMaterial.CommonMaterial",
    "projects": [
      { "project_path": "D:/UnrealProjects/Demo574c", "first_seen": "2026-04-09T12:00:00Z", "last_seen": "2026-04-13T17:42:18Z" },
      { "project_path": "E:/UnrealProjects/Home57t",  "first_seen": "2026-04-10T14:23:00Z", "last_seen": "2026-04-12T20:14:09Z" }
    ],
    "usage_count": 2
  }
}

editor.inventory.getUsageReport

Rank every asset by how many projects it appears in. Inverse lens to findUsages: given the catalog, which assets are the most reused? Powers the Slate "Asset Usage Report" tab and the agentux_inventory_get_usage_report MCP tool.

Rows are ordered by project_count DESC with unreal_path ASC as a stable tiebreaker. next_offset is offset + items.length when the page is full, and null on the terminal page.

Parameters:

NameTypeRequiredDescription
limitnumberNoDefault 100. Range [1, 1000]. Invalid returns -32602.
offsetnumberNoDefault 0. Must be non-negative.
min_project_countnumberNoDefault 2. Pass 1 to include single-project assets.

Returns:

NameTypeDescription
itemsarrayPer-asset rows: {unreal_path, name, source, class_name, project_count}.
next_offsetnumber or nulloffset + items.length on full page; null on terminal page.

Example Request:

{
  "jsonrpc": "2.0", "id": 6,
  "method": "editor.inventory.getUsageReport",
  "params": { "limit": 20, "offset": 0, "min_project_count": 2 }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 6,
  "result": {
    "items": [
      { "unreal_path": "/Game/Shared/CommonMaterial.CommonMaterial", "name": "CommonMaterial", "source": "fab", "class_name": "Material",   "project_count": 5 },
      { "unreal_path": "/Game/Shared/GreyConcrete.GreyConcrete",     "name": "GreyConcrete",   "source": "fab", "class_name": "Material",   "project_count": 3 },
      { "unreal_path": "/Game/Shared/Plank01.Plank01",               "name": "Plank01",        "source": "fab", "class_name": "StaticMesh", "project_count": 2 }
    ],
    "next_offset": null
  }
}

editor.inventory.syncToNeo4j Pro

Pro-tier bulk sync of the v2 SQLite inventory into Neo4j. Idempotent: re-running against an already-synced graph updates scalar properties and lastSeenAt timestamps without creating duplicate nodes or edges. Disjoint from the :GraphRAG label namespace by construction; disjoint from the retired v1 :FabAsset / :FabPack graph by separate labels.

Free callers receive -32007 from the router's pre-dispatch tier check. Neo4j is required and reachable at the configured UAgentUXSettings::Neo4jHost:7474. Credentials come from FAgentUXCredentialStore (OS keyring, key graphrag-neo4j) with NEO4J_PASSWORD as an environment-variable fallback.

Parameters:

NameTypeRequiredDescription
batch_sizenumberNoDefault 500. Range [1, 5000].
dry_runboolNoDefault false. Skips Neo4j writes; returns projected counters for preview.

Returns:

NameTypeDescription
assets_syncednumberInput rows processed for the asset pass.
projects_syncednumberInput rows for the project pass.
memberships_syncednumberInput rows for the membership pass.
collections_syncednumberInput rows for the collection pass.
errorsnumberCount of batch failures across all passes.
error_messagestringEmpty on clean runs; non-empty on status = partial.
statusstringcomplete when errors == 0; otherwise partial.
dry_runboolEcho of the input.

Example Request:

{
  "jsonrpc": "2.0", "id": 7,
  "method": "editor.inventory.syncToNeo4j",
  "params": { "batch_size": 500, "dry_run": false }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 7,
  "result": {
    "assets_synced": 1673,
    "projects_synced": 3,
    "memberships_synced": 1820,
    "collections_synced": 12,
    "errors": 0,
    "error_message": "",
    "status": "complete",
    "dry_run": false
  }
}

editor.inventory.migrateAsset

Copy an asset and its hard-package dependency cascade into another project. Mode is copy only. Cascade defaults to true and walks /Game/-scoped hard-package references via the Asset Registry. Conflict policy skips targets that already exist (no overwrite).

source_project must equal the project the editor is currently running. The handler resolves source disk paths via FPackageName::DoesPackageExist against the live mount table; cross-project source resolution is out of scope.

Parameters:

NameTypeRequiredDescription
source_unreal_pathstringYesFull UObject path to migrate.
source_projectstringYesMust equal the editor's running project.
target_projectstringYesDestination project root.
cascadeboolNoDefault true. Walks hard-package dependencies under /Game/.

Returns:

NameTypeDescription
migratedarrayPackages successfully copied.
skippedarrayRows carry a parenthesized reason (e.g. "/Game/Foo (source not found on disk)").
conflictsarrayTarget packages that already exist; skip-on-conflict leaves them untouched.
dependency_cascadearrayFull list of packages the walker considered.

Example Request:

{
  "jsonrpc": "2.0", "id": 5,
  "method": "editor.inventory.migrateAsset",
  "params": {
    "source_unreal_path": "/Game/Shared/CommonMaterial.CommonMaterial",
    "source_project":     "D:/UnrealProjects/Demo574c",
    "target_project":     "D:/UnrealProjects/NewProject",
    "cascade":            true
  }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 5,
  "result": {
    "source_unreal_path": "/Game/Shared/CommonMaterial.CommonMaterial",
    "target_project":     "D:/UnrealProjects/NewProject",
    "cascade":            true,
    "migrated":           ["/Game/Shared/CommonMaterial", "/Game/Shared/Textures/Wood_BC", "/Game/Shared/Textures/Wood_N"],
    "skipped":            [],
    "conflicts":          [],
    "dependency_cascade": ["/Game/Shared/CommonMaterial", "/Game/Shared/Textures/Wood_BC", "/Game/Shared/Textures/Wood_N"]
  }
}

editor.inventory.pruneProjectKeys

Maintenance method. Remove asset_project_membership rows whose project_path does not resolve to a directory that currently exists on disk. Typo'd inputs (Home57t, G:/Home57t on a machine with no G: drive) create phantom project rows; this method cleans them out. Asset rows in asset_v2 are preserved.

Parameters:

NameTypeRequiredDescription
dry_runboolNoDefault false. Report what would be pruned without deleting.

Returns:

NameTypeDescription
dry_runboolEcho of the input.
pruned_keysnumberCount of project keys removed (or that would be removed on dry-run).
removed_membershipsnumberMembership rows removed; 0 on dry-run.
prunedarrayPer-key audit rows.
keptarrayKeys not removed (directory exists on disk).

Example Request:

{
  "jsonrpc": "2.0", "id": 5,
  "method": "editor.inventory.pruneProjectKeys",
  "params": { "dry_run": true }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 5,
  "result": {
    "dry_run": true,
    "pruned_keys": 2,
    "removed_memberships": 0,
    "pruned": [
      { "project_path": "Home57t", "canonical_project_path": "G:/some/cwd/Home57t", "asset_membership_count": 12, "would_delete": true, "deleted": 0 }
    ],
    "kept": [
      { "project_path": "E:/UnrealProjects/Home56t", "canonical_project_path": "E:/UnrealProjects/Home56t", "asset_membership_count": 88, "would_delete": false }
    ]
  }
}

editor.inventory.deleteProject

Intentional-retirement counterpart to pruneProjectKeys. While pruneProjectKeys only sweeps rows whose project_path no longer resolves to a real directory, deleteProject removes a project's memberships regardless of whether the directory still exists. Use it when a project is retired and should drop out of cross-project reporting. Asset rows in asset_v2 are preserved. Deletes are terminal -- no undo.

Parameters:

NameTypeRequiredDescription
project_pathstringYesCanonicalized at handler entry; equivalent inputs collapse to one key.

Returns:

NameTypeDescription
project_pathstringEcho of the input.
canonical_project_pathstringCanonical form after normalization.
removed_membershipsnumberMembership rows deleted.

Example Request:

{
  "jsonrpc": "2.0", "id": 7,
  "method": "editor.inventory.deleteProject",
  "params": { "project_path": "E:/UnrealProjects/RetiredProject" }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 7,
  "result": {
    "project_path":           "E:/UnrealProjects/RetiredProject",
    "canonical_project_path": "E:/UnrealProjects/RetiredProject",
    "removed_memberships":    412
  }
}

editor.inventory.canonicalizeProjectKeys

One-shot maintenance. Rewrite every membership row's project_path to canonical form and merge duplicates produced by the rekeying. Use when the database contains pre-AIR-FIX-2 rows whose project path uses backslashes, lowercase drive letters, or trailing slashes -- variants that pruneProjectKeys cannot collapse because both forms resolve to a real directory.

Collisions merge via MIN(first_seen_at) and MAX(last_seen_at). The whole pass runs inside BEGIN IMMEDIATE / COMMIT; a partial failure rolls back rather than leaving the table half-rekeyed.

Parameters:

NameTypeRequiredDescription
dry_runboolNoDefault false. Report renames without mutating.

Returns:

NameTypeDescription
dry_runboolEcho of the input.
renamed_rowsnumberRows rekeyed without collision.
merged_rowsnumberRows folded into a pre-existing canonical row via MIN/MAX coalesce.
renamesarrayPer-key {from, to} pairs.

Example Request:

{
  "jsonrpc": "2.0", "id": 8,
  "method": "editor.inventory.canonicalizeProjectKeys"
}

Example Response:

{
  "jsonrpc": "2.0", "id": 8,
  "result": {
    "dry_run":      false,
    "renamed_rows": 7,
    "merged_rows":  2,
    "renames": [
      { "from": "e:\\UnrealProjects\\Home57t",  "to": "E:/UnrealProjects/Home57t" },
      { "from": "D:/UnrealProjects/Demo574c/", "to": "D:/UnrealProjects/Demo574c" }
    ]
  }
}

editor.inventory.deleteAssets

Bulk delete by list of unreal_paths. Two modes:

  • Project-scoped (project_path supplied): removes only the (unreal_path, project_path) membership rows. asset_v2 rows are preserved.
  • Unscoped (project_path omitted): deletes the asset_v2 rows themselves; FK ON DELETE CASCADE removes all memberships for those paths across every project.

Cap: 1000 paths per call (-32602 if exceeded). Batch on the caller side for larger sets. Deletes are terminal -- no undo. The destructive surface is JSON-RPC + CLI only; there is no Slate UI. Use the inventory_cleanup.py search-first / purge-second flow for safety.

Parameters:

NameTypeRequiredDescription
unreal_pathsarray<string>Yes1 to 1000 full UObject paths.
project_pathstringNoCanonicalized. Omit for unscoped delete.

Returns:

NameTypeDescription
removed_assets_countnumberNon-zero only in unscoped mode.
removed_memberships_countnumberMembership rows removed.
unreal_pathsarrayEcho of the input.
project_pathstringCanonical form if supplied; empty string if unscoped.

Example Request:

{
  "jsonrpc": "2.0", "id": 10,
  "method": "editor.inventory.deleteAssets",
  "params": {
    "unreal_paths": ["/Game/MyAssets/SM_Test1.SM_Test1", "/Game/MyAssets/SM_Test2.SM_Test2"],
    "project_path": "E:/UnrealProjects/Home57t"
  }
}

Example Response:

{
  "jsonrpc": "2.0", "id": 10,
  "result": {
    "removed_assets_count":      0,
    "removed_memberships_count": 2,
    "unreal_paths":              ["/Game/MyAssets/SM_Test1.SM_Test1", "/Game/MyAssets/SM_Test2.SM_Test2"],
    "project_path":              "E:/UnrealProjects/Home57t"
  }
}

Concurrency note

UE's embedded SQLite VFS on Windows cannot host two read-write opens in-process, and also fails read-write alongside a pre-existing read-only handle. Two sides of the same defect; each is handled differently.

Scan-then-open-panel (auto-fixed). Every editor.inventory.* method opens catalog.db, does its work, and releases the handle before returning. The Slate Asset Inventory panel's read-only open during tab activation succeeds cleanly even immediately after an RPC. No customer workaround required.

Destructive RPC while the panel is already open (rule still applies). If the Asset Inventory panel is open (holding its read-only handle), running a destructive RPC -- scanProject, migrateAsset, pruneProjectKeys, deleteProject, canonicalizeProjectKeys, deleteAssets -- from CLI or MCP fails with -32003 Failed to open catalog database. Close the Asset Inventory tab (Window → AgentUX → Asset Inventory → X) before running destructive CLIs like inventory_cleanup.py, then reopen after the call returns.

MCP tool mirrors

The MCP bridge ships nine @mcp.tool() wrappers corresponding to these methods. agentux_inventory_scan_project hides the scan_id poll loop internally, so Claude callers see one-call-one-result ergonomics. The four destructive maintenance methods (pruneProjectKeys, deleteProject, canonicalizeProjectKeys, deleteAssets) are intentionally JSON-RPC + CLI only; MCP exposure is reserved for the inventory_cleanup.py search-first flow and direct agentux_raw use.

Related