@uemcp/server 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +149 -3
  2. package/dist/index.mjs +3842 -738
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,6 +44,56 @@ The MCP tool surface currently exposes:
44
44
  - `redo`
45
45
  - `open_level`
46
46
  - `get_open_level_status`
47
+ - `get_source_control_status`
48
+ - `refresh_source_control_status`
49
+ - `checkout_assets`
50
+ - `get_source_control_job_status`
51
+ - `list_asset_factories`
52
+ - `create_asset`
53
+ - `duplicate_asset`
54
+ - `rename_asset`
55
+ - `move_asset`
56
+ - `delete_asset`
57
+ - `save_asset`
58
+ - `create_folder`
59
+ - `get_asset_metadata`
60
+ - `list_graph_nodes`
61
+ - `add_graph_node`
62
+ - `remove_graph_node`
63
+ - `connect_pins`
64
+ - `disconnect_pins`
65
+ - `set_pin_default`
66
+ - `list_available_nodes`
67
+ - `set_viewport_camera`
68
+ - `focus_actors`
69
+ - `set_view_mode`
70
+ - `toggle_show_flags`
71
+ - `high_res_screenshot`
72
+ - `duplicate_actor`
73
+ - `attach_actor`
74
+ - `add_actor_component`
75
+ - `set_actor_tags`
76
+ - `move_to_folder`
77
+ - `place_asset`
78
+ - `replace_actors`
79
+ - `build_lighting`
80
+ - `build_navigation`
81
+ - `build_hlod`
82
+ - `build_reflection_captures`
83
+ - `build_geometry`
84
+ - `build_texture_streaming`
85
+ - `build_virtual_textures`
86
+ - `precompute_visibility`
87
+ - `get_build_job_status`
88
+ - `create_gameplay_tag`
89
+ - `list_gameplay_tags`
90
+ - `create_landscape`
91
+ - `sculpt_landscape`
92
+ - `paint_landscape_layer`
93
+ - `run_geometry_script`
94
+ - `list_data_layers`
95
+ - `set_data_layer_state`
96
+ - `load_wp_region`
47
97
 
48
98
  ## Project Identity
49
99
 
@@ -101,9 +151,9 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
101
151
  ### `wait_for_job`
102
152
 
103
153
  - Blocks server-side until an async job reaches a terminal state, then returns the payload the matching `get_*_status` tool would have returned under `status`
104
- - Replaces a client-side poll loop for `kind` `compile` (`trigger_live_compile` / `trigger_hot_reload`), `create_class`, `test_run`, and `pie`; `kind` is required because job ids are bare GUIDs
154
+ - Replaces a client-side poll loop for `kind` `compile` (`trigger_live_compile` / `trigger_hot_reload`), `create_class`, `test_run`, `pie`, and `source_control` (`checkout_assets` / `refresh_source_control_status`); `kind` is required because job ids are bare GUIDs
105
155
  - `outcome` is `completed`, `timed_out` (last observed status still returned), or `unknown_job` (unknown/expired/cancelled id, returned on the first poll)
106
- - Per-kind default budgets (compile 120s, create_class 60s, pie 30s, test_run 300s), clamped to 600s; polls back off from 250ms (1s for test runs) and a transient bridge read timeout or dropped socket is retried rather than failing the wait
156
+ - Per-kind default budgets (compile 120s, create_class 60s, pie 30s, source_control 60s, test_run 300s), clamped to 600s; polls back off from 250ms (1s for test runs) and a transient bridge read timeout or dropped socket is retried rather than failing the wait
107
157
  - For `test_run`, `status.results` is the first page — page the rest with `get_test_run_status` and `nextCursor`
108
158
 
109
159
  ### `build_target` / `generate_project_files` / `clean_build`
@@ -182,6 +232,18 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
182
232
  - Actor paths do not survive a load: re-run `list_actors` against the new world rather than reusing paths from the old one
183
233
  - One load at a time per editor; a second request while one is running is refused with `open_level_in_progress` rather than queued
184
234
 
