Feedback after using ModelContextProtocol toolsets agentically

Just spent a long agentic session with the MCP toolsets driving an editor against a UE 5.8 project (custom plugin, GAS, EnhancedInput, content-heavy content tree). Most of it works — but a handful of papercuts repeatedly broke flow. Sharing in case any are easy fixes.

MCP session is tied to editor process lifetime — biggest pain by far.

Whenever the editor is restarted (which happens constantly in any C++ workflow — module reload, BP class-layout change, recovery from crash) the MCP HTTP server dies with it, the client session becomes stale (Unknown session id … client should reinitialize), and the user has to manually /mcp reconnect from the IDE side.
There’s no auto-reconnect, no “wait for next editor”, nothing. For an agent driving the editor this is brutal — every “make a C++ change + rebuild” cycle requires human-in-the-loop just to wire the MCP back up.

Suggestions, any of which would help:

  • Run the MCP server out-of-process so it survives editor restarts.

  • Have the running editor advertise on a stable socket the client can reconnect to without a manual /mcp step.

  • Or at least emit a sentinel the agent can poll for to know when the editor is back.

No landscape toolset.

PCG, Niagara, Skeletal/Static mesh, Materials, Sequencer, Animation, UMG… but nothing for landscapes (sculpt, paint, layers, foliage placement on a landscape). Forces falling back to BP/Python for terrain-heavy projects.

Bulk asset move/reorganize balloons VRAM.

AssetTools.move / duplicate / delete and equivalent reorganize workflows are individually fine, but driving them in bulk (renaming a hundred assets, moving a folder of textures + materials + meshes into a new layout) causes the editor’s VRAM to climb past anything reasonable — fast. The implicit reference fixup + thumbnail regeneration + content browser tree refreshes each fire their own load passes, and nothing gets evicted until
you bounce the editor. On a content-heavy project this is a hard ceiling on what an agent can do in one pass without OOMing.

Helpful directions:

  • A batch move_many / reorganize tool that groups fixups, debounces thumbnail regen, and forces a gc.MaxObjectsNotConsideredByGC flush at the end.

  • An option to skip thumbnail regen entirely during bulk ops (re-fire lazily).

  • Even a “this batch will touch N assets, suggest splitting” warning so the agent can self-throttle.

  • Doc note: current safe batch size on a typical project, and how to coordinate ForceGC / DDC clears mid-stream.

Other rough edges that ate time:

  • ObjectTools.set_properties on TArrays can’t simultaneously change size and contents. Returns ArrayRemove/ArrayAdd: elements changed alongside the size change; insertion points are ambiguous. To add one row to an existing array you have to round-trip the exact stored value (including positional subobject refPaths like IMC_X:InputModifierSwizzleAxis_0) back through get_properties. A delta-style API (add_array_element / remove_array_element) would be much friendlier.

  • CaptureViewport, find_actors, and similar declare optional struct params as required. E.g. Function “CaptureViewport”, input param “captureTransform” needs a default value. Even if you don’t want to override, you have to send a zero-filled struct.

  • No call_function / UFUNCTION invocation. You can read/write properties but can’t call native methods, so you can’t e.g. force RebuildControlMappings, run an editor utility, or call a BP function via reflection. Property edits to a BP CDO don’t reflect at runtime until you also remember to compile_blueprint + save_assets — set_properties on a BP could fire those automatically (or at least make the dependency explicit).

  • No simulated-input tool. Diagnosing why a key bind didn’t dispatch required adding C++ logs and rebuilding. A SimulateKey(W, IE_Pressed) would collapse hours-long debug into a one-call test.

  • No “inspect the live EnhancedPlayerInput binding list” equivalent of the GAS inspector. The GAS tooling for granted abilities / active effects / attribute values is great — Input deserves the same.

  • Huge JSON payloads. list_properties on a Character or actor returns 75k+ tokens; CaptureViewport returns the full PNG inline at >1M tokens. Token-budget annihilator. A filter param (only_properties=[…]) and/or returning an image as an out-of-band reference instead of base64-in-result would help a lot.

  • save_assets silently no-ops when PIE is running (some assets become read-only). An error rather than a silent partial-save would help.

  • Asset path quoting is inconsistent — sometimes /Game/foo.foo works, sometimes /Game/foo.foo_C is required (BP classes vs assets). Worth documenting per tool.

Tools that did shine and are worth highlighting in the docs as the model shape:

  • GASToolsets.AbilitySystemInspectorToolset — exactly the right shape for runtime inspection.

  • BlueprintTools.compile_blueprint + AssetTools.save_assets — once you know to call them.

  • LogsToolset.GetLogEntries with a regex pattern — saved my debugging.

  • EditorAppToolset.StartPIE — clean, predictable.

Thanks for the toolset, just want it to be slightly less of a knife-fight when agents drive it.

P.S. Big workflow win would be a Fab toolset — fab.search(query), fab.get_asset_info(id), fab.install_into_project(id) — so an agent can pull in marketplace content without the human having to drop into the Fab UI. Right now any “let’s add X pack” task hard-stops at “ok user, open Fab and install Y.” Pairs nicely with the bulk-reorganize point above since most freshly installed Fab packs immediately want to be moved into a project’s content tree

[MCP / EditorToolset] StringTableTools.set_entry & remove_entry don’t mark the package dirty — edits are silently dropped on save (UE 5.8)

Plugin: ModelContextProtocol / EditorToolset (Experimental), UE 5.8
File: Engine/Plugins/Experimental/Toolsets/EditorToolset/Content/Python/editor_toolset/toolsets/string_table.py

Repro

  1. Load any StringTable asset.
  2. Call StringTableTools.set_entry(table, "my.key", "value").
  3. list_keys / get_entry confirm the key is present in memory.
  4. Call AssetTools.save_assets(["/Game/.../ST"]) → returns True.
  5. Inspect the .uasset on disk (mtime / hex / git) → the new key is not there. At runtime the text renders <MISSING STRING TABLE ENTRY>.

Root cause

set_entry / remove_entry funnel through _import_entries(), which calls unreal.StringTableLibrary.import_table_from_csv_string(). That mutates the in-memory FStringTable but never calls Modify() / marks the owning UPackage dirty. So EditorAssetLibrary.save_asset (which honors only_if_is_dirty) treats the package as clean and no-ops — while still returning success. AssetTools.is_dirty correctly reports False, confirming the missing dirty flag.

Fix (verified)

Add string_table.modify() in _import_entries() before the import call, matching the pattern sibling toolsets already use (e.g. actor.py, static_mesh.py call obj.modify() before mutating):

table_id = unreal.StringTableLibrary.get_table_id(string_table)
string_table.modify()  # mark the package dirty so the edit actually persists
unreal.StringTableLibrary.import_table_from_csv_string(table_id, output.getvalue())

After this, is_dirty flips to True and save_assets persists correctly. I’ve applied this fix locally and verified both set_entry and remove_entry now write through to disk (temp key appears after save, disappears after remove+save).

Related / side notes

  • The silent success of save_assets (returning True while persisting nothing) is the same class of issue already raised in this thread. An explicit error/warning on a no-op save would have surfaced this immediately.
  • StringTableTools.import_file cannot overwrite an existing asset, and on the create-conflict path it can leave the original asset deleted on disk (HEAD intact, working tree gone) — worth hardening too.
1 Like