235
+ ### `get_source_control_status` / `refresh_source_control_status` / `checkout_assets` / `get_source_control_job_status`
236
+
237
+ - The prerequisite for every asset write on a team project. Under Perforce or Git-LFS file locking a `.uasset` stays read-only until it is checked out, so a write that skips this fails later at save time — several tool calls away from its cause
238
+ - `get_source_control_status` answers **inline**: it reads the provider's own state cache, which is a memory lookup rather than a server round trip. It reports the `provider` name, `enabled` (is source control configured at all), `available` (is it currently connected), and one entry per requested path with a `state` of `checked_out_by_me`, `checked_out_by_other` (plus `checkedOutBy` naming the holder), `marked_for_add`, `not_checked_out`, `not_in_depot`, or `unknown`
239
+ - `enabled` is what separates "no source control configured" from "configured, and this file is simply not checked out". Those are never collapsed into one answer
240
+ - A file the editor has never asked the server about reports `unknown`, **not** a guess. `refresh_source_control_status` is how it becomes a real answer. A path that resolves to no package on disk also reports `unknown`, but carries an `error` saying so — an unresolvable path never passes as a depot answer
241
+ - `refresh_source_control_status` and `checkout_assets` talk to the source control server over the network, which runs far past the plugin's 5 s game-thread budget, so neither runs inline: each returns `state: 'accepted'` with a `sourceControlJobId`. Poll it with `get_source_control_job_status`, or wait with `wait_for_job` using `kind: 'source_control'`
242
+ - The poll reports `successCount` / `failureCount` and one result per file with its post-operation `state`, so a checkout is confirmed without a second query. One asset the provider refuses does not abort the rest — it reports `ok: false` with its own `error`
243
+ - Checkout only. Submitting is never done for you, and a checked-out asset stays checked out until a human submits or reverts it
244
+ - A request against an editor with no provider configured is refused with `source_control_disabled`, and one against a configured-but-offline provider with `source_control_unavailable`. Neither degrades into a filesystem check
245
+ - In a non-editor plugin build all four report `state: 'unsupported'` and schedule nothing
246
+
185
247
  ### `read_data_table` / `write_data_table_row`
186
248
 
187
249
  - A DataTable is where a project keeps its tuning data, and it is a binary `.uasset` — `find_assets` can locate one but nothing can read it off disk, so these are the only way in. `read_data_table` loads the asset and reports the row struct it is typed on, an ordered column list (`{ name, type }`, using the same type rendering `get_class_reflection` emits), and each row as a column-name-to-value map
@@ -192,7 +254,7 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
192
254
  - Numeric and boolean values are range-checked **before** `ImportText` rather than after: the engine's importer takes `not-a-number` as `0` and reports success, and silent coercion is exactly the failure a tuning-data write cannot afford
193
255
  - The write runs inside a single `FScopedTransaction` (Ctrl+Z reverses it) and marks the package dirty **without saving** — compose with `save_dirty_packages`. `dryRun: true` validates everything and reports `state: 'validated'`, opening no transaction
194
256
  - Deleting a row that does not exist is **not** a failure: it reports `rowExisted: false` with an explanatory message, because the table is already in the requested state
195
- - A `.uasset` sits read-only on disk until it is checked out under Perforce or Git-LFS file locking. That case is detected before anything is mutated and reported as `source_control_checkout_required` rather than surfacing later as an unexplained save error
257
+ - A `.uasset` sits read-only on disk until it is checked out under Perforce or Git-LFS file locking. That case is detected before anything is mutated rather than surfacing later as an unexplained save error. Where a provider is configured the answer comes from the provider itself — `source_control_checkout_required` for a file that is simply not checked out, `source_control_checked_out_by_other` naming the holder when someone else has it — and only where none is does it fall back to the on-disk read-only check. Fix either with `checkout_assets`
196
258
  - In a non-editor plugin build `read_data_table` fails with `unsupported` and `write_data_table_row` returns `state: 'unsupported'`
197
259
 
198
260
  ### `export_data_table` / `import_data_table`
@@ -212,6 +274,90 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
212
274
  - `rename_data_table_row` keeps the row's position and values, which an upsert-under-the-new-name plus a delete-of-the-old does not: that pair costs two transactions and moves the row to the end. It refuses a missing row (`row_not_found`) and a name already taken (`row_already_exists`), since renaming onto an existing row would discard that row's values. **Row names are how other assets address rows**, so a rename breaks any `FDataTableRowHandle` pointing at the old name — nothing fixes those up
213
275
  - Rename and delete both need referencer data, which is only trustworthy once the Asset Registry's initial gather has finished — mid-scan `GetReferencers` returns a short list that reads as "nothing depends on this". Both drive the scan to completion first (starting it when a headless editor never did), and report `asset_registry_scanning` only if it is still incomplete afterwards. On a large project that wait can be long enough to hit the bridge read timeout
214
276
 
277
+ ### `list_asset_factories` / `create_asset`
278
+
279
+ - Everything in the Content Browser's Add menu is backed by a `UFactory`, so one discovery tool plus one creation tool cover the whole menu rather than a tool per asset type
280
+ - `list_asset_factories` enumerates every factory that can create new assets (`CanCreateNew()` and `ShouldShowInNewMenu()`), reporting the factory class, the asset class it produces, its menu category, and the **required options** — the object or class properties a factory would otherwise pop a modal picker for. Filter with `nameFilter`; results page by `nextCursor`
281
+ - `create_asset` goes through `IAssetTools::CreateAsset`, never the Content Browser's `CreateAssetWithDialog`, and never calls `UFactory::ConfigureProperties`. A modal window opened from a bridge request would block the editor with nobody to answer it
282
+ - A factory that needs configuring is configured from `options` instead: each key is a reflected property on the factory (`ParentClass` for a Blueprint, for example) set on a **fresh factory instance**, never on the CDO. A factory whose required options were not supplied is refused with their names rather than producing something the caller did not ask for
283
+ - The new asset is dirty and **unsaved** — compose with `save_asset`
284
+
285
+ ### `duplicate_asset` / `rename_asset` / `move_asset` / `delete_asset` / `save_asset` / `create_folder` / `get_asset_metadata`
286
+
287
+ - File-level content management. `find_assets` locates assets and `get_asset_dependencies` describes them; these are what reorganize them
288
+ - Rename and move both go through `IAssetTools::RenameAssets` rather than moving packages by hand: that is what fixes up referencers and leaves the redirector the engine expects, so existing hard references keep resolving
289
+ - Writes are refused outside `/Game`. Engine and plugin content is not the project's to reorganize, and a rename there silently breaks every project sharing the engine install
290
+ - `delete_asset` queries referencers first and refuses with their names rather than leaving them pointing at nothing; `force` deletes anyway. There is no transaction behind it — asset deletion is not undoable
291
+ - `save_asset` saves without prompting (the prompting variants can open a modal). A package that was already clean reports `saved: false` as a **success**, not a no-op error
292
+ - `create_folder` reports `state: 'exists'` rather than failing when the folder is already there, since that is the state the caller wanted. A content folder is an Asset Registry entry until something is saved into it, so an empty one does not survive an editor restart — the response says so
293
+ - `get_asset_metadata` answers from the Asset Registry's `FAssetData` and **never loads the package**: class, package name and path, on-disk size, and the registry tag map — which carries a Blueprint's parent class, a texture's dimensions, a DataTable's row struct. `loaded` reports whether it happens to be in memory already; `false` is normal
294
+
295
+ ### `list_graph_nodes` / `add_graph_node` / `remove_graph_node` / `connect_pins` / `disconnect_pins` / `set_pin_default` / `list_available_nodes`
296
+
297
+ - Blueprint graph authoring. `get_blueprint_graph` reads a graph; these write to one, and they work on any `UEdGraph` — an event graph or function, an Animation Blueprint AnimGraph, a Material graph
298
+ - **Nodes are addressed by GUID, never by index.** A graph's node array reorders on every edit and recompile, so an index read a moment ago can name a different node by the time it is written back. `nodeId` from `list_graph_nodes` is what every other tool here takes. `list_graph_nodes` is the only read here, so it is the only one that takes `loadIfNeeded`; the write tools need the asset in memory regardless
299
+ - `add_graph_node` builds the node through `FGraphNodeCreator`, so it allocates its own pins and gets the schema's default wiring. `kind` picks how it is addressed: `class` by node class, `function` by member name and parent class, `variable_get` / `variable_set` by variable. It returns the placed node **with its pins**, so the caller can wire it without a follow-up read
300
+ - `connect_pins` asks the schema's `CanCreateConnection` before connecting, so an illegal wire comes back with **the schema's own refusal text** rather than a generic failure after the fact
301
+ - `set_pin_default` refuses an output pin and a pin that is already connected — a connected pin takes its value from the wire, so a literal there would be stored and then ignored. The stored value is validated after the write and the previous literal restored if the schema rejects it
302
+ - `list_available_nodes` answers from `FBlueprintActionDatabase` filtered by this graph's context, so what it lists is what the graph would actually accept rather than every node class the editor knows about
303
+ - Every edit marks the Blueprint structurally modified and leaves it **dirty and uncompiled** — compose with `compile_blueprint` and `save_asset`
304
+
305
+ ### `set_viewport_camera` / `focus_actors` / `set_view_mode` / `toggle_show_flags` / `high_res_screenshot`
306
+
307
+ - Level viewport control, all targeting the active level editing viewport — the one a human would be looking at
308
+ - `set_viewport_camera` takes every field optionally, so a caller can change the rotation without restating a location it does not want moved. `orthoWidth` is only meaningful for an orthographic projection, and a perspective viewport reports it **absent** rather than as a zero that reads like a width
309
+ - `focus_actors` frames the given actors in the active viewport only, so focusing does not yank every open viewport to the same place
310
+ - `toggle_show_flags` reads each flag back after setting it rather than echoing the request: a flag the renderer forces cannot be turned off, and the caller should see that. A name that does not resolve comes back with **near matches** — the engine's flag names are CamelCase and easy to guess wrong
311
+ - `high_res_screenshot` **queues** the capture for the next rendered frame, so the reported `filePath` is where the file will be, not proof that it is there. `includeUi` cannot be combined with a multiplier above 1: the high-resolution path renders the scene alone. `fileName` is a name and not a path — the capture always lands under the project’s `Saved/Screenshots`, and a separator or `..` in the name is refused rather than followed
312
+
313
+ ### `duplicate_actor` / `attach_actor` / `add_actor_component` / `set_actor_tags` / `move_to_folder` / `place_asset` / `replace_actors`
314
+
315
+ - The level authoring surface beyond spawn and destroy. Each opens a single `FScopedTransaction` around the whole batch, so Ctrl+Z reverses the call as one Undo History step, and a per-item failure is reported in the result without aborting the rest
316
+ - `attach_actor` with an empty `parentActorPath` **detaches**; the location, rotation and scale rules map onto `EAttachmentRule` so a caller gets the same choice the editor offers rather than one hard-coded behaviour
317
+ - `place_asset` goes through the actor factory the engine picks for the asset, which is what turns a Static Mesh into an `AStaticMeshActor` and a Blueprint into an instance of its generated class
318
+ - `replace_actors` preserves each actor's transform, label and attachment, which spawn-then-destroy does not
319
+ - `dryRun: true` validates every entry and reports `state: 'validated'` without opening a transaction
320
+ - Every one of these dirties the level **without saving** — compose with `save_dirty_packages`
321
+
322
+ ### `build_lighting` / `build_navigation` / `build_hlod` / `build_reflection_captures` / `build_geometry` / `build_texture_streaming` / `build_virtual_textures` / `precompute_visibility` / `get_build_job_status`
323
+
324
+ - The editor's Build menu. A build runs for minutes to hours and blocks the game thread while it does, so none of these run inline: each returns `state: 'accepted'` with a `buildJobId`. Poll it with `get_build_job_status`, or wait with `wait_for_job` using `kind: 'build'`
325
+ - One build at a time. A second request while one is running reports `state: 'busy'` with the running job's id rather than racing two builders over the same level's build data
326
+ - **Failures are reported faithfully.** Lightmass and Swarm log the reason nothing was built and still return success, so the job captures the editor log for the duration and reports those errors verbatim — a failed build ends as `failed` with its own error text, never as succeeded-with-warnings
327
+ - A build interrupted by the bridge shutting down reports `cancelled`, not a `running` state that never resolves
328
+ - `build_hlod` takes an `hlodAction` because clusters and proxy meshes are separate passes in the editor and a caller usually wants one of them
329
+
330
+ ### `create_gameplay_tag` / `list_gameplay_tags`
331
+
332
+ - Gameplay tags live in `.ini` files rather than in an asset, so declaring one is a config write
333
+ - `create_gameplay_tag` goes through the GameplayTags editor module rather than writing the ini directly, which is what registers the tag with the running manager as well as persisting it — a hand-written ini would need an editor restart to take effect. It reports **which config file** the declaration landed in, so it can be put under source control
334
+ - A tag that already exists reports `state: 'exists'` and writes nothing
335
+ - `list_gameplay_tags` reports **declared** tags rather than every parent a declaration implies, so `Ability.Fire.Bolt` does not also list `Ability` and `Ability.Fire`. Filter with `prefix`; results page by `nextCursor`
336
+
337
+ ### `create_landscape` / `sculpt_landscape` / `paint_landscape_layer`
338
+
339
+ - **Height and weight data never travels over the wire.** A 505×505 landscape is a quarter of a million samples, which no 64 KiB bridge frame can carry, so sculpting is either described procedurally (`flatten`, `ramp`, `noise`) or pointed at a heightmap file the editor host can already read
340
+ - `create_landscape` reports the **vertex resolution**, which is the coordinate space the sculpt and paint regions are expressed in
341
+ - Heights are world-space centimetres, so a caller can ask for a 20 m ramp without knowing about the landscape's raw `uint16` encoding or its 32768 midpoint
342
+ - `heightmap_file` mode reads raw 16-bit little-endian data — what the landscape import dialog reads and what every terrain tool exports. A file whose size does not match the region is refused with both sizes rather than producing garbage terrain
343
+ - `noise` takes a seed, so the same request twice produces the same terrain and a caller can raise the amplitude without the shape moving underneath them
344
+ - `paint_landscape_layer` names the layers the landscape actually has when the requested one is missing, since a layer is only paintable once its material assigns it
345
+
346
+ ### `run_geometry_script`
347
+
348
+ - Procedural mesh authoring: a box minus a cylinder in **one call**. Each operation carries its own primitive rather than referring to a mesh stack, so a boolean does not need three calls to set up
349
+ - Built on the engine's own GeometryCore and DynamicMesh modules rather than on the Geometry Script plugin's Blueprint library — the same operations that plugin wraps — so it works whether or not that plugin is enabled
350
+ - `output: 'static_mesh'` creates a `UStaticMesh` asset (dirty and unsaved, like every other asset write here); `output: 'dynamic_mesh_actor'` spawns an `ADynamicMeshActor` that lives in the level and in no asset. Supply `label` with a static mesh output to also place it
351
+ - A boolean that produces a non-manifold result is reported as a **geometric failure with the operation index**, not swallowed: overlapping or open input geometry is the usual cause
352
+
353
+ ### `list_data_layers` / `set_data_layer_state` / `load_wp_region`
354
+
355
+ - World Partition. On a partitioned map most actors are unloaded most of the time, which is why `list_actors` there answers about what is **in memory** rather than about what the level contains
356
+ - `list_data_layers` reports `isWorldPartition`, so an empty list on an ordinary map reads as "there are none" rather than "none are loaded"
357
+ - `set_data_layer_state` changes runtime state, editor loading and editor visibility independently, since they are three separate things in the editor. An editor-only data layer asked for a runtime state is refused rather than silently ignored
358
+ - `load_wp_region` pins a bounding box into the editor through a loader adapter — the same mechanism the World Partition window uses when a human drags a loading region — and reports how many actors that brought into memory. The load is synchronous, so a box covering a large part of a big partitioned map blocks the editor while it runs: pin the area being worked on, not the whole world
359
+ - Pins are held by the bridge and released on `unpin_all`, and every pin is released when the bridge shuts down, so a session cannot silently accumulate loaded actors nobody can name any more
360
+
215
361
  ## MCP Resources
216
362
 
217
363
  Resources carry context a client can attach without spending a tool call and a model turn. Every one of them is served from in-memory state or the local filesystem: **a resource read never opens a bridge socket and never waits on the editor**, so an unreachable or absent editor produces a structured payload rather than a timeout.