# Auto-generated - do not edit by hand.
# Source: scripts/generate-default-config.ts

ue-mcp:
  version: 1

tasks:

  # ── project ──
  project.get_status:
    class_path: project.get_status
    group: project
    description: "Check server mode and editor connection. pluginBuildStale reports the compiled bridge being older than its source, read from disk. deployedPlugin is what the binary that answered says about itself: when it was built, and how many methods this server advertises that it does not register, which is what 'Unknown method' on a real action means. Params: none (#785, #1002, #1021)"
  project.set_project:
    class_path: project.set_project
    group: project
    description: "Switch project: moves both path resolution and the editor connection to the new .uproject. Params: projectPath"
  project.list_editors:
    class_path: project.list_editors
    group: project
    description: "List every editor session this server drives: name, project, bridge port, whether the socket is connected, whether anything is answering on that port, and which session untargeted calls fall through to. Params: none (#817)"
  project.use_editor:
    class_path: project.use_editor
    group: project
    description: "Make one editor session the default target for untargeted calls. Does not change the session set and never touches any editor process. Params: editorTarget (session name, project name, or .uproject path) (#817)"
  project.add_editor:
    class_path: project.add_editor
    group: project
    description: "Register another project as an addressable editor session, with its own bridge connection and port. Optionally launch its editor. Every category then accepts editor=\"<name>\" to run a call there. Params: projectPath, editorName? (defaults to the project name), start? (launch the editor and wait for it to be ready), timeout? (seconds, default 300) (#817)"
  project.drop_editor:
    class_path: project.drop_editor
    group: project
    description: "Forget an editor session and close its bridge socket. The editor process is LEFT RUNNING and untouched - this detaches, it does not stop anything (use editor(stop_editor) for that). Params: editorTarget (#817)"
  project.get_info:
    class_path: project.get_info
    group: project
    description: "Read .uproject file details. Params: none"
  project.read_config:
    class_path: project.read_config
    group: project
    description: "Read INI config. Params: configName (e.g. 'Engine', 'Game')"
  project.search_config:
    class_path: project.search_config
    group: project
    description: "Search INI files. Params: query"
  project.list_config_tags:
    class_path: project.list_config_tags
    group: project
    description: "Extract gameplay tags from config. Params: none"
  project.read_cpp_header:
    class_path: project.read_cpp_header
    group: project
    description: "Parse a .h file. Params: headerPath"
  project.read_module:
    class_path: project.read_module
    group: project
    description: "Read module source. Params: moduleName"
  project.list_modules:
    class_path: project.list_modules
    group: project
    description: "List C++ modules. Params: none"
  project.search_cpp:
    class_path: project.search_cpp
    group: project
    description: "Search .h/.cpp files. Params: query, directory?"
  project.read_engine_header:
    class_path: project.read_engine_header
    group: project
    description: "Parse a .h file from the engine source tree. Params: headerPath (relative to Engine/Source, or absolute)"
  project.find_engine_symbol:
    class_path: project.find_engine_symbol
    group: project
    description: "Grep engine headers for a symbol. Params: symbol, maxResults?"
  project.list_engine_modules:
    class_path: project.list_engine_modules
    group: project
    description: "List modules in Engine/Source/Runtime. Params: none"
  project.search_engine_cpp:
    class_path: project.search_engine_cpp
    group: project
    description: "Search engine .h/.cpp/.inl files across Runtime/Editor/Developer/Plugins. Params: query, tree? (Runtime|Editor|Developer|Plugins|all - default Runtime), subdirectory?, maxResults? (default 500)"
  project.search_tools:
    class_path: project.search_tools
    group: project
    description: "Search every ue-mcp tool + action by keyword or task INTENT (a synonym layer maps 'screenshot'->capture_scene_png, 'tile a texture'->the texture-bomb flow, etc.) and return ranked matches (tool, action, description, score). The first step before editor(execute_python); most tasks already have a dedicated action. Params: query (space-separated keywords/intent), limit? (default 20) (#704)"
  project.describe_action:
    class_path: project.describe_action
    group: project
    description: "Return the live parameter schema for one action: every parameter it accepts, with type, required/optional, description, allowed values and default, plus the bridge method it dispatches to. search_tools finds an action by keyword and hands back only prose; this answers what to actually pass, so the first call is the right one. name takes 'tool.action' (asset.set_property) or a bare action name, which reports every category providing it. A name that does not resolve comes back with the closest spellings rather than a bare failure. Reads the graph THIS editor advertises, so injected Epic and plugin actions are included. Each action also reports class: read (observes), mutate (changes the editor, its project on disk, or its process) or unknown (decided by a parameter, so gated like mutate) - MCP's own readOnlyHint is per tool, and every tool here is a category holding both, so a harness that gates writes reads it from here. Params: name (required), category? (return every action of one category instead of one action)"
  project.list_available_actions:
    class_path: project.list_available_actions
    group: project
    description: "Report which actions this server can serve RIGHT NOW and why the rest cannot. With no editor attached the surface is advertised in full but most of it cannot run, and this is the line between the two halves: an action either runs in this Node process (availability 'always') or dispatches to a bridge method only a running editor answers (availability 'editor', with bridgeMethod naming it). The offline half is the engine symbol index and the C++ correctness checks, the project config, source and file readers, the surface introspection, and the process lifecycle actions that start, stop and build. An action contributed by a plugin whose route is undeclared reports 'unknown' and should be treated as needing an editor. Counts come back by default, per category as well as overall; includeNames=true adds the actions themselves, and category narrows the whole report to one. With an editor attached everything is available and the classification still answers the question worth asking then, which is what keeps working once the editor is stopped for a rebuild. Params: category?, includeNames? (default false), state? (available|blocked|all, default available)"
  project.list_content_assets:
    class_path: project.list_content_assets
    group: project
    description: "List the project's assets from the package files on DISK, which is the one asset query that works with no editor running. Takes a mount path (/Game, /Game/Characters, or a plugin's /MyPlugin) and resolves it through the same mount table the live path uses, so an offline listing names assets exactly as the editor would. It answers existence, layout, size and modified time, and it deliberately does not answer class, registry tags or dependencies: those live in the editor's asset registry and are not in the file, so asset(list) and asset(search) remain the answer once an editor is up. maxResults stops the walk rather than trimming the result, and truncated says when it did. Params: contentPath? (default /Game), recursive? (default true), namePattern? (case-insensitive substring of the asset name), maxResults? (default 1000)"
  project.check_install:
    class_path: project.check_install
    group: project
    description: "Answer whether this project can run the bridge at all, from disk, with no editor running and nothing compiled. Reports the project kind (a project declaring no native modules of its own is Blueprint-only, which is NOT a blocker: UnrealBuildTool writes temporary target and module files under Intermediate/Source/ and compiles the plugin against them), the engine that will be used and where it was resolved from, whether the plugin is deployed, enabled in the .uproject, compiled and up to date with its source, and whether this machine has the C++ toolchain Unreal needs. Every problem carries a stable code, what is wrong and the exact fix, and nextSteps is those fixes in order. Read-only: it never deploys, enables or builds anything. Params: projectPath? (default the loaded project), skipToolchain? (skip the toolchain probe, which shells out to vswhere or the compiler)"
  project.execute_python_report:
    class_path: project.execute_python_report
    group: project
    description: "Measurement for #704: reads this session's execute_python calls and, for each, runs its taskSummary back through search_tools to flag calls that OVERLAPPED an existing dedicated action ('you used Python for X, but tool Y does X'). Returns totalCalls, overlapping[] and an overlapRate. Params: none (#704)"
  project.list_files:
    class_path: project.list_files
    group: project
    description: "List files on disk under a directory, optionally filtered by extension(s). Runs in the MCP server process (no editor round-trip). Params: directory (absolute, or relative to the project dir), extensions? (e.g. ['png','exr'] or 'png'), recursive? (default false), maxResults? (default 1000) (#608)"
  project.set_config:
    class_path: ue-mcp.bridge
    group: project
    description: "Write to INI. Params: configName, section, key, value"
    options:
      method: set_config
  project.build:
    class_path: project.build
    group: project
    description: "Build the project's C++ out of process with UnrealBuildTool. Works with the editor STOPPED, which a full rebuild requires (UBT cannot link while an editor holds the module DLLs). Blocks until the build finishes and returns the compiler output. Params: configuration? (default Development), platform? (default the host platform), clean? (#958)"
  project.generate_project_files:
    class_path: ue-mcp.bridge
    group: project
    description: "Generate IDE project files (Visual Studio, Xcode, etc.). Params: none"
    options:
      method: generate_project_files
  project.create_cpp_class:
    class_path: ue-mcp.bridge
    group: project
    description: "Create a new native UCLASS in a project module. Uses the same engine template path as File → New C++ Class. Writes .h + .cpp; returns both paths plus needsEditorRestart (true unless Live Coding successfully hot-reloaded). Params: className (no prefix), parentClass? (default UObject; accepts short names like 'Actor' or /Script/<Module>.<Class> paths), moduleName? (default: first project module, use list_project_modules to pick), classDomain? ('public'|'private'|'classes', default public), subPath?"
    options:
      method: create_cpp_class
  project.list_project_modules:
    class_path: ue-mcp.bridge
    group: project
    description: "List native modules in the current project (name, host type, source path), in the .uproject's own declaration order. Feed moduleName from here into create_cpp_class. Params: cursor?, limit?"
    options:
      method: list_project_modules
  project.list_loaded_modules:
    class_path: ue-mcp.bridge
    group: project
    description: "Enumerate ALL engine+project modules with runtime load state (loaded/gameModule), not just uproject-declared ones. Params: filter? (case-insensitive substring), loadedOnly? (default false), cursor?, limit? (#689)"
    options:
      method: list_loaded_modules
  project.is_module_loaded:
    class_path: ue-mcp.bridge
    group: project
    description: "Report whether a named module is currently loaded in the editor. Params: moduleName (#689)"
    options:
      method: is_module_loaded
  project.list_available_plugins:
    class_path: ue-mcp.bridge
    group: project
    description: "List every plugin installed in this engine or project, sorted by name, with its category, version, type, whether it is enabled in THIS editor session, whether it is enabled by default, and the .uproject's current reference to it under projectReference {present, enabled}. Those two disagree after enable_plugin until the editor restarts, which is the point of reporting both. Params: filter?, pluginCategory?, enabledOnly?, limit? (default 200, max 2000), cursor?"
    options:
      method: list_available_plugins
  project.enable_plugin:
    class_path: ue-mcp.bridge
    group: project
    description: "Enable a plugin in the .uproject. Plugin enablement is neither a UPROPERTY nor an INI key, it is a JSON array in the .uproject read once at startup, so set_config cannot reach it and without this a plugin-gated capability stays permanently unreachable through the bridge. Idempotent: a plugin already enabled, or enabled by default with no entry, reports existed and writes nothing. The change is a file change, so modules, classes, content and settings appear only after editor(restart_editor), which the result says. Params: pluginName"
    options:
      method: enable_plugin
  project.disable_plugin:
    class_path: ue-mcp.bridge
    group: project
    description: "Disable a plugin in the .uproject. removeReference deletes the entry outright instead of writing an explicit disable, which is the difference between handing a default-on plugin back to its default and overriding it, and the two are not the same file. Idempotent against whichever of the two was asked for. Refuses to disable the bridge itself, since that would leave no way to undo it. Takes effect on the next editor start. Params: pluginName, removeReference?"
    options:
      method: disable_plugin
  project.live_coding_compile:
    class_path: ue-mcp.bridge
    group: project
    description: "Trigger a Live Coding compile (Windows only). Hot-patches method bodies of existing UCLASSes without editor restart - the fast inner loop for UFUNCTION implementations. Does NOT reliably register brand-new UCLASSes; use build_project + editor restart for those. Params: wait? (default false - fire and return 'in_progress')."
    options:
      method: live_coding_compile
  project.live_coding_status:
    class_path: ue-mcp.bridge
    group: project
    description: "Report Live Coding availability/state (available, started, enabledForSession, compiling). Helps choose between live_coding_compile and build_project. Params: none"
    options:
      method: live_coding_status
  project.resolve_collision_profile:
    class_path: ue-mcp.bridge
    group: project
    description: "Read one collision profile's resolved per-channel responses: collisionEnabled, objectType, and every channel with Block/Overlap/Ignore. This is the project-side half of blueprint(get_component_collision) (#925): a component's ResponseArray only lists the channels it OVERRIDES, so the profile is where an inherited response actually comes from. Project trace and object channels appear under their configured names, with enumName (ECC_GameTraceChannel1) alongside so a caller can key on something stable. By default the eight engine channels plus every channel the project configured are returned; includeAllChannels=true adds the unused slots. channel narrows it to one. A profile that does not exist lists the ones that do. Params: profileName, channel?, includeAllChannels?"
    options:
      method: resolve_collision_profile
  project.write_cpp_file:
    class_path: project.write_cpp_file
    group: project
    description: "Write a .h / .cpp / .inl file under the project's Source/ tree. Used to append UPROPERTYs/UFUNCTIONs or method bodies after create_cpp_class. Writes are scoped to Source/ for safety. Params: path (relative to Source/ or absolute within Source/), content (full file contents). After editing, call live_coding_compile (for existing classes) or build_project (for new classes)."
  project.read_cpp_source:
    class_path: project.read_cpp_source
    group: project
    description: "Read a .cpp file from the project Source/ tree. Companion to read_cpp_header for round-trip edits. Params: sourcePath (relative to Source/ or absolute)."
  project.write_source_file:
    class_path: project.write_source_file
    group: project
    description: "Write a .h/.cpp/.inl into a named module's Public/Private folder (resolves the module dir for you, including plugin modules under Plugins/*/Source/ that write_cpp_file refuses). After a new file, build_project + restart; after a body edit, live_coding_compile. Params: module (module name, default the project's primary module), visibility (Public|Private, default Private), fileName, content."
  project.read_source_file:
    class_path: project.read_source_file
    group: project
    description: "Read a .h/.cpp/.inl from a named module's folder (companion to write_source_file; resolves plugin modules too). With no visibility it tries Public then Private then the module root. Params: module, visibility?, fileName."
  project.build_engine_index:
    class_path: project.build_engine_index
    group: project
    description: "Build or refresh the engine symbol index that verify_symbols, lint_cpp_header and suggest_build_deps read. Scans roughly 31,000 headers across Runtime, Editor, Developer and the includable half of Engine/Plugins, and records for each symbol the header that declares it, the module that owns it, its signature and any UE_DEPRECATED. The result is cached per engine under the user directory and shared by every project on that engine, so this is a one-time cost per engine install: expect several minutes cold (first touch of each file goes through the virus scanner on Windows) and a few seconds warm. The other actions build it on demand, so this is only needed to refresh after an engine upgrade or to pay the cost deliberately. Params: refresh? (rebuild even when a valid cache exists)"
  project.verify_symbols:
    class_path: project.verify_symbols
    group: project
    description: "Check that engine symbols exist BEFORE writing C++ that uses them, and get back what you need to write it: the header to #include, the owning module for Build.cs, the exact declaration, the base class, and any UE_DEPRECATED with its version and message. Accepts a qualified 'UGameplayStatics::GetPlayerPawn' as well as a bare type name, and covers plugin modules (GameplayAbilities, Niagara, PCG, EnhancedInput) as well as the engine. A name that does not resolve comes back with close spellings; a member miss on a class that does exist says so, which separates a misspelled method from a misspelled class. The aggregate includes[] and modules[] are the whole edit you need to make. Builds the index on first use, which can take several minutes on a cold filesystem. Params: names (string[] or comma-separated string, max 200)"
  project.suggest_build_deps:
    class_path: project.suggest_build_deps
    group: project
    description: "Given the engine symbols a module uses, report which modules its Build.cs has to depend on and which of those it does not list yet, plus the AddRange line to paste. Core and CoreUObject are omitted because every module already has them. buildCsPath defaults to the Build.cs owning modulePath, or the project's first module. Pair with add_module_dependency, which performs the edit. Params: names (string[] or comma-separated string), buildCsPath? (absolute), modulePath? (a file or directory whose owning Build.cs to use)"
  project.find_example_usage:
    class_path: project.find_example_usage
    group: project
    description: "Find real call sites for an engine symbol in the engine's own .cpp files, which answers 'how is this actually used' with code that compiles. Better than a signature for anything with a non-obvious calling convention. Searches sources rather than headers on purpose: a header gives the declaration, which verify_symbols already returns. An engine installed from the Epic launcher ships headers WITHOUT .cpp sources, so on those there are no engine call sites to find; the result says so via engineSourcesAvailable and falls back to inline code in headers and to this project's own Source tree, rather than returning an empty list that reads as 'nothing uses this'. Params: symbol (bare or Class::Member), limit? (default 10), trees? (Runtime|Editor|Developer, default Runtime)"
  project.class_hierarchy:
    class_path: project.class_hierarchy
    group: project
    description: "Report what a class derives from and what derives from it, which is the question behind 'what should I subclass' and 'what already does this'. Ancestors are the full chain up to the root, nearest parent first; descendants default to the direct subclasses only, because every transitive subclass of UObject is tens of thousands of names. Every node carries its module, its include and whether it crosses a module boundary from the queried class, since crossing one is what forces a Build.cs dependency; crossModuleDependencies is that list on its own. Reads the engine symbol index and no files, so it is fast once the index exists, and builds it on first use, which can take several minutes on a cold filesystem. A base the index cannot resolve (a template, a macro-generated type) stops the walk and is reported as unresolvedAncestor rather than silently ending the chain. Params: symbol (class or struct name, prefix optional), direction? (ancestors|descendants|both, default both), depth? (generations of descendants, default 1), limit? (max descendants, default 100)"
  project.find_references:
    class_path: project.find_references
    group: project
    description: "Find every line in the engine tree that names a symbol, which answers 'how is this woven into the engine' and 'what would break if this changed'. Broader than find_callers on purpose: a reference is a member declaration, a UPROPERTY type, a cast, a template argument or a call, and both headers and .cpp files are searched. Comment lines and preprocessor lines are skipped, since neither is a use. Each site reports its file, line, text and owning module. An engine installed from the Epic launcher ships headers WITHOUT .cpp sources, so engineSourcesAvailable says whether implementation files could be searched at all and the note says what was searched instead. Scans files rather than the index, so a rare name on a cold filesystem is slow. Params: symbol (bare or Class::Member), limit? (max sites, default 40), trees? (Runtime|Editor|Developer|Plugins|all, default Runtime), includeProject? (also search this project's Source and Plugins, default true)"
  project.find_callers:
    class_path: project.find_callers
    group: project
    description: "Find who calls a function, and from which enclosing function, which is how to see the conventions around a call before writing one: what is checked first, what is passed, what is done with the result. Searches .cpp bodies first, since a mention in a header is usually a declaration rather than a call, and excludes the function's own definition. Each site reports file, line, text, module and, for a site in a .cpp, the Class::Method it sits inside. An engine installed from the Epic launcher ships headers WITHOUT .cpp sources, so on those there are no engine call sites to find: engineSourcesAvailable reports that and the search falls back to inline code in headers and to this project's own Source tree, rather than returning an empty list that reads as 'nothing calls this'. Params: symbol (bare or Class::Method), limit? (max sites, default 25), trees? (Runtime|Editor|Developer|Plugins|all, default Runtime), includeProject? (also search this project's Source and Plugins, default true)"
  project.find_callees:
    class_path: project.find_callees
    group: project
    description: "Report what a function calls, by reading its body and looking every called name back up in the engine index. Answers 'what does doing this properly actually involve': the result carries each callee's module and include, and modules[] is the Build.cs cost of writing code that does the same thing. The body is found via the index, which keeps the search to the owning class's module rather than the whole tree, and the definition it read is reported with its file and line range. An engine installed from the Epic launcher ships headers WITHOUT .cpp sources, so only functions whose body is inline in a header can be read there; engineSourcesAvailable and the note say so instead of returning an empty list. Builds the index on first use. Params: symbol (Class::Method, or a bare exported free function), limit? (max callees, default 100), trees? (which trees to test for sources, default Runtime)"
  project.symbol_context:
    class_path: project.symbol_context
    group: project
    description: "Return the lines of engine source around a declaration, so the API surrounding a symbol can be read without opening the file: the sibling overloads, the UPROPERTY above it, the comment saying which of three similar methods to call. verify_symbols returns the declaration line alone, which is the signature and nothing else; this is that line in its neighbourhood. Accepts Class::Member as well as a bare type and resolves both exactly as verify_symbols does. When the declaration opens a body that closes inside the window the result ends at the closing brace instead of mid-type, and reports bodyEndLine. Builds the index on first use, which can take several minutes on a cold filesystem. Params: symbol (bare or Class::Member), contextBefore? (lines before the declaration, default 8), contextAfter? (lines after, default 40)"
  project.lint_cpp_header:
    class_path: project.lint_cpp_header
    group: project
    description: "Check a header you just wrote against the engine it has to build against, and report what the compiler would before the compiler runs. Covers the structural mistakes that produce baffling Unreal build errors (a reflected type with no .generated.h include, a .generated.h that is not last, a UCLASS or USTRUCT with no GENERATED_BODY, no #pragma once) and the engine-facing ones (a symbol that does not exist, one used without its include, one whose module is missing from Build.cs, one the engine deprecated). A forward declaration counts as satisfying an include, since in a header it usually is. Run this after write_cpp_file and before build_project. Params: path (absolute, or relative to the project Source/), buildCsPath? (defaults to the owning module's)"
  project.add_module_dependency:
    class_path: project.add_module_dependency
    group: project
    description: "Add a module to a target module's Build.cs dependency array. Params: moduleName (the Build.cs to edit - must exist in the project), dependency (module name to add, e.g. 'UMG'), access? ('public'|'private', default 'private'). Creates the corresponding AddRange block if missing. Rebuild required afterward."
  project.add_cpp_member:
    class_path: project.add_cpp_member
    group: project
    description: "Append a UPROPERTY/UFUNCTION declaration to an existing UCLASS header inside the access specifier you choose. Idempotent: if a declaration containing the same memberName is already present, returns existed:true. Params: headerPath (relative to Source/ or absolute), declaration (full multi-line UPROPERTY(...) / UFUNCTION(...) block plus its single-line member or function signature), memberName (the identifier the declaration introduces - used for idempotency), access? ('public'|'protected'|'private', default 'public')."
  project.epic_discover_tests:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic AutomationTestToolset.AutomationTestToolset] Initialize automation worker discovery and load the test list. Must be called once before ListTests or RunTests. Takes several seconds as it discovers the local automation worker and enumerates all registered tests. Returns an async result that completes with a JSON status object when tests are available, or an error if discovery fails. Params: bForceRediscover?"
    options:
      method: epic_call_tool
  project.epic_get_section_property_values:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Returns the current values of the specified properties as a JSON object. Raises an error if the section does not exist, has no settings object, or any requested property cannot be read. Params: containerName, categoryName, sectionName, propertyNames"
    options:
      method: epic_call_tool
  project.epic_get_section_schema:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Returns a JSON Schema describing the user-visible properties of a settings section. The schema maps each property name to its type, description, and constraints. Raises an error if the section does not exist or has no backing settings object (e.g. uses a custom widget instead). Params: containerName, categoryName, sectionName"
    options:
      method: epic_call_tool
  project.epic_get_test_results:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic AutomationTestToolset.AutomationTestToolset] Get detailed results for the current or most recent test run. Requires DiscoverTests() to have completed. Returns a JSON object with per-test state, duration, errors, and warnings. Params: none"
    options:
      method: epic_call_tool
  project.epic_get_test_status:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic AutomationTestToolset.AutomationTestToolset] Get a lightweight status snapshot of the automation controller. Requires DiscoverTests() to have completed. Returns a JSON object with the controller state, enabled test count, and completion/pass/fail counts. Params: none"
    options:
      method: epic_call_tool
  project.epic_list_categories:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Lists the names of all categories within a settings container, sorted alphabetically. Raises an error if the container does not exist. Params: containerName"
    options:
      method: epic_call_tool
  project.epic_list_containers:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Lists the names of all known settings containers, sorted alphabetically. Common containers are \"Editor\" and \"Project\". Params: none"
    options:
      method: epic_call_tool
  project.epic_list_sections:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Lists the names of all sections within a settings category, sorted alphabetically. Raises an error if the container or category does not exist. Params: containerName, categoryName"
    options:
      method: epic_call_tool
  project.epic_list_tests:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic AutomationTestToolset.AutomationTestToolset] List available automation tests. Requires DiscoverTests() to have completed. Returns a JSON object: {\"tests\": [\"path1\", ...], \"total\": N, \"returned\": N}. Params: nameFilter, tagFilter, limit?"
    options:
      method: epic_call_tool
  project.epic_reset_section_to_defaults:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Resets the settings in a section to their default values. Raises an error if the section does not exist or reset is not supported. Params: containerName, categoryName, sectionName"
    options:
      method: epic_call_tool
  project.epic_run_tests:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic AutomationTestToolset.AutomationTestToolset] Run a set of automation tests by name. Requires DiscoverTests() to have completed. Starts executing the specified tests and returns an async result that completes with a JSON summary when all tests finish. Params: testNames"
    options:
      method: epic_call_tool
  project.epic_run_tests_by_filter:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic AutomationTestToolset.AutomationTestToolset] Run automation tests selected by a filter expression. Requires DiscoverTests() to have completed. Much faster than RunTests when targeting a large batch because the engine narrows the report tree in a single pass instead of running a per-leaf membership check against the requested name list. Filter syntax (multiple expressions joined by '+'): \"StartsWith:System.Engine\" prefix match against the full test path \"^Foo\" prefix anchor (equivalent to StartsWith:) \"Bar$\" suffix anchor \"Substring\" bare token matches anywhere in the path \"Group:Smoke\" expand a named group from AutomationControllerSettings ini Groups Returns an async result that completes with the same JSON summary as RunTests. Params: filterExpression"
    options:
      method: epic_call_tool
  project.epic_save_section:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Saves the settings in a section. Raises an error if the section does not exist or saving is not supported. Params: containerName, categoryName, sectionName"
    options:
      method: epic_call_tool
  project.epic_set_section_properties:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic ConfigSettingsToolset.ConfigSettingsToolset] Sets one or more properties on a settings section from a JSON object and saves. PropertiesJson must be a JSON object mapping property names to new values, in the same format returned by GetSectionPropertyValues. Raises an error if the section does not exist, cannot be edited, has no settings object, the default config file is not writable, or any property cannot be set. Params: containerName, categoryName, sectionName, propertiesJson"
    options:
      method: epic_call_tool
  project.epic_stop_tests:
    class_path: ue-mcp.bridge
    group: project
    description: "[Epic AutomationTestToolset.AutomationTestToolset] Stop all currently running tests. Requires DiscoverTests() to have completed. If a RunTests async result is pending, it will be completed with an error. Params: none"
    options:
      method: epic_call_tool

  # ── asset ──
  asset.list:
    class_path: ue-mcp.bridge
    group: asset
    description: "List assets via the AssetRegistry (sees /Game and every mounted plugin root). Cursor-paginated, so a large folder is walked deterministically instead of dropping the bridge on one oversized response (#790): every page carries totalMatched, hasMore and a nextCursor to pass back. The row offset this used to page with is refused, because a row number cannot report that the folder changed underneath it. maxResults is a deprecated spelling of limit and sizes the page when limit is omitted. Params: directory? (default /Game), classFilter?, recursive? (default true), maxResults?, cursor?, limit?"
    options:
      method: list_assets
  asset.search:
    class_path: asset.search
    group: asset
    description: "Search by name/class/path. maxResults is a deprecated spelling of limit and sizes the page when limit is omitted. With extra content roots configured and no directory, this searches each root and pages ONE ROOT AT A TIME, so a cursor has to be passed back together with the directory it came from. Params: query, directory?, maxResults?, searchAll?, cursor?, limit?"
  asset.read:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read asset via reflection. Params: assetPath"
    options:
      method: read_asset
  asset.read_properties:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read asset properties with values. Blueprint paths resolve to the generated-class CDO (#568). propertyName accepts dotted/indexed paths into nested structs, array elements, and instanced subobjects (e.g. `Config.Traits[1].Params.Field`); landing on an array of subobjects also lists each element's index+class (#527). expandDepth inlines the properties of subobjects OWNED by this asset, so a data asset's nested payload comes back in ONE call instead of a reference you have to chase (#755); references to OTHER assets are marked expandable rather than followed, unless expandExternal=true. Capped by maxExpandedObjects with expansionTruncated reported. Params: assetPath, propertyName?, includeValues?, valueFormat?, expandDepth? (0-5, default 0), expandExternal?, maxExpandedObjects? (default 64)"
    options:
      method: read_asset_properties
  asset.list_properties:
    class_path: ue-mcp.bridge
    group: asset
    description: "List reflected properties on any asset. Params: assetPath, includeValues?, valueFormat? ('text'|'json')"
    options:
      method: read_asset_properties
  asset.get_properties:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read property values on any asset. propertyName accepts dotted/indexed paths into nested structs, array elements, and instanced subobjects. valueFormat='json' returns structured values. Params: assetPath, propertyName?, includeValues?, valueFormat?"
    options:
      method: read_asset_properties
  asset.duplicate:
    class_path: ue-mcp.bridge
    group: asset
    description: "Duplicate asset. Params: sourcePath, destinationPath"
    options:
      method: duplicate_asset
  asset.rename:
    class_path: ue-mcp.bridge
    group: asset
    description: "Rename asset. Params: assetPath, newName (or sourcePath, destinationPath), force?. World Partition levels are detected and their __ExternalActors__/__ExternalObjects__ packages migrate atomically alongside the .umap, source-side redirectors get fixed up, and the active editor world is swapped to blank if it matches the source (#409). Refuses if any package is dirty - save first. If a prior rename left externals orphaned at the old path, re-running reconciles them. Rollback descriptor is emitted even on partial failure so the inverse rename can recover. `force=true` lets the call merge into a destination with pre-existing externals (used by rollback). For batches of 3+ scene-referenced non-world assets use bulk_rename instead."
    options:
      method: rename_asset
  asset.bulk_rename:
    class_path: ue-mcp.bridge
    group: asset
    description: "Batched rename using IAssetTools::RenameAssets - single transaction with one redirector-fixup pass (matches Content Browser drag). Use this over looped rename for scene-referenced assets. World assets are rejected (status=rejected_world); use rename_asset which handles WP externals atomically (#409). The fix-up pass cannot see a referencer that is not loaded, so each renamed item reports redirectorLeft and the result carries redirectorsLeft/redirectorsRemoved plus redirectorPackages, which feeds straight into fixup_redirectors (#908). Params: renames[] where each entry is {sourcePath, destinationPath} OR {assetPath, newName}."
    options:
      method: bulk_rename_assets
  asset.fixup_redirectors:
    class_path: ue-mcp.bridge
    group: asset
    description: "Clean up ObjectRedirectors left at the old paths after a move, bounded to exactly the packages you name. Preflights each redirector, its destination, and its hard AND soft referencers, then reports packagesToLoad/packagesToSave before touching anything; dryRun=true stops there. The real run loads only those referencers (a soft reference in an unloaded package is what the rename's own fix-up pass misses), rewrites them, saves them, RE-QUERIES the asset registry, and deletes a redirector only when nothing references it any more, reporting kept vs deleted per package with the referencers that survived. Naming a whole content root is refused unless allowProjectWide=true, so this can never turn into a project-wide resave. Protected mounts are refused outright. dryRun is the way to preview: save=false only skips the explicit save pass, because the editor's own fix-up writes what it can regardless. Params: paths[] (redirector packages or the folders holding them), dryRun? (default false), save? (default true), allowProjectWide? (default false) (#908)"
    options:
      method: fixup_redirectors
  asset.move:
    class_path: ue-mcp.bridge
    group: asset
    description: "Move asset. Params: sourcePath, destinationPath"
    options:
      method: move_asset
  asset.delete:
    class_path: ue-mcp.bridge
    group: asset
    description: "Delete asset. force defaults to false and is a REAL guard: the bridge asks the Asset Registry for referencers first and refuses with success=false, reason='has_referencers' and the full referencers list rather than destroying an asset other packages point at (#976). force=true takes the force-delete path, which also auto-closes open asset editors (#278). On a delete the editor attempted and could not finish, the reason is open_in_editor / has_referencers / in_memory_referenced / package_read_only / package_dirty / unknown, with referencers, inMemoryReferencers, packageReadOnly and packageDirty diagnostics (#601). Params: assetPath, force?"
    options:
      method: delete_asset
  asset.delete_batch:
    class_path: ue-mcp.bridge
    group: asset
    description: "Batch-delete assets. Per-path status is deleted | absent | protected | refused | failed, plus reason+referencers on refused and failed entries. A referenced asset is refused per entry with force=false and the rest of the batch still runs, so one guarded asset never hides behind a bare failed count (#976, #278). Result carries deleted/absent/refused/failed/protected/total. Params: assetPaths[], force?"
    options:
      method: delete_asset_batch
  asset.create_data_asset:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create UDataAsset instance of custom class. className accepts the C++ spelling with or without the A/U/F/E prefix (UMyConfig and MyConfig both resolve), a /Script/Module.ClassName path, or a loaded class name; a failed lookup lists the spellings tried and the closest matches (#823). Params: name, className, packagePath?, properties? (key/value map)"
    options:
      method: create_data_asset
  asset.create_asset_by_class:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create an asset of ANY concrete UObject class (not just UDataAsset) - physical-material subclasses, curves, settings objects. className accepts the C++ spelling with or without the A/U/F/E prefix, a /Script/Module.ClassName path, or a loaded class name (#823). Params: name, className, packagePath?, properties? (key/value map), onConflict? (skip|replace|rename). Actors/components and specialized assets (Blueprint/Material) have dedicated actions (#726)"
    options:
      method: create_asset_by_class
  asset.create_subobject:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create a named subobject OWNED by an existing asset, inside that asset's package, and return its object path for later property writes. This is the missing half of editing a data asset whose payload is named subobjects referenced from a struct array: set_property, bulk_set_properties and append_array_elements can edit the entries, and this makes a new one. Component classes are accepted, unlike create_asset_by_class; Actors are refused because they are spawned into a level. className takes a /Script/Module.Class path, which is how you name a plugin class the Python `unreal` module never exposes. The new object is created RF_Standalone and the package is saved in the same call, so it survives the garbage collection that used to eat a fresh object within one call and is reachable by path from the next one. properties are applied to a throwaway instance first, so a bad path or value creates nothing. outer='asset' (default) gives '<asset>.<name>'; outer='package' puts it beside the asset in the package. Params: assetPath, className, name, properties?, outer? (asset|package), onConflict? (reuse|error), save? (default true) (#975)"
    options:
      method: create_subobject
  asset.bulk_upsert_data_assets:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create or update up to 500 UDataAsset instances in ONE call. Every descriptor is first applied to a transient copy, so a bad class, property path, or value rejects the whole batch before a package is touched; nothing is half-written by a typo. Per-item status is created | updated | unchanged | skipped | failed (dryRun reports wouldCreate | wouldUpdate | wouldRemainUnchanged | wouldSkip), each with its own error when it failed. Replaying the same request returns unchanged, and only changed packages are saved. Emits a rollback descriptor that restores prior values and deletes what it created. Params: items[]: [{name, packagePath, className, properties?}], onConflict? (update (default) | skip | error), dryRun? (default false), save? (default true)"
    options:
      method: bulk_upsert_data_assets
  asset.save:
    class_path: ue-mcp.bridge
    group: asset
    description: "Save one asset, or every dirty asset under /Game when assetPath is omitted. force=true saves regardless of the dirty flag - several edits (OFPA level actors, some subsystem property writes) never mark their package dirty, so a dirty-only save skipped them and still reported success. Returns the package name plus on-disk file path, size and mtime so the write can be verified rather than trusted (#768). Params: assetPath?, force?"
    options:
      method: save_asset
  asset.save_all_dirty:
    class_path: ue-mcp.bridge
    group: asset
    description: "Flush every dirty package to disk in one call. Reports the packages it attempted, which ones reached disk (with file path, size and mtime) and which are still dirty afterwards, because a bare savedAll boolean has come back true while packages were never written (#768). Params: saveMapPackages? (default true), saveContentPackages? (default true)"
    options:
      method: save_all_dirty
  asset.set_mesh_material:
    class_path: ue-mcp.bridge
    group: asset
    description: "Assign material to static mesh slot. Params: assetPath, materialPath, slotIndex?"
    options:
      method: set_mesh_material
  asset.set_mesh_materials_batch:
    class_path: ue-mcp.bridge
    group: asset
    description: "Assign materials across many meshes and slots in one call, so an N mesh x M slot kit costs one round trip instead of N*M. StaticMesh and SkeletalMesh both work. Address a slot by slotName (survives a reimport reordering slot indices) or by slotIndex (default 0); passing both is rejected when they disagree. Every submitted assignment returns its own index/ok/status/error, status being ok|updated|unchanged|invalid|protected|duplicate|not_found|slot_not_found|failed|skipped. Default is all-or-nothing: any preflight rejection aborts before a mesh is touched. Pass continueOnError to apply the assignments that did pass and keep the rejects reported alongside them. Each mesh is written and saved once no matter how many of its slots the batch names, and the rollback payload restores only the writes that landed. Params: assignments ([{assetPath, materialPath, slotName? | slotIndex?}], max 500), save? (default true), dryRun? (default false), continueOnError? (default false) (#822)"
    options:
      method: set_mesh_materials_batch
  asset.recenter_pivot:
    class_path: ue-mcp.bridge
    group: asset
    description: "Move static mesh pivot to geometry center. Params: assetPath OR assetPaths"
    options:
      method: recenter_pivot
  asset.import_static_mesh:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import from FBX, OBJ, or GLB/glTF (glTF routes through Interchange) (#549). importUniformScale=100 fixes metre-authored FBX (#687). Params: filePath, name?, packagePath?, combineMeshes?, importMaterials?, importTextures?, generateLightmapUVs?, importUniformScale?"
    options:
      method: import_static_mesh
  asset.import_skeletal_mesh:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import skeletal mesh from FBX. importUniformScale=100 fixes metre-authored FBX (Blender FBX_SCALE_ALL) that lands 100x too small on a cm skeleton (#687). Returns post-import readback: boxExtent, morphTargets[], numLODs, skeleton (#678). Params: filePath, name?, packagePath?, skeletonPath?, importMaterials?, importTextures?, importUniformScale? (default 1.0), importMorphTargets? (default true), createPhysicsAsset? (default false), replaceExisting? (default true)"
    options:
      method: import_skeletal_mesh
  asset.import_animation:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import anim from FBX. Params: filePath, name?, packagePath?, skeletonPath"
    options:
      method: import_animation
  asset.import_texture:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import image. sRGB/compressionSettings/lodGroup/neverStream are applied at import time (folded in, no second call needed) (#661). Params: filePath, name?, packagePath?, sRGB?, compressionSettings? (Default|Normalmap|Grayscale|HDR|BC7|...), lodGroup?, neverStream?"
    options:
      method: import_texture
  asset.create_render_target_2d:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create and persist a TextureRenderTarget2D asset. Render format is applied before resource initialization. Params: name, packagePath? (default /Game), width? (1-8192, default 512), height? (1-8192, default 512), format? (R8|RG8|RGBA8|RGBA8_SRGB|R16F|RG16F|RGBA16F|R32F|RG32F|RGBA32F|RGB10A2, default RGBA8_SRGB), clearColor? ({r,g,b,a}, default transparent), generateMips? (default false), targetGamma? (default 0), onConflict? (skip|error)"
    options:
      method: create_render_target_2d
  asset.read_skeletal_mesh_build_settings:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read a SkeletalMesh's per-LOD FSkeletalMeshBuildSettings without changing anything. Params: assetPath, lodIndex? (default 0), allLods? (every LOD; mutually exclusive with lodIndex). Returns assetPath, lodCount and lods[] of {lodIndex, beforeBuildSettings, afterBuildSettings (identical on a read), beforeOptimizeForInstancing, afterOptimizeForInstancing, changed}, where each settings object carries bRecomputeNormals, bRecomputeTangents, bUseMikkTSpace, bComputeWeightedNormals, bRemoveDegenerates, bUseHighPrecisionTangentBasis, bUseHighPrecisionSkinWeights, bUseFullPrecisionUVs, bUseBackwardsCompatibleF16TruncUVs, bOptimizeForInstancing, thresholdPosition, thresholdTangentNormal, thresholdUV, morphThresholdPosition and boneInfluenceLimit"
    options:
      method: read_skeletal_mesh_build_settings
  asset.set_skeletal_mesh_optimize_for_instancing:
    class_path: ue-mcp.bridge
    group: asset
    description: "Write bOptimizeForInstancing on a SkeletalMesh's LOD build settings, which reorders the skin data so instanced draws of the mesh batch. Only LODs whose current value differs are touched, and the asset is only rebuilt and saved when at least one changes, so a call that asks for the value already set is a no-op. Read the current state with read_skeletal_mesh_build_settings first. Params: assetPath, enabled, lodIndex? (default 0), allLods? (every LOD; mutually exclusive with lodIndex). Returns assetPath, lodCount and lods[] of {lodIndex, beforeBuildSettings, afterBuildSettings, beforeOptimizeForInstancing, afterOptimizeForInstancing, changed}"
    options:
      method: set_skeletal_mesh_optimize_for_instancing
  asset.read_skeletal_mesh_skin_weights:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read existing per-vertex skin influences from one SkeletalMesh source LOD and profile. The caller must name 1-256 source MeshDescription vertex IDs, so a read cannot dump a whole mesh. Each influence returns boneName, boneIndex, normalized weight and exact uint16 rawWeight. Render data may use 8-bit weights unless high-precision skin weights are enabled. Generated LODs without source geometry are refused. Params: assetPath, vertexIndices, lodIndex? (default 0), profileName? (default profile)"
    options:
      method: read_skeletal_mesh_skin_weights
  asset.set_skeletal_mesh_skin_weights:
    class_path: ue-mcp.bridge
    group: asset
    description: "Replace skin influences on explicitly selected source MeshDescription vertices in one existing SkeletalMesh LOD/profile. The complete batch is validated before mutation: invalid or duplicate vertices/bones, non-finite/negative/out-of-range weights and all-zero sets fail without a write. Unreal's FBoneWeights normalizes, quantizes to uint16 source weights and prunes ordinary edits to its influence limit; actual before/after values are read back. Other source vertices and source profiles are untouched. Rebuilding may refresh derived render data and dependent generated LODs. A changed mesh is rebuilt and saved; an identical request is a no-op. Params: assetPath, edits, lodIndex? (default 0), profileName? (default profile). Rollback payloads use restoreRawWeights with exact uint16 rawWeight values."
    options:
      method: set_skeletal_mesh_skin_weights
  asset.read_cloth_data:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read Chaos cloth data on a skeletal mesh: per clothing asset, its configs (reflected properties), LOD count, and per-LOD point-weight-map summary (name, target, vertex count, min/max - including the MaxDistances mask). Params: skeletalMeshPath (#595)"
    options:
      method: read_cloth_data
  asset.set_cloth_config:
    class_path: ue-mcp.bridge
    group: asset
    description: "Set properties on a clothing asset's Chaos cloth config via reflection. Params: skeletalMeshPath, properties (object), clothingAsset? (name filter), configType? (config class/key filter) (#595)"
    options:
      method: set_cloth_config
  asset.export_texture:
    class_path: ue-mcp.bridge
    group: asset
    description: "Export a Texture2D to a PNG on disk (for inspection or external diffing). Params: assetPath, outputPath (.png) (#697)"
    options:
      method: export_texture
  asset.compare_textures:
    class_path: ue-mcp.bridge
    group: asset
    description: "Compare two Texture2D assets by dimensions, pixel format, and source-content identity (FTextureSource id) - tells you whether an authored texture actually changed without offline pixel-diffing. Params: assetPathA, assetPathB (#697)"
    options:
      method: compare_textures
  asset.import_texture_batch:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import many textures in one call - the loop stays inside the editor (no per-file bridge round-trip), so this finishes far faster than N import_texture calls. Per-item result records mirror import_texture. Params: items[]: [{filePath, packagePath?, name?, replaceExisting?}], packagePath? (default for items that don't set it), save? (default true), automated? (default true). Returns requested/imported/failed counts + items[] (#430)"
    options:
      method: import_texture_batch
  asset.reimport:
    class_path: ue-mcp.bridge
    group: asset
    description: "Reimport asset from source file. Params: assetPath, filePath?"
    options:
      method: reimport_asset
  asset.read_datatable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read DataTable rows. Params: assetPath, rowFilter?"
    options:
      method: read_datatable
  asset.create_datatable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create DataTable. Params: name, packagePath?, rowStruct"
    options:
      method: create_datatable
  asset.reimport_datatable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Reimport DataTable from JSON. Params: assetPath, jsonPath?, jsonString?"
    options:
      method: reimport_datatable
  asset.set_datatable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Append or overwrite a single DataTable row. Params: assetPath, rowName, row (object with row-struct fields - partial updates merge with the existing row). Idempotent; rollback restores the prior row (#437)"
    options:
      method: set_datatable_row
  asset.add_datatable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Alias for set_datatable_row. Params: assetPath, rowName, row (or fields / data) (#437)"
    options:
      method: add_datatable_row
  asset.update_datatable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Alias for set_datatable_row; partial update merges with existing row. Params: assetPath, rowName, row (or fields / data) (#437)"
    options:
      method: update_datatable_row
  asset.remove_datatable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Remove a single DataTable row. Idempotent (alreadyDeleted=true if missing). Params: assetPath, rowName (#437)"
    options:
      method: remove_datatable_row
  asset.get_datatable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read one DataTable row's fields without dumping the whole table. Params: assetPath, rowName (#535)"
    options:
      method: get_datatable_row
  asset.set_datatable_cell:
    class_path: ue-mcp.bridge
    group: asset
    description: "Write a single field on a single existing row (merges, leaves other cells untouched). Errors if the row doesn't exist. Params: assetPath, rowName, fieldName, value (#535)"
    options:
      method: set_datatable_cell
  asset.rename_datatable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Rename a row key, preserving its values. Params: assetPath, oldName, newName (#535)"
    options:
      method: rename_datatable_row
  asset.fill_datatable_from_json:
    class_path: ue-mcp.bridge
    group: asset
    description: "Bulk-upsert rows from a {rowName: {field: value}} object without touching unrelated rows (non-destructive, unlike reimport_datatable). Params: assetPath, rows (object) or jsonString (#535)"
    options:
      method: fill_datatable_from_json
  asset.create_curvetable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create CurveTable asset. Params: name, packagePath?, onConflict?"
    options:
      method: create_curvetable
  asset.read_curvetable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read CurveTable rows and keys. Params: assetPath, rowFilter?"
    options:
      method: read_curvetable
  asset.list_curvetable_rows:
    class_path: ue-mcp.bridge
    group: asset
    description: "Alias for read_curvetable. Params: assetPath, rowFilter?"
    options:
      method: list_curvetable_rows
  asset.import_curvetable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import CurveTable from JSON/CSV string or file. Params: assetPath, jsonString?, csvString?, filePath?, format?, interpMode?"
    options:
      method: import_curvetable
  asset.add_curvetable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Add CurveTable row. Params: assetPath, rowName, curveType? ('simple'|'rich'), interpMode?"
    options:
      method: add_curvetable_row
  asset.remove_curvetable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Remove CurveTable row. Idempotent if missing. Params: assetPath, rowName"
    options:
      method: remove_curvetable_row
  asset.rename_curvetable_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "Rename CurveTable row. Params: assetPath, oldName, newName"
    options:
      method: rename_curvetable_row
  asset.get_curvetable_keys:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read keys from one CurveTable row. Params: assetPath, rowName"
    options:
      method: get_curvetable_keys
  asset.set_curvetable_keys:
    class_path: ue-mcp.bridge
    group: asset
    description: "Replace keys on one CurveTable row. Params: assetPath, rowName, keys:[{time,value,interpMode?,arriveTangent?,leaveTangent?}]"
    options:
      method: set_curvetable_keys
  asset.add_curvetable_key:
    class_path: ue-mcp.bridge
    group: asset
    description: "Add or update one key on a CurveTable row. Params: assetPath, rowName, time, value, interpMode?, keyTimeTolerance?"
    options:
      method: add_curvetable_key
  asset.list_textures:
    class_path: ue-mcp.bridge
    group: asset
    description: "List textures. maxResults is a deprecated spelling of limit and sizes the page when limit is omitted. Params: directory?, recursive?, maxResults?, cursor?, limit?"
    options:
      method: list_textures
  asset.get_texture_info:
    class_path: ue-mcp.bridge
    group: asset
    description: "Get texture details. Params: assetPath"
    options:
      method: get_texture_info
  asset.set_texture_settings:
    class_path: ue-mcp.bridge
    group: asset
    description: "Set texture settings. Params: assetPath, settings (object with compressionSettings?, lodGroup?, sRGB?, neverStream?). Keys may also be passed at the top level."
    options:
      method: set_texture_settings
  asset.create_stringtable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create a StringTable asset. Params: name, packagePath?, namespace?, onConflict?"
    options:
      method: create_stringtable
  asset.read_stringtable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read StringTable entries and keys. Params: assetPath, keyFilter?"
    options:
      method: read_stringtable
  asset.list_stringtable_keys:
    class_path: ue-mcp.bridge
    group: asset
    description: "List StringTable keys. Params: assetPath, keyFilter?"
    options:
      method: list_stringtable_keys
  asset.get_stringtable_entry:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read one StringTable entry. Params: assetPath, key"
    options:
      method: get_stringtable_entry
  asset.set_stringtable_entry:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create or update one StringTable entry. Params: assetPath, key, sourceString (or value)"
    options:
      method: set_stringtable_entry
  asset.remove_stringtable_entry:
    class_path: ue-mcp.bridge
    group: asset
    description: "Remove one StringTable entry. Idempotent (alreadyDeleted=true if missing). Params: assetPath, key"
    options:
      method: remove_stringtable_entry
  asset.import_stringtable:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import StringTable entries from CSV. Params: assetPath, filePath (or csvPath)"
    options:
      method: import_stringtable
  asset.import_stringtable_csv:
    class_path: ue-mcp.bridge
    group: asset
    description: "Import or refresh a String Table from a CSV that is the canonical source, using Unreal's own String Table CSV importer. The CSV is parsed into a throwaway table and checked against expectedKeys FIRST, so a malformed file or a key set that does not match leaves the asset untouched and comes back with missingKeys/unexpectedKeys instead. replaceExisting=true prunes entries the CSV no longer carries, which is what makes the CSV canonical rather than additive; the prune runs after a successful merge so a bad parse can never empty the table. Returns addedKeys, updatedKeys, removedKeys, entry counts and an explicit persisted/saved plus persistError, so a write that did not reach disk is never reported as a success. A relative csvPath is read against the project directory. Params: assetPath, csvPath (or filePath), expectedKeys? (string[]), requireExactKeys? (default false), replaceExisting? (default false), save? (default true) (#978)"
    options:
      method: import_stringtable_csv
  asset.add_input_mapping:
    class_path: ue-mcp.bridge
    group: asset
    description: "Append an Enhanced Input key mapping to an InputMappingContext (InputAction + key by name string e.g. 'Mouse2D','LeftMouseButton'). Idempotent on (action,key). For modifiers/triggers use gameplay(set_mapping_modifiers). Same as gameplay(add_imc_mapping) (#525). Params: mappingContext (IMC path), inputAction (IA path), key"
    options:
      method: add_imc_mapping
  asset.remove_input_mapping:
    class_path: ue-mcp.bridge
    group: asset
    description: "Remove an IMC key mapping. Same as gameplay(remove_imc_mapping) (#525). Params: mappingContext (IMC path), mappingIndex? | (inputAction? + key?)"
    options:
      method: remove_imc_mapping
  asset.list_input_mappings:
    class_path: ue-mcp.bridge
    group: asset
    description: "List an IMC's key->action bindings with triggers/modifiers. Same as gameplay(read_imc) (#525). Params: mappingContext (IMC path)"
    options:
      method: read_imc
  asset.add_socket:
    class_path: ue-mcp.bridge
    group: asset
    description: "Add socket to StaticMesh or SkeletalMesh. SkeletalMesh writes mesh-local sockets by default; pass a Skeleton asset path to edit skeleton-level sockets. Idempotent on socket name; pass onConflict='update' to overwrite an existing socket's transform with the supplied relativeLocation/relativeRotation/relativeScale (#412). Params: assetPath, socketName, boneName? (SkeletalMesh only, default 'root'), relativeLocation?, relativeRotation?, relativeScale?, onConflict? (skip\\|update\\|error, default skip)"
    options:
      method: add_socket
  asset.remove_socket:
    class_path: ue-mcp.bridge
    group: asset
    description: "Remove socket by name. Params: assetPath, socketName"
    options:
      method: remove_socket
  asset.list_sockets:
    class_path: ue-mcp.bridge
    group: asset
    description: "List sockets on a mesh (StaticMesh or SkeletalMesh). SkeletalMesh results include mesh-local sockets plus assigned Skeleton sockets, each with source='mesh' or source='skeleton'. Params: assetPath"
    options:
      method: list_asset_sockets
  asset.set_socket_transform:
    class_path: ue-mcp.bridge
    group: asset
    description: "Update an existing socket's relative transform on StaticMesh or SkeletalMesh. Pass any subset of relativeLocation/relativeRotation/relativeScale; omitted fields stay at their current values. Errors if the socket does not exist (use add_socket to create). Common after FBX import when SOCKET_* empties land with scale=(100,100,100) (#412). Params: assetPath, socketName, relativeLocation?, relativeRotation?, relativeScale?"
    options:
      method: set_socket_transform
  asset.set_property:
    class_path: ue-mcp.bridge
    group: asset
    description: "Set a UPROPERTY on any loaded asset (Material, DataAsset, DataTable, SubsurfaceProfile, etc.) using a dotted path. Blueprint paths resolve to the generated-class CDO so you can author its defaults + Instanced sub-object arrays (#568). Walks nested structs, array elements by index, and instanced subobjects internally - no more read-modify-write copies (e.g. `settings.mean_free_path_distance` on a UMaterial, or `Config.Traits[1].Params.Field` on a config asset #527). Value goes through MCPJsonProperty::SetJsonOnProperty so JSON null clears object refs, structs accept {x,y,z}, arrays/maps round-trip. TMap values take { \"Key\": value } or, for struct keys, [{ key: {...}, value: ... }]; a write that cannot store every entry fails and leaves the old value untouched (#820). The write is saved to the package by default and the result reports persisted plus the package name; a write that could not reach disk (save=false, a protected mount, a read-only file, a refused save) comes back with persisted=false and persistError naming the reason instead of a bare success that reverts on the next editor start (#931). Params: assetPath, propertyName (dotted path), value, save? (default true) (#420)"
    options:
      method: set_asset_property
  asset.append_array_elements:
    class_path: ue-mcp.bridge
    group: asset
    description: "Append one or more JSON values to a reflected TArray without replacing existing entries. Supports dotted property paths plus native and user-defined USTRUCT elements. All elements are validated before mutation; returns appended indices and rollback data. The append is saved to the package by default and reports persisted / persistError the same way set_property does (#931). Params: assetPath, propertyName, elements, save? (default true)"
    options:
      method: append_asset_array_elements
  asset.bulk_set_properties:
    class_path: ue-mcp.bridge
    group: asset
    description: "Set dotted UPROPERTY paths on as many as 500 assets in one preflighted batch. Every asset, path, and value is validated before anything is mutated, and every submitted item comes back with its own ok/status/error, so a bad path in item 300 never hides the other 499 verdicts. Default is all-or-nothing: any preflight rejection aborts before a single UObject is touched. Pass continueOnError to apply the items that did pass and keep the rejects reported alongside them. Returns per-property readback, aggregate counts, targeted save results, and a replayable rollback payload covering only the writes that landed. Params: items ([{assetPath, properties}]), save? (default true), dryRun? (default false), continueOnError? (default false)"
    options:
      method: bulk_set_asset_properties
  asset.bulk_read_properties:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read the SAME properties off many assets in one call, filtered and aggregated in the editor. read_properties answers one asset per call, which turns a library-wide question (concurrency settings across 582 sound assets, cull distances across 201 foliage types) into a loop. Select with assetPaths[] or directory + classNames[]; propertyNames accepts dotted paths into nested structs (e.g. 'CullDistance.Max'), and a Blueprint path reads its generated-class CDO. Predicates, groupBy and countBy all evaluate here: filtering happens before anything crosses the wire. Absent and null are reported separately, because 'this class has no such property' and 'this property is unset' are different findings; suspect is true for either, and suspectOnly returns just those rows. Never writes, and reports dirtiedPackages. Params: assetPaths? (string[]) OR directory? (+ recursive?, default true), classNames? (string[]), matchSubclasses? (default true), propertyNames (string[], required, max 32), where? ([{field, op, value}] over props.<name>, className, suspect; same operators as level(query_components)), whereMode? (all|any), suspectOnly?, groupBy?, countBy? (string[]), sampleLimit?, countOnly?, limit? (default 200, max 2000), startIndex?, maxAssets? (default 2000, max 20000), outputPath? (write every matched row to a JSON file and return the path instead of the rows) (#909)"
    options:
      method: bulk_read_asset_properties
  asset.set_texture_settings_by_type:
    class_path: ue-mcp.bridge
    group: asset
    description: "Apply the canonical (compressionSettings, sRGB, LOD group) combo to every texture in each group: normal -> Normalmap, grayscale -> Grayscale, baseColor -> Default sRGB, hdr -> HDR. Params: groups (object: {normal?:[paths], grayscale?:[paths], baseColor?:[paths], hdr?:[paths]}) (#421)"
    options:
      method: set_texture_settings_by_type
  asset.create_interchange_pipeline:
    class_path: ue-mcp.bridge
    group: asset
    description: "One-call factory for a UInterchangeGenericAssetsPipeline asset with the 15-property mesh-import boilerplate already applied (RecomputeNormals=false, MikkTSpace=true, HighPrecisionTangents=true, BuildNanite=false, CreatePhysicsAsset=false, etc.). Params: assetPath OR (name + packagePath?), meshType? (skeletal default | static), options? (dotted-path overrides on the resulting pipeline e.g. {'MeshPipeline.bBuildNanite': true}), onConflict? (#421)"
    options:
      method: create_interchange_pipeline
  asset.reload_package:
    class_path: ue-mcp.bridge
    group: asset
    description: "Force reload an asset package from disk. Params: assetPath"
    options:
      method: reload_package
  asset.health_check:
    class_path: ue-mcp.bridge
    group: asset
    description: "Diagnose stuck-unloadable asset. Returns onDisk/inRegistry/isLoaded/canLoad/isStuck flags so an agent can detect the half-shutdown state where load returns null but the file exists (#279). Params: assetPath"
    options:
      method: asset_health_check
  asset.force_reload:
    class_path: ue-mcp.bridge
    group: asset
    description: "Aggressive reload from disk: closes open editors, reloads the package (rebuilding a Blueprint's class and CDO so container properties come back fresh, not just scalars), and reports objectReplaced. Refuses a dirty package unless discardUnsaved=true, and fails loudly when the editor would not release the old object rather than serving stale values (#279/#820). Params: assetPath, discardUnsaved? (default false)"
    options:
      method: force_reload_asset
  asset.export:
    class_path: ue-mcp.bridge
    group: asset
    description: "Export asset to disk file (Texture2D → PNG, StaticMesh → FBX, etc.). Params: assetPath, outputPath"
    options:
      method: export_asset
  asset.search_fts:
    class_path: ue-mcp.bridge
    group: asset
    description: "Ranked asset search (token-scored over name/class/path). Every match is scored and the ranked list is paged, so the top page is a page rather than the whole answer. maxResults is a deprecated spelling of limit and sizes the page when limit is omitted. Params: query, maxResults?, classFilter?, cursor?, limit?"
    options:
      method: search_assets_fts
  asset.reindex_fts:
    class_path: ue-mcp.bridge
    group: asset
    description: "Rebuild the SQLite FTS5 asset index. Params: directory?"
    options:
      method: reindex_assets_fts
  asset.get_referencers:
    class_path: ue-mcp.bridge
    group: asset
    description: "Reverse dependency lookup (what references this). Params: packages[] OR packagePath (#150). Returns {referencersByPackage, totalReferencers}."
    options:
      method: get_asset_referencers
  asset.get_dependencies:
    class_path: ue-mcp.bridge
    group: asset
    description: "Forward dependency lookup (what packages this asset references). Params: packages[] OR packagePath, hard? (default true), soft? (default true) (#588). Returns {dependenciesByPackage, totalDependencies}."
    options:
      method: get_asset_dependencies
  asset.list_skeleton_bones:
    class_path: ue-mcp.bridge
    group: asset
    description: "List bones (names + rest-pose local and component-space transforms) from a SkeletalMesh or Skeleton asset, no live actor needed. Params: assetPath, includeTransforms? (default true) (#593). Returns {bones, boneCount, sourceKind}."
    options:
      method: list_skeleton_bones
  asset.get_primary_asset_ids:
    class_path: ue-mcp.bridge
    group: asset
    description: "Enumerate AssetManager-registered FPrimaryAssetIds (verify a primary-asset registration). Params: type? (FPrimaryAssetType; omit for all types), maxResults? (default 1000) (#579). Returns {primaryAssetIds:[{primaryAssetId, type, name, assetPath}], count, total}."
    options:
      method: get_primary_asset_ids
  asset.set_sk_material_slots:
    class_path: ue-mcp.bridge
    group: asset
    description: "Set materials on a USkeletalMesh by slot name or slotIndex (bypasses the blueprint override-materials path that UE's ICH silently reverts). Params: assetPath, slots[{slotName?|slotIndex?, materialPath}]"
    options:
      method: set_sk_material_slots
  asset.diagnose_registry:
    class_path: ue-mcp.bridge
    group: asset
    description: "Scan a content path and compare disk vs AssetRegistry (including in-memory pending-kill entries). Returns onDiskCount, inMemoryIncludedCount, ghostCount and paths. Params: path, recursive? (default true), reconcile? (forceRescan=true)"
    options:
      method: diagnose_registry
  asset.get_mesh_bounds:
    class_path: ue-mcp.bridge
    group: asset
    description: "Get StaticMesh OR SkeletalMesh bounding box. Params: assetPath. Returns min, max, boxExtent, boxCenter, meshKind (#193/#351)"
    options:
      method: get_mesh_bounds
  asset.get_mesh_info:
    class_path: ue-mcp.bridge
    group: asset
    description: "One-call mesh QA: bounds + material slots + skeleton + LOD/vertex counts. Works for both UStaticMesh and USkeletalMesh. Params: assetPath. Returns meshKind, boundsOrigin, boundsExtent, heightM, lodCount, vertexCount, skeletonPath (skeletal only), materialSlots:[{index, slotName, materialPath, isDefaultFallback}], materialCount (#431)"
    options:
      method: get_mesh_info
  asset.read_import_sources:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read AssetImportData source filenames on an imported asset (StaticMesh, SkeletalMesh, Texture, Animation, etc.). Returns sources[] of {relativeFilename, absolutePath, timestamp, fileHash, displayLabelName}. Params: assetPath (#270)"
    options:
      method: read_import_sources
  asset.get_mesh_collision:
    class_path: ue-mcp.bridge
    group: asset
    description: "Inspect StaticMesh collision setup. Params: assetPath. Returns collisionTraceFlag, hasSimple/ComplexCollision, element counts (#177)"
    options:
      method: get_mesh_collision
  asset.get_mesh_geometry:
    class_path: ue-mcp.bridge
    group: asset
    description: "Read actual vertex data off a StaticMesh OR SkeletalMesh: per-section positions, uvs, normals and triangle indices, from the engine's render data (no ProceduralMeshComponent plugin needed). Triangle indices are section-local, so they index that section's own positions array; add baseVertexIndex for LOD-global indices. Over 20000 vertices inline is refused - pass dumpToFile to write the full data to a JSON file instead, or narrow with sectionIndex. Params: assetPath, lodIndex? (default 0), sectionIndex? (omit for all sections), include? ([positions|uvs|normals|triangles], omit for all), uvChannel? (default 0), dumpToFile?, outputPath? (#948/#926/#953)"
    options:
      method: get_mesh_geometry
  asset.measure_mesh_geometry:
    class_path: ue-mcp.bridge
    group: asset
    description: "Measure a StaticMesh OR SkeletalMesh LOD: bounds, dimensions, surfaceArea, volume, triangleCount, vertexCount, isClosed, isManifold, boundaryEdgeCount, nonManifoldEdgeCount. surfaceArea and volume are NAMED fields, never a positional pair (the engine's get_mesh_volume_area returns them in the order opposite to its name, #938). Vertices are welded by position before topology analysis so a UV seam does not read as a hole. Params: assetPath, lodIndex? (default 0), sectionIndex? (omit to measure the whole LOD) (#938/#953)"
    options:
      method: measure_mesh_geometry
  asset.read_uv_channels:
    class_path: ue-mcp.bridge
    group: asset
    description: "Report every UV channel of a StaticMesh or SkeletalMesh LOD: bounds, UV area, unit-square coverage, overlap fraction and the triangles involved, island count, degenerate islands, seam edges, out-of-range and flipped triangles, plus which channel LightMapCoordinateIndex points at and the LOD's lightmap build settings. This is the verification half of the UV surface: every other UV action reports the same channel block after it writes, so a change can be checked rather than trusted. Overlap and coverage are RASTERISED at rasterSize rather than tested exactly, and the result says so via overlapMethod. Params: assetPath, lodIndex?, channels?, includeIslands? (default true), includeOverlap? (default true), rasterSize? (default 512)"
    options:
      method: read_uv_channels
  asset.set_uv_channel_count:
    class_path: ue-mcp.bridge
    group: asset
    description: "Add, remove, resize or copy a UV channel. Channel count is a mesh-description attribute with no UPROPERTY, so asset(set_property) cannot reach it. Setting the count it already has returns existed=true and skips the rebuild. Growing has an exact inverse and the rollback restores it; remove and copy-over-existing DESTROY coordinates, so those report rollbackRestoresChannelCountOnly rather than pretending the undo is complete. Params: assetPath, lodIndex?, op? (set|add|remove|copy, default set), channelCount? (op=set), count? (op=add, default 1), channel? (op=remove), fromChannel? / toChannel? (op=copy), save? (default true), dryRun?"
    options:
      method: set_uv_channel_count
  asset.unwrap_uvs:
    class_path: ue-mcp.bridge
    group: asset
    description: "Auto-unwrap and pack islands into one channel via Geometry Script, adding the channel if it does not exist. It converts the LOD to a DynamicMesh and back, so the WHOLE LOD is rewritten rather than only its UVs; the result says so. Pass backupToChannel for a lossless rollback, otherwise only the channel count is restorable. Without the Geometry Script plugin it returns reason='geometry_scripting_unavailable' naming what to enable instead of failing opaquely. Params: assetPath, lodIndex?, channel? (default 0), method? (xatlas|patchBuilder|expMap|conformal|spectralConformal|planar|box|cylinder, default xatlas), pack? (default true), textureResolution? (default 1024), maxIterations?, initialPatchCount?, islandSource? (UVIslands|PolyGroups), projectionTransform?, preserveVertexOrder? (default true), backupToChannel?, save? (default true), dryRun?, rasterSize?"
    options:
      method: unwrap_uvs
  asset.transform_uvs:
    class_path: ue-mcp.bridge
    group: asset
    description: "One action for every UV transform: the whole channel, chosen islands, triangles facing a direction, or one material slot, plus flips. The filter is the only thing that varies, so the maths lives in one place rather than in four near-identical actions. Edits the mesh description directly, touching nothing but the UV channel, and emits an EXACT inverse as its rollback. A zero scale component is refused because it has no inverse; an identity transform returns existed=true without rebuilding. Params: assetPath, lodIndex?, channel? (default 0), translate? ({u,v}), scale? ({u,v}), rotate? (degrees), origin? ({u,v}, default 0.5/0.5), flipU?, flipV?, order? (flipScaleRotateTranslate|translateRotateScaleFlip), selection? ({mode: all|island|normal|polygonGroup, islandIndices?, normalDirection?, normalAngleTolerance?, polygonGroups?, materialSlotNames?}), save? (default true), dryRun?"
    options:
      method: transform_uvs
  asset.generate_lightmap_uvs:
    class_path: ue-mcp.bridge
    group: asset
    description: "Apply the lightmap build settings AND run UStaticMesh::Build AND read the result back. Deliberately not a setter: bGenerateLightmapUVs, SrcLightmapIndex, DstLightmapIndex and LightMapCoordinateIndex are plain UPROPERTYs that asset(set_property) already writes, and writing them does NOTHING until the mesh rebuilds. That rebuild is the whole point. Returns the channel the build actually produced with its overlap and island report, and fails loudly when the channel did not appear. StaticMesh only. Rollback restores the settings, not the generated coordinates. Params: assetPath, lodIndex?, enable? (default true), sourceChannel?, destinationChannel?, minLightmapResolution? (default 64), lightmapResolution?, setLightmapCoordinateIndex? (default true), force?, save? (default true), dryRun?, rasterSize?"
    options:
      method: generate_lightmap_uvs
  asset.export_uv_layout:
    class_path: ue-mcp.bridge
    group: asset
    description: "Render one UV channel to a PNG under Saved/UVLayouts: white wireframe, one hue per island, red where two triangles share a texel, yellow unit-square border. Returns the file path plus the same channel statistics read_uv_channels reports, so the picture and the numbers describe the same rasterisation. Use it to SEE why a lightmap bake is wrong instead of inferring it from counts. Params: assetPath, lodIndex?, channel? (default 0), outputPath?, imageSize? (default 1024, max 4096), showIslands? (default true), showOverlaps? (default true), showGrid? (default true)"
    options:
      method: export_uv_layout
  asset.check_uvs:
    class_path: ue-mcp.bridge
    group: asset
    description: "One-call UV health report, the same idiom as measure_mesh_geometry. Flags a missing lightmap channel, a LightMapCoordinateIndex that disagrees with the build's DstLightmapIndex, overlap in the lightmap channel over budget, lightmap UVs outside 0..1, empty or degenerate channels and islands, and flipped triangles, each with a severity and the action that fixes it. Out-of-range UVs OUTSIDE the lightmap channel are reported as info rather than a fault, because tiling is legitimate there. Params: assetPath, lodIndex?, requireLightmapChannel? (default true for StaticMesh), maxOverlapFraction? (default 0.001), rasterSize? (default 512)"
    options:
      method: check_uvs
  asset.apply_mesh_simplify:
    class_path: ue-mcp.bridge
    group: asset
    description: "Reduce a StaticMesh's triangle count while keeping its silhouette, through Geometry Script. Nine strategies because 'simplify' means different things: a target triangleCount or vertexCount, a geometric tolerance (the furthest the surface may drift), an edgeLength, a fast cluster-based edgeLength, a structural collapse of coplanar regions (planar) or of PolyGroup faces (polygroup), and the editor's own reducer (editorTriangleCount, editorVertexCount). NOT reachable through asset(set_property): a StaticMesh's LOD reduction settings build a NEW LOD, they never rewrite LOD 0's source geometry, and no UPROPERTY means 'collapse this mesh to 500 triangles'. Writes a SEPARATE asset by default (outputPath, or '<assetPath>_Simplified'), whose rollback is a complete delete; inPlace=true overwrites the source and reports that its edit has no inverse, with backupPath as the escape. Idempotent: a mesh already at or under the target reports changed=false and is not rewritten. Needs the Geometry Script engine plugin, and without it returns reason='geometry_scripting_unavailable' naming what to enable. Params: assetPath, simplifyMode? (triangleCount|vertexCount|tolerance|edgeLength|clusterEdgeLength|planar|polygroup|editorTriangleCount|editorVertexCount, default triangleCount), triangleCount?, vertexCount?, tolerance?, edgeLength?, angleThreshold?, method? (StandardQEM|VolumePreserving|AttributeAware|AttributeAwareV2), allowSeamCollapse?, preserveVertexPositions?, autoCompact?, outputPath?, inPlace?, backupPath?, lodType?, lodIndex?, onConflict?, copyMaterialsFromSource?, copyCollisionFromSource?, nanite?, recomputeNormals?, recomputeTangents?, removeDegenerates?, save?, dryRun?"
    options:
      method: apply_mesh_simplify
  asset.apply_mesh_remesh:
    class_path: ue-mcp.bridge
    group: asset
    description: "Rebuild a StaticMesh's triangulation at a uniform or adaptive density. Different from apply_mesh_simplify: simplify only removes triangles, remesh splits AND collapses AND flips edges to reach an even edge length, which is what a mesh needs before deformation, baking, or a convex decomposition that would otherwise follow the original triangulation's bias. Writes a SEPARATE asset by default ('<assetPath>_Remeshed'). Deliberately NOT idempotent and says so in repeatIsIdempotent: remeshing a remeshed mesh keeps moving vertices, so a repeat is a second edit rather than a no-op, which is why the separate-output default matters here more than anywhere else. Needs the Geometry Script engine plugin. Params: assetPath, remeshMode? (uniform|adaptive, default uniform), targetType? (TriangleCount|TargetEdgeLength), targetTriangleCount?, targetEdgeLength?, smoothingType? (Uniform|UVPreserving|Mixed), smoothingRate?, boundaryConstraint? (Fixed|Refine|Free|Ignore), iterations?, discardAttributes?, reprojectToInputMesh?, relativeDensity?, outputPath?, inPlace?, backupPath?, lodType?, lodIndex?, onConflict?, copyMaterialsFromSource?, copyCollisionFromSource?, nanite?, recomputeNormals?, recomputeTangents?, removeDegenerates?, save?, dryRun?"
    options:
      method: apply_mesh_remesh
  asset.apply_mesh_mirror:
    class_path: ue-mcp.bridge
    group: asset
    description: "Reflect a StaticMesh across a plane, optionally cutting away the far side first and welding the seam. This is the 'model half of it and mirror' workflow, and it is real geometry rather than a negative component scale, which inverts the winding and lights wrong; no UPROPERTY on a StaticMesh mirrors its source geometry. Name the plane with axis=x|y|z through planeOrigin, or axis=custom with an explicit planeNormal. Writes a SEPARATE asset by default ('<assetPath>_Mirrored'). NOT idempotent and says so: mirroring a mirrored mesh doubles it again rather than returning the original. Needs the Geometry Script engine plugin. Params: assetPath, axis? (x|y|z|custom, default x), planeOrigin?, planeNormal?, applyPlaneCut?, flipCutSide?, weldAlongPlane?, outputPath?, inPlace?, backupPath?, lodType?, lodIndex?, onConflict?, copyMaterialsFromSource?, copyCollisionFromSource?, nanite?, recomputeNormals?, recomputeTangents?, removeDegenerates?, save?, dryRun?"
    options:
      method: apply_mesh_mirror
  asset.apply_mesh_hole_fill:
    class_path: ue-mcp.bridge
    group: asset
    description: "Close every open boundary loop on a StaticMesh so it becomes watertight, which is what a boolean, a voxel operation, a convex decomposition and a physics conversion all require and the most common reason those fail. WELDS FIRST by default: a great many 'holes' are not holes but duplicated vertices along a seam that no fill can close, and running the fill alone on such a mesh reports zero holes filled while the mesh stays open. Genuinely idempotent: a mesh with nothing left to fill reports existed=true and is not rewritten. Reports filledHoles, failedHoleFills, weldedOpenEdges and the before/after open-border-edge counts, so 'watertight now' can be verified rather than assumed. Needs the Geometry Script engine plugin. Params: assetPath, fillMethod? (Automatic|MinimalFill|PolygonTriangulation|TriangleFan|PlanarProjection), weldFirst?, weldTolerance?, removeDegenerateFirst?, deleteIsolatedTriangles?, outputPath?, inPlace?, backupPath?, lodType?, lodIndex?, onConflict?, copyMaterialsFromSource?, copyCollisionFromSource?, nanite?, recomputeNormals?, recomputeTangents?, removeDegenerates?, save?, dryRun?"
    options:
      method: apply_mesh_hole_fill
  asset.generate_mesh_collision:
    class_path: ue-mcp.bridge
    group: asset
    description: "Build simple collision shapes for a StaticMesh from its own geometry, or clear them. Eight methods from axis-aligned boxes to a full convex decomposition. NOT reachable through asset(set_property): UBodySetup AggGeom is a UPROPERTY, but what has to go into it is the OUTPUT of a decomposition solver running over the mesh, and there is no value a caller could supply; the shape count and trace flag stay ordinary property writes and asset(get_mesh_collision) is still the read half. Idempotent: generation from the same mesh with the same options is deterministic, so a repeat reports changed=false without rewriting the asset, compared by a structural signature (shape counts per kind, hull vertex counts, trace flag) rather than byte-for-byte. op='clear' is the remove half and needs no plugin at all. A generation that produces zero shapes is reported as a failure naming apply_mesh_hole_fill, not as a success that quietly left the mesh with no collision. Params: assetPath, op? (generate|clear, default generate), method? (AlignedBoxes|OrientedBoxes|MinimalSpheres|Capsules|ConvexHulls|SweptHulls|MinVolumeShapes|LevelSets), maxConvexHulls?, hullTargetFaceCount?, maxShapeCount?, minThickness?, autoDetectSpheres?, autoDetectBoxes?, autoDetectCapsules?, simplifyHulls?, removeFullyContainedShapes?, decompositionErrorTolerance?, decompositionSearchFactor?, sweptHullAxis? (X|Y|Z|SmallestBoxDimension|SmallestVolume), markAsCustomized?, lodType?, lodIndex?, save?, dryRun?"
    options:
      method: generate_mesh_collision
  asset.apply_mesh_fracture:
    class_path: ue-mcp.bridge
    group: asset
    description: "Cut a StaticMesh with planes and write each resulting piece out as its own StaticMesh asset, ready to be placed, simulated or destroyed individually. Three patterns: slice (parallel cuts along one axis), grid (cuts along all three) and random (seeded planes through the bounds). WHAT THIS IS NOT: it is not Chaos destruction and produces no UGeometryCollection, because every engine entry point for that carries no UFUNCTION and reflection cannot reach it; the result says so in producesGeometryCollection=false rather than leaving it to be discovered. The source asset is never modified, so the rollback is an exact delete of the pieces it wrote. Seeded, therefore repeatable: onConflict='error' (the default) refuses rather than writing over pieces already there. dryRun lists the cut planes and every path it would write. Params: assetPath, pattern? (slice|grid|random, default slice), axis? (x|y|z), pieces?, gridX?, gridY?, gridZ?, planeCount?, seed?, jitter?, gapWidth?, fillHoles?, minPieceTriangles?, outputBasePath?, onConflict?, copyMaterialsFromSource?, nanite?, recomputeNormals?, recomputeTangents?, lodType?, lodIndex?, save?, dryRun?"
    options:
      method: apply_mesh_fracture
  asset.audit_hygiene:
    class_path: ue-mcp.bridge
    group: asset
    description: "One read-only sweep answering the questions a project accumulates answers to and never gets asked: what nothing references (unreferenced), what references nothing (brokenReferences), what exists twice (duplicates), what breaks the naming convention (naming), and what renames left behind (redirectors). asset(get_referencers) and asset(get_dependencies) answer the first two for ONE named package; this is the project-wide form, which is the one an agent needs. The unreferenced section carries a caveat to read before acting on it: an asset loaded by name from an INI setting, from C++ with a hardcoded path, or from a soft reference resolved at runtime has no package dependency and appears there while being very much in use, so treat it as candidates to review rather than a delete list. Maps, World Partition external actors and Asset Manager primary assets are already excluded for that reason. Duplicate detection by content matches class, file size and saved package hash, which finds a file copied verbatim but NOT a copy saved under a different name (the name is inside the file); duplicateMethod='name' is what catches an asset imported twice into two folders. Naming rules match on the asset's class name, so WidgetBlueprint and AnimBlueprint carry their own prefixes rather than inheriting Blueprint's, and the effective table comes back in rulesApplied. Counts are always complete; only the listings are capped by maxIssues. Params: directory?, directories?, recursive?, maxAssets?, maxIssues?, checks? (unreferenced|brokenReferences|duplicates|naming|redirectors), classNames?, excludePaths?, keepPaths?, duplicateMethod? (content|name|both), namingRules?, namingRuleMode? (merge|replace), includeWorlds?, ignoreRedirectorReferencers?"
    options:
      method: audit_asset_hygiene
  asset.bulk_fix_hygiene:
    class_path: ue-mcp.bridge
    group: asset
    description: "The batched fix-up for the two audit_hygiene findings a machine can act on without deciding something a person should: a name that breaks the convention (fix='naming') and an asset nothing references (fix='unreferenced'). Same preflight shape as bulk_set_properties: every candidate is validated, every candidate gets a status back including the rejected ones, and a failed preflight aborts before anything is touched unless continueOnError. THREE SAFETY RULES that are not the shape: dryRun DEFAULTS TO TRUE and the dry run names every asset with its exact destination; fix='unreferenced' MOVES assets into quarantineFolder by default rather than deleting them, and that move has an exact inverse this call emits as its rollback; and findings are recomputed here rather than taken from an audit, so an asset that gained a referencer since then is skipped with the referencer named. maxFixes caps the batch far below the audit's scan ceiling. Worlds are refused outright, because moving one without migrating its external-actor packages in the same batch orphans every actor in the level; use asset(rename). fix='redirectors' is refused too and points at asset(fixup_redirectors), which already does it properly. Params: fix (naming|unreferenced), assetPaths?, directory?, directories?, recursive?, maxAssets?, classNames?, excludePaths?, keepPaths?, namingRules?, namingRuleMode?, unreferencedAction? (quarantine|delete), quarantineFolder?, ignoreRedirectorReferencers?, maxFixes?, continueOnError?, save?, dryRun?"
    options:
      method: fix_asset_hygiene
  asset.mesh_boolean:
    class_path: ue-mcp.bridge
    group: asset
    description: "Boolean CSG between two StaticMeshes: union, subtract, intersect, trimInside, trimOutside, newPolyGroupInside, newPolyGroupOutside. The target is the mesh being cut and the tool is what cuts it, each placed by its own optional transform. Writes to a SEPARATE output asset by default (outputPath, or '<targetPath>_<Operation>' when omitted); inPlace=true opts into overwriting the target and is the only destructive form. An existing outputPath is refused unless onConflict='replace'. An empty result is refused and nothing is written unless allowEmptyResult=true, because two meshes that never overlap otherwise report success having deleted everything. Returns triangle and vertex counts for both inputs and the result, plus the written asset's triangles, vertices, LOD count, material slots and bounds, so the operation can be verified rather than trusted. dryRun runs the boolean and reports those counts without writing. Materials and simple collision are copied from the target by default; nanite is inherit (match the target) | enable | disable. Needs the Geometry Script engine plugin: without it the call returns reason='geometry_scripting_unavailable' naming what to enable, rather than failing opaquely. Params: operation, targetPath, toolPath, outputPath?, inPlace?, targetTransform?, toolTransform?, lodType? (MaxAvailable|HiResSourceModel|SourceModel|RenderData), lodIndex?, fillHoles? (default true), simplifyOutput? (default true), simplifyPlanarTolerance? (default 0.01), allowEmptyResult?, recomputeNormals?, recomputeTangents?, removeDegenerates?, copyCollisionFromTarget? (default true), copyMaterialsFromTarget? (default true), nanite?, onConflict? (error|replace), dryRun?, save? (default true) (#916)"
    options:
      method: mesh_boolean
  asset.migrate:
    class_path: asset.migrate
    group: asset
    description: "Copy assets and their dependencies into ANOTHER project's Content directory - the scripted form of the content browser's Migrate (#760). destinationContentDir is the TARGET project's Content folder. While this server drives more than one editor, a 'toEditor' parameter is offered as well: name the destination editor and its Content folder is resolved for you and its asset registry rescanned afterwards, so the assets are visible there without a manual rescan (#817). The call runs in the editor holding the SOURCE assets, so it pushes assets out of the project it is attached to. Unsaved or never-saved assets are refused, because migrate copies files and would otherwise silently omit your edits. Every asset is resolved before anything is copied, and the destination is checked for the packages afterwards rather than reporting success on the call returning. Params: assetPaths (string[]) or assetPath, toEditor OR destinationContentDir, includeDependencies? (default true), onConflict? (skip|overwrite, default skip), allowDirty?, dryRun?"
  asset.move_folder:
    class_path: ue-mcp.bridge
    group: asset
    description: "Move/rename entire content folder with redirector fixup in one transaction. Params: sourcePath, destinationPath (#192)"
    options:
      method: move_folder
  asset.create_folder:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create empty content browser folder(s). Params: path OR paths[] (e.g. /Game/Foo, /Game/Bar/Baz). Returns per-path created/existed/failed (#212)"
    options:
      method: create_folder
  asset.delete_folder:
    class_path: ue-mcp.bridge
    group: asset
    description: "Delete content browser folder(s) - counterpart to delete_asset, which leaves the parent directory entry behind as an orphan. Empty folders only by default; pass force=true to also delete any assets still inside (Content Browser 'Delete folder' equivalent). Per-path status (deleted/absent/failed) with reason (invalid_path/protected_path/not_empty/delete_failed) and a sample of contained assets on not_empty entries. Params: path OR paths[], force?"
    options:
      method: delete_folder
  asset.set_mesh_nav:
    class_path: ue-mcp.bridge
    group: asset
    description: "Set StaticMesh nav contribution. Params: assetPath, bHasNavigationData?, clearNavCollision? (#167)"
    options:
      method: set_mesh_nav
  asset.create_user_defined_enum:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create a UserDefinedEnum content asset, optionally pre-populated with values. Params: name, packagePath? (default /Game), values? ([display-name strings]), onConflict? (#686)"
    options:
      method: create_user_defined_enum
  asset.list_enum_values:
    class_path: ue-mcp.bridge
    group: asset
    description: "List a UEnum's enumerators (index, authored short name, display name, value). Works on native and UserDefinedEnum assets. Params: assetPath (#686)"
    options:
      method: list_enum_values
  asset.edit_user_defined_enum:
    class_path: ue-mcp.bridge
    group: asset
    description: "Author a UserDefinedEnum content asset. op=add_value appends an enumerator (authored name is auto-assigned; pass displayName - or name - to set the editable display text). op=rename_value sets a new displayName on the enumerator resolved by index or name (matches short or display name). op=remove_value deletes it. Recompiles dependents automatically. Native UEnums are not editable. Params: assetPath, op (add_value|rename_value|remove_value), displayName?, name?, index? (#686)"
    options:
      method: edit_user_defined_enum
  asset.create_user_defined_struct:
    class_path: ue-mcp.bridge
    group: asset
    description: "Create a UserDefinedStruct content asset, optionally pre-populated with fields. Each field is {name, type} where type is a MakePinType string (bool|int|int64|float|string|name|text|byte, a struct like Vector, an enum, or an object ref like Actor). Params: name, packagePath? (default /Game), structFields? ([{name, type}]), onConflict? (#735)"
    options:
      method: create_user_defined_struct
  asset.list_struct_fields:
    class_path: ue-mcp.bridge
    group: asset
    description: "List a UserDefinedStruct's members (index, internal name, friendly/display name, GUID, type label). Use this to find the GUID for a stable rename/retype. Native structs are not editable. Params: assetPath (#735)"
    options:
      method: list_struct_fields
  asset.edit_user_defined_struct:
    class_path: ue-mcp.bridge
    group: asset
    description: "Author a UserDefinedStruct content asset. op=add_field appends a member (type via MakePinType string; pass fieldName for its display name). op=rename_field sets a new newDisplayName on the member resolved by fieldGuid or fieldName - the member GUID is preserved so existing Blueprint pins and DataTable rows survive. op=set_field_type changes a member's type. op=remove_field deletes it. Recompiles dependents automatically. Native structs are not editable. Params: assetPath, op (add_field|rename_field|set_field_type|remove_field), fieldName?, fieldGuid?, newDisplayName?, type? (#735)"
    options:
      method: edit_user_defined_struct
  asset.rename_struct_field:
    class_path: ue-mcp.bridge
    group: asset
    description: "Rename a UserDefinedStruct field's display name while preserving its member GUID, so Blueprint pins and DataTable rows keyed off it survive. Convenience wrapper over edit_user_defined_struct(op=rename_field). Resolve the field by fieldGuid or fieldName (matches friendly or internal name). Params: assetPath, fieldName | fieldGuid, newDisplayName (#735)"
    options:
      method: edit_user_defined_struct
  asset.lock:
    class_path: asset.lock
    group: asset
    description: "Acquire an exclusive lock on an asset for this editor. Returns acquired=true, or acquired=false with holder{sessionId,ttlSecondsRemaining} when another session holds it. Params: assetPath, ttlSeconds? (default 300), sessionId?"
  asset.unlock:
    class_path: asset.unlock
    group: asset
    description: "Release an asset lock held by this editor (or force=true to break any holder's lock). Params: assetPath, force?, sessionId?"
  asset.list_locks:
    class_path: ue-mcp.bridge
    group: asset
    description: "List all currently-held asset locks with holder session id, acquiredAt, and ttlSecondsRemaining. Params: none"
    options:
      method: list_locks
  asset.unlock_all:
    class_path: asset.unlock_all
    group: asset
    description: "Release every lock held by one session in a single call, returning the number released. Defaults to the addressed editor's own session; pass sessionId to clear a different one (for example after a crashed session left assets wedged). Params: sessionId?"
  asset.diff:
    class_path: ue-mcp.bridge
    group: asset
    description: "Semantic structural diff between two assets, dispatching on the asset's class. Blueprints: parent class, variables, functions, components, per-graph node and connection deltas. Skeleton and SkeletalMesh: raw bone additions and removals, reparenting (bone, fromParent, toParent), raw-index changes (bone, fromIndex, toIndex), and declared virtual-bone additions and removals, with hierarchyCompatible and editorCompatible reported SEPARATELY. That separation is the point: it answers whether two skeletons are bone-compatible enough to register as Compatible Skeletons or whether a retarget is required, because appending bones (virtual ones especially) leaves the shared hierarchy intact while reparenting an existing bone does not (#879). Deliberately OUT of scope and reported as such: reference-pose transforms (referencePoseCompared=false), export names (exportNamesCompared=false), sockets and retarget sources; structureScope spells the boundary out in the result. Both paths must be the same class. Other asset types report that diffing is not supported yet rather than failing opaquely. Params: assetPath, otherPath"
    options:
      method: diff_asset
  asset.epic_add_key:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Adds a key to a row. Params: curve_table, row_name, key"
    options:
      method: epic_call_tool
  asset.epic_add_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Adds a new row to the curve table with an optional default value. Params: curve_table, row_name, default_value?"
    options:
      method: epic_call_tool
  asset.epic_add_rows:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Adds new rows with default values to the data table. Params: data_table, row_names"
    options:
      method: epic_call_tool
  asset.epic_can_edit_asset:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Checks whether an asset can be edited. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_create:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Creates a new CurveTable asset. Params: folder_path, asset_name"
    options:
      method: epic_call_tool
  asset.epic_create__data_asset_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_asset.DataAssetTools] Creates a new DataAsset asset in the project. Params: folder_path, asset_name, asset_type"
    options:
      method: epic_call_tool
  asset.epic_create__data_table_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Creates a new DataTable asset with the specified column schema. Params: folder_path, asset_name, schema"
    options:
      method: epic_call_tool
  asset.epic_create__string_table_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Creates a new StringTable asset. Params: folder_path, asset_name"
    options:
      method: epic_call_tool
  asset.epic_create_folder:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Creates a folder at the specified path. Params: path"
    options:
      method: epic_call_tool
  asset.epic_delete:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Deletes an asset or folder. Params: path"
    options:
      method: epic_call_tool
  asset.epic_duplicate:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Makes a copy of a folder or asset. Params: path, new_path"
    options:
      method: epic_call_tool
  asset.epic_exists:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Determines if a folder or asset exists. Params: path"
    options:
      method: epic_call_tool
  asset.epic_find_assets:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Searches the project for assets that match specific criteria. Params: folder_path, name, asset_type?, recursive?, tags?"
    options:
      method: epic_call_tool
  asset.epic_find_similar:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic SemanticSearchToolset.SemanticSearchToolset] Find assets whose embeddings are semantically similar to the given asset's embedding. Vector-only (no BM25). The source asset must already be indexed by the SemanticSearch plugin. Params: assetPath, classFilter, pathRegexes, k?"
    options:
      method: epic_call_tool
  asset.epic_generate_convex_collisions:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Generates convex hull collision shapes for a static mesh. Convex hulls provide accurate collision for physics simulation. More hulls improve accuracy but increase runtime cost. Replaces any existing collision. Params: mesh, hull_count?, max_hull_verts?, hull_precision?"
    options:
      method: epic_call_tool
  asset.epic_generate_lods:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Auto-generates LODs for a static mesh using triangle reduction. Each entry in triangle_percents creates one additional LOD. The value is the fraction of triangles to keep relative to LOD 0, from just above 0.0 (nearly empty) to 1.0 (full detail). For example, [0.5, 0.25] creates LOD1 with 50% of the original triangles and LOD2 with 25%. Params: mesh, triangle_percents"
    options:
      method: epic_call_tool
  asset.epic_get_asset_class:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Gets the class of an asset. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_get_asset_tags:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Gets the asset registry tags for an asset. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_get_bounds:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns the local-space bounding box of a static mesh. Params: mesh"
    options:
      method: epic_call_tool
  asset.epic_get_dependencies:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Lists assets that the specified asset depends on. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_get_entry:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Returns the source string for a specific key. Params: string_table, key"
    options:
      method: epic_call_tool
  asset.epic_get_items:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic DataRegistryToolset.DataRegistryTools] Returns cached item data. Items must be loaded in the registry cache to be returned. Params: registryName, itemNames"
    options:
      method: epic_call_tool
  asset.epic_get_keys:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Returns all keys for a row. Params: curve_table, row_name"
    options:
      method: epic_call_tool
  asset.epic_get_lod_count:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns the number of LODs in a static mesh asset. Params: mesh"
    options:
      method: epic_call_tool
  asset.epic_get_lod_thresholds:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns the screen-size thresholds at which each LOD becomes active. Screen size is a ratio of the mesh's screen height to the viewport height. A value of 1.0 means the mesh fills the full viewport height; values above 1.0 are valid and mean the mesh must appear larger than the viewport before the next LOD activates. Each LOD activates when the mesh appears smaller than its threshold. Params: mesh"
    options:
      method: epic_call_tool
  asset.epic_get_material:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns the material assigned to a named slot on a static mesh. Params: mesh, slot_name"
    options:
      method: epic_call_tool
  asset.epic_get_material_slots:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns the names of all material slots in a static mesh. Material slot names are used when assigning materials to specific parts of the mesh. Use these names with get_material and set_material. Params: mesh"
    options:
      method: epic_call_tool
  asset.epic_get_metadata_tags:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Gets the metadata tags for an asset. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_get_namespace:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Returns the namespace of a StringTable asset. Params: string_table"
    options:
      method: epic_call_tool
  asset.epic_get_plugin_content_paths:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Returns the root content paths for plugins that have content. Params: include_engine?"
    options:
      method: epic_call_tool
  asset.epic_get_referencers:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Lists assets that reference the specified asset. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_get_registry_info:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic DataRegistryToolset.DataRegistryTools] Returns detailed information about a specific registry. Params: registryName"
    options:
      method: epic_call_tool
  asset.epic_get_rows:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Returns the column values for one or more rows as a JSON string. Params: data_table, row_names"
    options:
      method: epic_call_tool
  asset.epic_get_schema:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic DataRegistryToolset.DataRegistryTools] Returns the item struct schema as JSON. Params: registryName"
    options:
      method: epic_call_tool
  asset.epic_get_schema__data_table_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Returns the column schema of the data table as a JSON string. Params: data_table"
    options:
      method: epic_call_tool
  asset.epic_get_size:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.texture.TextureTools] Returns the dimensions of a Texture2D in pixels. Params: texture"
    options:
      method: epic_call_tool
  asset.epic_get_table_id:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Returns the table ID for a StringTable asset. The table ID is derived from the asset's package path and is used to reference the string table in text properties and localisation. Params: string_table"
    options:
      method: epic_call_tool
  asset.epic_get_triangle_count:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns the number of triangles in a specific LOD of a static mesh. Params: mesh, lod_index?"
    options:
      method: epic_call_tool
  asset.epic_get_vertex_count:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns the number of vertices in a specific LOD of a static mesh. Params: mesh, lod_index?"
    options:
      method: epic_call_tool
  asset.epic_import_file:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Imports a file from disk as a CurveTable asset. The file's first column is the row name; subsequent columns are sample times and values. interp_mode controls how the imported keys are interpolated between samples. Params: folder_path, asset_name, source_file, interp_mode"
    options:
      method: epic_call_tool
  asset.epic_import_file__data_table_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Imports a file from disk as a DataTable asset. The file's columns must match the property names in schema. Use search_row_structs to discover usable schema structs. Params: folder_path, asset_name, source_file, schema"
    options:
      method: epic_call_tool
  asset.epic_import_file__static_mesh_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Imports a mesh file from disk as a StaticMesh asset. Params: folder_path, asset_name, source_file, import_materials?, import_textures?, combine_meshes?"
    options:
      method: epic_call_tool
  asset.epic_import_file__string_table_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Imports a file from disk as a StringTable asset. The file must have a header row with at least 'Key' and 'SourceString' columns. Additional meta-data columns are imported but the namespace is not - the StringTable's namespace is derived from its asset path. Params: folder_path, asset_name, source_file"
    options:
      method: epic_call_tool
  asset.epic_import_file__texture_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.texture.TextureTools] Imports an image file from disk as a Texture2D asset. Params: folder_path, asset_name, source_file"
    options:
      method: epic_call_tool
  asset.epic_is_checked_out:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Checks whether an asset is checked out by the current user. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_is_dirty:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Checks whether an asset has unsaved changes. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_is_nanite_enabled:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Returns whether Nanite is enabled for a static mesh. Nanite is Unreal's virtualized geometry system that renders highly detailed meshes efficiently. It is most beneficial for meshes with many triangles. Params: mesh"
    options:
      method: epic_call_tool
  asset.epic_list_data_sources:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic DataRegistryToolset.DataRegistryTools] Returns the editor-defined sources configured on a Data Registry. These are the sources as authored on the registry asset, before any runtime expansion of meta sources. Params: registryName"
    options:
      method: epic_call_tool
  asset.epic_list_folders:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Lists the folders contained within a folder. Params: root_path, recursive?"
    options:
      method: epic_call_tool
  asset.epic_list_items:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic DataRegistryToolset.DataRegistryTools] Returns all item names in a Data Registry. Params: registryName"
    options:
      method: epic_call_tool
  asset.epic_list_keys:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Lists all keys in the string table. Params: string_table"
    options:
      method: epic_call_tool
  asset.epic_list_registries:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic DataRegistryToolset.DataRegistryTools] Returns the names of all registered Data Registries. Params: structFilter?"
    options:
      method: epic_call_tool
  asset.epic_list_rows:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Lists the names of all rows in the curve table. Params: curve_table"
    options:
      method: epic_call_tool
  asset.epic_list_rows__data_table_tools:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Lists the names of all rows in the data table. Params: data_table"
    options:
      method: epic_call_tool
  asset.epic_list_runtime_sources:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic DataRegistryToolset.DataRegistryTools] Returns the runtime sources for a Data Registry. This is the expanded list including transient child sources generated from meta sources. Will equal ListDataSources when the registry has no meta sources. Params: registryName"
    options:
      method: epic_call_tool
  asset.epic_load_asset:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Loads an asset from the project. Params: asset_path"
    options:
      method: epic_call_tool
  asset.epic_move:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Moves or renames an asset or folder. Params: path, new_path"
    options:
      method: epic_call_tool
  asset.epic_read_file:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Reads a text file from disk and returns its contents. Only files under /Game/, an enabled plugin's Content/ directory, or the project Saved/ directory may be read. Only plain text formats are supported. Params: file_path"
    options:
      method: epic_call_tool
  asset.epic_remove_collisions:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Removes all collision shapes from a static mesh. Params: mesh"
    options:
      method: epic_call_tool
  asset.epic_remove_entry:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Removes an entry from the string table. Params: string_table, key"
    options:
      method: epic_call_tool
  asset.epic_remove_lods:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Removes all auto-generated LODs from a static mesh, keeping only LOD 0. Params: mesh"
    options:
      method: epic_call_tool
  asset.epic_remove_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Removes a row from the curve table. Params: curve_table, row_name"
    options:
      method: epic_call_tool
  asset.epic_remove_rows:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Removes rows from the data table. Params: data_table, row_names"
    options:
      method: epic_call_tool
  asset.epic_rename_row:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Renames a row in the curve table. Params: curve_table, row_name, new_row_name"
    options:
      method: epic_call_tool
  asset.epic_rename_rows:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Renames one or more rows in the data table. Params: data_table, renames"
    options:
      method: epic_call_tool
  asset.epic_save_assets:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Saves assets to disk. Params: asset_paths"
    options:
      method: epic_call_tool
  asset.epic_search:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic SemanticSearchToolset.SemanticSearchToolset] Run a semantic search over the Content Browser assets indexed by the SemanticSearch plugin. Params: query, classFilter, pathRegexes, k?"
    options:
      method: epic_call_tool
  asset.epic_search_row_structs:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Finds structs that can be used as a DataTable schema. Params: struct_name?"
    options:
      method: epic_call_tool
  asset.epic_set_entry:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.string_table.StringTableTools] Adds or updates an entry in the string table. If the key already exists its value is replaced; otherwise a new entry is created. Params: string_table, key, value"
    options:
      method: epic_call_tool
  asset.epic_set_keys:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.curve_table.CurveTableTools] Replaces all keys in a row with the provided list. Params: curve_table, row_name, keys"
    options:
      method: epic_call_tool
  asset.epic_set_lod_thresholds:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Sets the screen-size thresholds at which each LOD becomes active. Screen size is a ratio of the mesh's screen height to the viewport height. A value of 1.0 means the mesh fills the full viewport height; values above 1.0 are valid and mean the mesh must appear larger than the viewport before the next LOD activates. Thresholds must be in strictly descending order (LOD 0 has the largest threshold), and there must be exactly one threshold per LOD. Params: mesh, thresholds"
    options:
      method: epic_call_tool
  asset.epic_set_material:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Assigns a material to a named slot on a static mesh asset. This affects all instances of the mesh that do not override the slot material. Use set_component_material_override to change materials on a single instance. Params: mesh, slot_name, material"
    options:
      method: epic_call_tool
  asset.epic_set_nanite_enabled:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.static_mesh.StaticMeshTools] Enables or disables Nanite for a static mesh. Changing this setting triggers a mesh rebuild. Nanite is most beneficial for high-polygon meshes. Low-polygon meshes may not benefit from Nanite. Params: mesh, enabled"
    options:
      method: epic_call_tool
  asset.epic_set_rows:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.data_table.DataTableTools] Sets column values for one or more rows. Params: data_table, values"
    options:
      method: epic_call_tool
  asset.epic_update_metadata_tags:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Sets or removes metadata tags on an asset. Params: asset_path, set_tags?, remove_tags?"
    options:
      method: epic_call_tool
  asset.epic_write_file:
    class_path: ue-mcp.bridge
    group: asset
    description: "[Epic editor_toolset.toolsets.asset.AssetTools] Writes text content to a file on disk. Only files under /Game/, an enabled plugin's Content/ directory, or the project Saved/ directory may be written. Only plain text formats are supported. Overwrites the file if it already exists. Params: file_path, content"
    options:
      method: epic_call_tool

  # ── blueprint ──
  blueprint.read:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read BP structure incl. SCS components AND inherited native components from the CDO (CharacterMesh0, CharMoveComp, etc.). assetPath also accepts a World/umap path such as /Game/Maps/SomeLevel, which resolves to that map's level script Blueprint at PersistentLevel.<MapName>; the result reports blueprintPath and isLevelScript so you can see what answered (#942). Params: assetPath, includeComponentProperties? (dump UPROPERTY name/type/value per component template; off by default) (#353/#370)"
    options:
      method: read_blueprint
  blueprint.list_variables:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List variables with name, type, guid, category, tooltip, exposeOnSpawn, blueprintReadOnly, private, and editFlag ('EditAnywhere'|'EditDefaultsOnly'|'EditInstanceOnly'|'none'). instanceEditable is true only for EditAnywhere and EditInstanceOnly - EditDefaultsOnly is class-defaults-only and reports false (#744). private is reported separately because it is a Blueprint-graph access flag, not a details-panel one. Pass includeValues to add each variable's RESOLVED default from the generated-class CDO (value, valueText, cppType, declaringClass, inherited) plus one packageDirty/persisted verdict for the listing; off by default so the declaration list stays small (#902). Params: assetPath, includeValues?, cursor?, limit?"
    options:
      method: list_blueprint_variables
  blueprint.list_functions:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List the whole graph surface of a Blueprint, matching what list_graphs reports. Every entry carries kind ('function' own declaration | 'override' of a parent function | 'interface' implementation | 'event_graph' | 'event' entry point on an event graph | 'macro' | 'delegate_signature' | 'subgraph' collapsed graph | 'inherited'), source ('own'|'parent'|'interface'), graphName, and declaringClass/declaringClassPath where one applies. name and nodeCount are unchanged; nodeCount is 0 for entries that are not graphs. Params: assetPath, includeInherited? (append overridable parent/interface functions this Blueprint has not implemented, default false), cursor?, limit? (#809)"
    options:
      method: list_blueprint_functions
  blueprint.read_graph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read graph nodes. Supports pagination, file dumps, and title/class node filters. assetPath accepts a World/umap path, resolved to that map's level script Blueprint (#942). With includeDefaults, literal FText pin values are returned inline (defaultValue falls back to the pin's text literal, plus an explicit defaultTextValue) and object-ref pins report defaultObject (#743). Params: assetPath, graphName, offset?, limit?, includePins?, includeDefaults?, includeComments?, dumpToFile?, outputPath?, titleFilter?, classFilter? (#560)"
    options:
      method: read_blueprint_graph
  blueprint.read_graph_summary:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Lightweight graph summary (~10KB). Returns nodes plus wires split across two arrays, execEdges and dataEdges, not a single combined edges array. Filterable node list. assetPath accepts a World/umap path, resolved to that map's level script Blueprint (#942). Params: assetPath, graphName?, titleFilter?, classFilter? (#560)"
    options:
      method: read_blueprint_graph_summary
  blueprint.get_execution_flow:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Trace exec pins from an entry point. assetPath accepts a World/umap path, resolved to that map's level script Blueprint (#942). Params: assetPath, graphName?, entryPoint?"
    options:
      method: get_blueprint_execution_flow
  blueprint.get_dependencies:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Forward (classes/functions/assets) or reverse (referencers) deps. Params: assetPath, reverse?"
    options:
      method: get_blueprint_dependencies
  blueprint.diff:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Semantic structural diff between two Blueprints (binary uassets are unreviewable in git). Compares parent class, variables (type/default), functions/macros, components, and per-graph node + connection deltas keyed on stable node GUIDs so edits are matched rather than shown as remove+add. Returns a structured delta plus a human summary and changeCount. Params: assetPath (base/A), otherPath (compare/B). Revision-based diffing (fromRevision/toRevision via source control) is a staged follow-up."
    options:
      method: diff_blueprint
  blueprint.create:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Create Blueprint. assetPath is the full destination, e.g. /Game/Blueprints/BP_Example; a name plus packagePath pair is accepted as the same thing. A .uasset suffix, an object suffix, and backslashes are normalized away rather than reaching the editor as an invalid asset name. Params: assetPath, name?, packagePath?, parentClass?"
    options:
      method: create_blueprint
  blueprint.add_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add variable. varType accepts bool/int/float/string/name/text/byte/vector/rotator/transform/gameplaytag, object:/Script/Module.Class, struct:/Game/Path/To/Struct, or a full class/struct path. Unrecognized types are rejected rather than silently defaulting. Params: assetPath, name, varType (alias: type), onConflict? (skip|error, default skip) (#745)"
    options:
      method: add_variable
  blueprint.set_variable_properties:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Edit variable properties. Note: replication is set with networking(set_property_replicated), and blueprintReadOnly is not writable here - list_variables reports it. Params: assetPath, name, editFlag? (EditAnywhere|EditDefaultsOnly|EditInstanceOnly|none - the exact value list_variables reports, and the only form that round-trips every state), instanceEditable? (two-state shorthand; mutually exclusive with editFlag), private?, category?, tooltip?, exposeOnSpawn?"
    options:
      method: set_variable_properties
  blueprint.create_function:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Create function. Params: assetPath, functionName"
    options:
      method: create_function
  blueprint.delete_function:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Delete function. Params: assetPath, functionName"
    options:
      method: delete_function
  blueprint.rename_function:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Rename function. Params: assetPath, oldName, newName"
    options:
      method: rename_function
  blueprint.add_node:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add graph node. For a CallFunction node bound to a custom C++ UFUNCTION, pass nodeParams {functionName, className (or targetClass) = /Script/Module.Class}; the function also resolves against the BP's own component classes and an unambiguous loaded BlueprintCallable function, producing a bound node with pins instead of a stub (#546). nodeClass='CallParent' places a 'Parent: <Function>' call bound to the parent implementation (functionName resolves against ParentClass) so an override graph can chain to the base (#688). Params: assetPath, graphName?, nodeClass, nodeParams?"
    options:
      method: add_node
  blueprint.delete_node:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Delete a node, addressed by nodeId (the GUID get_connections and find_nodes report) or by nodeName. Prefer the GUID: a title is ambiguous the moment a graph holds two nodes with the same one, and a GUID survives a recompile. Params: assetPath, graphName?, nodeId OR nodeName (#996)"
    options:
      method: delete_node
  blueprint.set_node_property:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set node pin default or struct property. Params: assetPath, graphName, nodeName, propertyName, value"
    options:
      method: set_node_property
  blueprint.connect_pins:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Wire nodes. Address each end by sourceNodeId/targetNodeId (the GUIDs get_connections and find_nodes report) or by sourceNode/targetNode title. Prefer the GUIDs: a title cannot pick one of five identically titled math nodes apart, which is what makes re-targeting wiring by title unsafe. Params: assetPath, sourceNodeId OR sourceNode, sourcePin, targetNodeId OR targetNode, targetPin, graphName?, breakExistingSource?, breakExistingTarget? (#996)"
    options:
      method: connect_pins
  blueprint.add_component:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add BP component. componentClass accepts short names (e.g. 'ChildActorComponent') or full paths. For a ChildActorComponent, pass childActorClass to set its ChildActorClass in the same call (a Blueprint path with or without _C, or a C++ class) (#526). Params: assetPath, componentClass, componentName?, parentComponent? (SCS parent for hierarchy - #115), childActorClass?"
    options:
      method: add_component
  blueprint.remove_component:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Remove SCS component. Params: assetPath, componentName"
    options:
      method: remove_component
  blueprint.set_component_property:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set property on SCS or inherited component. Inherited components go through the child BP's InheritableComponentHandler override template so the parent stays untouched. Pass value=null to clear a TObjectPtr/SoftObject/WeakObject/UClass/Interface reference (e.g. clear AnimClass on CharacterMesh0) (#420). Params: assetPath, componentName, propertyName, value"
    options:
      method: set_blueprint_component_property
  blueprint.set_component_override_materials:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Write OverrideMaterials on a mesh-component template (StaticMeshComponent / SkeletalMeshComponent / any UMeshComponent). Pass materialPaths as a string[] of material asset paths (empty array clears). Avoids any TArray<UObject*> coercion on the generic set_component_property path (#442). Params: assetPath, componentName, materialPaths"
    options:
      method: set_component_override_materials
  blueprint.add_timeline_track:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add a track to a Blueprint timeline. Creates the UTimelineTemplate if missing, builds the matching curve asset (float/vector/color/event), applies keyframes, bumps TimelineLength to cover the last key, then recompiles so K2Node_Timeline regenerates its output pins. Params: assetPath, timelineName, trackName, trackType ('float'|'vector'|'color'|'event'), keyframes ([{time, value}]). value is a number for float/event, {x,y,z} for vector, {r,g,b,a} for color (#457)"
    options:
      method: add_timeline_track
  blueprint.set_capsule_size:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Call UCapsuleComponent::SetCapsuleSize on a CapsuleComponent template (CharacterMovement-friendly path; raw property writes leave the visualizer stale). Pass either or both of halfHeight/radius. Returns the new + previous values. Params: assetPath, componentName, halfHeight?, radius? (#419)"
    options:
      method: set_capsule_size
  blueprint.get_component_property:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read a single property value from an SCS or inherited component template. Returns the ICH override value for child BPs if one exists. Params: assetPath, componentName, propertyName"
    options:
      method: get_blueprint_component_property
  blueprint.set_class_default:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set UPROPERTY on Blueprint CDO. Pass value=null to clear an object/class/interface reference (#420). Params: assetPath, propertyName, value"
    options:
      method: set_class_default
  blueprint.delete_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Delete a member variable. Params: assetPath, name"
    options:
      method: delete_variable
  blueprint.add_function_parameter:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add input or output parameter to a function. Params: assetPath, functionName, parameterName, parameterType?, isOutput?"
    options:
      method: add_function_parameter
  blueprint.set_variable_default:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set default value on a BP variable. Params: assetPath, name, value"
    options:
      method: set_variable_default
  blueprint.get_variable_default:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read the RESOLVED default of a BP variable off the generated-class CDO, which is what a write-compile-readback loop has to compare against (list_variables alone only proves the variable exists). Returns value (typed JSON), valueText (UE export text), cppType, declaringClass/declaringClassPath and inherited. It also states whether that value is on DISK: packageDirty is the package's own unsaved-changes flag and persisted is its inverse, so a value written into the CDO but never saved reports persisted=false with a persistenceNote instead of reading back as if it had landed (#931). authoredDefault/matchesAuthoredDefault appear when the variable carries a non-empty authored default string that the next recompile could apply. Params: assetPath, name (#902)"
    options:
      method: get_blueprint_variable_default
  blueprint.compile:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Compile Blueprint. Params: assetPath"
    options:
      method: compile_blueprint
  blueprint.list_node_types:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List node types. Params: category?, includeFunctions?, cursor?, limit?"
    options:
      method: list_node_types
  blueprint.get_connections:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read a Blueprint graph's edges, addressed by node GUID. read_graph reports each pin as connected true or false and never says to what, and read_graph_summary carries exec edges but no data edges, so there was no native way to answer \"what feeds this pin\" - which is what verifying or re-targeting wiring needs. Each edge carries fromNodeId/fromPin and toNodeId/toPin plus both node titles and classes, and a data edge also carries its pin category. Nodes are named by GUID because a title is exactly what is ambiguous when a graph holds five \"float * float\" nodes, and a GUID survives a recompile. Every edge is reported once, from its output side, which is the direction it actually has. Omit graphName to read every graph the Blueprint owns. Params: assetPath, graphName? OR graphSelector?, kind? (exec|data|all, default all), includeNestedGraphs? (default true), cursor?, limit? (#996)"
    options:
      method: get_blueprint_connections
  blueprint.find_nodes:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Find nodes anywhere in ONE Blueprint, across every graph it owns including collapsed and nested ones. Narrow by `titles` (case-insensitive substrings matched against the node title), `nodeClasses` (exact class names such as K2Node_VariableGet), or `variableName` with an optional `variableAccess` of get|set|any - which is how you find every read and write of a member, the variable analogue of search_call_sites. The three are alternatives rather than a conjunction, so one pass answers \"every get of bIsAiming, plus anything titled Sprint\". read_graph and epic_find_nodes take one named graph at a time and do not descend into a collapsed subgraph; this walks them all. Each hit carries the graph selector list_graphs reports, the node title, class and GUID, which filter matched it, and for a variable node the member name, whether it is a get or a set, and its parent class. authoredOnly (default true) leaves out transient compiler graphs. A call naming none of the three filters is refused rather than dumping the Blueprint. Params: assetPath (a World path resolves to its level script), titles?, nodeClasses?, variableName?, variableAccess?, includeNestedGraphs? (default true), authoredOnly? (default true), cursor?, limit? (#998/#1015)"
    options:
      method: search_blueprint_nodes
  blueprint.search_call_sites:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Find authored call-site NODES for named functions across a whole directory in ONE call, including nested and collapsed graphs. Replaces the enumerate-every-Blueprint / read-every-graph sweep that overwhelms the bridge on a large project. Narrowing is done by the Asset Registry BEFORE any package is loaded: candidates come from a recursive Blueprint filter over `directory`, then every candidate whose package does not depend on a package declaring one of `functionNames` is ruled out unloaded. That step is reported (stats.narrowedByRegistry, stats.blueprintsSkippedByRegistry) and can be turned off with narrowByRegistry=false; it is also skipped automatically when a requested name has no resolvable declaring class, so a filter never silently drops a hit. Each hit carries assetPath, packageName, graphName + graphSelector (the same selector list_graphs reports, so it is unambiguous for duplicate nested graph names) + graphObjectPath, nodeId, nodeTitle, memberName, resolvedFunction (absent with unresolvedTarget=true when the function is gone, which is itself an audit finding), declaringClass/declaringClassPath, parentCall, and pinDefaults for unlinked input pins. Pass includeNeighbours for the immediate execution and data neighbours of each call. Level script Blueprints are NOT registry assets (they live inside their map package), so they are excluded unless you pass includeLevelScripts, which sweeps the World assets under the directory and costs a full map load each. Bounded and paginated: limit (default 200, max 1000) + offset, maxBlueprints (default 2000) caps package loads, and dumpToFile writes the whole result set to a JSON file the way read_graph does. Params: functionNames[], className?, directory? (default /Game), includeNestedGraphs? (default true), includeLevelScripts?, includeNeighbours?, narrowByRegistry?, offset?, limit?, maxBlueprints?, dumpToFile?, outputPath?, cursor? (#945)"
    options:
      method: search_blueprint_call_sites
  blueprint.search_node_types:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Search placeable nodes across every loaded Blueprint function library (KismetMathLibrary, GameplayStatics, custom libraries) plus UEdGraphNode classes. Matches the C++ name, the palette label from DisplayName metadata, and Keywords metadata, ignoring case/spaces/underscores, so 'is point in box' finds IsPointInBox and 'vector length' finds VSize. Results are ranked and each carries an addNode block (nodeClass + nodeParams incl. targetClass) that add_node accepts verbatim. Params: query, limit? (default 50), className? (narrow to one owning class), includeGraphNodes?, cursor? (#808)"
    options:
      method: search_node_types
  blueprint.create_interface:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Create BP Interface. Params: assetPath"
    options:
      method: create_blueprint_interface
  blueprint.add_interface:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Implement interface. Params: blueprintPath, interfacePath"
    options:
      method: add_blueprint_interface
  blueprint.override_function:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Override an inherited interface implementation (inherited via the parent class) or an overridable parent virtual function, with the matching signature so it actually binds as the override (create_function makes a blank, non-binding graph). Returns kind ('function' with a graphName, or 'event' with a nodeId in EventGraph). Event-shaped functions become override events unless preferFunction=true. This is the path for BehaviorTree TASK Blueprint entry points: ReceiveExecuteAI and ReceiveAbortAI on a UBTTask_BlueprintBase child come back as kind='event' in EventGraph, and their FinishExecute / FinishAbort terminators are placed with add_node nodeClass='CallFunction', className='/Script/AIModule.BTTask_BlueprintBase' (#886). Pass interfacePath to also implement the interface first if it is not present. Then wire the body with add_node (use nodeClass='CallParent' to chain to the base implementation). An animation layer override in a child Anim BP is created as an AnimationGraph on the animation schema, mirrored from the graph that declared the layer, so it actually binds instead of compiling to an inert K2 function graph; the result reports graphClass, schemaClass and animationGraph (#894). Params: assetPath, functionName, source? ('auto'|'interface'|'parent', advisory), preferFunction?, interfacePath? (#688)"
    options:
      method: override_function
  blueprint.list_overridable_functions:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List functions this Blueprint can override: inherited interface implementations and overridable parent virtuals. Each entry has name, source ('interface'|'parent'), declaringClass, canBeEvent. Params: assetPath (#688)"
    options:
      method: list_overridable_functions
  blueprint.list_graphs:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List all graphs in a blueprint. Returns selector/objectPath plus duplicateIndex/duplicateCount; pass selector back as graphName for unambiguous nested graph edits. assetPath accepts a World/umap path, resolved to that map's level script Blueprint (#942). Params: assetPath"
    options:
      method: list_blueprint_graphs
  blueprint.resolve_graph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Resolve a Blueprint graph name to selectors accepted by read_graph/add_node/connect_pins and other graph actions. assetPath accepts a World/umap path, resolved to that map's level script Blueprint (#942). Duplicate nested AnimBP names such as Locomotion return every match with selectors like Locomotion[3], object paths, and ambiguity metadata. Params: assetPath, graphName"
    options:
      method: resolve_blueprint_graph
  blueprint.add_event_dispatcher:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add event dispatcher (multicast delegate variable + signature graph + UFunction). Without parameters, broadcasters fire void(). With parameters, the signature graph gets typed user pins so K2Node_CallDelegate compiles cleanly (#276). Params: blueprintPath, name, parameters?: [{name, type}] where type is bool/int/float/string/name/text/vector/rotator/transform/object:/Script/Module.Class/struct:/Script/Module.Struct"
    options:
      method: add_event_dispatcher
  blueprint.duplicate:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Duplicate blueprint asset. Params: sourcePath, destinationPath"
    options:
      method: duplicate_blueprint
  blueprint.add_local_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add function-scope local variable. Params: assetPath, functionName, name, varType? (alias: type, default bool)"
    options:
      method: add_local_variable
  blueprint.list_local_variables:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List local variables in a function. Params: assetPath, functionName"
    options:
      method: list_local_variables
  blueprint.validate:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Validate blueprint without saving (compile + collect diagnostics). Params: assetPath"
    options:
      method: validate_blueprint
  blueprint.read_component_properties:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Dump ALL UPROPERTYs on a BP component template incl. array contents (#105). Params: assetPath, componentName"
    options:
      method: read_component_properties
  blueprint.get_component_collision:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read a component's EFFECTIVE collision: enabled mode, object type, profile name, and the resolved Block/Overlap/Ignore per channel. read_component_properties cannot answer this - it exports BodyInstance as text, and that text carries only CollisionProfileName plus the responses that DIFFER from the profile, so a response INHERITED from the profile (the Pawn profile blocking Camera) is invisible in it. Each channel reports response (what GetCollisionResponseToChannel actually returns), profileResponse, and overridesProfile, so you can see whether an answer came from the profile or from the component. assetPath takes a Blueprint path OR a native class (/Script/MyGame.MyCharacter or 'MyCharacter'), which is how you verify a C++ constructor's SetCollisionResponseToChannel really landed in the CDO after a rebuild. Project trace and object channels appear under their configured names with enumName alongside. channel narrows it to one; includeAllChannels adds the unused GameTraceChannel slots. Params: assetPath, componentName, channel?, includeAllChannels? (#925)"
    options:
      method: get_component_collision
  blueprint.read_node_property:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read a node pin default OR a reflected node property for verification (#102). Params: assetPath, graphName?, nodeName, propertyName"
    options:
      method: read_node_property
  blueprint.reparent_component:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Reparent an SCS component under a new parent, recompile and save (#115). Refuses before touching anything when the Blueprint's .uasset is read-only on disk (not checked out) or on a protected mount, and reports saved plus saveError when the write did not reach disk (#932). Params: assetPath, componentName, newParent"
    options:
      method: reparent_component
  blueprint.reparent:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Change a Blueprint's ParentClass, recompile and save (#138). The save is part of the operation, so writability is checked FIRST: a read-only .uasset (never checked out of source control) or a protected mount returns reason='package_not_writable' naming the file, with nothing reparented, instead of taking the editor down with a fatal save error (#932). A write that still did not reach disk comes back with saved=false and saveError. Params: assetPath, parentClass (short name or full path)."
    options:
      method: reparent_blueprint
  blueprint.flush_ich:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Flush orphaned InheritableComponentHandler override records (invalid component-override entries invisible to read/remove_component). Recompiles + saves. Params: assetPath. Returns recordsBefore/After/Removed (#580)"
    options:
      method: flush_inheritable_component_handler
  blueprint.flush_component_templates:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Flush orphaned UBlueprint::ComponentTemplates left by deleted Add Component nodes using Unreal's native maintenance routine. Live Add Component templates are preserved. Recompiles + saves only when changed. Params: assetPath. Returns recordsBefore/After/Removed and removed template identities."
    options:
      method: flush_blueprint_component_templates
  blueprint.set_actor_tick_settings:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set actor CDO tick settings (#116). Params: assetPath, bCanEverTick?, bStartWithTickEnabled?, TickInterval?"
    options:
      method: set_actor_tick_settings
  blueprint.export_nodes_t3d:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Export graph nodes as T3D text (Ctrl+C equivalent) for bulk round-trip (#130). Params: assetPath, graphName?, nodeIds? (omit = whole graph)"
    options:
      method: export_nodes_t3d
  blueprint.import_nodes_t3d:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Paste a T3D node blob into a graph (Ctrl+V equivalent) for bulk authoring (#130). Params: assetPath, graphName?, t3d, posX?, posY?"
    options:
      method: import_nodes_t3d
  blueprint.set_cdo_property:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set UPROPERTY on any C++ class CDO (not just Blueprints). Params: className, propertyName, value (#182/#183)"
    options:
      method: set_cdo_property
  blueprint.get_cdo_properties:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read UPROPERTY values from any C++ class CDO. Params: className, propertyNames? (#183)"
    options:
      method: get_cdo_properties
  blueprint.run_construction_script:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Spawn temp actor, run construction script, return generated components and transforms. Params: assetPath, location? (#195)"
    options:
      method: run_construction_script
  blueprint.compile_all:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Batch compile + save Blueprints. Params: assetPaths[], save? (default true). Returns per-path status (compiled/failed/not_found) (#284)"
    options:
      method: compile_blueprints
  blueprint.author:
    class_path: blueprint.author
    group: blueprint
    description: "Author a whole Blueprint in one call: optionally create it (parentClass), then add components, variables and function stubs, then compile - one agent-facing action instead of a dozen add_* round-trips. Params: assetPath, parentClass? (create/ensure the BP if given), components? [{componentClass, componentName?, parentComponent?, childActorClass?}], variables? [{name, varType}], functions? [{functionName}], compile? (default true). Returns per-step results + created flag. (#607)"
  blueprint.cleanup_graph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Remove orphan/corrupted nodes (no class, blank title+no pins, missing target UFunction). Params: assetPath, graphName? (default: every graph) (#285)"
    options:
      method: cleanup_graph
  blueprint.connect_pins_batch:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Apply many pin connections in one call (single compile + save). Params: assetPath, graphName?, connections[]: [{sourceNode, sourcePin, targetNode, targetPin}] (#267)"
    options:
      method: connect_pins_batch
  blueprint.set_node_position:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Move a graph node to (posX, posY). Params: assetPath, graphName?, nodeId, posX, posY (#277)"
    options:
      method: set_node_position
  blueprint.auto_layout:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Topological layered layout for a graph. Eliminates the (0,0) stack from programmatic add_node. This has no inverse: it rewrites every node's position and set_node_position moves one node per call. It answers that by returning previousPositions, every node's coordinates from before the layout ran, which you replay through set_node_position to undo it. That capture is skipped above previousPositionsLimit nodes (default 200) so a large EventGraph does not return an unbounded array; capturePreviousPositions=true forces it on at any size, and the result says through previousPositionsCaptured which of the two happened. Decide BEFORE running it: once the layout has run the old coordinates are gone. Params: assetPath, graphName?, columnGap? (default 360), rowGap? (default 200), previousPositionsLimit? (default 200), capturePreviousPositions? (default false) (#277)"
    options:
      method: auto_layout_graph
  blueprint.list_interfaces:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List the interfaces this Blueprint implements AND the ones it inherits from anywhere in its parent chain. The two halves are told apart by source: an inherited entry carries inheritedFrom, naming the ancestor class that declares it, which for a grandparent is not the immediate parent; an entry the Blueprint declares itself has no ancestor to name and carries no inheritedFrom. Where an inherited interface extends another the base is listed as well, since declaring the derived one puts the base in force too. Every function each declares is reported and whether anything actually answers it: implementedAs is 'graph' (a function graph), 'event' (an override event on the event graph, which is what an interface function with no outputs becomes) or 'missing'. add_interface creates the contract; this is the only thing that reports whether the contract is met, and unimplementedCount is the number to watch. removable is false for an inherited interface, which remove_interface refuses. Params: assetPath"
    options:
      method: list_blueprint_interfaces
  blueprint.remove_interface:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Stop implementing an interface. The paired remove for add_interface, which until now had no inverse and therefore emitted no rollback. preserveFunctions=true keeps the implementations as ordinary Blueprint functions and is the non-destructive form; the default false deletes the interface's function graphs with it, and the result says so through rollbackLossy and names what re-adding would restore. Idempotent: an interface that is not implemented reports alreadyRemoved. An INHERITED interface is refused by name, and the refusal names the ancestor that declares it, which for a grandparent is not the immediate parent: reparenting or editing that class is the only way to drop it. Params: assetPath, interfacePath, preserveFunctions? (default false)"
    options:
      method: remove_blueprint_interface
  blueprint.set_function_properties:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set the flags and metadata of a Blueprint function or macro: pure, isConst, accessSpecifier, category, tooltip, keywords, compactNodeTitle, callInEditor, threadSafe, deprecated, deprecationMessage. These are NOT UPROPERTYs on the asset - they live on the function graph's entry node as FUNC_ flags and FKismetUserDeclaredFunctionMetadata - so asset(set_property) cannot reach them. Validates everything before writing anything, reports existed plus unchanged when the request is already the current state, and the rollback restores exactly the fields this call changed. A macro accepts the metadata half only; the flag half is refused by name because a macro compiles to no UFunction. Params: assetPath, functionName, pure?, isConst?, accessSpecifier? (public|protected|private), category?, tooltip?, keywords?, compactNodeTitle?, callInEditor?, threadSafe?, deprecated?, deprecationMessage?"
    options:
      method: set_function_properties
  blueprint.list_graph_parameters:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read the parameter signature of a function, macro, event-dispatcher signature or custom event: inputs and outputs with name, type, default value, by-ref and const. The read half of add_function_parameter and edit_graph_parameters. The reported type is the same string the write half accepts (int[], set<Name>, map<Name,int>, object:/Script/Engine.Actor), so a read can be echoed straight back as a call; typeRoundTrips is false for the few pin categories that vocabulary cannot spell. Name exactly one target. Params: assetPath, functionName? OR eventName?, graphName? (narrows an eventName search to one graph)"
    options:
      method: list_graph_parameters
  blueprint.edit_graph_parameters:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Full parameter CRUD over one mechanism, for every node that owns user-defined pins: a function, a macro, an event-dispatcher signature or a custom event. op=add appends one (creating the function's return node on the first output), remove deletes it, rename rewrites it after probing for a collision so a clash refuses instead of half-renaming, set_type retypes it, set_default writes its default as export text, reorder takes the COMPLETE order and validates every entry before a single pin moves. This is what closes add_function_parameter's documented missing remove. isOutput selects the return side; a custom event and a dispatcher signature are input-only and say so. Params: assetPath, op (add|remove|rename|set_type|set_default|reorder), functionName? OR eventName?, graphName?, isOutput?, parameterName?, parameterType?, newName?, defaultValue?, order?"
    options:
      method: edit_graph_parameters
  blueprint.rename_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Rename a member variable, rewriting every get and set node in this Blueprint's graphs, the RepNotify function name and the SCS entry for a component variable. delete_variable plus add_variable loses all of them. Reports referencesBefore, referencesUpdated and referencesStillOnOldName, so the fixup is counted rather than asserted. Refuses a name already taken by another variable on this Blueprint or by a property on the parent class, naming what is taken. Self-inverse rollback. Other Blueprints bind this member by its variable GUID, which the rename preserves; asset(get_asset_referencers) plus compile_all confirms them. Params: assetPath, oldName, newName"
    options:
      method: rename_blueprint_variable
  blueprint.get_variable_metadata:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read every metadata key on a member or local variable (ClampMin, UIMin, EditCondition, Bitmask, BitmaskEnum, MakeEditWidget, MultiLine, and so on). list_variables reports the declaration and set_variable_properties covers category, tooltip and the edit flags; this is the arbitrary-key half, which is not a UPROPERTY and has no other reader. Pass functionName for a local variable. Params: assetPath, name, functionName?"
    options:
      method: get_blueprint_variable_metadata
  blueprint.set_variable_metadata:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Write metadata keys on a member or local variable. metadata is an object of key/value strings; a JSON null value removes that key. Unreal stores every metadata value as text, so ClampMin is '0' and EditCondition is 'bEnabled'. Validates the whole object and proves the variable exists in the named scope before writing anything, because the engine setter silently does nothing for a name it cannot find. Idempotent, and the rollback restores each key's previous value or removes it again if it did not exist. Pass functionName for a local variable. Params: assetPath, name, metadata, functionName?"
    options:
      method: set_blueprint_variable_metadata
  blueprint.edit_local_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Complete the local-variable CRUD that add_local_variable and list_local_variables leave half-built. op=rename rewrites the get and set nodes inside the function (a remove-then-add pair would delete them), remove drops the declaration, set_type retypes it, set_default writes its default as export text. remove is idempotent and reports alreadyRemoved. A Blueprint that has never compiled has no function signature to address a local variable through, and that is reported by name rather than failing silently. Params: assetPath, functionName, name, op (rename|remove|set_type|set_default), newName?, varType?, defaultValue?"
    options:
      method: edit_local_variable
  blueprint.list_event_dispatchers:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "List this Blueprint's event dispatchers with their parameter signatures. A dispatcher is a multicast-delegate member variable PLUS a separate signature graph, and hasSignatureGraph=false is exactly the state that produces a missing SignatureFunction error at compile time, so it is reported rather than left to be discovered. Params: assetPath"
    options:
      method: list_event_dispatchers
  blueprint.remove_event_dispatcher:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Remove an event dispatcher: BOTH the member variable and its signature graph. delete_variable removes only the variable and leaves an orphan graph the compiler still walks, which is why add_event_dispatcher's rollback pointed at delete_variable and was lossy. Captures the parameter signature first so the inverse re-declares the same one. Idempotent; a variable that is not a dispatcher is refused by name with its actual type. Bind, Unbind and Call nodes go with the variable and are not restored, and the result says so. Params: assetPath, name"
    options:
      method: remove_event_dispatcher
  blueprint.add_custom_event:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Add a custom event with a TYPED signature in one call. add_node(nodeClass='CustomEvent') can set the name and nothing else, and has no natural key, so calling it twice silently produces two events; this is idempotent on the event name. parameters is [{name, type}] and becomes the event's output pins. netMode (none|multicast|server|client) with reliable sets the replication flags, and callInEditor exposes the button in the details panel. Every parameter type is validated before the node is created. Params: assetPath, eventName, graphName? (default EventGraph), parameters?, netMode? (none|multicast|server|client), reliable? (default true), callInEditor?, posX?, posY?"
    options:
      method: add_custom_event
  blueprint.create_macro:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Create a Blueprint macro graph with its input and output signature. Macros could be listed by list_functions and read by read_graph but never authored: create_function makes a function, and there was no macro path at all. inputs and outputs are [{name, type}] and land on the entry and exit tunnel nodes; every type is validated before the graph exists. Idempotent on the macro name, and a clash with an existing function or dispatcher graph is refused because graph names share one namespace. Params: assetPath, macroName, inputs?, outputs?, onConflict? (skip|error)"
    options:
      method: create_macro
  blueprint.delete_graph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Delete any graph in a Blueprint, addressed the way list_graphs names one. This is the call for a collapsed subgraph that outlived its composite node: delete_function only walks FunctionGraphs and delete_macro only walks MacroGraphs, so both answer alreadyDeleted for an orphan that list_graphs plainly still reports, and leftover variable gets inside it go on blocking delete_variable. While the owning node still exists, delete_node is the right call instead - it takes the bound graph with it - and this refuses rather than leaving the node behind, unless you pass force. Deleting an event graph is refused for the same reason. Idempotent, and reports what it removed: kind, whether it was orphaned, and how many nodes went with it. Params: assetPath, graphName OR graphSelector (needed when two graphs share a name), force? (#1010)"
    options:
      method: delete_graph
  blueprint.delete_macro:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Delete a macro graph. Idempotent (alreadyDeleted). Reports orphanedInstances, the number of macro instance nodes elsewhere in the Blueprint that referenced it and are now broken - blueprint(cleanup_graph) removes those. The rollback records the macro's input and output signature so re-creating restores the contract; the BODY is not restored and the result says so. Params: assetPath, macroName"
    options:
      method: delete_macro
  blueprint.read_enum:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read a UserDefinedEnum's whole definition: description, the bitflags switch, and every enumerator with its index, authored short name, editable display name, numeric value and tooltip. asset(list_enum_values) reports the first four columns; this adds the tooltips, the bitflags state and the description, which nothing else could read. Works on native UEnums too, reporting isUserDefined=false. Returns objectPath, because EnumDescription is an ordinary UPROPERTY and belongs to asset(set_property) rather than to a setter here. Params: assetPath"
    options:
      method: read_user_defined_enum
  blueprint.reorder_enum_values:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Reorder a UserDefinedEnum's enumerators. Enumerator order is authored data with no property behind it, so nothing else could change it. order is the COMPLETE list by display name, authored name or index; a partial list, an unknown entry or a repeat is refused before anything moves, naming the valid values. Idempotent: an order that already holds reports unchanged. The rollback replays the previous order by authored name, which survives further reordering. Params: assetPath, order"
    options:
      method: reorder_enum_values
  blueprint.set_enum_metadata:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set a UserDefinedEnum's bitflags switch and its per-enumerator tooltips. The bitflags state is a class metadata key and a tooltip is package metadata keyed by enumerator index, so neither is reachable by a property write. entries is [{name or index, tooltip}] and every entry is resolved before the first write, so a bad entry at position nine leaves the first eight untouched. Idempotent, with a rollback carrying the previous bitflags state and the previous tooltip text keyed by authored name. The enum's own description is a UPROPERTY: use asset(set_property) with propertyName='EnumDescription'. Params: assetPath, bitflags?, entries?"
    options:
      method: set_enum_metadata
  blueprint.read_struct:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Read a UserDefinedStruct's whole definition: compile status, validity including the recursion check, the struct tooltip, and per field the display name, GUID, type, default value, tooltip, editableOnInstance, saveGame, multiLineText, widget3D and metadata. asset(list_struct_fields) reports name, GUID and a display label; everything else here had no reader. Two fields earn their place on their own. type is a round-trippable spec (int[], set<Name>, map<Name,int>, struct:/Script/CoreUObject.Vector) that can be handed back to a write. propertyName is the generated FProperty name (<name>_<n>_<guid>), which is what a DataTable row and any exported default actually carry, and it is a different thing from the display name; nameMatchesProperty reports whether the two still agree, so after a rename you can see whether the storage key still carries the old spelling. That divergence is the visible cost of a GUID-preserving rename, which is what lets existing pins and rows survive one at all. Params: assetPath"
    options:
      method: read_user_defined_struct
  blueprint.set_struct_field_default:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set a UserDefinedStruct member's default value. A member is an FStructVariableDescription inside the struct's editor data, not a UPROPERTY on the asset, so asset(set_property) cannot reach it; the engine call also parses the text against the member's real property and recompiles the struct so every dependent default is repaired, which a raw write would skip. defaultValue is Unreal export text ('5', 'true', '(X=1.000000,Y=2.000000,Z=0.000000)'); an empty string clears it. Resolve the field by fieldName (display or internal name) or fieldGuid. Idempotent, with a rollback carrying the previous value. Params: assetPath, defaultValue, fieldName? OR fieldGuid?"
    options:
      method: set_struct_field_default
  blueprint.reorder_struct_fields:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Reorder a UserDefinedStruct's members. Member order decides the details-panel layout and the DataTable column order and is authored data with no property behind it. order is the COMPLETE list by display name, internal name or GUID; a partial list, an unknown entry or a repeat is refused before anything moves. Idempotent. A move the engine refuses mid-sequence is reported with complete=false and the count that landed, never as a plain success, and the rollback restores the original order by GUID. Params: assetPath, order"
    options:
      method: reorder_struct_fields
  blueprint.edit_struct_metadata:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "Set a UserDefinedStruct's tooltip and its per-member tooltip, editableOnInstance, saveGame, multiLineText, widget3D and arbitrary metadata, in one batched call. All of these live in the struct's editor data rather than as UPROPERTYs, and each has an engine setter that recompiles the struct. fields is [{fieldName or fieldGuid, tooltip?, editableOnInstance?, saveGame?, multiLineText?, widget3D?, metadata?}]. Every member is resolved and every type-gated switch is checked (multiLineText needs a text-like member, widget3D a Vector or Transform) BEFORE the first write, so a refusal on entry nine leaves entries one through eight untouched. Idempotent; the rollback restores every value by GUID, and states the one thing it cannot: a metadata key that did not exist before is restored to an empty string rather than removed. Params: assetPath, tooltip?, fields?"
    options:
      method: edit_struct_metadata
  blueprint.epic_add_component_bound_event:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Creates a component bound event node in the event graph. Params: component, event_name, graph"
    options:
      method: epic_call_tool
  blueprint.epic_add_event:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds an event node to the Blueprint's event graph. If event_name matches an inherited overridable event, the new node is an override of that event. Otherwise a new custom event with the given name is created. Idempotent - if an event node with that name already exists, that node is returned. Params: blueprint, event_name, position?"
    options:
      method: epic_call_tool
  blueprint.epic_add_event_dispatcher:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds an event dispatcher to a Blueprint. Params: blueprint, name"
    options:
      method: epic_call_tool
  blueprint.epic_add_function_graph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds a function graph to the Blueprint. If graph_name matches an inherited overridable function, the new graph is a function-graph override of that function. Idempotent - if a graph with that name already exists, that graph is returned. Params: blueprint, graph_name"
    options:
      method: epic_call_tool
  blueprint.epic_add_function_param:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds an input or output to a function or event dispatcher Params: graph, param_name, param_type, input_param, container_type?"
    options:
      method: epic_call_tool
  blueprint.epic_add_node_pin:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds a pin to a node that supports dynamic pin addition. Works for Switch nodes (adds one case pin), Sequence nodes (adds one Then output), commutative binary operators like Add/Multiply (adds one input), Make Array nodes, etc. Params: node"
    options:
      method: epic_call_tool
  blueprint.epic_add_object_function_param:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds an object reference input or output to a function or event dispatcher. Params: graph, param_name, object_class, input_param, container_type?"
    options:
      method: epic_call_tool
  blueprint.epic_add_object_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds a member or local variable that holds an object reference to a Blueprint. Params: blueprint, name, object_class, graph?, container_type?"
    options:
      method: epic_call_tool
  blueprint.epic_add_struct_function_param:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds a struct input or output to a function or event dispatcher. Params: graph, param_name, struct_type, input_param, container_type?"
    options:
      method: epic_call_tool
  blueprint.epic_add_struct_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds a member or local variable of a struct type to a Blueprint. Use this to add variables of any UStruct type, including custom structs and engine structs not in the basic list supported by add_variable (e.g. HitResult, GameplayTag). Params: blueprint, name, struct_type, graph?, container_type?"
    options:
      method: epic_call_tool
  blueprint.epic_add_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds a member or local variable to a Blueprint. Supported type names: Primitives: 'bool', 'int', 'float', 'byte', 'string', 'name', 'text' Structs: 'Vector', 'Rotator', 'Transform', 'Vector2D', 'LinearColor' Params: blueprint, name, type_name, graph?, container_type?"
    options:
      method: epic_call_tool
  blueprint.epic_arrange_nodes:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Arranges a list of nodes in a readable left-to-right layout. Organizes nodes into columns based on data/execution flow, with producer nodes to the left of the nodes they feed into. Call this after building a graph to avoid nodes overlapping. Connections to nodes outside the list are used as anchors so the arranged nodes integrate cleanly with the rest of the graph. Params: nodes"
    options:
      method: epic_call_tool
  blueprint.epic_break_pins:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Breaks the connection between two pins. Params: output_pin, input_pin"
    options:
      method: epic_call_tool
  blueprint.epic_compile_blueprint:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Compiles the given Blueprint. Blueprints should be compiled after all graph modifications are complete. Params: blueprint, warnings_as_errors?"
    options:
      method: epic_call_tool
  blueprint.epic_connect_pins:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Makes a connection between source (output) and dest (input) pins. Params: output_pin, input_pin"
    options:
      method: epic_call_tool
  blueprint.epic_create:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Creates a new Blueprint asset in the project. Params: folder_path, asset_name, asset_type"
    options:
      method: epic_call_tool
  blueprint.epic_create_node:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Adds a new node to the graph. Params: graph, type_id, pos, declaring_class?"
    options:
      method: epic_call_tool
  blueprint.epic_delete_node:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Deletes the node from its graph. Params: node"
    options:
      method: epic_call_tool
  blueprint.epic_find_node_categories:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Retrieves a list of available node categories in a Blueprint graph, optionally filtered by compatible input and/or output pin types Params: graph, category_filter, context_pins"
    options:
      method: epic_call_tool
  blueprint.epic_find_node_types:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Finds node types that can be created in a particular graph meeting the search criteria. Params: graph, type_id_filter, context_pins"
    options:
      method: epic_call_tool
  blueprint.epic_find_nodes:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Finds nodes in a graph by title, class, and/or execution role. All filters are optional and ANDed together. Useful for locating specific event chains in large graphs like EventGraph before reading them with get_connected_subgraph. Params: graph, title, node_class?, entry_points_only?"
    options:
      method: epic_call_tool
  blueprint.epic_get_connected_subgraph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Returns detailed information for all nodes connected to the given node. Use this alongside find_nodes to read a single event chain from a large graph (like Event Graph) without reading the entire graph. Params: node"
    options:
      method: epic_call_tool
  blueprint.epic_get_create_event_function:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Returns the function currently bound to a Create Event node. Params: node"
    options:
      method: epic_call_tool
  blueprint.epic_get_default_object:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Returns the Class Default Object (CDO) for a Blueprint's. ObjectTools list/set/get property will get the CDO automatically, so this should primarily be used before calling tools that want to operate on the object inside the blueprint (like ActorTools). Params: blueprint"
    options:
      method: epic_call_tool
  blueprint.epic_get_graph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Retrieves a specific graph from a Blueprint asset by name. Params: blueprint, graph_name"
    options:
      method: epic_call_tool
  blueprint.epic_get_graph_dsl_docs:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Returns the full syntax reference for write_graph_dsl. Call this before using write_graph_dsl for the first time. Params: none"
    options:
      method: epic_call_tool
  blueprint.epic_get_node_infos:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Retrieves detailed information for a list of Blueprint graph nodes. Params: nodes"
    options:
      method: epic_call_tool
  blueprint.epic_get_node_type_pins:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Returns the pin names and types for a node type. Params: graph, type_id"
    options:
      method: epic_call_tool
  blueprint.epic_get_parent:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Returns the parent class of a Blueprint. Params: blueprint"
    options:
      method: epic_call_tool
  blueprint.epic_get_pin_value:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Gets the value of a Blueprint graph pin. Params: pin"
    options:
      method: epic_call_tool
  blueprint.epic_get_variable_category:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Gets the user-defined category of a Blueprint member variable. Categories group variables in the My Blueprint panel. Variables with no explicit category default to the Blueprint's name in the UI. Params: blueprint, variable_name"
    options:
      method: epic_call_tool
  blueprint.epic_get_variable_replication:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Gets the replication mode of a Blueprint member variable. Params: blueprint, variable_name"
    options:
      method: epic_call_tool
  blueprint.epic_list_compatible_event_functions:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Lists functions that can be bound to a Create Event node. Params: node"
    options:
      method: epic_call_tool
  blueprint.epic_list_component_events:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Lists the bindable delegate events available on a component. Params: component"
    options:
      method: epic_call_tool
  blueprint.epic_list_event_dispatchers:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Lists all event dispatchers defined on a Blueprint. Params: blueprint"
    options:
      method: epic_call_tool
  blueprint.epic_list_events:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Lists all events visible on the Blueprint - locally defined custom events plus inheritable events from the parent class chain and implemented interfaces. Params: blueprint"
    options:
      method: epic_call_tool
  blueprint.epic_list_functions:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Lists all functions visible on the Blueprint - locally defined plus inheritable ones from the parent class chain and implemented interfaces. Params: blueprint"
    options:
      method: epic_call_tool
  blueprint.epic_list_graphs:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Lists all graphs in the Blueprint. Params: blueprint"
    options:
      method: epic_call_tool
  blueprint.epic_list_variables:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Lists member or local variables defined on a Blueprint. Params: blueprint, graph?"
    options:
      method: epic_call_tool
  blueprint.epic_read_graph_dsl:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Reads a Blueprint graph and returns a DSL script. The returned code uses the same syntax that write_graph_dsl accepts and can be edited and passed back to write_graph_dsl to modify the graph. Call get_graph_dsl_docs() for the full syntax reference. Params: graph"
    options:
      method: epic_call_tool
  blueprint.epic_remove_function_graph:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Removes a function graph or event dispatcher from the Blueprint. Params: blueprint, graph_name"
    options:
      method: epic_call_tool
  blueprint.epic_remove_function_param:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Removes an input or output from a function or event dispatcher. Params: graph, param_name, input_param"
    options:
      method: epic_call_tool
  blueprint.epic_remove_node_pin:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Removes a specific pin from a node that supports dynamic pin removal. Works for Switch nodes (removes one case pin), Sequence nodes (removes one Then output), commutative binary operators like Add/Multiply (removes one input), Make Array nodes, etc. Params: node, pin"
    options:
      method: epic_call_tool
  blueprint.epic_remove_variable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Removes a member or local variable from a Blueprint. Params: blueprint, name, graph?"
    options:
      method: epic_call_tool
  blueprint.epic_retarget_node_class:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Replaces a node's baked-in class reference from old_class to new_class in place. If the node's current class reference matches old_class it is replaced with new_class and the node is reconstructed so its pins reflect the new type. If the node already references new_class the call is a no-op. When a Blueprint is duplicated, nodes in the copied graph retain class references pointing to the original Blueprint. Calling this on each node with the original and duplicate classes retargets them without manual delete-recreate-rewire cycles. Handles cast, function call, event, and multicast delegate nodes. The Blueprint must be compiled after all retargeting is complete. Params: node, old_class, new_class"
    options:
      method: epic_call_tool
  blueprint.epic_set_create_event_function:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Binds a function to a Create Event node. Use list_compatible_event_functions to find valid function names. Params: node, function_name"
    options:
      method: epic_call_tool
  blueprint.epic_set_node_position:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Sets a new position for the node. Params: node, pos"
    options:
      method: epic_call_tool
  blueprint.epic_set_parent:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Reparents a Blueprint to a new parent class. The Blueprint must be recompiled after reparenting. Params: blueprint, parent_class"
    options:
      method: epic_call_tool
  blueprint.epic_set_pin_value:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Sets the value of a Blueprint graph pin. Params: pin, value"
    options:
      method: epic_call_tool
  blueprint.epic_set_variable_category:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Sets the user-defined category on a Blueprint member variable. Categories group variables in the My Blueprint panel. Pass an empty string to reset to the default (the Blueprint's name). Params: blueprint, variable_name, category"
    options:
      method: epic_call_tool
  blueprint.epic_set_variable_instance_editable:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Sets whether a member variable is editable per-instance on actors placed in the level. Params: blueprint, variable_name, instance_editable"
    options:
      method: epic_call_tool
  blueprint.epic_set_variable_replication:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Sets the replication mode on a Blueprint member variable. RepNotify will automatically create an OnRep_ function on the Blueprint if one does not already exist. Params: blueprint, variable_name, replication"
    options:
      method: epic_call_tool
  blueprint.epic_write_graph_dsl:
    class_path: ue-mcp.bridge
    group: blueprint
    description: "[Epic editor_toolset.toolsets.blueprint.BlueprintTools] Populates a Blueprint graph with nodes from a DSL script and compiles the Blueprint. Call get_graph_dsl_docs() for the full syntax reference and examples. Params: graph, code"
    options:
      method: epic_call_tool

  # ── level ──
  level.get_outliner:
    class_path: ue-mcp.bridge
    group: level
    description: "List actors (each row includes editorHidden - hidden in the viewport but still rendering in game, and folderPath). classFilter is a case-sensitive SUBSTRING over the class name by default; pass exactClass=true to require the exact class instead. folderPath matches one World Outliner folder exactly and folderPathPrefix matches that folder and everything nested under it, so narrowing a folder to a single class no longer needs a get_actor_details round trip per entry (#911). Params: classFilter?, exactClass?, nameFilter? (case-sensitive substring over the internal name OR the label), folderPath?, folderPathPrefix?, editorHidden? (filter to only hidden/only visible), world? (editor|pie|auto), limit?, includeStreaming?, cursor? (#717)"
    options:
      method: get_world_outliner
  level.set_editor_visibility:
    class_path: ue-mcp.bridge
    group: level
    description: "Bulk set editor-only visibility (temporarily-hidden-in-editor) on actors. Targets actorLabels[] or all=true. Params: hidden (required; true=hide, false=show), actorLabels?, all? (#717)"
    options:
      method: set_editor_visibility
  level.place_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Spawn actor. Pass world:pie to spawn into the running PIE world (#585). Params: actorClass, label?, location?, rotation?, scale?, staticMesh?, material?, world? (editor|pie)"
    options:
      method: place_actor
  level.delete_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Remove actor. Params: actorLabel OR actorPath. A label matching several actors is refused with the candidates rather than deleting one of them (#983)"
    options:
      method: delete_actor
  level.get_actor_details:
    class_path: ue-mcp.bridge
    group: level
    description: "Inspect actor. With includeProperties=true, a UPROPERTY declared as a C-style FIXED ARRAY (int32 Foo[3], one FProperty with ArrayDim 3) comes back as a JSON ARRAY of its elements plus arrayDim, instead of element 0 presented as the whole value. That mattered on RecastNavMesh NavMeshResolutionParams, whose three entries are the Low, Default and High generation tiers, and where reporting only Low sent a user tuning cell sizes against numbers the engine was not using (#927). Params: actorLabel OR actorPath, includeProperties?, propertyName?, world? (editor|pie)"
    options:
      method: get_actor_details
  level.move_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Transform actor. Pass world:pie to move a live PIE actor (resolves labels/names from get_outliner {world:pie}) (#586). Params: actorLabel OR actorPath, location?, rotation?, scale?, world? (editor|pie)"
    options:
      method: move_actor
  level.aim_actor_at:
    class_path: ue-mcp.bridge
    group: level
    description: "Rotate an actor so its forward (+X) points at a target. Params: actorLabel OR actorPath, targetPoint (Vec3) OR targetActor (label) OR targetActorPath, roll? (default 0), world? (editor|pie) (#566/#983)"
    options:
      method: aim_actor_at
  level.nav_project_point:
    class_path: ue-mcp.bridge
    group: level
    description: "Project a world point onto the navmesh. Returns onNavMesh + projectedLocation. Params: point (Vec3), extent? (Vec3, default 100), world? (editor|pie) (#585)"
    options:
      method: nav_project_point
  level.select:
    class_path: ue-mcp.bridge
    group: level
    description: "Select actors. A label naming several actors selects all of them; selectedPaths reports exactly which. Params: actorLabels[] and/or actorPaths[] (#983)"
    options:
      method: select_actors
  level.get_selected:
    class_path: ue-mcp.bridge
    group: level
    description: "Get selection. Params: none"
    options:
      method: get_selected_actors
  level.add_component:
    class_path: ue-mcp.bridge
    group: level
    description: "Add component to actor. Params: actorLabel OR actorPath, componentClass, componentName?"
    options:
      method: add_component_to_actor
  level.remove_component:
    class_path: ue-mcp.bridge
    group: level
    description: "Remove instance component from a level actor by name. Idempotent: returns alreadyDeleted=true if no matching component exists. Params: actorLabel OR actorPath, componentName (#426)"
    options:
      method: remove_component_from_actor
  level.set_component_property:
    class_path: ue-mcp.bridge
    group: level
    description: "Set component prop. A C-style FIXED ARRAY element is addressed by index in the dotted path ('Foo[2].Bar'); without an index every write lands on element 0 (#927). Pass value=null to clear a TObjectPtr/SoftObject/WeakObject/UClass/Interface reference (#420). Resolves inherited/SCS components on placed Blueprint instances case-insensitively, and refreshes the scene transform after RelativeLocation/RelativeRotation/RelativeScale3D writes (#539). Pass world='pie' (with optional pieInstance) to write to a LIVE PIE component rather than the editor one (#763). Params: actorLabel OR actorPath, componentName, propertyName, value, world?, pieInstance?"
    options:
      method: set_component_property
  level.nudge_component:
    class_path: ue-mcp.bridge
    group: level
    description: "Adjust an exact named SceneComponent using frame-relative translation, quaternion rotation, or uniform relative scale. frame=parent uses the parent COMPONENT transform; the socket is reported separately. viewRotation is observer-relative to the selected frame, not a player camera: clockwise/counterclockwise is resolved using Unreal screen basis and returned as signed degrees. dryRun=false by default; dryRun returns current spatial inspection plus requested world location/rotation and relative scale without a transaction, modification, setter, dirtiness, or rollback. Params: actorLabel OR actorPath, componentName, frame? (world|actor default|parent|component), translationDelta? ({forwardCm?,rightCm?,upCm?}), axisRotation? ({axis:forward|right|up,degrees}) XOR viewRotation? ({viewFrom:front|back|right|left|above|below,direction:clockwise|counterclockwise,degrees>0}), scaleMultiplier? (>0), dryRun?, world? (editor|pie), pieInstance?"
    options:
      method: nudge_component
  level.get_component_details:
    class_path: ue-mcp.bridge
    group: level
    description: "Read a placed actor's component transforms. With componentName returns that component's relative+world location/rotation/scale, class, and attach parent; without it lists every component. With includeValues=true also dumps arbitrary UPROPERTY values (custom fields, CharacterMovement MaxWalkSpeed, etc.); world='pie' reads the live PIE instance (#539/#584). Params: actorLabel OR actorPath, componentName?, includeValues?, propertyNames? (filter), world? (editor|pie)"
    options:
      method: get_component_details
  level.get_current:
    class_path: ue-mcp.bridge
    group: level
    description: "Get current level name and path. Params: none"
    options:
      method: get_current_level
  level.load:
    class_path: ue-mcp.bridge
    group: level
    description: "Load level. Params: levelPath"
    options:
      method: load_level
  level.clear_level_script:
    class_path: ue-mcp.bridge
    group: level
    description: "Preview or remove every node and member variable from the currently loaded persistent level's Level Blueprint. Defaults to dryRun=true. Actors are untouched; save=true saves only the current level after a successful compile. Params: dryRun?, save?"
    options:
      method: clear_level_script
  level.save:
    class_path: ue-mcp.bridge
    group: level
    description: "Save the level currently being edited, and say what happened to each package. Takes the same package path editor(save_dirty) uses, so the two can no longer disagree about whether one package was written: the old action returned a bare {success:false, error:'Failed to save current level'} on a map that save_dirty then wrote seconds later, and a false failure makes an agent redo work that was already on disk (#964). On a World Partition or one-file-per-actor map the actors live in their own packages, so those are saved too and each is reported separately. A package that was already clean is reported as skipped, not as a failure. On failure the error names the package, the resolved file, whether that file exists and is read-only, the engine's ESavePackageResult and the error/warning lines the save itself emitted. Saving during PIE is refused by name rather than failing silently. Params: force? (write even a clean package, default false), includeExternalActors? (default true)"
    options:
      method: save_level
  level.list:
    class_path: ue-mcp.bridge
    group: level
    description: "List levels: the persistent level first, then every streaming level with its loaded and visible state, in the world's own streaming order. Params: directory?, recursive?, cursor?, limit?"
    options:
      method: list_levels
  level.create:
    class_path: ue-mcp.bridge
    group: level
    description: "Create a new level at a path and open it. levelPath is required and is validated before the engine is asked: an omitted or malformed path is refused outright, because NewLevel(\"\") logs an engine error and lands the editor on an untitled map that cannot then be saved. Params: levelPath, templateLevel? (omit, \"Empty\" or \"None\" for a blank level) (#833)"
    options:
      method: create_new_level
  level.spawn_volume:
    class_path: ue-mcp.bridge
    group: level
    description: "Place volume. Params: volumeType, location?, extent?, label?"
    options:
      method: spawn_volume
  level.list_volumes:
    class_path: ue-mcp.bridge
    group: level
    description: "List volumes, sorted by actor path so a page boundary is stable. Params: volumeType? (substring over the volume class name), cursor?, limit?"
    options:
      method: list_volumes
  level.set_volume_properties:
    class_path: ue-mcp.bridge
    group: level
    description: "Edit volume. Params: actorLabel OR actorPath, properties"
    options:
      method: set_volume_properties
  level.spawn_light:
    class_path: ue-mcp.bridge
    group: level
    description: "Place light. Params: lightType (point|spot|directional|rect|sky), location?, rotation?, intensity?, color? ({r,g,b} 0-255), attenuationRadius? (point/spot/rect only; #723), mobility? (static|stationary|movable; default movable so the light renders without a build), label? (#331/#310)"
    options:
      method: spawn_light
  level.set_light_properties:
    class_path: ue-mcp.bridge
    group: level
    description: "Edit a light OR SkyLight (intensity/color now work on SkyLight too, #608). Params: actorLabel OR actorPath, intensity?, color?, rotation? (DirectionalLight sun angle), mobility? (static|stationary|movable), recaptureSky?, volumetricScatteringIntensity?, sourceRadius? (point/spot), innerConeAngle?/outerConeAngle? (spot)"
    options:
      method: set_light_properties
  level.set_fog_properties:
    class_path: ue-mcp.bridge
    group: level
    description: "Edit ExponentialHeightFog incl. volumetric fog (#608). Params: actorLabel? OR actorPath?, fogDensity?, fogHeightFalloff?, startDistance?, fogInscatteringColor?, enableVolumetricFog?, volumetricFogScatteringDistribution?, volumetricFogExtinctionScale?, volumetricFogDistance?, volumetricFogAlbedo?"
    options:
      method: set_fog_properties
  level.get_actors_by_class:
    class_path: ue-mcp.bridge
    group: level
    description: "List actors by class. Matches Blueprint subclasses of a native base via IsChildOf when className resolves to a UClass (short name, /Script path, or BP class path), else falls back to name-substring. Each actor includes location/rotation/scale. Params: className, world? (editor|pie), matchSubclasses? (default true), includeTransforms? (default true) (#675)"
    options:
      method: get_actors_by_class
  level.get_actors_by_component_class:
    class_path: ue-mcp.bridge
    group: level
    description: "List actors that own a component of a given class (exact or substring match). Returns each actor plus its matchedComponents. For a whole-map question use level(query_components), which filters and counts in the editor instead of returning every candidate. Params: componentClass, world? (editor|pie) (#582)"
    options:
      method: get_actors_by_component_class
  level.query_components:
    class_path: ue-mcp.bridge
    group: level
    description: "Ask one level-wide question about components and get the answer, not the candidates. Selection, projection, predicates, grouping and counts all evaluate in the editor, so a 4,000 actor map costs one call instead of thousands. Computed (not raw) values: shadow.effectiveCastShadow crosses CastShadow with visibility, bHiddenInGame, owner hidden and bCastHiddenShadow, so a hidden debug marker with CastShadow=true is correctly NOT a shadow caster; every mobility and collision enum comes back as a clean name (Movable, QueryAndPhysics), never as EComponentMobility.MOVABLE; navigation.effectiveNavRelevant crosses bCanEverAffectNavigation with collision and custom navigable geometry. Never writes, and reports dirtiedPackages so you can see that. Field groups for 'fields' and for dot paths: transform, bounds, localBounds (unscaled, includes UBoxComponent BoxExtent), shadow, nanite, navigation, tick, materials (per slot: effective path, componentOverride vs meshDefault, slot name/index, isNull, isWorldGrid), mesh (adds instanceCount + hierarchical for an ISM/HISM, #986), decal, health (roll-up incl. suspect). A dot path used in where/groupBy/countBy turns its own group on. groupBy and countBy COUNT COMPONENTS, not instances: one HISM holding 5,000 instances counts as 1, so for per-mesh placement totals use level(summarize_static_mesh_usage), which sums instances and counts distinct actors. Params: componentClass?, actorClass?, matchSubclasses? (default true), componentNameContains?, actorLabelPrefix?, actorLabelContains?, actorTag?, folderPath?, folderPathPrefix?, fields? (string[]), propertyNames? (string[], projected component UPROPERTYs under props.*), where? ([{field, op, value}]; ops eq/ne/lt/lte/gt/gte/contains/notContains/startsWith/endsWith/in/notIn/exists/notExists/isNull/isNotNull/isTrue/isFalse), whereMode? (all|any, default all), suspectOnly?, groupBy?, countBy? (string[]), sampleLimit?, countOnly?, limit? (default 200, max 2000), startIndex?, duplicateTransformTolerance? (cm; reports duplicateTransformGroups), levelPath? (temporarily opens another map: refuses if anything is dirty and restores the map that was open), world? (editor|pie) (#910/#943/#912)"
    options:
      method: query_components
  level.count_actors_by_class:
    class_path: ue-mcp.bridge
    group: level
    description: "Histogram of actor classes in the level (sorted desc). Params: world? (editor|pie), topN? (#146)"
    options:
      method: count_actors_by_class
  level.get_runtime_virtual_texture_summary:
    class_path: ue-mcp.bridge
    group: level
    description: "List RuntimeVirtualTextureVolume actors + their bound VirtualTexture assets. Params: none (#150)"
    options:
      method: get_runtime_virtual_texture_summary
  level.set_water_body_property:
    class_path: ue-mcp.bridge
    group: level
    description: "Set a property on an actor's WaterBodyComponent (ShapeDilation, WaterLevel, etc.). Params: actorLabel OR actorPath, propertyName, value. Requires Water plugin. For spline edits use level(set_spline_points) (#151)"
    options:
      method: set_water_body_property
  level.build_lighting:
    class_path: ue-mcp.bridge
    group: level
    description: "Build lights. Params: quality?"
    options:
      method: build_lighting
  level.get_spline_info:
    class_path: ue-mcp.bridge
    group: level
    description: "Read a spline component's points, closedLoop, length, and per-point tangents/types. Works in editor or PIE (#553). Optional componentName picks a specific (custom) spline; projectPoint (Vec3) returns the closest location, inputKey, distanceAlongSpline, distanceToSpline, and tangent (#555). Params: actorLabel OR actorPath, componentName?, world? (editor|pie), projectPoint?"
    options:
      method: get_spline_info
  level.set_spline_points:
    class_path: ue-mcp.bridge
    group: level
    description: "Set spline points. A point is either a bare {x,y,z} or the typed form, which keeps what a position alone loses: pointType (Linear|Curve|Constant|CurveClamped|CurveCustomTangent), arriveTangent, leaveTangent, rotation and scale. The whole batch is validated before any existing geometry is cleared, so a malformed request leaves the spline as it was, and the edit goes through an editor transaction so Undo reaches it. componentName picks one spline on an actor carrying several. Params: actorLabel OR actorPath, points[], componentName?, closedLoop?, loopPosition?, loopPositionOverride? (#1029)"
    options:
      method: set_spline_points
  level.set_actor_material:
    class_path: ue-mcp.bridge
    group: level
    description: "Set material on actor. Params: actorLabel OR actorPath, materialPath, slotIndex?"
    options:
      method: set_actor_material
  level.get_world_settings:
    class_path: ue-mcp.bridge
    group: level
    description: "Read world settings (GameMode, KillZ, gravity, etc.). Params: none"
    options:
      method: get_world_settings
  level.set_world_settings:
    class_path: ue-mcp.bridge
    group: level
    description: "Set world settings. Params: defaultGameMode?, killZ?, globalGravityZ?, enableWorldBoundsChecks?"
    options:
      method: set_world_settings
  level.get_actor_bounds:
    class_path: ue-mcp.bridge
    group: level
    description: "Get actor AABB. Params: actorLabel. Returns origin + extent (#188)"
    options:
      method: get_actor_bounds
  level.get_component_tree:
    class_path: ue-mcp.bridge
    group: level
    description: "Deep component-tree dump for an actor. Returns per-component: name, class, attachParent, attachSocket, mobility, visibility, relative+world transforms, tags. For PrimitiveComponents adds collisionProfile/collisionEnabled/bounds/castShadow. For StaticMeshComponent adds staticMesh + materials[], and for an ISM/HISM also instanceCount plus instanced{hierarchical,numCustomDataFloats} - the number you need before deciding whether to touch an ISM at all, since a component holding three instances and one holding three hundred thousand were otherwise the same row (#986). For SkeletalMeshComponent adds skeletalMesh + skeleton + materials[]. For NiagaraComponent adds niagara{asset,active,visible} and for AudioComponent audio{sound,playing} - runtime FX state in PIE (#581). Params: actorLabel OR actorPath, world? (editor|pie), componentClass? (substring filter), includeProperties? (dump UPROPERTY name/type/value per component) (#240/#241/#302/#320/#370/#353/#581)"
    options:
      method: get_component_tree
  level.get_relative_transform:
    class_path: ue-mcp.bridge
    group: level
    description: "Compute target's transform in reference's local space (location/rotation/scale). Common dungeon/calibration workflow. Params: target/targetLabel (actor label) OR targetPath, reference/referenceLabel OR referencePath, world? (#386/#387/#983)"
    options:
      method: get_relative_transform
  level.resolve_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Resolve internal/runtime actor name to editor label. Params: internalName (e.g. StaticMeshActor_141). Returns actorLabel, actorPath, className, location (#178)"
    options:
      method: resolve_actor
  level.set_actor_property:
    class_path: ue-mcp.bridge
    group: level
    description: "Set per-instance UPROPERTY on a level actor. Params: actorLabel OR actorPath ('WorldSettings' as the label targets the world settings actor), propertyName (dotted paths like 'Foo.Bar' supported, and a C-style FIXED ARRAY element is addressed by index: 'NavMeshResolutionParams[1].CellSize' reaches the Default tier rather than always writing element 0 (#927)), value (string/number/bool/object/array; a label string resolves to an AActor* ref, and a JSON array of labels populates a TArray of actor refs #538), force? (bypass EditDefaultsOnly), world? (editor|pie) (#202/#230)"
    options:
      method: set_actor_property
  level.read_actor_motion:
    class_path: ue-mcp.bridge
    group: level
    description: "Snapshot motion telemetry for one or many actors: location, rotation, velocity, scale, angularVelocity (when simulating physics), grounded + distanceToGround (downward 200u trace). Defaults to the PIE world with editor fallback. Loop at your sample interval for long telemetry probes. Params: actorLabel? OR actorLabels (string[]) OR actorPath? OR actorPaths (string[]), world? ('pie'|'editor'), pieInstance? (#453)"
    options:
      method: read_actor_motion
  level.add_hismc_instances:
    class_path: ue-mcp.bridge
    group: level
    description: "Bulk-add transforms to a HISMC / ISMC component on an actor (Python add_instance crashes on UE 5.7; this is the C++ path). Params: actorLabel OR actorPath, componentName? (default: first ISMC/HISMC found), transforms ([{location: {x,y,z}, rotation?: {pitch,yaw,roll}, scale?: {x,y,z}}]), worldSpace? (default true) (#434)"
    options:
      method: add_hismc_instances
  level.get_instance_transforms:
    class_path: ue-mcp.bridge
    group: level
    description: "Read every instance transform on an actor's ISMC/HISMC. Params: actorLabel OR actorPath, componentName? (default first), worldSpace? (default true). Returns instances[{index, location, rotation, scale}] (#697)"
    options:
      method: get_instance_transforms
  level.update_instance_transform:
    class_path: ue-mcp.bridge
    group: level
    description: "Update a single ISMC/HISMC instance by index; unspecified location/rotation/scale are preserved. Params: actorLabel OR actorPath, componentName?, index, location?, rotation?, scale?, worldSpace? (default true) (#697)"
    options:
      method: update_instance_transform
  level.remove_instance:
    class_path: ue-mcp.bridge
    group: level
    description: "Remove a single ISMC/HISMC instance by index. Params: actorLabel OR actorPath, componentName?, index (#697)"
    options:
      method: remove_instance
  level.set_nanite_settings:
    class_path: ue-mcp.bridge
    group: level
    description: "Enable/disable + force-build Nanite on a UStaticMesh asset (rebuilds render data immediately, not on next cook). Params: assetPath, enabled? (default true), positionPrecision? (#696)"
    options:
      method: set_nanite_settings
  level.get_nanite_info:
    class_path: ue-mcp.bridge
    group: level
    description: "Read a UStaticMesh's Nanite state (naniteEnabled, positionPrecision, numLODs). Params: assetPath (#696)"
    options:
      method: get_nanite_info
  level.add_post_process_blendable:
    class_path: ue-mcp.bridge
    group: level
    description: "Add a material blendable (post-process material or MID, e.g. a toon/outline pass) to a PostProcessVolume's WeightedBlendables. Params: actorLabel OR actorPath (the PPV), materialPath, weight? (default 1) (#666)"
    options:
      method: add_post_process_blendable
  level.set_post_process_settings:
    class_path: ue-mcp.bridge
    group: level
    description: "Write post-process settings AND enable the bOverride_<Name> flag for every key given. Inside FPostProcessSettings each value is gated by its own override bit, so writing AutoExposureMinBrightness through the generic property setter stores the number, leaves the bit off, and the engine keeps using the default while the details panel shows the value you asked for. Nothing errors. That is the trap this closes: the auto-enable IS the action. Works off the struct's reflected fields, so every setting is reachable, not just exposure, and a key that has no override bit (WeightedBlendables) says so instead of implying one was enabled. Every key is resolved BEFORE anything is written, so a typo fails the call rather than half-applying it, and each key reports previousValue, newValue, overrideWasEnabled and overrideEnabled read back from the struct. Finds the settings on the actor or on one of its components; a camera with several camera components needs componentName (or propertyName) and is refused by listing the candidates rather than by picking one. The level is left dirty and is NOT saved. Params: actorLabel OR actorPath, settings (object of settingName -> value), enableOverrides? (default true), componentName?, propertyName? (#950)"
    options:
      method: set_post_process_settings
  level.get_post_process_settings:
    class_path: ue-mcp.bridge
    group: level
    description: "Read post-process settings with the override bit that decides whether each one applies. onlyOverridden answers 'is this volume actually overriding exposure' directly, instead of dumping the whole FPostProcessSettings as one ExportText blob to parse. Each row is name, type, value, overridden (the bOverride_<Name> bit) and hasOverrideFlag; the bits themselves are not listed as settings of their own, because a caller who writes the bit without the value has changed nothing. Also returns totalSettings and overriddenCount, so 'nothing is overridden here' is a fact rather than an empty list. Params: actorLabel OR actorPath, onlyOverridden? (default false), names? (string[] exact names), nameContains? (substring filter), componentName?, propertyName? (#950)"
    options:
      method: get_post_process_settings
  level.set_fixed_exposure:
    class_path: ue-mcp.bridge
    group: level
    description: "Pin exposure on a post-process volume or camera in one call. Writes the same value to AutoExposureMinBrightness and AutoExposureMaxBrightness, which is how the engine disables eye adaptation, and enables both override flags so the values actually apply. Params: actorLabel OR actorPath, exposure (the fixed adaptation brightness), bias? (AutoExposureBias, also override-enabled), componentName?, propertyName? (#950)"
    options:
      method: set_fixed_exposure
  level.export_actor_fbx:
    class_path: ue-mcp.bridge
    group: level
    description: "Export an actor's skeletal/static mesh to FBX and write a metadata sidecar JSON (actor transform, mesh, materials, skeleton) for a downstream bridge (e.g. MetaTailor). Params: actorLabel OR actorPath, outputPath (.fbx). Returns fbx + metadata paths (#637)"
    options:
      method: export_actor_fbx
  level.spawn_skeletal_mesh_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Spawn a SkeletalMeshActor for visual/deform verification. Optional per-slot COMPONENT material overrides (they write the component's OverrideMaterials, not the mesh ASSET's slots, which material(build_material) writes) and single-node animation preview (animSequence + loop). Each entry in materials[] comes back with ok, and a path that does not load or a slot index past the mesh's slot count is reported rather than silently dropped, because a silently dropped assignment reads as one that worked. To dress an actor that is already placed, or to put one material on every slot, use level(set_component_materials) (#946). Returns actorLabel + boxExtent + materialSlotCount. The animSequence is written to the component's editable AnimationData, so it persists across a level save/reload instead of only driving the runtime preview and coming back in A-pose (#790). Params: skeletalMesh, label?, location?, rotation?, scale?, materials? (path[]), animSequence?, loop? (#679/#677)"
    options:
      method: spawn_skeletal_mesh_actor
  level.delete_actors:
    class_path: ue-mcp.bridge
    group: level
    description: "Bulk-delete actors. The filter names say exactly what they match, because two level actions taking similar-sounding filters with different semantics is how a caller ends up trusting a wrong zero (#963): labelPrefix is a CASE-SENSITIVE PREFIX over the EDITOR LABEL, labelContains a case-insensitive SUBSTRING over the label, nameContains a case-insensitive substring over the INTERNAL name, className a case-sensitive substring over the class name. get_outliner's nameFilter is looser than any of them (label OR internal name, substring), so a string that selects a set there can select a different set here. When nothing matches, the response explains it rather than returning a bare zero: zeroMatchHint reports how many actors the same string WOULD have matched under the other semantics with samples, and a World Partition map says that only loaded actors were enumerated. classPathContains is a case-insensitive substring of the generated UClass path (e.g. /SurvivalGameKitV2/), so a whole vendor folder can be selected without listing every Blueprint class, and classPathContainsAny is the same match against any of several substrings (#924). Params: at least one of labelPrefix, labelContains, nameContains, className, tag, classPathContains, classPathContainsAny; dryRun? to preview. Returns matched/deleted/labels/classPaths (#220/#924/#963)"
    options:
      method: delete_actors
  level.delete_exact_labeled_actors_in_levels:
    class_path: ue-mcp.bridge
    group: level
    description: "Delete actors by EXACT label across several map packages in one call, without leaving the maps open or dirty. Previews by default (dryRun defaults to TRUE, the opposite of delete_actors), and every level is loaded and preflighted before anything is deleted, so a bad label in the last map cannot half-commit the first. Refuses the whole request on duplicate labels, a class that does not match expectedClassPath, actors living in a streaming sublevel, WorldSettings/default brush/level blueprint, World Partition maps, a read-only .umap, or an editor that is already dirty. Saves only the maps it actually changed, then returns to the map that was open. Limits: 16 levels, 256 labels per level. Params: levels[] ({levelPath, actorLabels[], expectedClassPath?}), dryRun? (default true), onMissing? (error|ignore, default error), restoreOriginalLevel? (default true)"
    options:
      method: delete_exact_labeled_actors_in_levels
  level.list_actor_descs:
    class_path: ue-mcp.bridge
    group: level
    description: "World Partition ONLY: list on-disk actor descriptors, including actors whose cell is not loaded and which every other actor query therefore reports as absent. Returns guid/label/class/bounds/runtimeGrid/dataLayers plus a loaded flag, and separate loadedMatches/unloadedMatches counts so 'not streamed in' is distinguishable from 'does not exist'. Needs UE 5.5 or newer. Params: filter? (case-insensitive over label/name/class/path), className?, guids?, bounds? ({min:{x,y,z},max:{x,y,z}} intersection test), loadedOnly?, unloadedOnly?, limit? (default 500), cursor? (#746)"
    options:
      method: list_actor_descs
  level.load_actor_descs:
    class_path: ue-mcp.bridge
    group: level
    description: "World Partition ONLY: pin matched actors so they become resident and the normal actor actions can reach them, or unpin to release. Pinning is used rather than transient streaming because a transient load is dropped again by the streaming system mid-measurement. Takes the same filters as list_actor_descs and refuses to act on more than maxActors (default 256) so an unfiltered call cannot pin a whole map by accident. Reports residentAfter, read back from the world rather than assumed. Needs UE 5.5 or newer. Params: mode? (pin|unpin, default pin), filter?, className?, guids?, bounds?, maxActors?, dryRun? (#746)"
    options:
      method: load_actor_descs
  level.get_world_partition_settings:
    class_path: ue-mcp.bridge
    group: level
    description: "World Partition ONLY: the streaming knobs on the open map, plus the runtime cell transformer stack. Returns the world partition and runtime hash classes, whether streaming is enabled, the default HLOD layer, and grids[] with each grid's name, CellSize, LoadingRange and a dotted path rooted at the world partition. Grids are found by REFLECTION rather than against a fixed class, so this answers for the RuntimeHashSet layout and the older spatial hash alike. cellTransformers[] lists each stack entry with its class, its instancePath and the current values of the instanced transformer object's editable properties, which no read exposed. Params: none (#985)"
    options:
      method: get_world_partition_settings
  level.set_world_partition_settings:
    class_path: ue-mcp.bridge
    group: level
    description: "World Partition ONLY: write the streaming knobs. cellSize and loadingRange are the shorthand for the primary tuning pair and pick the map's only grid automatically, or name one with grid (its name) or gridPath (the dotted path from get_world_partition_settings). settings takes any dotted path rooted at the world partition, which is how the INSTANCED runtime cell transformer object is configured ('RuntimeCellsTransformerStack[0].Instance.SomeProperty') - that object is read-only through Python and was previously set by hand in the editor UI. Every path is resolved BEFORE anything is written, so a bad path fails the call rather than leaving the map streaming on a mixture nobody asked for, and each write reports previousValue and the value read back. Streaming settings only apply to cells generated after the change, so rebuild the streaming data before measuring. The map package is left dirty and is NOT saved. Params: cellSize?, loadingRange?, grid? (name), gridPath?, settings? (object of dotted path -> value) (#985)"
    options:
      method: set_world_partition_settings
  level.add_runtime_cell_transformer:
    class_path: ue-mcp.bridge
    group: level
    description: "World Partition ONLY: append a WorldPartitionRuntimeCellTransformer to RuntimeCellsTransformerStack and configure its instanced object in the same call. Adding the entry without being able to write the instance would leave the manual editor step exactly where it was, so the instance is created here and properties are applied to it. skipIfPresent (default true) makes a rerun a no-op that reports the existing index instead of stacking duplicates. position inserts at an index rather than appending, because the stack is ordered. Params: transformerClass (short name, Module.Class or /Script path), properties? (object applied to the instance), position? (default -1 = append), skipIfPresent? (default true) (#985)"
    options:
      method: add_runtime_cell_transformer
  level.set_actor_hlod_layer:
    class_path: ue-mcp.bridge
    group: level
    description: "Assign (or clear) the per-actor HLODLayer override across every actor a selector picks. Assigning one across 295 InstancedFoliageActors was a Python loop. Goes through the engine's own AActor::SetHLODLayer rather than writing the private property directly, so the result behaves like an edit made in the details panel. hlodLayer=null clears the override. An actor already carrying the requested layer is reported as unchanged rather than counted as an update, so a rerun is readable. Optionally sets bEnableAutoLODGeneration in the same pass, since the layer only matters when the actor is allowed to build HLODs at all. On a World Partition map this dirties one package per actor and does NOT save. Params: hlodLayer (asset path or null), at least one of actorLabels[]/labelPrefix/labelContains/tag/classFilter/folderPath/folderPathPrefix, matchSubclasses? (default true), enableAutoLODGeneration?, dryRun?, transactionLabel? (#985)"
    options:
      method: set_actor_hlod_layer
  level.set_actor_folder_path:
    class_path: ue-mcp.bridge
    group: level
    description: "Assign World Outliner folder paths in bulk. Filter with actorLabels (exact), labelPrefix, className or tag; pass an empty folderPath to move actors back to the root. Runs in one transaction so the whole move is a single undo, reads each write back (verified), and reports missingLabels for names that matched no actor. Editor-only organisation: the level is left dirty and is NOT saved. Params: folderPath, actorLabels?, labelPrefix?, className?, tag?, dryRun?, transactionLabel? (#767)"
    options:
      method: set_actor_folder_path
  level.add_actor_tag:
    class_path: ue-mcp.bridge
    group: level
    description: "Append a tag to an actor's Tags array. Params: actorLabel OR actorPath, tag (#219)"
    options:
      method: add_actor_tag
  level.remove_actor_tag:
    class_path: ue-mcp.bridge
    group: level
    description: "Remove a tag from an actor's Tags array. Params: actorLabel OR actorPath, tag (#219)"
    options:
      method: remove_actor_tag
  level.set_actor_tags:
    class_path: ue-mcp.bridge
    group: level
    description: "Replace an actor's Tags array. Params: actorLabel OR actorPath, tags[] (#219)"
    options:
      method: set_actor_tags
  level.list_actor_tags:
    class_path: ue-mcp.bridge
    group: level
    description: "List an actor's Tags, in the order they are authored on the actor. Params: actorLabel OR actorPath, cursor?, limit? (#219/#983)"
    options:
      method: list_actor_tags
  level.attach_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Attach actor as child. Params: childLabel OR childPath, parentLabel OR parentPath, attachRule? (KeepWorld|KeepRelative|SnapToTarget; default KeepWorld), socketName? (#205)"
    options:
      method: attach_actor
  level.detach_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Detach actor from parent. Params: childLabel OR childPath (#205/#983)"
    options:
      method: detach_actor
  level.attach_component:
    class_path: ue-mcp.bridge
    group: level
    description: "Attach an actor root or exact named child SceneComponent to an actor root or exact named parent SceneComponent. A non-root child changes only component hierarchy, not actor parentage. An already-matching attachment is a no-op and does not reapply transform rules. Returns verified resolved component and socket details; native attachment failure is an error. Params: childLabel OR childPath, parentLabel OR parentPath, childComponentName? (default child root), parentComponentName? (default parent root), socketName?, attachRule? (KeepWorld|KeepRelative|SnapToTarget; default KeepWorld), weldSimulatedBodies? (default false)"
    options:
      method: attach_component
  level.detach_component:
    class_path: ue-mcp.bridge
    group: level
    description: "Detach an actor root or exact named child SceneComponent while preserving world transform. Omitted childComponentName selects the actor root. An already-detached component is a verified no-op. Returns previous parent component/socket details plus alreadyDetached and detachmentChanged. Params: childLabel OR childPath, childComponentName?"
    options:
      method: detach_component
  level.set_actor_mobility:
    class_path: ue-mcp.bridge
    group: level
    description: "Set actor root component Mobility. Params: actorLabel OR actorPath, mobility (static|stationary|movable) (#205)"
    options:
      method: set_actor_mobility
  level.get_current_edit_level:
    class_path: ue-mcp.bridge
    group: level
    description: "Read the active edit-target sub-level. Params: none (#204)"
    options:
      method: get_current_edit_level
  level.set_current_edit_level:
    class_path: ue-mcp.bridge
    group: level
    description: "Set the active edit-target sub-level so subsequent spawns land in it. Params: levelName (e.g. SubLevel_A) (#204)"
    options:
      method: set_current_edit_level
  level.list_streaming_sublevels:
    class_path: ue-mcp.bridge
    group: level
    description: "List streaming sub-levels with transform + initially-loaded/visible flags. Params: none (#206)"
    options:
      method: list_streaming_sublevels
  level.add_streaming_sublevel:
    class_path: ue-mcp.bridge
    group: level
    description: "Add a streaming sub-level. Params: levelPath, streamingClass? (LevelStreamingDynamic|LevelStreamingAlwaysLoaded), location?, initiallyLoaded?, initiallyVisible? (#206)"
    options:
      method: add_streaming_sublevel
  level.remove_streaming_sublevel:
    class_path: ue-mcp.bridge
    group: level
    description: "Remove a streaming sub-level. Params: levelName | levelPath (#206)"
    options:
      method: remove_streaming_sublevel
  level.set_streaming_sublevel_properties:
    class_path: ue-mcp.bridge
    group: level
    description: "Update sub-level transform/visibility flags. Params: levelName | levelPath, location?, initiallyLoaded?, initiallyVisible?, editorVisible? (#206)"
    options:
      method: set_streaming_sublevel_properties
  level.spawn_grid:
    class_path: ue-mcp.bridge
    group: level
    description: "Batch-spawn StaticMeshActors on a grid. Params: staticMesh, min, max (Vec3 bounds), countX?, countY?, countZ?, jitter?, labelPrefix? (#203)"
    options:
      method: spawn_grid
  level.batch_translate:
    class_path: ue-mcp.bridge
    group: level
    description: "Translate a set of actors by an offset. Params: offset (Vec3), actorLabels[] and/or actorPaths[] OR tag (#203/#983)"
    options:
      method: batch_translate
  level.place_actors_batch:
    class_path: ue-mcp.bridge
    group: level
    description: "Bulk-spawn StaticMeshActors with per-instance mesh + transform. Params: actors[]: [{staticMesh, location?, rotation?, scale?, label?}]. Mesh loads cached per path. Returns spawned/failedMesh/failedSpawn counts + labels (#264)"
    options:
      method: place_actors_batch
  level.batch_set_properties:
    class_path: ue-mcp.bridge
    group: level
    description: "Set the same properties on every actor an editor-side SELECTOR picks. batch_translate needs an explicit label list, which for auto-numbered labels is a payload you have to generate first; this takes tag / classFilter / labelPrefix / labelContains / folderPath instead, so '190 SpotLights tagged LichtTest' is one call. Property writes delegate to set_actor_property, so dotted paths, actor-label object references and TArray-of-actor-labels all behave identically to the single-actor action. The whole batch is ONE undo transaction. dryRun resolves every property against every matched actor's class and reports currentValue without writing. The level is left dirty and is NOT saved. Params: properties (object of propertyName -> value, max 32), at least one of actorLabels[]/labelPrefix/labelContains/tag/classFilter/folderPath/folderPathPrefix, matchSubclasses? (default true), dryRun?, force? (bypass EditDefaultsOnly), transactionLabel? (#984)"
    options:
      method: batch_set_actor_properties
  level.bulk_set_component_property:
    class_path: ue-mcp.bridge
    group: level
    description: "Set one property on the SAME NAMED COMPONENT across every actor a selector picks (the real case: NavModifierComponent.AreaClass on dozens of placed StaticMeshActors). Delegates to set_component_property per actor, so value semantics match exactly, including JSON null to clear an object reference. An actor that has no component of that name is reported as no_such_component rather than counted as a failure or silently skipped. One undo transaction. The level is left dirty and is NOT saved. Params: componentName, propertyName, value, at least one of actorLabels[]/labelPrefix/labelContains/tag/classFilter/folderPath/folderPathPrefix, matchSubclasses? (default true), dryRun?, transactionLabel? (#941)"
    options:
      method: bulk_set_component_property
  level.remove_components_by_class:
    class_path: ue-mcp.bridge
    group: level
    description: "Remove every instance component of a class across the actors a selector picks. remove_component takes one name at a time, which cannot reach 230 orphaned SplineMeshComponents whose names are auto-generated (SplineMeshComponent_0, _1, ...). dryRun DEFAULTS TO TRUE here, unlike delete_actors: this deletes components you cannot enumerate by name, so you see the list first. Only INSTANCE components are removed; a native or SCS component belongs to the class and is reported under skippedNonInstanceComponents rather than destroyed, because destroying it would come back on the next construction rerun. Omitting every actor selector means every loaded actor, which is a legitimate request and the reason for the dryRun default. One undo transaction. Params: componentClass, actorClassFilter? (or classFilter), componentNameContains?, matchComponentSubclasses? (default true), actorLabels?/labelPrefix?/labelContains?/tag?/folderPath?/folderPathPrefix?, dryRun? (default TRUE), save? (default false; the level is otherwise left dirty), transactionLabel? (#907)"
    options:
      method: remove_components_by_class
  level.spawn_actors_batch:
    class_path: ue-mcp.bridge
    group: level
    description: "Bulk-spawn actors of ANY class with per-instance properties, from explicit transforms or from positions DERIVED in the editor. place_actors_batch is StaticMeshActor only and spawn_light takes one position. Exactly one source: instances[] ({location?, rotation?, scale?, label?, properties?}); fromComponents ({componentClass?, componentNameContains?, actor selector, offset?, extentFraction?, space?, inheritRotation?}) which places one actor per matched component at boundsOrigin + boxExtent*extentFraction + offset; or alongSpline ({actorLabel, componentName?, spacing (cm), startDistance?, endDistance?, offset?, alignToTangent? (default true)}). fromComponents defaults to space='local' on purpose: a component's WORLD bounds are axis-aligned, so 'the top of the mesh' computed from them is not on a rotated lamp post, while local bounds carry the component's own orientation. That is what makes 'a SpotLight at 72 percent of each of 190 lamp posts' one call instead of 190 bounds reads and 190 spawns. dryRun returns every computed transform without spawning. maxSpawn (default 500) refuses a plan bigger than you meant. The level is left dirty and is NOT saved. Params: actorClass, exactly one of instances[]/fromComponents/alongSpline, properties? (applied to every spawn; per-instance properties win), labelPrefix?, dryRun?, maxSpawn?, transactionLabel? (#987)"
    options:
      method: spawn_actors_batch
  level.set_component_materials:
    class_path: ue-mcp.bridge
    group: level
    description: "Set per-slot COMPONENT material overrides on placed actors. This is the other half of material assignment and the half that had no action: material(build_material) and asset(set_property) write the mesh ASSET's slots, while a placed actor usually needs the instance override the details panel edits (OverrideMaterials). set_actor_material does one slot on whichever primitive component it finds first, so dressing a placed skeletal mesh meant a Python loop over get_num_materials(). Exactly one of: materials[] (index = slot; a null or empty entry clears just that slot's override), material (one path applied to EVERY slot), clearOverrides=true (drop every override so the asset's own slots show through). Every slot comes back with its previous and new path AND whether each is a componentOverride or the meshDefault, read back rather than echoed. Every material path is resolved before anything is written, so a bad path fails the call instead of half-applying it. Works on any UMeshComponent (static or skeletal). One undo transaction; the LEVEL is dirtied and NOT saved, the mesh asset is untouched. Params: at least one of actorLabels[]/labelPrefix/labelContains/tag/classFilter/folderPath/folderPathPrefix, componentName? (default the actor's first mesh component), materials? OR material? OR clearOverrides?, dryRun?, transactionLabel? (#946)"
    options:
      method: set_component_materials
  level.spawn_transient_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Spawn an actor for VERIFICATION that cannot be saved into the map. Checking anything that only exists on a live actor needs a subject, and spawning a normal one and forgetting to delete it is how a SceneCapture2D ended up committed to source control. So this actor is transient by construction: RF_Transient (the package serializer skips it), bTemporaryEditorActor (an editor preview actor, kept out of the level's persistent actor list), no external actor package on a World Partition map, and its label is set with bMarkDirty=false because SetActorLabel dirties the map by default. It carries a UEMCP_TransientVerification tag so it can be listed and so destroy refuses anything it did not create, and the response reports dirtiedPackages rather than promising nothing was dirtied. initialize controls how far towards a running state it is taken, because the EDITOR WORLD HAS NOT BEGUN PLAY and a spawned actor therefore gets neither InitializeComponent nor BeginPlay: 'none' spawns only, 'construction' (default) reruns construction scripts, 'beginPlay' also registers components and dispatches the begin-play cycle on this actor, which is what a component doing its setup in BeginPlay (a GAS ability system component's attribute-set scan, for one) needs before it can be read. Each returned component reports registered and initialized so the caller can see what actually happened. Destroy it with destroy_transient_actor. Params: actorClass, location?, rotation?, scale?, label?, initialize? (none|construction|beginPlay), hideFromOutliner?, world? (editor|pie) (#956)"
    options:
      method: spawn_transient_actor
  level.destroy_transient_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "Destroy an actor created by spawn_transient_actor. It will ONLY destroy an actor carrying the UEMCP_TransientVerification tag and RF_Transient: a label or path that resolves to a real level actor is refused by name rather than deleted, because this is the cleanup call and a mistyped label at cleanup time is how real work disappears. Use delete_actor or delete_actors for a real level actor. Params: actorPath OR actorLabel OR all=true (every transient verification actor in this world), world? (editor|pie) (#956)"
    options:
      method: destroy_transient_actor
  level.list_transient_actors:
    class_path: ue-mcp.bridge
    group: level
    description: "List the verification actors spawned by spawn_transient_actor that are still in the world, so nothing is left behind by accident. They cannot be saved into the map and do not survive a map reload, but they are live until destroyed. Params: world? (editor|pie), cursor?, limit? (#956)"
    options:
      method: list_transient_actors
  level.convert_brushes_to_static_mesh:
    class_path: ue-mcp.bridge
    group: level
    description: "Convert BSP brushes into StaticMeshActors via UEditorActorSubsystem::ConvertActors, generating the static meshes into destinationPath. Finishing a blockout pass otherwise meant doing it by hand. dryRun DEFAULTS TO TRUE, because conversion DESTROYS the source brushes and there is no way back to a builder brush once the map is saved. Every candidate the selector picked comes back with a verdict, so nothing is silently dropped: the default builder brush, brush shapes, volumes (both derive from ABrush and neither is geometry a player sees) and subtractive brushes (they carve space and have no surface, so converting one yields an empty mesh that looks like success) are all excluded unless you opt in by name. classFilter is EXACT by default here. Reports convertedFrom (recorded before the call, since the actors are gone after it), newActorLabels and newStaticMeshes. Params: actorLabels[] OR folderPath (+ recursiveFolder?, default true), classFilter?, exactClass? (default true), destinationPath? (default /Game/Meshes/Converted), dryRun? (default TRUE), allowSubtractive?, includeVolumes? (#911)"
    options:
      method: convert_brushes_to_static_mesh
  level.rerun_construction:
    class_path: ue-mcp.bridge
    group: level
    description: "Re-run the construction script on PLACED actor instances, so an SCS or construction-script change reaches the copies already in the level instead of only new ones. blueprint(run_construction_script) builds a TEMPORARY actor and throws it away, and Python cannot do this at all because Actor.rerun_construction_scripts() is missing from the wrapper. Requires actorLabels or className: this destroys and rebuilds every generated component on each match, so it will not run against a whole level implicitly. Reports reran, failed[], samples[], and how many matches have no user construction script (their generated components are still rebuilt, but a caller expecting a script change to land needs to know). Leaves the level dirty and does NOT save. Params: actorLabels? (string[]) and/or className? (Blueprint or native class), matchSubclasses? (default true), world? (editor|pie) (#944)"
    options:
      method: rerun_construction_scripts
  level.recreate_physics_state:
    class_path: ue-mcp.bridge
    group: level
    description: "Rebuild the physics and collision state on the components a selector picks, after the mesh data underneath them changed. Stale collision makes line_trace report a MISS through solid geometry, which is the worst kind of wrong answer because nothing about it looks wrong. dryRun DEFAULTS TO TRUE and is the preflight: it lists which components would be rebuilt and what their collision enabled state and profile are right now. Requires at least one bound (actorLabels, labelPrefix, tag, classFilter, componentClass, componentNameContains) and never refreshes a whole world implicitly. Returns per-component before/after collision state plus a post-refresh collisionSummary histogram. Params: actorLabels?, labelPrefix?, tag?, classFilter?, componentClass?, componentNameContains?, dryRun? (default TRUE), maxComponents? (default 2000), world? (editor|pie) (#915)"
    options:
      method: recreate_physics_state
  level.test_component_overlap:
    class_path: ue-mcp.bridge
    group: level
    description: "Do two placed components overlap? Returns the boolean plus both sides' local bounds, world bounds and world transforms, and separation (when apart) or penetration depth (when overlapping). For a UBoxComponent it also returns boxExtentUnscaled, the authored BoxExtent the details panel shows, which no other read exposed. method=OBB (default) runs a separating-axis test on the components' oriented local bounds, so a rotated box is tested as a rotated box; method=AABB compares the axis-aligned world bounds and adds a per-axis gap. Deliberately geometric rather than a physics overlap query: a physics answer depends on collision state, and stale collision state is what makes a trace lie (see recreate_physics_state). Omitting a componentName selects that actor's root component. Params: actorLabelA OR actorPathA, actorLabelB OR actorPathB, componentNameA?, componentNameB?, method? (OBB|AABB), world? (editor|pie) (#914)"
    options:
      method: test_component_overlap
  level.line_trace:
    class_path: ue-mcp.bridge
    group: level
    description: "Line trace. world selects which world is traced: 'editor' (default) or 'pie' for the LIVE running PIE world, which is the only way to check geometry that exists once play has begun (#933). Traces simple collision by default, the same as a gameplay trace, so the result matches what the running game hits; pass traceComplex to trace per-triangle instead. channel picks the collision channel, including channels this project renamed in its collision settings. The response echoes traceComplex, channel and the world that actually answered, and faceIndex is only present on a complex hit. Returns hit + actorLabel/actorClass/componentName/componentClass/location/impactPoint/normal/distance/faceIndex/boneName/physicalMaterial. Params: start (Vec3), end? (Vec3) OR direction? (Vec3) + distance? (default 200000), traceComplex? (default false), channel? (default Visibility), ignoreActors? (array of labels), world? (editor|pie), pieInstance? (#420, #807, #933)"
    options:
      method: line_trace
  level.bulk_line_trace:
    class_path: ue-mcp.bridge
    group: level
    description: "Batch of line traces. Each traces[] item uses the same semantics as line_trace (start + end or direction+distance, simple collision by default, optional traceComplex/channel/ignoreActors). The top-level world applies to the WHOLE batch, so one call cannot straddle the editor and PIE worlds and present the results as comparable. Caps at 256. Returns results[] in traces[] order, one per item, each with the same fields as line_trace (or success=false + error for that item). Params: traces[], world? (editor|pie), pieInstance? (#933)"
    options:
      method: bulk_line_trace
  level.snap_instances_to_surface:
    class_path: ue-mcp.bridge
    group: level
    description: "Project ISMC/HISMC instances down onto surface geometry, preserving each instance's rotation and scale. Dry-run by default: preflights every requested instance and reports what would move before anything does, then applies successful projections in one transaction with rollback. Filter the surfaces traced against with surfaceActorClass/surfaceActorLabels so instances do not snap onto each other. onMiss controls whether a miss aborts the batch or is skipped. Does not save packages. Params: actorLabel OR actorPath, componentName?, instanceIndices? (omit for all), maxInstances?, traceStartOffset?, traceDistance?, surfaceOffset?, channel?, traceComplex?, surfaceActorClass?, surfaceActorLabels?, onMiss? ('error'|'skip', default error), dryRun? (default true) (#905)"
    options:
      method: snap_instances_to_surface
  level.summarize_static_mesh_usage:
    class_path: ue-mcp.bridge
    group: level
    description: "Read-only inventory of which static meshes are actually placed in the loaded world, aggregated in one game-thread scan instead of walking every actor and component. This is the action for placement counts, and query_components is not a substitute for it: grouping there only increments, so one HISM holding 5,000 instances counts as 1, while this SUMS real ISM/HISM instances and counts distinct actors per mesh, and it sorts every aggregate before truncating so the top N is the true top N with an exact totalUniqueMeshes. Distinguishes actors, components and placements. Results are bounded; full-scan totals are preserved alongside the truncated rows. Loads nothing and saves nothing. Params: world? (editor|pie), maxResults?, includeOccurrences?, maxOccurrences? (#903)"
    options:
      method: summarize_static_mesh_usage
  level.snap_actor_to_floor:
    class_path: ue-mcp.bridge
    group: level
    description: "Snap an actor's bounds-bottom to the first downward line-trace hit. Equivalent of the End-key shortcut, works on arbitrary geometry (not just Landscape). The actor lookup and the downward trace both run in the world named by world, so a PIE snap measures against PIE geometry (#933). Params: actorLabel OR actorPath, floorOffset? (added to impact Z, default 0), maxDistance? (default 100000), world? (editor|pie), pieInstance? (#419, #933)"
    options:
      method: snap_actor_to_floor
  level.epic_add_component:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Adds a component to an actor instance or blueprint. Params: owner, component_type, name"
    options:
      method: epic_call_tool
  level.epic_add_cone:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.primitive.PrimitiveTools] Adds a cone-shaped StaticMeshComponent to an actor. Params: actor, name, radius?, height?, local_transform?"
    options:
      method: epic_call_tool
  level.epic_add_cube:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.primitive.PrimitiveTools] Adds a cube-shaped StaticMeshComponent to an actor. Params: actor, name, dimensions?, local_transform?"
    options:
      method: epic_call_tool
  level.epic_add_cylinder:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.primitive.PrimitiveTools] Adds a cylinder-shaped StaticMeshComponent to an actor. Params: actor, name, radius?, height?, local_transform?"
    options:
      method: epic_call_tool
  level.epic_add_sphere:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.primitive.PrimitiveTools] Adds a sphere-shaped StaticMeshComponent to an actor. Params: actor, name, radius?, local_transform?"
    options:
      method: epic_call_tool
  level.epic_add_tag:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Adds a tag to an actor. Params: actor, tag"
    options:
      method: epic_call_tool
  level.epic_add_to_scene_from_asset:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Creates a new actor in the scene from an asset. Params: asset_path, name, xform, parent?, snap_to_ground?"
    options:
      method: epic_call_tool
  level.epic_add_to_scene_from_class:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Creates a new instance of the specified object at the specified transform. Params: actor_type, name, xform, parent?, snap_to_ground?"
    options:
      method: epic_call_tool
  level.epic_can_edit:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Checks whether an actor can be edited. Params: actor"
    options:
      method: epic_call_tool
  level.epic_commit_level_instance:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Saves or discards edits to a level instance and exits edit mode. Params: level_instance, discard?"
    options:
      method: epic_call_tool
  level.epic_create_level_instance:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Creates a Level Instance actor in the scene referencing an existing level asset. Params: level_path, name, xform, parent?"
    options:
      method: epic_call_tool
  level.epic_delete_folder:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Deletes a folder from the outliner. Actors directly in the folder are moved to the parent folder. Sub-folders and their actors are preserved by re-rooting them under the parent. For example, deleting 'Lighting' with a sub-folder 'Lighting/Spotlights' leaves 'Spotlights' intact under the parent. Params: folder_path"
    options:
      method: epic_call_tool
  level.epic_edit_level_instance:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Opens a level instance for editing. While in edit mode, scene tools such as add_to_scene_from_class and remove_from_scene operate within the level instance's sub-level. Only one level instance can be in edit mode at a time. Call commit_level_instance when done to save or discard changes. Params: level_instance"
    options:
      method: epic_call_tool
  level.epic_find_actors:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Searches the scene for actors that match specific criteria. Params: root?, name, actor_type?, tag, bounds?, collision_channels"
    options:
      method: epic_call_tool
  level.epic_get_actor_bounds:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the bounding box of an actor. Params: actor"
    options:
      method: epic_call_tool
  level.epic_get_actor_transform:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the position, rotation, and scale of an actor. Params: actor"
    options:
      method: epic_call_tool
  level.epic_get_actors_in_folder:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Returns the actors in the specified outliner folder. Params: folder_path, recursive?"
    options:
      method: epic_call_tool
  level.epic_get_collision_channels:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Returns all available collision channels for use with find_actors. Params: none"
    options:
      method: epic_call_tool
  level.epic_get_component_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the actor that owns the specified component. Params: component"
    options:
      method: epic_call_tool
  level.epic_get_components:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the components that an actor contains. Params: actor, component_type?"
    options:
      method: epic_call_tool
  level.epic_get_current_level:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Returns the path to the current level asset. Params: none"
    options:
      method: epic_call_tool
  level.epic_get_folders:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Returns all folder paths currently in use in the outliner. Includes all intermediate parent paths. For example, if an actor is assigned to 'Lighting/Spotlights', both 'Lighting' and 'Lighting/Spotlights' are returned. Params: none"
    options:
      method: epic_call_tool
  level.epic_get_label:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the actor's human friendly name as it appears in the editor. Params: actor"
    options:
      method: epic_call_tool
  level.epic_get_parent_component:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the parent component that this component is attached to, if any. Params: component"
    options:
      method: epic_call_tool
  level.epic_get_root_component:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the root component of an actor, if any. Params: actor"
    options:
      method: epic_call_tool
  level.epic_get_tags:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns the list of tags on an actor. Params: actor"
    options:
      method: epic_call_tool
  level.epic_has_tag:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Returns whether an actor has a specific tag. Params: actor, tag"
    options:
      method: epic_call_tool
  level.epic_is_checked_out:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Checks whether an actor is checked out by the current user. Params: actor"
    options:
      method: epic_call_tool
  level.epic_load_level:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Loads a level in the editor. Params: level_path"
    options:
      method: epic_call_tool
  level.epic_look_at:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Rotates an actor so its forward vector points at a world-space position. Params: actor, target"
    options:
      method: epic_call_tool
  level.epic_merge_actors:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Merges multiple StaticMesh actors into a single mesh asset and actor. Params: actors, output_path, name, destroy_source_actors?"
    options:
      method: epic_call_tool
  level.epic_remove_component:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Removes a component from an actor instance or blueprint. Params: component"
    options:
      method: epic_call_tool
  level.epic_remove_from_scene:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Deletes an actor from the scene. Params: actor"
    options:
      method: epic_call_tool
  level.epic_remove_tag:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Removes a tag from an actor. Params: actor, tag"
    options:
      method: epic_call_tool
  level.epic_rename_folder:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Renames a folder in the outliner. Updates the folder path for all actors in the folder and any sub-folders. For example, renaming 'Lighting' to 'Lights' also updates actors in 'Lighting/Spotlights' to 'Lights/Spotlights'. If the new path already exists then the affected actors will be merged into it. Params: old_path, new_path"
    options:
      method: epic_call_tool
  level.epic_save_actor:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Saves the actor to disk. Params: actor"
    options:
      method: epic_call_tool
  level.epic_set_actor_folder:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Assigns an actor to the specified folder in the outliner. Creates the folder implicitly if it does not already exist. Pass an empty string to move the actor to the root of the outliner. Params: actor, folder_path"
    options:
      method: epic_call_tool
  level.epic_set_actor_transform:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Updates the position, rotation, and/or scale of an actor. Params: actor, xform, worldspace?"
    options:
      method: epic_call_tool
  level.epic_set_label:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Sets the human-friendly name of the actor. Params: actor, label"
    options:
      method: epic_call_tool
  level.epic_set_parent_component:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.actor.ActorTools] Sets the parent for the specified scene component. For blueprint actors, passing a component as the parent of the root promotes it to the scene root, making the current root a child of it. If the current root is a DefaultSceneRoot, Unreal will automatically remove it. Params: component, parent?"
    options:
      method: epic_call_tool
  level.epic_trace_world:
    class_path: ue-mcp.bridge
    group: level
    description: "[Epic editor_toolset.toolsets.scene.SceneTools] Traces a line through the world and returns the distance to the first hit. Params: start, end"
    options:
      method: epic_call_tool

  # ── material ──
  material.read:
    class_path: ue-mcp.bridge
    group: material
    description: "Read material structure. Accepts a Material or a MaterialInstance; an instance answers with its lineage, shading setup and resolved parameters instead of a graph (#952). Params: assetPath"
    options:
      method: read_material
  material.list_parameters:
    class_path: ue-mcp.bridge
    group: material
    description: "List parameters. On a MaterialInstance each entry carries the resolved value, the parent's defaultValue and whether this instance overrides it (#952). Params: assetPath"
    options:
      method: list_material_parameters
  material.set_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "Set parameter on MaterialInstance. Params: assetPath, parameterName, parameterType? (scalar|vector|texture, auto-detected when omitted), value, association?. A vector/colour value may be passed as value OR color, shaped {r,g,b,a}, [r,g,b,a] or '(R=..,G=..,B=..,A=..)'. Saves the instance and returns readBack + saved so a set/verify round trip needs no second call (#952)"
    options:
      method: set_material_parameter
  material.read_instance:
    class_path: ue-mcp.bridge
    group: material
    description: "Read a MaterialInstanceConstant parent and override summary. Params: assetPath"
    options:
      method: read_material_instance
  material.set_instance_parent:
    class_path: ue-mcp.bridge
    group: material
    description: "Set a MaterialInstanceConstant parent. Params: assetPath, newParentPath (or parentPath)"
    options:
      method: set_material_instance_parent
  material.batch_set_instances:
    class_path: ue-mcp.bridge
    group: material
    description: "Batch reparent + reassign parameters across many Material Instances in one call. Params: instances[] = [{assetPath, parentPath?, parameters?:[{name, type (scalar|vector|texture), value}]}]. value: number (scalar), {r,g,b,a} (vector), or texture path (texture). Returns per-instance results (#594)"
    options:
      method: batch_set_material_instances
  material.clear_instance_parameters:
    class_path: ue-mcp.bridge
    group: material
    description: "Clear all MaterialInstanceConstant parameter overrides. Params: assetPath"
    options:
      method: clear_material_instance_parameters
  material.list_static_switches:
    class_path: ue-mcp.bridge
    group: material
    description: "List static switch parameters on a Material or MaterialInstance. Params: assetPath"
    options:
      method: list_material_static_switches
  material.set_static_switch:
    class_path: ue-mcp.bridge
    group: material
    description: "Set a MaterialInstanceConstant static switch parameter. Params: assetPath, parameterName, value, association?, parameterIndex?"
    options:
      method: set_material_static_switch
  material.set_expression_value:
    class_path: ue-mcp.bridge
    group: material
    description: "Set a value on an expression node. Common typed knobs: value (constant/param default), texturePath (TextureSample/TextureSampleParameter2D default texture). Constant2Vector/3Vector/4Vector and VectorParameter all take value (or color) as {r,g,b,a}, {x,y,z,w}, [r,g,b,a] or '(R=..,G=..)'; Constant2Vector reads the first two components (#979). Saves the material and reports saved. A TextureCoordinate node takes uTiling, vTiling and coordinateIndex, which are separate top-level keys rather than anything inside `value`. For any OTHER UPROPERTY on the expression (e.g. SamplerType, SamplerSource, Group), pass propertyName + value to hit the generic reflection setter. Reports previousValue and rolls back to it where the previous value can be expressed in this action's own vocabulary. Params: materialPath, expressionIndex, value?, color?, texturePath?, uTiling?, vTiling?, coordinateIndex?, propertyName? (#663)"
    options:
      method: set_expression_value
  material.set_custom_expression:
    class_path: ue-mcp.bridge
    group: material
    description: "Read/write a MaterialExpressionCustom (HLSL) node: code, named inputs[] (rebuilds input pins; wire them with connect_expressions targetInput=<name>), outputType (float1|float2|float3|float4|materialAttributes), description. Omit code/inputs to read. Add the node first via add_expression expressionType=Custom. Params: materialPath, expressionIndex, code?, inputs?, outputType?, description? (#617)"
    options:
      method: set_custom_expression
  material.disconnect_property:
    class_path: ue-mcp.bridge
    group: material
    description: "Disconnect a material property input. Params: materialPath, property"
    options:
      method: disconnect_material_property
  material.create_instance:
    class_path: ue-mcp.bridge
    group: material
    description: "Create material instance. Params: parentPath, name?, packagePath?"
    options:
      method: create_material_instance
  material.create:
    class_path: ue-mcp.bridge
    group: material
    description: "Create material. Params: name, packagePath?"
    options:
      method: create_material
  material.create_function:
    class_path: ue-mcp.bridge
    group: material
    description: "Create a MaterialFunction asset. Params: name, packagePath? (default /Game/Materials/Functions), description? (#463)"
    options:
      method: create_material_function
  material.add_function_expression:
    class_path: ue-mcp.bridge
    group: material
    description: "Add an expression node to a MaterialFunction graph. Params: functionPath, expressionType (e.g. Constant3Vector, FunctionInput, FunctionOutput, If), positionX?, positionY?, inputName? (for FunctionInput), inputType? (Scalar|Vector2|Vector3|Vector4|Texture2D|TextureCube|StaticBool|MaterialAttributes), outputName? (for FunctionOutput) (#463)"
    options:
      method: add_expression_in_function
  material.connect_function_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "Wire two expressions inside a MaterialFunction. Params: functionPath, sourceExpression (name or index), sourceOutput?, targetExpression (name or index), targetInput? (#463)"
    options:
      method: connect_expressions_in_function
  material.list_function_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "List expression nodes inside a MaterialFunction. Params: functionPath (#463)"
    options:
      method: list_expressions_in_function
  material.build_material:
    class_path: ue-mcp.bridge
    group: material
    description: "Build a PBR material from a texture set in one call: creates a TextureSample per entry, picks each sampler type FROM THE TEXTURE (a virtual/UDIM texture silently loses its material connection on recompile unless it samples through the Virtual* variant), wires them, recompiles, saves, and reports which connections survived the recompile. Params: name + packagePath? (create) OR materialPath (build into an existing material); textures = {baseColor, normal, roughness, metallic, specular, emissive, opacity, opacityMask, ambientOcclusion, ...} plus packed keys orm (R=AO,G=Roughness,B=Metallic) and rma; samplerTypes? = per-key override (e.g. {normal: 'VirtualNormal'}); clearExisting?; assignToMesh? (StaticMesh or SkeletalMesh asset) with meshSlots? (names or indices, default all) (#946)"
    options:
      method: build_material
  material.create_simple:
    class_path: ue-mcp.bridge
    group: material
    description: "Single-call simple material. Params: name, packagePath?, baseColor? ({r,g,b}), metallic?, specular?, roughness?, emissive?, usages?[] (e.g. InstancedStaticMeshes, Nanite, NiagaraSprites). Recompiles + saves in one shot (#225)"
    options:
      method: create_material_simple
  material.get_usage:
    class_path: ue-mcp.bridge
    group: material
    description: "Read the EMaterialUsage flags on a material. `usages[]` carries every flag this engine build defines with its `enabled` state and the bUsedWith* `propertyName` behind it, and `enabled[]` is just the ones that are on. Pass a material INSTANCE and the flags come from the base material it resolves to, which is the shader map it renders through: `materialPath` names that material and `inherited` is true. Params: assetPath (#1004)"
    options:
      method: get_material_usage
  material.set_usage:
    class_path: ue-mcp.bridge
    group: material
    description: "Turn on EMaterialUsage flag(s) for a material, then recompile and save. Flag names come from this engine own EMaterialUsage reflection, so every usage the build defines is accepted (VolumetricCloud, Voxels, MeshDeformer and the rest included) and `unknown` names only what it truly could not resolve. This only ever turns flags ON: it routes each one through UMaterial::SetMaterialUsage, which is an ensure rather than a write, so a replay also repairs a material whose bit is set but whose shaders were never built. `applied` names the flags this call turned on and `alreadySet` those that were on already. To CLEAR a flag, write its reflected property with editor(set_property): every usage this action recognises is backed by a bUsedWith* UPROPERTY on the material, and the response returns the exact {objectPath, propertyName, value} arguments per flag under `clearCalls`. Clearing a flag does not discard the shader permutations that were compiled for it. Params: assetPath, usage OR usages[] (#225)"
    options:
      method: set_material_usage
  material.set_shading_model:
    class_path: ue-mcp.bridge
    group: material
    description: "Set shading model. Params: assetPath, shadingModel"
    options:
      method: set_material_shading_model
  material.set_blend_mode:
    class_path: ue-mcp.bridge
    group: material
    description: "Set blend mode. Params: assetPath, blendMode"
    options:
      method: set_material_blend_mode
  material.set_domain:
    class_path: ue-mcp.bridge
    group: material
    description: "Set material domain. Params: assetPath, materialDomain (Surface | DeferredDecal | LightFunction | Volume | PostProcess | UI | RuntimeVirtualTexture). Required for post-process / decal / UI authoring (#299/#356)."
    options:
      method: set_material_domain
  material.set_base_color:
    class_path: ue-mcp.bridge
    group: material
    description: "Set base color. Params: assetPath, color"
    options:
      method: set_material_base_color
  material.connect_texture:
    class_path: ue-mcp.bridge
    group: material
    description: "Connect texture to property. Params: materialPath, texturePath, property"
    options:
      method: connect_texture_to_material
  material.add_expression:
    class_path: ue-mcp.bridge
    group: material
    description: "Add expression node. Params: materialPath, expressionType, name?, parameterName?, group?, sortPriority?, defaultValue? (scalar number or {r,g,b,a} for vector params), value? (number for Constant, {r,g,b} for Constant3Vector, {x,y} for Constant2Vector), channels? ({r,g,b,a} bools for ComponentMask), positionX?, positionY? (#318)"
    options:
      method: add_material_expression
  material.connect_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "Wire two expressions. Params: materialPath, sourceExpression, sourceOutput?, targetExpression, targetInput?"
    options:
      method: connect_material_expressions
  material.connect_to_property:
    class_path: ue-mcp.bridge
    group: material
    description: "Wire expression to material output. Params: materialPath, expressionName, outputName?, property"
    options:
      method: connect_to_material_property
  material.list_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "List expression nodes, in the material's own stored order, which is what nodeId indexes into. Params: materialPath, cursor?, limit?"
    options:
      method: list_material_expressions
  material.delete_expression:
    class_path: ue-mcp.bridge
    group: material
    description: "Remove expression. Params: materialPath, expressionName"
    options:
      method: delete_material_expression
  material.list_expression_types:
    class_path: ue-mcp.bridge
    group: material
    description: "List available expression types, in the curated order they are grouped in. Params: cursor?, limit?"
    options:
      method: list_expression_types
  material.recompile:
    class_path: ue-mcp.bridge
    group: material
    description: "Recompile material. Pass recompileChildren=true to cascade to every MaterialInstanceConstant whose parent chain reaches this material (#421). Params: materialPath, recompileChildren?"
    options:
      method: recompile_material
  material.duplicate:
    class_path: ue-mcp.bridge
    group: material
    description: "Duplicate material asset. Params: sourcePath, destinationPath"
    options:
      method: duplicate_material
  material.validate:
    class_path: ue-mcp.bridge
    group: material
    description: "Validate material graph - find orphans, broken refs. Params: assetPath"
    options:
      method: validate_material
  material.get_shader_stats:
    class_path: ue-mcp.bridge
    group: material
    description: "Shader compile stats, sampler+param counts. Params: assetPath"
    options:
      method: get_material_shader_stats
  material.export_graph:
    class_path: ue-mcp.bridge
    group: material
    description: "Export material graph as JSON. Params: assetPath"
    options:
      method: export_material_graph
  material.import_graph:
    class_path: ue-mcp.bridge
    group: material
    description: "Rebuild graph from JSON. Params: assetPath, nodes, propertyConnections?"
    options:
      method: import_material_graph
  material.build_graph:
    class_path: ue-mcp.bridge
    group: material
    description: "Build graph from spec. Params: assetPath, nodes, propertyConnections?"
    options:
      method: build_material_graph
  material.render_preview:
    class_path: ue-mcp.bridge
    group: material
    description: "Render preview PNG. Params: assetPath, outputPath, width?, height?"
    options:
      method: render_material_preview
  material.begin_transaction:
    class_path: ue-mcp.bridge
    group: material
    description: "Begin undo transaction. Params: label?"
    options:
      method: begin_material_transaction
  material.end_transaction:
    class_path: ue-mcp.bridge
    group: material
    description: "End undo transaction. Params: none"
    options:
      method: end_material_transaction
  material.create_rvt:
    class_path: ue-mcp.bridge
    group: material
    description: "Create a RuntimeVirtualTexture asset. materialType is validated against RuntimeVirtualTexture::IsMaterialTypeSupported, which is the reason it is a parameter rather than a follow-up property write: a project can disable individual material types to cut shader permutations, and set_property would happily store one that then compiles to nothing. Everything else (tileCount, tileSize, tileBorderSize, compression, adaptive/private/packed page table, continuous update, LOD group) is a plain UPROPERTY: write it with asset(set_property) at the returned objectPath. Reads back the DERIVED layout - size, pageTableSize, layer count and per-layer pixel format and colour space - which no property read produces. Idempotent by path; rollback deletes the asset. Params: name, packagePath? (default /Game/Textures/RVT), materialType? (BaseColor | BaseColor_Normal_Roughness | BaseColor_Normal_Specular | BaseColor_Normal_Specular_YCoCg | BaseColor_Normal_Specular_Mask_YCoCg | Mask4 | WorldHeight | Displacement), onConflict? (skip|error)"
    options:
      method: create_runtime_virtual_texture
  material.read_rvt:
    class_path: ue-mcp.bridge
    group: material
    description: "Read a RuntimeVirtualTexture and its whole reference graph: the derived layout (size, pageTableSize, per-layer pixel format, sRGB and YCoCg), every volume bound to it with its transform, every primitive and landscape that writes into it, and every material sample node that reads it. problems[] names the failures that otherwise only show up visually - no volume (so no world bounds), no writer (so every page renders empty), a disabled material type, and the classic one: a sample node whose materialType disagrees with the asset, which decodes the wrong channels and still compiles. level(get_runtime_virtual_texture_summary) answers the level-side question; this answers the asset-side one. Params: rvtPath (assetPath is accepted for it too)"
    options:
      method: read_runtime_virtual_texture
  material.add_rvt_volume:
    class_path: ue-mcp.bridge
    group: material
    description: "Spawn the RuntimeVirtualTextureVolume that gives an RVT its world bounds, bind the asset through the engine's SetVirtualTexture (which re-registers the render state, unlike a raw property write), and fit the volume to what actually writes into the texture. An unfitted volume sits at the origin at unit scale and renders a 100cm cube of the world, so the fit is the point. Idempotent BY BINDING rather than by label: two volumes on one RVT is a real misconfiguration (one wins, the other renders nothing) and an existing binding is reported rather than duplicated. When nothing writes into the RVT yet there is nothing to fit to, and the response says so in boundsFitNote rather than reporting a fit that did not happen. Rollback deletes the actor. Params: rvtPath (assetPath is accepted for it too), actorLabel?, boundsMode? (writers|alignActor), boundsAlignActor?"
    options:
      method: add_rvt_volume
  material.set_rvt_volume_bounds:
    class_path: ue-mcp.bridge
    group: material
    description: "Refit an RVT volume to the world, which is the Set Bounds button and is not a property write: it DERIVES a transform from what is in the level. boundsMode='writers' covers every primitive whose RuntimeVirtualTextures array names this RVT; boundsMode='alignActor' matches one actor's box and rotation, which is what a landscape wants because the volume's axes have to be the terrain's axes. The volume's own local extent is read back through CalcBounds and solved against the target box rather than assumed to be a unit cube, so the fit survives engine changes. Honest limitation: it does NOT perform the landscape texel snap (that lives in a module the bridge does not link) and says so when bSnapBoundsToLandscape is set. Rollback restores the previous transform exactly. Params: rvtPath? OR actorLabel? OR actorPath?, boundsMode? (writers|alignActor, default writers), boundsAlignActor?"
    options:
      method: set_rvt_volume_bounds
  material.add_rvt_sampler:
    class_path: ue-mcp.bridge
    group: material
    description: "Add or rebind the RuntimeVirtualTextureSample node that READS an RVT in a material, then call the engine's InitVirtualTextureDependentSettings, which is the one step add_expression + set_expression_value cannot do: it copies the asset's material type and packed/adaptive page-table flags onto the node and rebuilds its output pins. Setting VirtualTexture alone leaves those stale, which compiles cleanly and decodes the wrong channels. Optionally connects each output pin to the material property of the same name and reports the pins that have no counterpart (WorldHeight, Mask, Mask4, Displacement) rather than dropping them silently. Idempotent by expressionName. MipValueMode, TextureAddressMode and bEnableFeedback stay editor(set_property) territory at the returned expressionObjectPath. Params: materialPath (assetPath is accepted for it too), rvtPath, expressionName?, connectOutputs? (default true), positionX?, positionY?, recompile? (default true)"
    options:
      method: add_rvt_sampler
  material.add_rvt_output:
    class_path: ue-mcp.bridge
    group: material
    description: "Add the RuntimeVirtualTextureOutput node that makes a material WRITE into an RVT, and mirror the material's existing BaseColor / Normal / Roughness / Specular / Opacity connections into it, which by hand is one connect_expressions call per channel with each source expression and output index looked up by eye. Reads the node's inputs by NAME through the same GetInput/GetInputName pair connect_expressions uses, so a channel added by an engine version is picked up rather than mis-wired. Inputs with no material counterpart are reported with the call that wires them. Idempotent by expressionName. Note this does not choose WHICH RVT: that is the primitive's RuntimeVirtualTextures array (assign_rvt_to_landscape, or editor(set_property) on a mesh component). Params: materialPath (assetPath is accepted for it too), expressionName?, mirrorProperties? (default true), positionX?, positionY?, recompile? (default true)"
    options:
      method: add_rvt_output
  material.assign_rvt_to_landscape:
    class_path: ue-mcp.bridge
    group: material
    description: "Assign RVTs to a landscape across EVERY proxy that shares its ULandscapeInfo, not just the actor named. A streaming or World Partition landscape is many ALandscapeStreamingProxy actors, and writing RuntimeVirtualTextures on one of them with set_property leaves the rest rendering nothing into the RVT, which is invisible until the pages come back empty. Also calls PostEditChange plus MarkComponentsRenderStateDirty per proxy, without which the change does not take effect, and checks the two things that make an assignment useless anyway: a landscape material with no RuntimeVirtualTextureOutput node, and an RVT whose material type is disabled for the project. assignMode='set' with an empty list clears the assignment. Rollback replays the named landscape's previous list, with previousPerProxy carrying the exact per-proxy state. Params: actorLabel OR actorPath (the landscape), rvtPaths? (array) OR rvtPath?, assignMode? (set|add|remove, default set)"
    options:
      method: assign_rvt_to_landscape
  material.epic_add_expression:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Adds a new expression node to a Material or MaterialFunction graph. Use list_expression_classes to discover available types. Params: material_or_function, expression_class, x?, y?"
    options:
      method: epic_call_tool
  material.epic_clear_parameters:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Clears all parameter overrides on a material instance, reverting to parent defaults. Params: instance"
    options:
      method: epic_call_tool
  material.epic_connect_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Connects an expression node's output pin to another expression node's input pin. Params: from_expression, from_output_name, to_expression, to_input_name"
    options:
      method: epic_call_tool
  material.epic_connect_to_output:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Connects an expression node's output to one of the material's output properties. Params: expression, output_name, material_property"
    options:
      method: epic_call_tool
  material.epic_create:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Creates a new MaterialInstanceConstant asset derived from a parent material. Material instances expose the parent's parameters without triggering a full shader recompile when parameter values change. Params: folder_path, asset_name, parent"
    options:
      method: epic_call_tool
  material.epic_create_function:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Creates a new empty MaterialFunction asset. Params: folder_path, asset_name"
    options:
      method: epic_call_tool
  material.epic_create_material:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Creates a new empty Material asset. Warning: Each new Material increases shader compile times. Prefer creating a MaterialInstance from an existing Material where possible. Params: folder_path, asset_name"
    options:
      method: epic_call_tool
  material.epic_create_parameter_collection:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Creates a new empty MaterialParameterCollection (MPC) asset. An MPC holds named Scalar and Vector parameters with default values that materials can reference at runtime without recompiling shaders. Params: folder_path, asset_name"
    options:
      method: epic_call_tool
  material.epic_delete_expression:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Removes an expression node from a Material or MaterialFunction graph. Params: material_or_function, expression"
    options:
      method: epic_call_tool
  material.epic_delete_parameter_group:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Removes a parameter group, ungrouping all parameters that belong to it. The parameter expressions themselves are not deleted - only their group assignment is cleared. Params: material_or_function, group_name"
    options:
      method: epic_call_tool
  material.epic_delete_unused_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Deletes all expression nodes not connected to any material output. Useful for cleaning up a material graph after reorganising or after the AI has added experimental nodes that were later abandoned. Params: material"
    options:
      method: epic_call_tool
  material.epic_disconnect_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Disconnects the input pin of an expression node, removing whatever is connected to it. Params: to_expression, to_input_name"
    options:
      method: epic_call_tool
  material.epic_disconnect_from_output:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Disconnects the expression currently connected to a material output property. Params: material, material_property"
    options:
      method: epic_call_tool
  material.epic_get_expression_input_names:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns the names of all input pins on a material expression node. Use these names as to_input_name when calling connect_expressions. Params: expression"
    options:
      method: epic_call_tool
  material.epic_get_expression_inputs:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns the current wiring of each input pin on a material expression. Use after building or modifying a graph to verify the wiring matches expectations. Params: material_or_function, expression"
    options:
      method: epic_call_tool
  material.epic_get_expression_output_names:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns the names of all output pins on a material expression node. Use these names as from_output_name when calling connect_expressions or connect_to_output. The empty string represents the default (first) output of nodes that expose only an unnamed output. Params: expression"
    options:
      method: epic_call_tool
  material.epic_get_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns all expression nodes in a Material or MaterialFunction graph. Params: material_or_function"
    options:
      method: epic_call_tool
  material.epic_get_property_input:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns the expression and output pin feeding a material output property. Use to inspect what drives MP_EmissiveColor, MP_Opacity, MP_BaseColor, etc. Params: material, material_property"
    options:
      method: epic_call_tool
  material.epic_get_referencing_materials:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns asset data for all Materials that reference this MaterialFunction. Params: material_function"
    options:
      method: epic_call_tool
  material.epic_get_scalar_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Gets the current value of a scalar parameter on a material instance. Params: instance, name"
    options:
      method: epic_call_tool
  material.epic_get_static_switch_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Gets the value of a static switch parameter on a material instance. Params: instance, name"
    options:
      method: epic_call_tool
  material.epic_get_texture_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Gets the texture assigned to a texture parameter on a material instance. Params: instance, name"
    options:
      method: epic_call_tool
  material.epic_get_vector_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Gets the current value of a vector parameter on a material instance. Params: instance, name"
    options:
      method: epic_call_tool
  material.epic_layout_expressions:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Automatically arranges all expression nodes in a Material or MaterialFunction graph. Params: material_or_function"
    options:
      method: epic_call_tool
  material.epic_list_expression_classes:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns MaterialExpression subclasses valid for the given context. Use the results with add_expression. Pass a search string to filter by name, e.g. 'Multiply' or 'Parameter'. Params: material_or_function, search"
    options:
      method: epic_call_tool
  material.epic_list_parameter_groups:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Returns the unique parameter group names defined in a Material or MaterialFunction. Parameters are organised into groups in the Material Instance editor. This returns the distinct set of group names found across all parameter expressions in the graph. The empty string represents parameters that have not been assigned to a named group. Params: material_or_function"
    options:
      method: epic_call_tool
  material.epic_list_parameters:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Returns all parameters exposed by a material or instance, with their names and types. Params: material"
    options:
      method: epic_call_tool
  material.epic_recompile:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Recompiles a Material or MaterialFunction after edits. For Materials, raises if the shader fails to compile. For MaterialFunctions, also recompiles any Materials that reference the function. Call this once after a set of graph modifications is complete - after adding or deleting expressions, making connections, or changing expression properties such as parameter names or default values. Params: material_or_function"
    options:
      method: epic_call_tool
  material.epic_rename_parameter_group:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material.MaterialTools] Renames a parameter group across all parameter expressions in a Material or MaterialFunction. All parameters currently in old_name will be moved to new_name. If new_name already exists, the parameters are merged into it. Params: material_or_function, old_name, new_name"
    options:
      method: epic_call_tool
  material.epic_set_parameter_override:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Enables or disables a parameter override on a material instance. Enabling sets the override to the current effective value. Disabling reverts to the parent. For non-static parameter types, disabling also discards the prior override value; re-enabling later restores the parent value, not the prior override. Static switches and static component masks preserve their value across toggle. Params: instance, name, override"
    options:
      method: epic_call_tool
  material.epic_set_parent:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Changes the parent of a material instance. Params: instance, parent"
    options:
      method: epic_call_tool
  material.epic_set_scalar_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Sets the value of a scalar parameter on a material instance. Params: instance, name, value"
    options:
      method: epic_call_tool
  material.epic_set_static_switch_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Sets the value of a static switch parameter on a material instance. Params: instance, name, value"
    options:
      method: epic_call_tool
  material.epic_set_texture_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Assigns a texture to a texture parameter on a material instance. Params: instance, name, value"
    options:
      method: epic_call_tool
  material.epic_set_vector_parameter:
    class_path: ue-mcp.bridge
    group: material
    description: "[Epic editor_toolset.toolsets.material_instance.MaterialInstanceTools] Sets the value of a vector parameter on a material instance. Params: instance, name, value"
    options:
      method: epic_call_tool

  # ── animation ──
  animation.read_anim_blueprint:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read AnimBP structure. Params: assetPath"
    options:
      method: read_anim_blueprint
  animation.read_montage:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read montage. Params: assetPath"
    options:
      method: read_anim_montage
  animation.read_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read anim sequence. Params: assetPath"
    options:
      method: read_anim_sequence
  animation.scan_animation_tracks:
    class_path: ue-mcp.bridge
    group: animation
    description: "Scan AnimSequence bone-track counts. Params: directory?, recursive?, assetPaths?, skeletonPath?, targetTrackCount?, includeTrackNames?"
    options:
      method: scan_animation_tracks
  animation.read_blendspace:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read blendspace. Params: assetPath"
    options:
      method: read_blendspace
  animation.add_blend_sample:
    class_path: ue-mcp.bridge
    group: animation
    description: "Append a sample to a BlendSpace. Params: assetPath, animation (AnimSequence path), position {x,y} (or flat x,y) (#248)"
    options:
      method: add_blend_sample
  animation.set_blend_sample:
    class_path: ue-mcp.bridge
    group: animation
    description: "Move an existing BlendSpace sample or swap its animation. Params: assetPath, sampleIndex, position? {x,y} (or flat x,y), animation? (#272)"
    options:
      method: set_blend_sample
  animation.list:
    class_path: ue-mcp.bridge
    group: animation
    description: "List anim assets (AnimSequence, AnimMontage, AnimBlueprint, BlendSpace). directory scopes the read to one folder and is now honoured; it was advertised and ignored, so a scoped call used to get the whole project back. Params: directory?, recursive?, cursor?, limit?"
    options:
      method: list_anim_assets
  animation.create_montage:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create montage. Params: animSequencePath, name?, packagePath?"
    options:
      method: create_anim_montage
  animation.author_montages_batch:
    class_path: ue-mcp.bridge
    group: animation
    description: "Batch-author montages in one call: idempotent create, slot name, blend/rate/length properties, sections and notifies, then save. Every item reports success plus the failing stage (validate|create|slot|properties|sections|notifies|save) and error, so one bad item does not hide the rest. Newly created montages come back as a delete_asset_batch rollback. Each montage still holds the single segment create_montage builds. Params: items[] (each: name, animSequencePath, packagePath?, onConflict?, slotName?, trackIndex?, rateScale?, blendIn?, blendOut?, sequenceLength?, sections? [{sectionName, startTime?, linkedSection?}], notifies? [{notifyName, triggerTime, notifyClass?, properties?}])"
    options:
      method: author_montages_batch
  animation.create_anim_blueprint:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create AnimBP. Params: skeletonPath, name?, packagePath?, parentClass?"
    options:
      method: create_anim_blueprint
  animation.create_blendspace:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create blendspace (2D). Params: skeletonPath, name?, packagePath?, axisHorizontal?, axisVertical?"
    options:
      method: create_blendspace
  animation.create_blendspace_1d:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create BlendSpace1D. Params: skeletonPath, name?, packagePath?, axisName? (default Speed), axisMin?, axisMax?, gridNum? (#459)"
    options:
      method: create_blendspace_1d
  animation.populate_blendspace:
    class_path: ue-mcp.bridge
    group: animation
    description: "One-call axis params + samples authoring for BlendSpace 1D/2D. Params: assetPath, axis? ({name?, min?, max?, gridNum?}) for axis 0, blendspaceAxes? (per-axis array), axisHorizontal?/axisVertical? + horizontalMin/horizontalMax/verticalMin/verticalMax/gridNumHorizontal/gridNumVertical (back-compat), samples ([{animationPath, x, y?}]), clearExisting? (default true) (#459)"
    options:
      method: populate_blendspace
  animation.add_notify:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add notify. For PlayMontageNotify the notifyName is also written onto the spawned notify object so OnPlayMontageNotifyBegin broadcasts it (not 'None'), and montage branching-point markers refresh (#528/#880). On a montage the PlayMontageNotify classes are added as BRANCHING POINT notifies, which is the only tick type UAnimNotify_PlayMontageNotify::BranchingPointNotify runs at, so OnPlayMontageNotifyBegin broadcasts without a montage reload; pass branchingPoint to force it either way, and read branchingPointMarkerCount in the response to see what the montage cached. notifyProperties writes EditAnywhere fields onto the spawned notify object and therefore requires a notifyClass that resolves. Params: assetPath, notifyName, triggerTime, notifyClass?, notifyProperties?"
    options:
      method: add_anim_notify
  animation.remove_notify:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove notify(s) by name and/or class. Pass at least one of notifyName/notifyClass; both filters AND. Idempotent: alreadyDeleted=true if no match. Params: assetPath, notifyName?, notifyClass? (#471)"
    options:
      method: remove_anim_notify
  animation.get_skeleton_info:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read skeleton. Params: assetPath"
    options:
      method: get_skeleton_info
  animation.list_sockets:
    class_path: ue-mcp.bridge
    group: animation
    description: "List sockets. Params: assetPath"
    options:
      method: list_animation_sockets
  animation.list_skeletal_meshes:
    class_path: ue-mcp.bridge
    group: animation
    description: "List skeletal meshes. directory scopes the read to one folder and is now honoured; it was advertised and ignored, so a scoped call used to get the whole project back. Params: directory?, recursive?, cursor?, limit?"
    options:
      method: list_skeletal_meshes
  animation.get_physics_asset:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read physics asset. Params: assetPath"
    options:
      method: get_physics_asset_info
  animation.create_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create blank AnimSequence. Params: name, skeletonPath, packagePath?, numFrames?, frameRate?"
    options:
      method: create_sequence
  animation.set_bone_keyframes:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set bone transform keyframes. Params: assetPath, boneName, keyframes"
    options:
      method: set_bone_keyframes
  animation.bake_keyframes_batch:
    class_path: ue-mcp.bridge
    group: animation
    description: "Bake per-bone keyframe arrays for many bones into an AnimSequence in one call. Auto-creates each bone track first (set_bone_keyframes silently leaves a T-pose if the track is missing), wraps the batch in one transaction, and raises if any bone fails instead of reporting hollow success (#540). Params: assetPath, tracks ([{bone, keyframes:[{location,rotation{x,y,z,w},scale?}]}]), save? (default true)"
    options:
      method: bake_keyframes_batch
  animation.get_bone_transforms:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read reference pose transforms for one, many, or ALL bones. Omit boneNames to return every bone with index/parentIndex/location/rotation/scale. With boneNames, returns only the named bones. Params: skeletonPath, boneNames? (omit = all bones), space? ('local' default, or 'component' for composed parent-chain transforms - retarget-chain / anatomical-scale work) (#245)"
    options:
      method: get_bone_transforms
  animation.inspect_anim_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "Deep-dump the FAnimNode_* struct of anim graph nodes (PoseDriver PoseTargets/PoseAsset/RBF params/source bones, etc.) that read_anim_graph omits because it skips the 'Node' property. Params: assetPath, graphName? (default AnimGraph), nodeClass? (substring filter, e.g. 'PoseDriver') (#657)"
    options:
      method: inspect_anim_nodes
  animation.compare_curves_to_morph_targets:
    class_path: ue-mcp.bridge
    group: animation
    description: "Compare an AnimSequence/PoseAsset's curve names against a SkeletalMesh's morph target names. Returns curves[], morphTargets[], matched[], curvesWithoutMorph[], morphsWithoutCurve[] - verify authored curves drive morphs without Python. Params: animPath (AnimSequence or PoseAsset), skeletalMeshPath (#656)"
    options:
      method: compare_curves_to_morph_targets
  animation.set_montage_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "Replace the animation sequence in a montage slot. With segmentIndex, replaces only that one segment; without it, replaces every segment in the slot. Params: assetPath, animSequencePath, slotIndex? (default 0), segmentIndex? (#626)"
    options:
      method: set_montage_sequence
  animation.set_montage_properties:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set montage properties. Params: assetPath, sequenceLength?, rateScale?, blendIn?, blendOut?"
    options:
      method: set_montage_properties
  animation.create_state_machine:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create state machine in AnimBP. Params: assetPath, name?, graphName?"
    options:
      method: create_state_machine
  animation.add_state:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add state to a state machine. Params: assetPath, stateMachineName, stateName"
    options:
      method: add_state
  animation.add_transition:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add directed transition between states. Params: assetPath, stateMachineName, fromState, toState"
    options:
      method: add_transition
  animation.set_state_animation:
    class_path: ue-mcp.bridge
    group: animation
    description: "Assign anim asset to state. Params: assetPath, stateMachineName, stateName, animAssetPath"
    options:
      method: set_state_animation
  animation.set_transition_blend:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set blend type/duration on transition. Params: assetPath, stateMachineName, fromState, toState, blendDuration?, blendLogic?"
    options:
      method: set_transition_blend
  animation.set_transition_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set a transition's 'can enter transition' condition from a bool variable, keyed by transition (not graph name - every rule graph is named 'Transition' so blueprint graph tools can only reach the first). Wires VariableGet(bool) -> bCanEnterTransition, replacing any prior condition. Identify the transition by transitionGuid (from add_transition/read_state_machine) OR fromState+toState. Params: assetPath, stateMachineName, variableName (existing bool var), transitionGuid? OR fromState?+toState?, negate? (default false) (#707)"
    options:
      method: set_transition_condition
  animation.read_state_machine:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read state machine topology. Params: assetPath, stateMachineName"
    options:
      method: read_state_machine
  animation.set_state_machine_entry:
    class_path: ue-mcp.bridge
    group: animation
    description: "Point the state machine's ENTRY node at a state, which is the only thing that gives a machine an initial state. create_state_machine and add_state never wired it, so a machine authored through the bridge compiled with no entry and produced the reference pose at runtime. There is no property behind this: it is the pin link from UAnimationStateMachineGraph::EntryNode to the state's input pin. An omitted or empty stateName clears the link instead, so set and clear are one pair. Reports previousEntryState, and unchanged=true on a replay. Params: assetPath, stateMachineName, stateName?"
    options:
      method: set_state_machine_entry
  animation.remove_state:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove a state from a state machine, together with every transition that touched it and the graphs they own - a transition whose endpoint is gone fails the blueprint compile. Returns removedTransitions so they can be replayed, and warns when the removed state was the entry state. Idempotent: alreadyDeleted=true when the state is not there. Params: assetPath, stateMachineName, stateName"
    options:
      method: remove_state
  animation.remove_transition:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove a transition and its rule graph. Address it by transitionGuid (from add_transition or read_state_machine) or by fromState plus toState, which can match several and removes all of them. A rule graph SHARED with another transition is left in place and counted in sharedRuleGraphsKept. Idempotent: alreadyDeleted=true when nothing matched. Params: assetPath, stateMachineName, transitionGuid?, fromState?, toState?"
    options:
      method: remove_transition
  animation.remove_state_machine:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove a state machine node and everything inside it: its states, its transitions, each of their bound graphs, and the machine's own graph. Leaving a bound graph behind after its node is gone is what makes the next compile assert, so the teardown is explicit rather than left to the node. Idempotent: alreadyDeleted=true when the machine is not there. Params: assetPath, stateMachineName"
    options:
      method: remove_state_machine
  animation.read_anim_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read AnimBP AnimGraph nodes with properties & pins. Params: assetPath, graphName?"
    options:
      method: read_anim_graph
  animation.add_curve:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add float curve to AnimSequence. Params: assetPath, curveName, curveType?"
    options:
      method: add_curve
  animation.remove_curve:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove a float curve from an AnimSequence through the animation data controller, so the model, the compressed data and the editor stay in step. Reports removedKeyCount, which is what the rollback cannot restore: add_curve puts back an empty curve of the same name and the keys have to be replayed with set_anim_curve_keys. Idempotent: alreadyDeleted=true when there is no such curve, and the miss lists the curves that do exist. Params: assetPath, curveName"
    options:
      method: remove_anim_curve
  animation.set_anim_curve_keys:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set float-curve key VALUES on an AnimSequence (add_curve only creates an empty named curve - it cannot set keyframe values). Adds the curve if missing, then replaces its keys. Use for authoring Distance/Speed/any float curve directly. Params: assetPath, curveName, keys ([{time, value, interp?('linear'|'constant'|'cubic')}]), interpolation? (default 'linear', applied to keys without their own interp) (#712)"
    options:
      method: set_anim_curve_keys
  animation.apply_animation_modifier:
    class_path: ue-mcp.bridge
    group: animation
    description: "Instantiate a UAnimationModifier subclass and run it on an AnimSequence. Headline use: modifierClass='DistanceCurveModifier' bakes a Distance curve from the clip's root motion for distance matching (needs root motion baked first - see bake_root_motion_from_bone). Registers the modifier on the sequence so it re-applies on reimport. props sets the modifier's EditAnywhere fields (e.g. DistanceCurveModifier: {CurveName, Axis:'XY'|'X'|..., bStopAtEnd, StopSpeedThreshold, SampleRate}). Note: DistanceCurveModifier ships in the 'Animation Locomotion Library' plugin (off by default) - enable it first. Params: assetPath, modifierClass (short name or /Script path), props? (#712)"
    options:
      method: apply_animation_modifier
  animation.set_montage_slot:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set slot name on a montage track. Params: assetPath, slotName, trackIndex?"
    options:
      method: set_montage_slot
  animation.remove_montage_section:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove a composite section from a montage through UAnimMontage::DeleteAnimCompositeSection, then clear every OTHER section whose next-section link pointed at it, because a montage that jumps to a section which no longer exists stops dead. The cleared ones come back in clearedNextLinks so they can be re-pointed with asset(set_property) on CompositeSections[i].NextSectionName. Idempotent: alreadyDeleted=true when there is no such section, and the miss lists the sections that exist. Params: assetPath, sectionName"
    options:
      method: remove_montage_section
  animation.add_notify_state:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add a windowed notify (a UAnimNotifyState) to a sequence or montage: the form that spans a duration and fires NotifyBegin, NotifyTick and NotifyEnd, which is what a combo window, a hit window or a timed particle effect is built from. add_notify only ever writes the instant form, so this was unreachable. notifyStateClass takes a class name, a bare suffix ('TimedParticleEffect' resolves AnimNotifyState_TimedParticleEffect), or a full path, and an unresolved one is refused rather than silently dropped. notifyProperties are validated against the class before anything is written. Returns objectPath for further editor(set_property) writes, and the full notifyStates list on the asset. Params: assetPath, notifyName, notifyStateClass, triggerTime, duration, notifyProperties?, branchingPoint?"
    options:
      method: add_anim_notify_state
  animation.remove_notify_state:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove windowed notifies by name and/or class; both filters apply together and at least one is required. This is a separate action rather than a flag on remove_notify because that handler's class filter only ever inspects FAnimNotifyEvent::Notify, so a notify STATE is invisible to it. Idempotent: alreadyDeleted=true when nothing matched. Params: assetPath, notifyName?, notifyStateClass?"
    options:
      method: remove_anim_notify_state
  animation.set_sync_markers:
    class_path: ue-mcp.bridge
    group: animation
    description: "Author an AnimSequence's sync markers: apply the list, refresh the sequence's marker index, register the names on the skeleton, then read back what actually landed. A plain property write reaches AuthoredSyncMarkers and leaves it inert, because the runtime reads UniqueMarkerNames and the index built by RefreshSyncMarkerDataFromAuthored, and the editor only offers marker names the skeleton has seen. markerMode 'replace' (default) makes the array the whole list, so an empty array clears them; 'merge' adds or moves only the named ones. The whole batch is validated against the clip length before anything is written. Params: assetPath, markers?, removeMarkers?, markerMode?"
    options:
      method: set_sync_markers
  animation.add_montage_section:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add composite section to montage. Pass segmentIndex (with slotName or slotIndex) to anchor the section to a specific segment: its startTime is taken from that segment and it stays linked, so inserting a segment ahead of it moves the marker with its animation. Without segmentIndex the section is a bare absolute-time marker. Params: assetPath, sectionName, startTime?, linkedSection?, segmentIndex?, slotName?, slotIndex? (#826)"
    options:
      method: add_montage_section
  animation.add_montage_segment:
    class_path: ue-mcp.bridge
    group: animation
    description: "Append (or insert) an animation segment into a montage slot's anim track. This is the only way to get more than one animation into a montage: create_montage builds exactly one segment, set_montage_sequence replaces rather than appends, and add_montage_section only writes a time marker with no animation behind it. Creates the named slot when it does not exist. Validates that the source shares the montage's skeleton and matches the track's additive type, then relays out the segments, refreshes linked sections and notifies, and rewrites the montage length. Params: assetPath, animSequencePath, slotName? (created if absent), slotIndex? (default 0, used when slotName is omitted), startPos? (trim into the source, default 0), endPos? (default source play length), playRate? (default 1, negative reverses), loopCount? (default 1), insertIndex? (default appends) (#826)"
    options:
      method: add_montage_segment
  animation.remove_montage_segment:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove a segment from a montage slot by index, then relay out the remaining segments and rewrite the montage length. Idempotent: alreadyDeleted=true when the slot already holds no segments. Params: assetPath, segmentIndex, slotName?, slotIndex? (default 0) (#826)"
    options:
      method: remove_montage_segment
  animation.list_montage_segments:
    class_path: ue-mcp.bridge
    group: animation
    description: "List every slot's segments on a montage so a caller can address them by index: animation path, startPos/endPos trim, playRate, loopCount, track position and length per segment, plus the sections with the slot and segment each one links to. Params: assetPath, slotName? (filter to one slot) (#826)"
    options:
      method: list_montage_segments
  animation.create_ik_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create IKRigDefinition asset, optionally with retargetRoot + chains[]. Params: name, skeletalMeshPath, packagePath?, retargetRoot?, chains?: [{name, startBone, endBone, goal?}]"
    options:
      method: create_ik_rig
  animation.read_ik_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read an IK Rig's preview mesh, skeleton roots/bones, ancestry-validated chains and goal assignments, concrete goals, exclusions, and structured solver/FBIK effector state. Params: assetPath"
    options:
      method: read_ik_rig
  animation.configure_ik_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "UE 5.8 only. Author an existing IK Rig through UIKRigController with strict bone, ancestry, goal, and setting validation, native readback, one transaction, and checked save; older engines return unsupported_engine_version. autoSetup='retarget' installs the native retarget definition; 'full_body' installs the retarget definition then Full Body IK before requested desired-state upserts. Params: rigPath, autoSetup? ('retarget'|'full_body'), retargetRoot?, rootMotionBone?, chains?: [{name,startBone,endBone,goal?}], fullBodyIK?: {solverIndex?,rootBone,enabled?,goals:[{name,bone,positionAlpha?,rotationAlpha?,chainDepth?,strengthAlpha?,pullChainAlpha?,pinRotation?}]}, exclusions?: [{bone,excluded}]."
    options:
      method: configure_ik_rig
  animation.list_control_rig_variables:
    class_path: ue-mcp.bridge
    group: animation
    description: "List ControlRig variables and hierarchy. Params: assetPath"
    options:
      method: list_control_rig_variables
  animation.read_control_rig_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read a Control Rig's RigVM models: every graph with its nodes (name, node path, class), each node's pins (name, pin path, cppType, direction, execute flag, default value, nested sub-pins) and the links between them, plus full member-variable metadata (type, subtype, array-ness, default, public/read-only). list_control_rig_variables only ever reported a node COUNT, which is not enough to verify solver wiring (#774). Params: assetPath, graphName? (substring filter), includePins? (default true), includeDefaults? (default true), includeLinks? (default true), limit? (nodes per graph, default 200)"
    options:
      method: read_control_rig_graph
  animation.read_control_rig_hierarchy:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read a Control Rig's per-element hierarchy metadata: each element's name, type (Bone|Control|Null|Curve...), index, and parent. Params: assetPath (#619)"
    options:
      method: read_control_rig_hierarchy
  animation.begin_control_rig_edit:
    class_path: ue-mcp.bridge
    group: animation
    description: "UE 5.8 only. Create a Sequencer Control Rig editing session over a source AnimSequence; native returns unsupported_engine_version on older engines. Baseline first: before this call, reuse or create a Control Rig for the target character, bind/import the exact target skeleton, add the intended controls, author Forward Solve, add Backward/Inverse Solve, verify it with read_control_rig_hierarchy/read_control_rig_graph, and pass an unchanged source round-trip. For a new baseline, the bundled Epic 5.8 controlrig actions include epic_create, epic_import_bones_from_asset, epic_add_control, and epic_add_backward_solve_graph; epic_create alone is not a usable rig. There is no silent fallback to raw bone-key authoring. rigMode='fk' uses UFKControlRig only when generated FK controls are sufficient; rigMode='asset' requires the verified controlRigPath and rejects rigs without inverse execution. bindingTag is the stable natural key for replay. onConflict is skip|error (default error); existing sessions are never modified. layered defaults false. startFrame is inclusive and endFrame is exclusive. Params: sequencePath, skeletalMeshPath, sourceAnimationPath, rigMode ('fk'|'asset'), controlRigPath?, layered?, startFrame?, endFrame?, displayRate?, bindingTag?, onConflict?. Returns the resolved bindingTag/binding GUID, rig, frame range, controls and created/existed status."
    options:
      method: begin_control_rig_edit
  animation.read_control_rig_edit:
    class_path: ue-mcp.bridge
    group: animation
    description: "UE 5.8 only. Read transform, bool, float/scale-float, and integer/enum controls from a Control Rig editing session without changing editor state; native returns unsupported_engine_version on older engines and has no silent fallback. Params: sequencePath, bindingTag, controlNames?, frames?, space? ('local'|'global'). Scalar samples return value instead of transform. Control metadata includes native controlType, animatable, and enum path/options where applicable. Returns session identity, layered mode, range/rate, filtered control metadata, and requested frame samples."
    options:
      method: read_control_rig_edit
  animation.capture_control_rig_pose:
    class_path: ue-mcp.bridge
    group: animation
    description: "UE 5.8 only. Capture current live local UControlRig values without opening, scrubbing, refreshing, or evaluating Sequencer; native returns unsupported_engine_version on older engines and has no silent fallback. The addressed LevelSequence must already be focused. Omit controlNames to capture the current Control Rig selection. Returns an immutable captureVersion=1 snapshot with sequence, binding, track, section, rig-object, current-frame, selection, control-type, and typed-value identity for later propagate_pose validation. Params: sequencePath, bindingTag, controlNames?."
    options:
      method: capture_control_rig_pose
  animation.apply_control_rig_edits:
    class_path: ue-mcp.bridge
    group: animation
    description: "UE 5.8 only. Apply typed Control Rig edits in one transaction; native returns unsupported_engine_version on older engines. There is no silent fallback to raw bone tracks. set_keys writes strictly ordered full per-frame transforms from normalized quaternions and preserves shortest-arc quaternion continuity. A set operation writes one full absolute transform at frame or frames. An offset operation applies translation/rotation/scale deltas across an inclusive frame range with optional edge blends. propagate_pose validates caller-held baseline and accepted live snapshots from the same session, exact rig instance, frame, and complete control set, then keys only controls whose values changed across each control's explicit original donorFrames. Snapshot selection is provenance metadata; the explicit propagation controls are authoritative. mode='fixed' copies the accepted changed channels; mode='local_delta' preserves donor motion while applying the captured local delta. Bool, enum, and int controls support fixed only. contact_lock densely constrains a translatable driver control, or an optional driven bone/socket reference, to a fixed component-space target with smooth edge blends and optional pole/control stabilization. Driver and stabilizer keys are read back transactionally. A drivenReference contact returns verification='bake_and_analyze_required'; bake it and analyze every constrained frame before accepting the bone/socket result. set_bool, set_float, and set_int key matching scalar controls; enum controls use set_int with one of the integer values reported in enumOptions. Params: sequencePath, bindingTag, operations[]. Sequencer's current interpolation mode is retained. Returns per-operation counts, affected controls/frames and changedChannels; a failed key/readback batch is undone."
    options:
      method: apply_control_rig_edits
  animation.bake_control_rig_edit:
    class_path: ue-mcp.bridge
    group: animation
    description: "UE 5.8 only. Bake the evaluated Control Rig session to a new AnimSequence asset; native returns unsupported_engine_version on older engines and has no raw-track fallback. The source LevelSequence remains unchanged. outputAssetPath is the output natural key; onConflict is skip|error (default error), never overwrite. Key reduction and Sequencer links are not supported yet, so reduceKeys/createLink must be false or omitted. Params: sequencePath, bindingTag, outputAssetPath, frameRate?, reduceKeys?, tolerance?, createLink?, onConflict?. Returns output asset metadata, frame/rate counts, status, and delete-created-asset rollback."
    options:
      method: bake_control_rig_edit
  animation.analyze_animation:
    class_path: ue-mcp.bridge
    group: animation
    description: "Cross-version, data-driven AnimSequence inspection using the native animation APIs available in the compiled engine. Samples an AnimSequence and reports deterministic numeric motion diagnostics without Python or viewport inference. Params: assetPath (required AnimSequence), skeletalMeshPath?, boneNames?, frames?, sampleRate?, loop?, outputDirectory? (must resolve under Project/Saved/Codex/AnimationQA and must not already contain artifacts). Returns source/rate/range metadata, sampled local/component transforms, root-motion and continuity metrics, and any written analysis artifacts."
    options:
      method: analyze_animation
  animation.set_root_motion:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set root motion settings on AnimSequence. Params: assetPath, enableRootMotion?, forceRootLock?, useNormalizedRootMotionScale?, rootMotionRootLock?"
    options:
      method: set_root_motion_settings
  animation.begin_skeleton_edit:
    class_path: ue-mcp.bridge
    group: animation
    description: "Open a batched bone-editing session over a skeletal mesh's reference skeleton. NOTHING is written until commit_skeleton_edit: a per-edit commit would re-derive the reference skeleton once per change and can leave a half-edited hierarchy, which is why this mirrors the begin/apply/bake lifecycle the Control Rig actions already use. sessionTag is the stable key every later call addresses and defaults to Skel_<MeshName>; reopening the same tag on the same mesh is idempotent. Returns the current bone list, the baseline bone count, and how many bones have skinned vertices. Params: skeletalMeshPath, sessionTag?"
    options:
      method: begin_skeleton_edit
  animation.edit_skeleton_bones:
    class_path: ue-mcp.bridge
    group: animation
    description: "Apply a whole batch of hierarchy edits to an open session, validating every entry against the state its predecessors leave BEFORE mutating anything, so a bad entry at position nine does not leave the first eight applied. Removing a bone that has children, or that mesh sections skin vertices to, is refused with the dependents listed unless removeChildren or force says otherwise; a reparent that would form a cycle names the offending bone. Returns a per-edit changed/alreadyApplied row and an inverse-edit rollback. Params: sessionTag? OR skeletalMeshPath?, edits[] (each one of {op:'add',bone,parent,transform?} | {op:'remove',bone,removeChildren?} | {op:'rename',bone,newName} | {op:'reparent',bone,parent} | {op:'set_transform',bone,transform,moveChildren?}), force?"
    options:
      method: edit_skeleton_bones
  animation.commit_skeleton_edit:
    class_path: ue-mcp.bridge
    group: animation
    description: "Write the session's batched edits into the skeletal mesh and its skeleton in one transaction, and save both packages. This is the ONLY call that touches the assets. A session with no pending edits closes without dirtying anything. The rollback is flagged lossy on purpose: skin weights and dependent-asset fix-ups cannot be reversed by replaying inverse edits. Params: sessionTag? OR skeletalMeshPath?"
    options:
      method: commit_skeleton_edit
  animation.cancel_skeleton_edit:
    class_path: ue-mcp.bridge
    group: animation
    description: "Discard an open session's working copy without writing; the assets on disk are untouched. Cancelling a session that is not open reports alreadyClosed rather than failing, so replaying a rollback is safe. Returns the discarded edits so they can be re-sent after a fix. Params: sessionTag? OR skeletalMeshPath?"
    options:
      method: cancel_skeleton_edit
  animation.set_bone_retargeting:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set each bone's translation retargeting mode, which lives in the skeleton's private BoneTree and has no addressable UPROPERTY, so set_property cannot reach it. Omitting bones applies to every bone; includeChildren uses the engine's own recursive setter. Returns the prior and new mode per bone, and a per-bone restore rollback that replays through this same action. Params: skeletonPath, mode (Animation|Skeleton|AnimationScaled|AnimationRelative|OrientAndScale), bones? OR bone?, includeChildren?, restore? ([{bone, mode}])"
    options:
      method: set_bone_retargeting
  animation.author_blend_profile:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create a blend profile if it is absent and write its per-bone scales. A UBlendProfile is a per-skeleton subobject, so asset(set_property) can neither create it nor address the per-bone map. remove deletes the profile and reports every entry it destroyed, so the inverse can rebuild it. Returns the full entry list plus the skeleton's profile names. Params: skeletonPath, profileName, operation? (upsert|remove|rename), newProfileName?, mode? (TimeFactor|WeightFactor|BlendMask), entries? ([{bone, scale, recursive?}]), removeEntries? (bone names)"
    options:
      method: author_blend_profile
  animation.edit_curve_metadata:
    class_path: ue-mcp.bridge
    group: animation
    description: "Author the skeleton curve metadata that compare_curves_to_morph_targets could only read, including the material and morph-target flags that drive curves into materials and morphs. The whole batch validates first, and every operation is idempotent: adding an existing curve or removing an absent one reports rather than errors. Returns the full curve metadata table after the edit. Params: skeletonPath, add? (curve names), remove? (curve names), rename? ([{from, to}]), flags? ([{curve, material?, morphTarget?}])"
    options:
      method: edit_curve_metadata
  animation.register_compatible_skeleton:
    class_path: ue-mcp.bridge
    group: animation
    description: "Register, or with remove=true unregister, another skeleton as compatible, so its animations are usable on this one. This closes the loop asset(diff) opens: diff two skeletons for the hierarchy delta, then act on it. The engine call has been exercised in this repo's tests for a long time and was simply never shipped as an action. Returns the compatible list before and after and, per entry, whether the engine's own editor compatibility check agrees plus the source's bone count. Params: skeletonPath, compatibleSkeletonPath? OR compatibleSkeletonPaths?, remove?"
    options:
      method: register_compatible_skeleton
  animation.add_virtual_bone:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add virtual bone. Params: skeletonPath, sourceBone, targetBone"
    options:
      method: add_virtual_bone
  animation.remove_virtual_bone:
    class_path: ue-mcp.bridge
    group: animation
    description: "Remove virtual bone. Params: skeletonPath, virtualBoneName"
    options:
      method: remove_virtual_bone
  animation.create_composite:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create AnimComposite. Params: name, skeletonPath, packagePath?"
    options:
      method: create_anim_composite
  animation.list_modifiers:
    class_path: ue-mcp.bridge
    group: animation
    description: "List applied animation modifiers. Params: assetPath"
    options:
      method: list_anim_modifiers
  animation.create_ik_retargeter:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create IKRetargeter asset and (default) initialize the UE 5.7 ops stack: assigns sourceRig+targetRig to all ops, runs AutoMapChains. Returns chainsMapped count. Params: name, packagePath?, sourceRig?, targetRig?, autoMapChains? (default true) (#246)"
    options:
      method: create_ik_retargeter
  animation.read_ik_retargeter:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read an IK Retargeter's source/target rigs and preview meshes, flattened and per-op chain mappings, typed op stack, and all named/current pose offsets when the compiled engine exposes them. Each op now carries its `settings` object in full, reflected off the op's own settings struct: the Root Motion op's RootMotionSource, the Pelvis Motion op's alphas and offsets, and the FK Chains op's per-chain RotationMode and TranslationMode. Those settings decide what a retarget actually does, and they were the part only Python could see (#1000). Params: assetPath (#246)"
    options:
      method: read_ik_retargeter
  animation.configure_ik_retargeter:
    class_path: ue-mcp.bridge
    group: animation
    description: "UE 5.8 only. `ops` writes the per-op settings that decide what a retarget does and that nothing else could reach: pass {name or index, enabled?, settings?, chainSettings?}. `settings` writes properties on the op's own settings struct; `chainSettings` merges per-chain FK properties into the chain you name rather than replacing the whole ChainsToRetarget list, so setting one chain's RotationMode leaves the others alone. Every op write is validated before the transaction opens and applied inside it, so a bad property name changes nothing (#1000/#1034). Configure an existing IK Retargeter through UIKRetargeterController with the correct default-op and per-op rig assignment order, auto/manual chain mappings, named pose authoring, processor validation, native readback, transaction rollback, and checked save; older engines return unsupported_engine_version. Whole-pose auto-align resets that pose first: create a new pose or pass pose.reset=true to acknowledge replacement, then manual offsets are applied. Params: retargeterPath, sourceRig?, targetRig?, sourcePreviewMesh?, targetPreviewMesh?, ensureDefaultOps? (default true), autoMapMode? ('exact'|'fuzzy'|'clear'), forceRemap? (default false), chainMappings?: [{targetChain,sourceChain?:string|null}], pose?: {side,name,create?,reset?,autoAlign?,bones?,rotationOffsets?:[{bone,rotationQuaternion}],rootOffsetZ?,snapBoneToGround?}."
    options:
      method: configure_ik_retargeter
  animation.set_ik_rig_mesh:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set the preview/source skeletal mesh on an EXISTING IK Rig. Params: rigPath, meshPath (#701)"
    options:
      method: set_ik_rig_mesh
  animation.set_ik_retargeter_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set the source or target IK Rig on an EXISTING IK Retargeter. Params: retargeterPath, rigPath, side? (source|target, default target) (#703)"
    options:
      method: set_ik_retargeter_rig
  animation.auto_align_retarget_pose:
    class_path: ue-mcp.bridge
    group: animation
    description: "Auto-align all bones of the source/target retarget pose (chain-to-chain) - fixes a retargeter that outputs a static reference pose. Params: retargeterPath, side? (source|target, default target) (#701)"
    options:
      method: auto_align_retarget_pose
  animation.reset_retarget_pose:
    class_path: ue-mcp.bridge
    group: animation
    description: "Reset the current retarget pose (all bones) to the reference pose. Params: retargeterPath, side? (source|target, default target) (#701)"
    options:
      method: reset_retarget_pose
  animation.batch_retarget_animations:
    class_path: ue-mcp.bridge
    group: animation
    description: "Bake validated source AnimSequences onto the target skeleton through an IK Retargeter (RunBatchRetarget), save every output, and roll back newly created outputs if the batch is incomplete or unsavable. Overwrite is rejected. Returns mapping completeness and every unmapped target chain so partial retargets are explicit; pass requireCompleteMapping=true only when the target should have no intentional extra chains. Params: retargeterPath, sourceMesh, targetMesh, animPaths[], outputPath? (default: alongside source), prefix?, suffix? (default _Retargeted), overwrite? (must be false), requireCompleteMapping? (default false) (#701)"
    options:
      method: batch_retarget_animations
  animation.set_anim_blueprint_skeleton:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set target skeleton on AnimBP. Params: assetPath, skeletonPath"
    options:
      method: set_anim_blueprint_skeleton
  animation.read_bone_track:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read bone transform samples from AnimSequence. Params: assetPath, boneName, frames?: [int]"
    options:
      method: read_bone_track
  animation.create_pose_search_database:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create a PoseSearchDatabase asset (motion matching). Pass skeletonPath and it authors the matching PoseSearchSchema (<name>_Schema, default channels) alongside it, which is what makes the database indexable at all; pass schemaPath to reuse an existing one. A schema that cannot index (no skeleton, or no feature channels) is refused rather than assigned, because the editor then reports the DATABASE as the invalid asset. Without either, the database is created empty and unindexable and says so in note. Params: name, packagePath?, skeletonPath?, schemaPath?, onConflict? (#833)"
    options:
      method: create_pose_search_database
  animation.set_pose_search_schema:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set the Schema on an existing PoseSearchDatabase. Params: assetPath, schemaPath"
    options:
      method: set_pose_search_schema
  animation.add_pose_search_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "Append an AnimSequence/AnimComposite/AnimMontage/BlendSpace to a PoseSearchDatabase, with optional per-clip flags. Params: assetPath, sequencePath, mirror? ('original'|'mirrored'|'both'), disableReselection?, sampleStart?, sampleEnd?, enabled? (#684)"
    options:
      method: add_pose_search_sequence
  animation.set_pose_search_clips:
    class_path: ue-mcp.bridge
    group: animation
    description: "Author the whole clip list of a PoseSearchDatabase in one call (the 'duplicate a stock PSD, swap its clips' pipeline step). Replaces the list by default. Each clip carries per-entry flags. Params: assetPath, clips ([{sequencePath, mirror? ('original'|'mirrored'|'both'), disableReselection?, sampleStart?, sampleEnd?, enabled?}] - a bare string path also works), clearExisting? (default true). Follow with build_pose_search_index (#684)"
    options:
      method: set_pose_search_clips
  animation.build_pose_search_index:
    class_path: ue-mcp.bridge
    group: animation
    description: "Build (or rebuild) the search index. Params: assetPath, wait? (default true)"
    options:
      method: build_pose_search_index
  animation.read_pose_search_database:
    class_path: ue-mcp.bridge
    group: animation
    description: "Inspect a PoseSearchDatabase: schema, animation entries, cost biases, tags. Params: assetPath"
    options:
      method: read_pose_search_database
  animation.set_pose_search_database_settings:
    class_path: ue-mcp.bridge
    group: animation
    description: "Tune a PoseSearchDatabase: cost biases, KD-tree neighbours, search mode, PCA components, normalization set. Params: assetPath, continuingPoseCostBias?, baseCostBias?, loopingCostBias?, kdTreeQueryNumNeighbors?, numberOfPrincipalComponents?, poseSearchMode? ('bruteforce'|'pcakdtree'|'vptree'|'eventonly'), normalizationSetPath? (motion matching)"
    options:
      method: set_pose_search_database_settings
  animation.create_pose_search_schema:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create a PoseSearchSchema (the feature definition a database indexes against). Binds a skeleton (and optional mirror table) and, by default, adds Trajectory+Pose default channels so the schema is immediately buildable. Refine with add_pose_search_schema_*_channel. Params: name, skeletonPath, packagePath?, mirrorDataTablePath?, sampleRate?, addDefaultChannels? (default true) (motion matching)"
    options:
      method: create_pose_search_schema
  animation.add_pose_search_schema_pose_channel:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add a Pose feature channel to a schema (samples named bones for velocity/position/rotation/phase). Params: schemaPath, bones ([{bone, flags?:['velocity','position','rotation','phase'], weight?}] - a bare bone-name string defaults to position), weight? (motion matching)"
    options:
      method: add_pose_search_schema_pose_channel
  animation.add_pose_search_schema_trajectory_channel:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add a Trajectory feature channel to a schema (past/future motion samples). Params: schemaPath, samples ([{offset (seconds; negative=history, positive=prediction), flags?:['position','velocity','facingDirection','velocityDirection', ...XY variants], weight?}]), weight? (motion matching)"
    options:
      method: add_pose_search_schema_trajectory_channel
  animation.read_pose_search_schema:
    class_path: ue-mcp.bridge
    group: animation
    description: "Inspect a PoseSearchSchema: skeleton(s), mirror table, sample rate, feature channels. Params: schemaPath (motion matching)"
    options:
      method: read_pose_search_schema
  animation.create_mirror_data_table:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create a MirrorDataTable for a skeleton (needed for mirrored poses in motion matching / mirror nodes). Auto-derives bone-pair rows from find/replace expressions (defaults to UE mannequin _l/_r suffix swap). Params: name, skeletonPath, packagePath?, expressions? ([{find, replace, method?:'suffix'|'prefix'|'regex'}]), mirrorAxis? (X|Y|Z, default X), mirrorRootMotion? (default true)"
    options:
      method: create_mirror_data_table
  animation.read_mirror_data_table:
    class_path: ue-mcp.bridge
    group: animation
    description: "Inspect a MirrorDataTable: skeleton and bone-pair rows (name -> mirroredName). Params: assetPath (motion matching)"
    options:
      method: read_mirror_data_table
  animation.create_pose_search_normalization_set:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create a PoseSearchNormalizationSet grouping databases so they normalize their cost space together (consistent blending across a locomotion set). Assign it via set_pose_search_database_settings(normalizationSetPath). Params: name, packagePath?, databases? ([PoseSearchDatabase paths]) (motion matching)"
    options:
      method: create_pose_search_normalization_set
  animation.add_motion_matching_node:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add a Motion Matching node to an AnimBP AnimGraph and point it at a PoseSearchDatabase (the runtime node that searches the database each frame). Connects its output to the Output Pose by default. For chooser-driven database selection, bind an anim-node function that calls SetDatabasesToSearch. Params: assetPath (AnimBP), databasePath, graphName? (default AnimGraph), connectToOutput? (default true), blendTime? (motion matching)"
    options:
      method: add_motion_matching_node
  animation.add_pose_history_node:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add a Pose History (PoseSearchHistoryCollector) node to an AnimBP AnimGraph - the Motion Matching node needs it in the graph to query pose/trajectory history. Defaults to self-generated trajectory (no external trajectory pin needed) and inserts itself into the pose chain feeding the Output Pose. Params: assetPath (AnimBP), graphName? (default AnimGraph), poseCount?, samplingInterval?, generateTrajectory? (default true), trajectoryHistoryCount?, trajectoryPredictionCount?, insertBeforeOutput? (default true) (motion matching)"
    options:
      method: add_pose_history_node
  animation.set_motion_matching_chooser:
    class_path: ue-mcp.bridge
    group: animation
    description: "Drive the Motion Matching node's Database from a ChooserTable so the database is selected at runtime by character state. Wires a thread-safe EvaluateChooser (result typed to PoseSearchDatabase) into the MM node's Database pin. contextSource selects what the chooser reads its columns from: 'self' (default, the anim instance - choosers branching on AnimBP variables) or 'pawn' (the owning pawn via TryGetPawnOwner - choosers branching on character/pawn state). Params: assetPath (AnimBP), chooserPath (ChooserTable), graphName? (default AnimGraph), contextSource? ('self'|'pawn') (motion matching)"
    options:
      method: set_motion_matching_chooser
  animation.create_skeleton:
    class_path: ue-mcp.bridge
    group: animation
    description: "Create a real USkeleton from a SkeletalMesh through the Unreal skeleton factory. The factory assigns the new skeleton to that mesh, then both packages are saved. An existing destination is an onConflict=skip idempotency hit only when the mesh already points at it and the reference skeleton has exactly the same bone count, names in index order, and parent indexes; reference-pose transforms are not compared. Otherwise the action refuses to repurpose it. If either save fails, it restores and saves the mesh's previous skeleton reference, then deletes the new skeleton when safe; an incomplete cleanup returns a machine-readable recovery descriptor. Returns the previous skeleton and a lossy rollback that restores the mesh association; delete the created skeleton separately only after confirming nothing references it. Params: name, skeletalMeshPath, packagePath? (default /Game), onConflict? (skip|error)"
    options:
      method: create_skeleton
  animation.add_sequence_evaluator:
    class_path: ue-mcp.bridge
    group: animation
    description: "Add a Sequence Evaluator node (explicit-time player) to an AnimBP graph - the node distance matching drives by setting its ExplicitTime each frame. graphName can be the top-level AnimGraph or a state's inner graph (pass the state name). Defaults bTeleportToExplicitTime=false so time advances and root motion extracts. Connects to the Output Pose by default. Returns nodeGuid for bind_anim_node_function. Params: assetPath (AnimBP), sequencePath? (AnimSequence to evaluate), graphName? (default AnimGraph), explicitTime?, shouldLoop?, teleportToExplicitTime? (default false), connectToOutput? (default true) (#713)"
    options:
      method: add_sequence_evaluator
  animation.bind_anim_node_function:
    class_path: ue-mcp.bridge
    group: animation
    description: "Bind a thread-safe anim-node function to an anim graph node's update slot - the mechanism distance matching uses to advance a Sequence Evaluator's explicit time each frame (function calls AnimDistanceMatchingLibrary::DistanceMatchToTarget / AdvanceTimeByDistanceMatching). The function must already exist on the AnimBP (create it as a BlueprintThreadSafe function first). Identify the node by nodeGuid (from add_sequence_evaluator / add_*_node). Params: assetPath (AnimBP), nodeGuid, functionName, graphName? (default AnimGraph), binding? ('update' (default)|'becomeRelevant'|'initialUpdate') (#713)"
    options:
      method: bind_anim_node_function
  animation.set_sequence_properties:
    class_path: ue-mcp.bridge
    group: animation
    description: "Batch-set properties on AnimSequence assets. If a path is a Montage and resolveFromMontages is true (default), resolves to its first AnimSequence. Params: assetPaths[], properties{enableRootMotion?, forceRootLock?, useNormalizedRootMotionScale?, rootMotionRootLock?}, resolveFromMontages?"
    options:
      method: set_sequence_properties
  animation.bake_root_motion_from_bone:
    class_path: ue-mcp.bridge
    group: animation
    description: "Bake delta translation from a source bone (e.g. pelvis) onto the root bone across the whole sequence; compensates the source bone so world-space position is unchanged. Params: assetPath, sourceBone, rootBone? (default 'root'), axes? (default ['x','y']), interpolation? ('linear'|'per_frame', default 'linear')"
    options:
      method: bake_root_motion_from_bone
  animation.get_bone_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read a bone or socket transform on a live actor's SkeletalMeshComponent. Wraps GetBoneTransform / GetSocketTransform. Params: actorLabel OR actorPath, boneName (or socket name), componentName? (default: CharacterMesh0 / Mesh / first SK component), world? (auto|pie|game|editor, default auto), space? (world|component|local, default world). Returns location, rotation, scale (#420)"
    options:
      method: get_bone_transform
  animation.list_bones:
    class_path: ue-mcp.bridge
    group: animation
    description: "List bones in a live actor's SkeletalMeshComponent ref skeleton (name, index, parent), in reference-skeleton order so parents precede children. Params: actorLabel OR actorPath, componentName?, world? (auto|pie|game|editor, default auto), cursor?, limit? (#420)"
    options:
      method: list_bones
  animation.rebind_leader_pose:
    class_path: ue-mcp.bridge
    group: animation
    description: "Re-bind every secondary SkeletalMeshComponent on an actor to a body component (default CharacterMesh0 / Mesh). One-call fix for the 'character explodes after rotating the actor' failure mode. Params: actorLabel OR actorPath, bodyComponent? (#419)"
    options:
      method: rebind_leader_pose
  animation.sample_pose:
    class_path: ue-mcp.bridge
    group: animation
    description: "Evaluate an AnimSequence (or a BlendSpace at a blend position) and return COMPOSED bone transforms, which read_bone_track (raw local space) and get_bone_transforms (reference pose only) cannot give. Params: assetPath, boneNames? (omit for every bone), frames? or times? (omit for every sampled key), space? (component default | local | world), skeletalMeshPath? (evaluate with that mesh's proportions), incorporateRootMotion? (default true), blendPosition? {x,y,z} (BlendSpace only). Returns samples:[{frame, time, bones:[{name, location, rotation, scale}]}]. A montage is refused with a pointer to its segments, because a montage has no pose of its own (#923/#926/#922)"
    options:
      method: sample_pose
  animation.get_live_bone_transforms:
    class_path: ue-mcp.bridge
    group: animation
    description: "Read the EVALUATED pose off a live SkeletalMeshComponent in the editor world or PIE, for many bones at once. This is the read that tells 'standing' from 'prone' when the reference pose cannot (#922). Also returns componentTransform and an evaluation block (animationMode, animInstanceClass, componentSpaceTransformCount) so a clean component transform sitting on a dead anim instance is visible in one call. Params: actorLabel OR actorPath, componentName? (default CharacterMesh0 / Mesh), boneNames? (omit for every bone, max 1000), space? (world default | component | local), world? (auto|pie|game|editor) (#922/#926)"
    options:
      method: get_live_bone_transforms
  animation.measure_natural_speed:
    class_path: ue-mcp.bridge
    group: animation
    description: "Measure a locomotion clip's planted-foot speed, in cm/s, by evaluating the pose and tracking the lowest foot's horizontal travel while it is in contact. Every retarget changes natural speed by the target skeleton's leg-length ratio, so a BlendSpace built on retargeted clips has to re-measure per clip per character (#923). Params: assetPath, footBones[] (e.g. ['foot_l','foot_r']), contactThreshold? (contact height in cm; omit to derive it from the clip), skeletalMeshPath?, frames?/times?, blendPosition? (BlendSpace only). Returns naturalSpeed, plantedDistance, plantedTime, contactThreshold, per-foot breakdown"
    options:
      method: measure_natural_speed
  animation.preview_animation:
    class_path: ue-mcp.bridge
    group: animation
    description: "Toggle bUpdateAnimationInEditor + VisibilityBasedAnimTickOption=AlwaysTickPoseAndRefreshBones on every SkeletalMeshComponent of an actor. Bypasses the 'cannot be edited on templates' guard for level instances. Params: actorLabel OR actorPath, enabled (#419/#420)"
    options:
      method: preview_animation
  animation.set_live_post_process_anim_blueprint:
    class_path: ue-mcp.bridge
    group: animation
    description: "Set or clear a transient post-process AnimBP override on one live SkeletalMeshComponent in the editor world or PIE. Pass the AnimBlueprintGeneratedClass object path (for example /Game/Animations/ABP_Name.ABP_Name_C), not the AnimBlueprint asset path; pass clear=true to remove the component override and fall back to the skeletal mesh asset setting. Incompatible skeletons and the component's main AnimBP class are refused before mutation. Reads back the override, effective class, and live post-process instance. Repeating the active override is a no-op. This never edits or saves a mesh, Blueprint, or component template. Params: actorLabel OR actorPath, animBlueprintClassPath? OR clear=true, componentName?, world? (auto|pie|game|editor)"
    options:
      method: set_live_post_process_anim_blueprint
  animation.epic_add_actors:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add actors from the level to the currently open sequence. Params: actors"
    options:
      method: epic_call_tool
  animation.epic_add_actors_by_name:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add actors to the sequence by their names in the level. Finds actors by label in the current level and adds them to the currently open sequence as possessable bindings. Params: actor_names"
    options:
      method: epic_call_tool
  animation.epic_add_actors_to_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add actors to an existing binding. Params: actors, binding"
    options:
      method: epic_call_tool
  animation.epic_add_backward_solve_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Create a backward solve graph with InverseExecution event. Params: control_rig, name?"
    options:
      method: epic_call_tool
  animation.epic_add_binding_to_folder:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add a binding into a folder for organization. Params: folder, binding"
    options:
      method: epic_call_tool
  animation.epic_add_bone:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Add a bone to the Control Rig hierarchy. Params: control_rig, name, parent?, transform?"
    options:
      method: epic_call_tool
  animation.epic_add_control:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Add a control to the hierarchy. Params: control_rig, name, parent?, settings?"
    options:
      method: epic_call_tool
  animation.epic_add_element:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Add a bone or null element to the Control Rig hierarchy. Params: control_rig, name, element_type, parent?, transform?"
    options:
      method: epic_call_tool
  animation.epic_add_event_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Create a new graph with the specified event type. Params: control_rig, event_type, name?"
    options:
      method: epic_call_tool
  animation.epic_add_event_node:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Add an event node to the graph. Params: control_rig, graph, event_type, position?"
    options:
      method: epic_call_tool
  animation.epic_add_event_repeater_section:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add an event repeater section to an event track. Params: track"
    options:
      method: epic_call_tool
  animation.epic_add_event_trigger_section:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add an event trigger section to an event track. Params: track"
    options:
      method: epic_call_tool
  animation.epic_add_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Create a new empty graph in the Control Rig. Params: control_rig, name"
    options:
      method: epic_call_tool
  animation.epic_add_interaction_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Create an interaction graph with InteractionExecution event. Params: control_rig, name?"
    options:
      method: epic_call_tool
  animation.epic_add_key_bool:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Add a bool key to a channel on a section. Params: section, channel_name, frame, value"
    options:
      method: epic_call_tool
  animation.epic_add_key_float:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Add a float key to a channel on a section. Params: section, channel_name, frame, value, interpolation"
    options:
      method: epic_call_tool
  animation.epic_add_key_integer:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Add an integer key to a channel on a section. Params: section, channel_name, frame, value"
    options:
      method: epic_call_tool
  animation.epic_add_key_string:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Add a string key to a channel on a section. Params: section, channel_name, frame, value"
    options:
      method: epic_call_tool
  animation.epic_add_layer_from_selection:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Add an animation layer from the currently selected objects in Sequencer. Params: none"
    options:
      method: epic_call_tool
  animation.epic_add_marked_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add a marked frame (bookmark) to the sequence. Params: sequence, frame"
    options:
      method: epic_call_tool
  animation.epic_add_null:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Add a null (locator) to the Control Rig hierarchy. Params: control_rig, name, parent?, transform?"
    options:
      method: epic_call_tool
  animation.epic_add_root_folder:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Create a new root-level folder in the sequence. Params: sequence, name"
    options:
      method: epic_call_tool
  animation.epic_add_section:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add a new section to a track. Params: track"
    options:
      method: epic_call_tool
  animation.epic_add_socket:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Adds a named socket to a skeletal mesh attached to a bone. Sockets are named attachment points used to attach weapons, accessories, or effects at a consistent position relative to a bone. Params: mesh, socket_name, bone_name"
    options:
      method: epic_call_tool
  animation.epic_add_spawnable_from_class:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Create a spawnable binding from an actor class. Params: sequence, actor_class_path"
    options:
      method: epic_call_tool
  animation.epic_add_spawnable_from_instance:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Create a spawnable binding from an existing object instance. Params: sequence, obj"
    options:
      method: epic_call_tool
  animation.epic_add_track_to_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add a track of the given type to a binding. Params: binding, track_type"
    options:
      method: epic_call_tool
  animation.epic_add_track_to_folder:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add a track into a folder for organization. Params: folder, track"
    options:
      method: epic_call_tool
  animation.epic_add_track_to_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Add a sequence-level (master) track. Params: sequence, track_type"
    options:
      method: epic_call_tool
  animation.epic_add_variable:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Add a member variable to the Control Rig. Params: control_rig, name, type_path, is_public?, is_read_only?, default_value"
    options:
      method: epic_call_tool
  animation.epic_add_variable_node:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Create a variable getter or setter node. The variable must already exist (created via add_variable first). Params: control_rig, graph, variable_name, is_getter?, position?"
    options:
      method: epic_call_tool
  animation.epic_assign_physics_asset:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Assigns a physics asset to a skeletal mesh. The physics asset must be compatible with the mesh's skeleton. Use this to swap physics assets or assign one to a mesh that has none. Params: mesh, physics_asset"
    options:
      method: epic_call_tool
  animation.epic_bake_channel_keys:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Bake a channel's values over a frame range. Evaluates the channel curve at every frame in the range and returns the computed values. Useful for extracting animation data or verifying interpolation results. Params: section, channel_name, start_frame, end_frame"
    options:
      method: epic_call_tool
  animation.epic_bake_space:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Bake Control Rig controls' space over a frame range. Params: sequence, control_rig_asset_path, control_names, start_frame, end_frame, reduce_keys?, tolerance?"
    options:
      method: epic_call_tool
  animation.epic_bake_to_control_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Bake existing animation on a binding into a Control Rig track. Params: sequence, binding, control_rig_asset_path, reduce_keys?, tolerance?, reset_controls?"
    options:
      method: epic_call_tool
  animation.epic_bake_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Bake transforms for the given bindings at every frame. Params: bindings"
    options:
      method: epic_call_tool
  animation.epic_blend_values_on_selected:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Perform a blend operation on selected keys or controls. Params: sequence, operation, blend_value"
    options:
      method: epic_call_tool
  animation.epic_change_actor_template_class:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Set the actor class for a spawnable or replaceable template. Params: binding, actor_class"
    options:
      method: epic_call_tool
  animation.epic_change_variable_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Change the type of an existing variable. Params: control_rig, name, new_type"
    options:
      method: epic_call_tool
  animation.epic_clear_section_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Remove the condition from a section. Params: section"
    options:
      method: epic_call_tool
  animation.epic_clear_selection:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Clear the current Control Rig control selection. Params: none"
    options:
      method: epic_call_tool
  animation.epic_clear_track_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Remove the condition from a track. Params: track"
    options:
      method: epic_call_tool
  animation.epic_clear_track_row_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Remove the condition from a specific track row. Params: track, row_index"
    options:
      method: epic_call_tool
  animation.epic_close_curve_editor:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Close the Sequencer Curve Editor panel. Params: none"
    options:
      method: epic_call_tool
  animation.epic_close_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Close the currently open level sequence editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_collapse_anim_layers:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Collapse all sections and layers on a Control Rig track into one section. Params: sequence, control_rig_asset_path, reduce_keys?, tolerance?"
    options:
      method: epic_call_tool
  animation.epic_connect_pins:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Connect two pins together. Params: control_rig, graph, source_pin, target_pin"
    options:
      method: epic_call_tool
  animation.epic_convert_to_custom_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Convert a binding to a custom binding type. Params: binding, binding_type_class"
    options:
      method: epic_call_tool
  animation.epic_convert_to_possessable:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Convert a spawnable binding to a possessable. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_convert_to_spawnable:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Convert a possessable binding to a spawnable. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_copy_bindings:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Copy one or more bindings to the Sequencer clipboard. Returns a paste token that can be passed to paste_bindings, or an empty string to consume from the clipboard. The token also lands in the editor clipboard for interactive use. Params: bindings"
    options:
      method: epic_call_tool
  animation.epic_copy_folders:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Copy one or more folders to the Sequencer clipboard. Params: folders"
    options:
      method: epic_call_tool
  animation.epic_copy_sections:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Copy one or more sections to the Sequencer clipboard. Params: sections"
    options:
      method: epic_call_tool
  animation.epic_copy_tracks:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Copy one or more tracks to the Sequencer clipboard. Params: tracks"
    options:
      method: epic_call_tool
  animation.epic_create:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Creates a new Control Rig at the given location. Params: path"
    options:
      method: epic_call_tool
  animation.epic_create_camera:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Create a new cine camera actor in the sequence. Params: spawnable?"
    options:
      method: epic_call_tool
  animation.epic_create_level_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Create a new Level Sequence asset. If an asset already exists at the given path, it will be deleted first to avoid triggering a modal overwrite dialog. Params: package_path, asset_name"
    options:
      method: epic_call_tool
  animation.epic_create_node:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Create a new RigUnit node in the graph. Params: control_rig, graph, node_type, position?, node_name"
    options:
      method: epic_call_tool
  animation.epic_curve_editor_empty_selection:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Clear all key selection in the Curve Editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_curve_editor_select_keys:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Select keys by index in the Curve Editor. Params: channel, indices"
    options:
      method: epic_call_tool
  animation.epic_delete_all_marked_frames:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Delete all marked frames from the sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_delete_anim_layer:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Delete an animation layer at the specified index. Params: index"
    options:
      method: epic_call_tool
  animation.epic_delete_marked_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Delete a marked frame by index. Params: sequence, index"
    options:
      method: epic_call_tool
  animation.epic_delete_node:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Delete a node from the graph. Params: control_rig, graph, node"
    options:
      method: epic_call_tool
  animation.epic_delete_space:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Delete a space-switch key at a specific frame. Performs compensation to the new space automatically. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_disconnect_pins:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Disconnect two pins. Params: control_rig, graph, source_pin, target_pin"
    options:
      method: epic_call_tool
  animation.epic_duplicate_anim_layer:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Duplicate an animation layer at the specified index. Params: index"
    options:
      method: epic_call_tool
  animation.epic_empty_selection:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Clear all selection in the Sequencer editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_export_anim_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.import_export.SequencerImportExportTools] Export animation from a sequence binding to an AnimSequence asset. Params: world, sequence, anim_sequence, binding, create_link?"
    options:
      method: epic_call_tool
  animation.epic_export_fbx:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.import_export.SequencerImportExportTools] Export a level sequence to FBX. Params: world, sequence, bindings, fbx_file_path, override_options?"
    options:
      method: epic_call_tool
  animation.epic_export_fbx_from_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Export an FBX file from a Control Rig section. Params: sequence, control_rig_asset_path, export_file_path, ascii?"
    options:
      method: epic_call_tool
  animation.epic_find_binding_by_name:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Find a binding by its display name. Params: sequence, name"
    options:
      method: epic_call_tool
  animation.epic_find_binding_by_tag:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Find the first binding with the given tag in the sequence. Tags are authored via tag_binding() in this toolset, or via RMB -> Expose on a binding in the Sequencer editor. Params: sequence, tag_name"
    options:
      method: epic_call_tool
  animation.epic_find_bindings_by_tag:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Find all bindings with the given tag in the sequence. Params: sequence, tag_name"
    options:
      method: epic_call_tool
  animation.epic_find_marked_frame_by_label:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Find a marked frame by label. Params: sequence, label"
    options:
      method: epic_call_tool
  animation.epic_find_or_create_track:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Add a Control Rig track to a binding using a Control Rig asset. This is the standard way to add a Control Rig to Sequencer. Uses ControlRigSequencerLibrary.find_or_create_control_rig_track. Params: sequence, binding, control_rig_asset_path, is_layered?"
    options:
      method: epic_call_tool
  animation.epic_find_tracks_by_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Find all tracks of a specific type on a binding. Params: binding, track_type"
    options:
      method: epic_call_tool
  animation.epic_fix_actor_references:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Attempt to auto-fix broken actor references in the current sequence. Params: none"
    options:
      method: epic_call_tool
  animation.epic_focus_parent_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Navigate up one level in the sub-sequence hierarchy. Params: none"
    options:
      method: epic_call_tool
  animation.epic_focus_sub_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Navigate into a sub-sequence via its sub-section. Use get_sections() on a sub-track to find the sub-section, then pass it here to focus the sub-sequence it references. Params: sub_section"
    options:
      method: epic_call_tool
  animation.epic_force_evaluate:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Force the Sequencer to evaluate and update the viewport. Params: none"
    options:
      method: epic_call_tool
  animation.epic_frame_selection:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Frame the viewport to the current Control Rig control selection. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_actor_transform_at_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get an actor's world transform at a specific frame. Finds the actor by name in the current editor world. Params: sequence, actor_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_all_binding_tags:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get every tag name registered in the sequence. Uses the MovieSceneBindingTagExtensions C++ library. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_all_bones:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get all bones in the Control Rig hierarchy. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_get_all_controls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get all controls in the Control Rig hierarchy. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_get_all_nulls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get all nulls (locators) in the Control Rig hierarchy. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_get_anim_layers:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get all animation layers from the active Sequencer. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_anim_mode_gizmo_scale:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get the editor's transform gizmo size. Reads UTransformGizmoEditorSettings::TransformGizmoSize. The CR-specific gizmo scale was removed in UE 5.8 in favor of this editor-wide setting, so this affects every transform gizmo (level, sequencer, CR, etc.). Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_anim_mode_hide_manips:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get whether Animation Mode hides all manipulators. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_anim_mode_hierarchy:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get whether the Animation Mode draws hierarchy lines/dots. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_anim_mode_local_spaces:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get whether multi-select transforms act in each control's own space. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_anim_mode_nulls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get whether the Animation Mode draws nulls. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_anim_mode_only_rig_sel:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get whether Animation Mode restricts viewport selection to rig controls. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_backward_solve_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get the backward solve graph. The backward solve graph contains the InverseExecution event and runs during IK operations. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_get_binding_id:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the binding ID for a binding proxy. The binding ID can be used with get_bound_objects to resolve what actor or component the binding references at runtime. Params: sequence, binding"
    options:
      method: epic_call_tool
  animation.epic_get_binding_name:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the display name of a binding. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_get_binding_tags:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the tags currently attached to a specific binding. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_get_bindings:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all bindings in the sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_bone_children:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the direct children of a bone. Params: mesh, bone_name"
    options:
      method: epic_call_tool
  animation.epic_get_bone_names:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the names of all bones in a skeletal mesh in hierarchy order. Bone names are used to target specific bones for socket attachment, physics constraints, and animation retargeting. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_bone_parent:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the name of a bone's parent, or an empty string for the root bone. Params: mesh, bone_name"
    options:
      method: epic_call_tool
  animation.epic_get_bool:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get a bool control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_bound_objects:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the objects currently resolved by a binding. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_get_bounds:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the local-space bounding volume of a skeletal mesh. The bounds represent the reference pose and do not account for animation. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_channel_names:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Get the names of all channels on a section. For example, a 3D Transform section has channels named 'Location.X', 'Location.Y', 'Location.Z', 'Rotation.X', etc. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_child_possessables:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get component bindings under an actor binding. Actor bindings can own child possessable bindings for their components (e.g. SkeletalMeshComponent, CameraComponent). Use this to find the component binding when you need to add tracks to a specific component rather than the actor. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_get_children:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get children of a hierarchy element. Params: control_rig, item, recursive?"
    options:
      method: epic_call_tool
  animation.epic_get_clock_source:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the clock source for the sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_connected_pins:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get all pins connected to this pin. Params: control_rig, pin"
    options:
      method: epic_call_tool
  animation.epic_get_control_rigs:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get all Control Rigs currently in the sequence. Returns proxy objects with track and rig references. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_controls_info:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get all controls on a Control Rig with their names and types. Returns a list of controls with name and type so the caller can find controls of a specific type (e.g. find a Float control to use with set_float). Possible types: Bool, Float, Integer, Vector2D, Position, Rotator, Scale, Transform, TransformNoScale, EulerTransform. Params: sequence, control_rig_asset_path"
    options:
      method: epic_call_tool
  animation.epic_get_controls_mask:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Check if a control is visible (unmasked) on a section. Params: section, control_name"
    options:
      method: epic_call_tool
  animation.epic_get_current_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the root level sequence currently open in the Sequencer editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_curve_editor_selected_keys:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Get selected key indices for a channel in the Curve Editor. Params: channel"
    options:
      method: epic_call_tool
  animation.epic_get_custom_binding_objects:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Get the custom binding instances for a binding. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_get_custom_binding_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Get the custom binding class for a binding. Returns the class path of the custom binding type, or an empty string for standard possessable bindings. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_get_custom_bindings_of_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Find all bindings of a given custom type in the current sequence. Params: binding_type_class"
    options:
      method: epic_call_tool
  animation.epic_get_deactivated_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get the currently deactivated outliner nodes. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_default_value:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Get the default value of a float channel. Params: section, channel_name"
    options:
      method: epic_call_tool
  animation.epic_get_display_rate:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the display frame rate of a sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_elements:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get hierarchy elements of the specified type. Params: control_rig, element_type?"
    options:
      method: epic_call_tool
  animation.epic_get_euler_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get an EulerTransform control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_evaluation_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the evaluation type of a sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_event_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get a graph containing the specified event type. Params: control_rig, event_type"
    options:
      method: epic_call_tool
  animation.epic_get_float:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get a float control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_focused_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the currently focused level sequence in the hierarchy. When navigated into a sub-sequence, this returns the sub-sequence rather than the root sequence. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_folder_contents:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the tracks and bindings inside a folder. Params: folder"
    options:
      method: epic_call_tool
  animation.epic_get_forward_solve_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get the forward solve graph (main execution graph). This graph contains the BeginExecution event and runs during normal animation evaluation. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_get_global_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get global transform of a hierarchy element. Params: control_rig, item, initial?"
    options:
      method: epic_call_tool
  animation.epic_get_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get a specific graph from the Control Rig by name. Params: control_rig, graph_name"
    options:
      method: epic_call_tool
  animation.epic_get_int:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get an integer control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_interaction_graph:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get the interaction graph. The interaction graph contains the InteractionExecution event and runs during user interaction with controls. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_get_keys:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Get all keys on a channel, returned as a JSON array. Each key entry includes its frame number and value. Params: section, channel_name"
    options:
      method: epic_call_tool
  animation.epic_get_keys_by_index:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Get specific keys on a channel by their indices, returned as JSON. Useful for resolving the keys behind a Curve Editor selection, which provides indices rather than key objects. Params: section, channel_name, indices"
    options:
      method: epic_call_tool
  animation.epic_get_linked_anim_sequences:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.import_export.SequencerImportExportTools] Get content paths of all AnimSequences linked to a LevelSequence. Linked AnimSequences auto-update when the LevelSequence changes. They are created via export_anim_sequence with create_link=True. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_linked_level_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.import_export.SequencerImportExportTools] Get the content path of the LevelSequence linked to an AnimSequence. Params: anim_sequence"
    options:
      method: epic_call_tool
  animation.epic_get_local_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get local transform of a hierarchy element. Params: control_rig, item, initial?"
    options:
      method: epic_call_tool
  animation.epic_get_locked_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get the currently locked outliner nodes. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_lod_count:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the number of LODs in a skeletal mesh asset. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_loop_mode:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the current loop playback mode. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_marked_frames:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all marked frames (bookmarks) in the sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_material:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the material assigned to a named slot on a skeletal mesh. Params: mesh, slot_name"
    options:
      method: epic_call_tool
  animation.epic_get_material_slots:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the names of all material slots in a skeletal mesh. Material slot names are used when assigning materials to specific parts of the mesh. Use these names with get_material and set_material. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_morph_target_names:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the names of all morph targets on a skeletal mesh. Morph targets (blend shapes) are per-vertex offsets used to deform the mesh, commonly used for facial expressions and cloth simulation. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_muted_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get the currently muted outliner nodes. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_node_label:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get the display label of an outliner node. Params: node"
    options:
      method: epic_call_tool
  animation.epic_get_node_position:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get the position of a node in the graph editor. Params: control_rig, node"
    options:
      method: epic_call_tool
  animation.epic_get_outliner_children:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get child nodes of an outliner node. Params: node, type_filter?"
    options:
      method: epic_call_tool
  animation.epic_get_outliner_selection:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get the currently selected nodes in the outliner. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_outliner_tree:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get a full snapshot of the Sequencer outliner tree. Builds a recursive tree structure from the outliner root nodes. Useful for testing and UI verification. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_parent:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get the parent of a hierarchy element. Params: control_rig, item"
    options:
      method: epic_call_tool
  animation.epic_get_physics_asset:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the physics asset assigned to a skeletal mesh. The physics asset defines the collision bodies and constraints used for ragdoll simulation and per-bone physics. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_pin_value:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get the default value of a pin. Params: control_rig, pin"
    options:
      method: epic_call_tool
  animation.epic_get_pinned_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get the currently pinned outliner nodes. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_playback_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the playback start and end frames of a sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_playback_speed:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the current playback speed multiplier. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_playhead_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the current playhead position in display rate frames. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_position:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get a position control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_priority_order:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get the evaluation priority order of a Control Rig track. Params: sequence, control_rig_asset_path"
    options:
      method: epic_call_tool
  animation.epic_get_root_folders:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all root-level folders in the sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_rotator:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get a rotator control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_scale:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get a scale control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_section_blend_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the blend type of a section. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_completion_mode:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the completion mode of a section. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Get the condition on a section. Returns the class path of the condition, or an empty string if no condition is set. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_count:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the number of sections in a specific LOD of a skeletal mesh. Sections correspond to individual material slots rendered by a single draw call. A mesh may have more sections than material slots if multiple sections share the same material. Params: mesh, lod_index?"
    options:
      method: epic_call_tool
  animation.epic_get_section_ease_in:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the effective ease-in duration of a section in frames. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_ease_out:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the effective ease-out duration of a section in frames. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_post_roll_frames:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the number of post-roll frames configured on a section. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_pre_roll_frames:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the number of pre-roll frames configured on a section. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_properties:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all common properties of a section in a single call. Useful for testing and verification. Returns range, easing, blend type, and completion mode. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the frame range of a section. Params: section"
    options:
      method: epic_call_tool
  animation.epic_get_section_to_key:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the active section that receives new keys on a track. When keying properties, Sequencer writes to a specific section. This returns that section, which is usually the first section or the one the user has designated. Params: track"
    options:
      method: epic_call_tool
  animation.epic_get_sections:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all sections on a track. Params: track"
    options:
      method: epic_call_tool
  animation.epic_get_sections_for_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get sections associated with the given outliner nodes. Params: nodes"
    options:
      method: epic_call_tool
  animation.epic_get_selected_bindings:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the currently selected bindings in the Sequencer editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_selected_channels:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Get the currently selected channels in the Sequencer editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_selected_controls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get the currently selected controls on a Control Rig. Params: sequence, control_rig_asset_path"
    options:
      method: epic_call_tool
  animation.epic_get_selected_folders:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the currently selected folders in the Sequencer editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_selected_key_channels:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Get channels that have selected keys in the Curve Editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_selected_sections:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the currently selected sections in the Sequencer editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_selected_tracks:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the currently selected tracks in the Sequencer editor. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_selection_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the selection range (green bar) start and end frames. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_sequence_lock_state:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check whether the current level sequence is locked. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_skeleton:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the skeleton asset associated with a skeletal mesh. The skeleton defines the bone hierarchy shared across all meshes and animations that use it. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_socket_bone:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the name of the bone that a socket is attached to. Params: mesh, socket_name"
    options:
      method: epic_call_tool
  animation.epic_get_socket_names:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the names of all sockets on a skeletal mesh. Sockets are named attachment points parented to bones. They are used to attach weapons, accessories, or effects at a consistent location. Params: mesh"
    options:
      method: epic_call_tool
  animation.epic_get_socket_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the local transform of a socket relative to its parent bone. Params: mesh, socket_name"
    options:
      method: epic_call_tool
  animation.epic_get_soloed_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Get the currently soloed outliner nodes. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_sub_sequence_hierarchy:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the current sub-sequence hierarchy path. Returns a list of sub-sections from the root down to the currently focused sub-sequence. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_tick_resolution:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the internal tick resolution of a sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_track_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Get the track-level condition. Params: track"
    options:
      method: epic_call_tool
  animation.epic_get_track_display_name:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the display name of a track. Params: track"
    options:
      method: epic_call_tool
  animation.epic_get_track_filter_names:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all available track filter names. Params: none"
    options:
      method: epic_call_tool
  animation.epic_get_track_row_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Get the condition on a specific track row. Params: track, row_index"
    options:
      method: epic_call_tool
  animation.epic_get_tracks_on_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all tracks on a binding. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_get_tracks_on_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get all sequence-level (master) tracks. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get the transform value of a Control Rig control at a frame. Automatically detects whether the control is a Transform or EulerTransform type and uses the appropriate API. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_variable:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Get a specific variable by name. Params: control_rig, name"
    options:
      method: epic_call_tool
  animation.epic_get_vector2d:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get a Vector2D control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_get_vertex_count:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Returns the number of vertices in a specific LOD of a skeletal mesh. Params: mesh, lod_index?"
    options:
      method: epic_call_tool
  animation.epic_get_view_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the visible time range in the Sequencer timeline. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_work_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Get the work range of the sequence. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_get_world_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Get a control's world-space transform at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame"
    options:
      method: epic_call_tool
  animation.epic_has_section_end_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check if a section has a bounded end frame (vs infinite). Params: section"
    options:
      method: epic_call_tool
  animation.epic_has_section_start_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check if a section has a bounded start frame (vs infinite). Params: section"
    options:
      method: epic_call_tool
  animation.epic_hide_all_controls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Hide all controls on a Control Rig section (mask everything). Params: section"
    options:
      method: epic_call_tool
  animation.epic_import_bones_from_asset:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Import bones from the given skeletal mesh to the Control Rig hierarchy. Params: control_rig, skeletal_mesh"
    options:
      method: epic_call_tool
  animation.epic_import_fbx:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.import_export.SequencerImportExportTools] Import FBX data into a level sequence. Params: world, sequence, bindings, import_settings, fbx_file_path"
    options:
      method: epic_call_tool
  animation.epic_import_fbx_to_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Import an FBX file onto a Control Rig track. Params: sequence, control_rig_asset_path, import_file_path, selected_controls"
    options:
      method: epic_call_tool
  animation.epic_import_file:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Imports a mesh file from disk as a SkeletalMesh asset. The source file must contain a skeleton hierarchy and skinned mesh data. Params: folder_path, asset_name, source_file, skeleton?, import_materials?, import_textures?, import_animations?, create_physics_asset?"
    options:
      method: epic_call_tool
  animation.epic_is_camera_cut_locked:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check if the camera cut is locked to the viewport. Params: none"
    options:
      method: epic_call_tool
  animation.epic_is_curve_editor_open:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Check whether the Curve Editor panel is currently open. Params: none"
    options:
      method: epic_call_tool
  animation.epic_is_curve_shown:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Check if a curve is visible in the Curve Editor. Params: channel"
    options:
      method: epic_call_tool
  animation.epic_is_fk_control_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Check if a Control Rig is an FK Control Rig. Params: sequence, control_rig_asset_path"
    options:
      method: epic_call_tool
  animation.epic_is_layered_control_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Check if a Control Rig in the sequence is in layered mode. Params: sequence, control_rig_asset_path"
    options:
      method: epic_call_tool
  animation.epic_is_node_expanded:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Check whether an outliner node is expanded. Params: node"
    options:
      method: epic_call_tool
  animation.epic_is_playback_range_locked:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check if the playback range is locked. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_is_playing:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check whether the sequence is currently playing. Params: none"
    options:
      method: epic_call_tool
  animation.epic_is_sequence_locked:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check if the current sequence and its descendants are locked. Params: none"
    options:
      method: epic_call_tool
  animation.epic_is_track_filter_active:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Check whether a track filter is currently active. Params: name"
    options:
      method: epic_call_tool
  animation.epic_key_controls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Key the specified controls on the section at the current Sequencer time. Params: section, control_names"
    options:
      method: epic_call_tool
  animation.epic_key_controls_at_frames:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Key the specified controls at specific frame numbers. Params: section, control_names, frames"
    options:
      method: epic_call_tool
  animation.epic_link_anim_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.import_export.SequencerImportExportTools] Link an AnimSequence asset to a level sequence binding. When the sequence is modified, the linked AnimSequence can be automatically updated. Params: sequence, anim_sequence, binding"
    options:
      method: epic_call_tool
  animation.epic_list_graphs:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] List all graphs in the Control Rig. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_list_nodes:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] List all nodes in a graph. Params: control_rig, graph"
    options:
      method: epic_call_tool
  animation.epic_list_pins:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] List all pins on a node. Params: control_rig, node"
    options:
      method: epic_call_tool
  animation.epic_list_variables:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] List all member variables in the Control Rig. Params: control_rig"
    options:
      method: epic_call_tool
  animation.epic_load_anim_into_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Load an animation sequence into a Control Rig section. Finds the skeletal mesh component from the binding associated with the section's track. Params: cr_section, anim_sequence_path, start_frame?, reset_controls?, key_reduce?, tolerance?"
    options:
      method: epic_call_tool
  animation.epic_merge_anim_layers:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Merge specified animation layers into one. Merges onto the layer with the lowest index. Params: indices"
    options:
      method: epic_call_tool
  animation.epic_mirror_selected_controls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Apply a mirrored pose to the currently selected controls. Params: none"
    options:
      method: epic_call_tool
  animation.epic_move_space:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Move a space-switch key from one frame to another. Params: sequence, control_rig_asset_path, control_name, old_frame, new_frame"
    options:
      method: epic_call_tool
  animation.epic_open_curve_editor:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Open the Sequencer Curve Editor panel. Params: none"
    options:
      method: epic_call_tool
  animation.epic_open_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Open a level sequence asset in the Sequencer editor. Params: sequence"
    options:
      method: epic_call_tool
  animation.epic_paste_bindings:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Paste bindings from the clipboard (or a token returned by copy_bindings). Params: paste_token, sequence, parent_folder?"
    options:
      method: epic_call_tool
  animation.epic_paste_folders:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Paste folders from the clipboard into the sequence. Params: paste_token, sequence, parent_folder?"
    options:
      method: epic_call_tool
  animation.epic_paste_sections:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Paste sections from the clipboard onto the given tracks. Params: paste_token, target_tracks, paste_frame?"
    options:
      method: epic_call_tool
  animation.epic_paste_tracks:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Paste tracks from the clipboard onto the given bindings. Params: paste_token, sequence, target_bindings, parent_folder?"
    options:
      method: epic_call_tool
  animation.epic_pause:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Pause playback of the current sequence. Params: none"
    options:
      method: epic_call_tool
  animation.epic_play:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Start playback of the current sequence. Params: none"
    options:
      method: epic_call_tool
  animation.epic_play_to:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Play from the current position to a specific frame, then stop. Params: frame"
    options:
      method: epic_call_tool
  animation.epic_rebind_component:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Rebind component bindings to a named component. Params: component_bindings, component_name"
    options:
      method: epic_call_tool
  animation.epic_refresh_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Force refresh the Sequencer editor UI on the next tick. Params: none"
    options:
      method: epic_call_tool
  animation.epic_remove_actors_from_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove specific actors from a binding. Params: actors, binding"
    options:
      method: epic_call_tool
  animation.epic_remove_all_bindings:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove all bound actors from a binding. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_remove_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove a binding from the sequence. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_remove_binding_tag:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove a tag from the sequence entirely. Clears the tag from every binding that had it and unregisters the tag name from the sequence. Params: sequence, tag_name"
    options:
      method: epic_call_tool
  animation.epic_remove_invalid_bindings:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove missing or broken actor references from a binding. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_remove_key_at_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Remove a key at a specific frame from a channel. Params: section, channel_name, frame"
    options:
      method: epic_call_tool
  animation.epic_remove_root_folder:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove a root-level folder from the sequence. Params: sequence, folder"
    options:
      method: epic_call_tool
  animation.epic_remove_section:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove a section from a track. Params: track, section"
    options:
      method: epic_call_tool
  animation.epic_remove_socket:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Removes a named socket from a skeletal mesh. Params: mesh, socket_name"
    options:
      method: epic_call_tool
  animation.epic_remove_track:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove a track from a binding. Params: binding, track"
    options:
      method: epic_call_tool
  animation.epic_remove_track_from_sequence:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove a sequence-level (master) track. Params: sequence, track"
    options:
      method: epic_call_tool
  animation.epic_remove_variable:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Remove a member variable from the Control Rig. Params: control_rig, name"
    options:
      method: epic_call_tool
  animation.epic_rename_socket:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Renames a socket on a skeletal mesh. Params: mesh, old_name, new_name"
    options:
      method: epic_call_tool
  animation.epic_reorder_anim_layers:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Move an animation layer from one index to another. Cannot move the base layer (index 0). Params: old_index, new_index"
    options:
      method: epic_call_tool
  animation.epic_replace_binding_with_actors:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Replace all bound actors on a binding with new ones. Params: actors, binding"
    options:
      method: epic_call_tool
  animation.epic_save_default_spawnable_state:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.custom_bindings.SequencerCustomBindingTools] Save the current state of a spawnable as its default. Params: binding"
    options:
      method: epic_call_tool
  animation.epic_select_bindings:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the binding selection in the Sequencer editor. Params: bindings"
    options:
      method: epic_call_tool
  animation.epic_select_channels:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Set the channel selection in the Sequencer editor. Params: channels"
    options:
      method: epic_call_tool
  animation.epic_select_control:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Select or deselect a control on a Control Rig. Params: sequence, control_rig_asset_path, control_name, selected?"
    options:
      method: epic_call_tool
  animation.epic_select_folders:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the folder selection in the Sequencer editor. Params: folders"
    options:
      method: epic_call_tool
  animation.epic_select_mirrored_controls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Select the mirrored counterparts of the currently selected controls. Replaces the current selection with mirrored controls. Params: none"
    options:
      method: epic_call_tool
  animation.epic_select_sections:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the section selection in the Sequencer editor. Params: sections"
    options:
      method: epic_call_tool
  animation.epic_select_tracks:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the track selection in the Sequencer editor. Params: tracks"
    options:
      method: epic_call_tool
  animation.epic_set_anim_mode_gizmo_scale:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set the editor's transform gizmo size. Writes UTransformGizmoEditorSettings::TransformGizmoSize. The CR-specific gizmo scale was removed in UE 5.8 in favor of this editor-wide setting. Params: scale"
    options:
      method: epic_call_tool
  animation.epic_set_anim_mode_hide_manips:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Toggle whether Animation Mode hides all manipulators. Params: hide"
    options:
      method: epic_call_tool
  animation.epic_set_anim_mode_hierarchy:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Toggle the Animation Mode hierarchy lines/dots display. Params: enabled"
    options:
      method: epic_call_tool
  animation.epic_set_anim_mode_local_spaces:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Toggle multi-select transforms acting in each control's own space. When True, transforming multiple selected controls respects each control's own local space. When False, all use a shared reference. Params: enabled"
    options:
      method: epic_call_tool
  animation.epic_set_anim_mode_nulls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Toggle the Animation Mode nulls display. Params: enabled"
    options:
      method: epic_call_tool
  animation.epic_set_anim_mode_only_rig_sel:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Toggle Animation Mode restricting viewport selection to rig controls. Params: only_rig"
    options:
      method: epic_call_tool
  animation.epic_set_binding_name:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the display name of a binding. Params: binding, name"
    options:
      method: epic_call_tool
  animation.epic_set_bool:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a bool control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, value, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_byte_track_enum:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Configure a byte track to use a specific enum type. Byte tracks can animate enum properties. Call this after adding the track and before setting property_name_and_path. Params: track, enum_class_path"
    options:
      method: epic_call_tool
  animation.epic_set_camera_cut_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set which camera a camera cut section uses. Params: section, camera_binding_id"
    options:
      method: epic_call_tool
  animation.epic_set_camera_lock:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Lock or unlock the camera cut to the viewport. Params: lock"
    options:
      method: epic_call_tool
  animation.epic_set_clock_source:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the clock source for the sequence. Params: sequence, clock_source"
    options:
      method: epic_call_tool
  animation.epic_set_controls_mask:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set the visibility mask for the specified controls on a section. Params: section, control_names, visible"
    options:
      method: epic_call_tool
  animation.epic_set_default_value:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Set the default value of a channel. Params: section, channel_name, value"
    options:
      method: epic_call_tool
  animation.epic_set_display_rate:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the display frame rate of a sequence. Params: sequence, numerator, denominator?"
    options:
      method: epic_call_tool
  animation.epic_set_euler_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set an EulerTransform control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, location_x?, location_y?, location_z?, rotation_pitch?, rotation_yaw?, rotation_roll?, scale_x?, scale_y?, scale_z?, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_evaluation_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the evaluation type of a sequence. Params: sequence, eval_type"
    options:
      method: epic_call_tool
  animation.epic_set_float:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a float control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, value, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_global_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Set global transform of a hierarchy element. Params: control_rig, item, transform, initial?"
    options:
      method: epic_call_tool
  animation.epic_set_int:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set an integer control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, value, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_layered_mode:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a Control Rig track to layered or absolute mode. Params: sequence, control_rig_asset_path, is_layered"
    options:
      method: epic_call_tool
  animation.epic_set_local_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Set local transform of a hierarchy element. Params: control_rig, item, transform, initial?"
    options:
      method: epic_call_tool
  animation.epic_set_loop_mode:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Enable or disable loop playback. Params: loop"
    options:
      method: epic_call_tool
  animation.epic_set_material:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Assigns a material to a named slot on a skeletal mesh asset. This affects all instances of the mesh that do not override the slot material. Params: mesh, slot_name, material"
    options:
      method: epic_call_tool
  animation.epic_set_node_deactivated:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Deactivate or reactivate outliner nodes. Params: nodes, deactivated"
    options:
      method: epic_call_tool
  animation.epic_set_node_expanded:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Expand or collapse outliner nodes. Params: nodes, expanded"
    options:
      method: epic_call_tool
  animation.epic_set_node_locked:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Lock or unlock outliner nodes for editing. Params: nodes, locked"
    options:
      method: epic_call_tool
  animation.epic_set_node_muted:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Mute or unmute outliner nodes. Params: nodes, muted"
    options:
      method: epic_call_tool
  animation.epic_set_node_pinned:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Pin or unpin outliner nodes. Params: nodes, pinned"
    options:
      method: epic_call_tool
  animation.epic_set_node_position:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Set the position of a node in the graph editor. Params: control_rig, graph, node, position"
    options:
      method: epic_call_tool
  animation.epic_set_node_solo:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Solo or unsolo outliner nodes. Params: nodes, soloed"
    options:
      method: epic_call_tool
  animation.epic_set_outliner_selection:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.outliner.SequencerOutlinerTools] Set the outliner selection. Params: nodes"
    options:
      method: epic_call_tool
  animation.epic_set_pin_value:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig.ControlRigTools] Set the default value of a pin. Params: control_rig, graph, pin, value"
    options:
      method: epic_call_tool
  animation.epic_set_playback_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the playback start and end frames of a sequence. Params: sequence, start_frame, end_frame"
    options:
      method: epic_call_tool
  animation.epic_set_playback_range_locked:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Lock or unlock the playback range. Params: sequence, locked"
    options:
      method: epic_call_tool
  animation.epic_set_playback_speed:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the playback speed multiplier. Params: speed"
    options:
      method: epic_call_tool
  animation.epic_set_playhead_frame:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the playhead position in display rate frames. Params: frame"
    options:
      method: epic_call_tool
  animation.epic_set_position:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a position control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, x?, y?, z?, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_priority_order:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set the evaluation priority order of a Control Rig track. Params: sequence, control_rig_asset_path, order"
    options:
      method: epic_call_tool
  animation.epic_set_property_name_and_path:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Configure a property track to animate a specific UProperty. This binds a generic property track (Float, Bool, Byte, etc.) to a specific property on the bound object. For nested properties use dot notation in the path. Examples: display_name=\"Intensity\", property_path=\"Intensity\" display_name=\"Focus Distance\", property_path=\"FocusSettings.ManualFocusDistance\" display_name=\"Animation Mode\", property_path=\"AnimationMode\" Params: track, display_name, property_path"
    options:
      method: epic_call_tool
  animation.epic_set_rotator:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a rotator control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, pitch?, yaw?, roll?, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_scale:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a scale control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, x?, y?, z?, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_section_animation:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the animation asset on a skeletal animation section. After adding a MovieSceneSkeletalAnimationTrack and section, call this to assign which AnimSequence plays in that section. Params: section, anim_sequence_path"
    options:
      method: epic_call_tool
  animation.epic_set_section_blend_type:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the blend type of a section. Valid values: 'Absolute', 'Additive', 'Relative', 'Override'. Params: section, blend_type"
    options:
      method: epic_call_tool
  animation.epic_set_section_completion_mode:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the completion mode of a section. Valid values: 'KeepState', 'RestoreState', 'ProjectDefault'. Params: section, completion_mode"
    options:
      method: epic_call_tool
  animation.epic_set_section_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Set a condition on a section. Common condition classes: - /Script/MovieSceneTracks.MovieScenePlatformCondition - /Script/MovieSceneTracks.MovieSceneDirectorBlueprintCondition - /Script/MovieScene.MovieSceneGroupCondition Params: section, condition_class"
    options:
      method: epic_call_tool
  animation.epic_set_section_ease_in:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the ease-in duration of a section in frames. Enables manual ease override if not already active. Params: section, duration"
    options:
      method: epic_call_tool
  animation.epic_set_section_ease_out:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the ease-out duration of a section in frames. Enables manual ease override if not already active. Params: section, duration"
    options:
      method: epic_call_tool
  animation.epic_set_section_end_bounded:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set whether the section end frame is bounded or infinite. Params: section, bounded"
    options:
      method: epic_call_tool
  animation.epic_set_section_post_roll_frames:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the number of frames to post-roll this section after it ends. Post-roll continues evaluation after the section's real end, useful for simulations that need to settle. Params: section, frames"
    options:
      method: epic_call_tool
  animation.epic_set_section_pre_roll_frames:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the number of frames to pre-roll this section before it starts. Pre-roll evaluates the section before its real start so physics, cloth, or simulation state can warm up. The pre-roll frames do not affect the rendered output of the section. Params: section, frames"
    options:
      method: epic_call_tool
  animation.epic_set_section_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the frame range of a section. Params: section, start_frame, end_frame"
    options:
      method: epic_call_tool
  animation.epic_set_section_start_bounded:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set whether the section start frame is bounded or infinite. Params: section, bounded"
    options:
      method: epic_call_tool
  animation.epic_set_selection_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the selection range (green bar) start and end frames. Params: start_frame, end_frame"
    options:
      method: epic_call_tool
  animation.epic_set_sequence_locked:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Lock or unlock the current sequence and its descendants. Params: lock"
    options:
      method: epic_call_tool
  animation.epic_set_socket_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic editor_toolset.toolsets.skeletal_mesh.SkeletalMeshTools] Sets the local transform of a socket relative to its parent bone. Params: mesh, socket_name, transform"
    options:
      method: epic_call_tool
  animation.epic_set_space:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set the space for a Control Rig control at a given frame. Params: sequence, control_rig_asset_path, control_name, space_type, frame, space_target"
    options:
      method: epic_call_tool
  animation.epic_set_tick_resolution:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the internal tick resolution of a sequence. Params: sequence, numerator, denominator?"
    options:
      method: epic_call_tool
  animation.epic_set_track_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Set a condition on a track. Params: track, condition_class"
    options:
      method: epic_call_tool
  animation.epic_set_track_display_name:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the display name of a track. Params: track, name"
    options:
      method: epic_call_tool
  animation.epic_set_track_filter_active:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Enable or disable a track filter. Params: name, active"
    options:
      method: epic_call_tool
  animation.epic_set_track_row_condition:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.conditions.SequencerConditionTools] Set a condition on a specific track row. Params: track, row_index, condition_class"
    options:
      method: epic_call_tool
  animation.epic_set_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a transform value on a Control Rig control and optionally key it. Uses ControlRigSequencerLibrary.set_local_control_rig_transform. Params: sequence, control_rig_asset_path, control_name, frame, location_x?, location_y?, location_z?, rotation_pitch?, rotation_yaw?, rotation_roll?, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_vector2d:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a Vector2D control value at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, x?, y?, set_key?"
    options:
      method: epic_call_tool
  animation.epic_set_view_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the visible time range in the Sequencer timeline. Params: sequence, start_seconds, end_seconds"
    options:
      method: epic_call_tool
  animation.epic_set_work_range:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Set the work range of the sequence. Params: sequence, start_seconds, end_seconds"
    options:
      method: epic_call_tool
  animation.epic_set_world_transform:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Set a control's world-space transform at a specific frame. Params: sequence, control_rig_asset_path, control_name, frame, location_x?, location_y?, location_z?, rotation_pitch?, rotation_yaw?, rotation_roll?, set_key?"
    options:
      method: epic_call_tool
  animation.epic_show_all_controls:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Show all controls on a Control Rig section (unmask everything). Params: section"
    options:
      method: epic_call_tool
  animation.epic_show_curve:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.keyframing.SequencerKeyframingTools] Show or hide a curve in the Curve Editor. Params: channel, show"
    options:
      method: epic_call_tool
  animation.epic_snap_control_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Snap Control Rig controls to a target actor over a frame range. Params: sequence, control_rig_asset_path, control_names, target_actor_name, start_frame, end_frame, keep_offset?, snap_position?, snap_rotation?, snap_scale?"
    options:
      method: epic_call_tool
  animation.epic_tag_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Attach a tag to a binding. If the tag has not been seen in the sequence before, it is automatically registered. Params: binding, tag_name"
    options:
      method: epic_call_tool
  animation.epic_tween_control_rig:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Perform a tween operation on a Control Rig at the current Sequencer time. The tween blends between the previous and next keyframe values. Params: sequence, control_rig_asset_path, tween_value"
    options:
      method: epic_call_tool
  animation.epic_untag_binding:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.sequencer.SequencerTools] Remove a tag from a binding. Params: binding, tag_name"
    options:
      method: epic_call_tool
  animation.epic_zero_transforms:
    class_path: ue-mcp.bridge
    group: animation
    description: "[Epic animation_toolset.toolsets.controlrig_sequencer.SequencerControlRigTools] Reset Control Rig transforms to their default (usually zero) values. Params: selection_only?, include_channels?"
    options:
      method: epic_call_tool

  # ── landscape ──
  landscape.get_info:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Get landscape setup. Params: none"
    options:
      method: get_landscape_info
  landscape.list_layers:
    class_path: ue-mcp.bridge
    group: landscape
    description: "List paint layers. Params: none"
    options:
      method: list_landscape_layers
  landscape.sample:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Read the surface height AND every paint-layer weight at one world XY. Height and weights come from the landscape's own height and weight data (the merged result of every edit layer), the same data landscape(paint_layer) writes, so they are exact and do not depend on built collision. Layers come back with weight (0..1), weight255 (the weightmap byte the editor shows), the LayerInfo path and its physical material, plus dominantLayer and totalWeight - which is what makes reading a paint weight one call instead of rendering the weightmap to a render target and reading that back. Also returns quad, quadExtent and inBounds, so a zero weight OFF the landscape reads as out of bounds rather than as a measured zero, and traceHeight/normal from a confirmation collision trace (absent when collision is unbuilt or the proxy is streamed out). Position accepts x + y, point {x, y}, or worldX + worldY. Params: x + y (or point, or worldX + worldY), actorLabel? OR actorPath? (which landscape, when the map has several), layerName? (one layer only), includeLayers? (default true) (#939)"
    options:
      method: sample_landscape
  landscape.sculpt:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Raise, lower or flatten terrain with a circular brush. mode=raise|lower|flatten; amount is world centimetres at full strength (raise/lower), and flatten pulls toward the height under the brush centre. falloff (0..1) is the smoothstepped soft edge. Runs in one transaction and leaves the level dirty and unsaved. Writes into an edit layer (editLayer by name, else editLayerIndex, default 0) - required on UE 5.8, where a write without one is regenerated away by the layer system. Idempotent: a brush that rounds to the height already there reports unchanged=true and updated=false rather than claiming it moved ground. The rollback record carries the exact previous heights over the brush rectangle and replays through set_height_region; above rollbackMaxVertices the response says rollbackOmitted with the reason instead of a record that would restore part of the footprint. Params: center ({x,y} world space), radius (default 500), mode? (default raise), amount? (default 100), falloff? (default 0.5), actorLabel? OR actorPath?, editLayer?, editLayerIndex?, maxVertices?, rollbackMaxVertices? (#742)"
    options:
      method: sculpt_landscape
  landscape.paint_layer:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Paint a weight layer with a circular brush. The layer must already have a LayerInfo on this landscape (see add_layer_info) - an unregistered name errors and lists the layers that ARE registered instead of silently painting nothing. Weights are written as given - the engine no longer renormalises other layers for you, so set them explicitly if they must sum to 1. Writes into an edit layer (editLayer by name, else editLayerIndex, default 0). Idempotent: a brush that changes no weight reports unchanged=true. The rollback record carries the exact previous weights over the brush rectangle and replays through set_layer_weight_region, with the same rollbackOmitted cap as the region writes. Params: layerName, center ({x,y} world space), radius (default 500), strength? (0..1, default 1), falloff? (default 0.5), actorLabel? OR actorPath?, editLayer?, editLayerIndex?, maxVertices?, rollbackMaxVertices? (#742)"
    options:
      method: paint_landscape_layer
  landscape.list_splines:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Read landscape splines. Params: none"
    options:
      method: list_landscape_splines
  landscape.get_component:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Inspect component. Params: componentIndex"
    options:
      method: get_landscape_component
  landscape.set_material:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Set landscape material. Params: materialPath"
    options:
      method: set_landscape_material
  landscape.add_layer_info:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Register paint layer (creates LayerInfo asset + binds to active landscape). Idempotent: a layer already registered reports existed and emits no record, so a replay cannot delete a layer it did not create. Rolls back through remove_layer on the parent Landscape actor, which un-registers the layer; marked lossy when the LayerInfo ASSET was created here, because remove_layer deliberately leaves it on disk. Params: layerName, packagePath?, landscapeName?"
    options:
      method: add_landscape_layer_info
  landscape.create_layer_info:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Standalone LayerInfo asset creation - no landscape required. Params: layerName, name? (default LI_<layerName>), packagePath? (default /Game/Landscape/LayerInfos), physMaterial? (asset path), hardness? (#251)"
    options:
      method: create_landscape_layer_info
  landscape.create:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Spawn a new ALandscape with a flat heightmap. Defaults match the Editor's Landscape Mode 'create new' (8x8 components, 63 quads/subsection, 2 subsections/component = 1016x1016 quads). Params: location? (Vec3), scale? (Vec3, default 100,100,100), componentCountX? (default 8), componentCountY? (default 8), subsectionSizeQuads? (one of 7|15|31|63|127|255, default 63), numSubsections? (1|2, default 2), heightOffset? (uint16, default 32768 = mid-elevation), label? (#303)"
    options:
      method: create_landscape
  landscape.get_material_usage_summary:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Per-proxy summary: landscape/hole material paths + component/grass/nanite counts. Params: none (#150)"
    options:
      method: get_landscape_material_usage_summary
  landscape.list_proxies:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Enumerate loaded World Partition LandscapeStreamingProxy actors with per-proxy objectPath and worldBounds (origin/extent), sorted by object path, plus loadedProxies + parentLandscapes counts. Unloaded proxies are not spawned as actors, so only loaded ones appear - use this to confirm a proxy is streamed in before trusting a layer/height readback (#733). Params: cursor?, limit?"
    options:
      method: list_landscape_proxies
  landscape.find_proxy_at:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Resolve which loaded LandscapeStreamingProxy covers a world X/Y. Returns found/loaded + label, or loaded:false when the covering proxy is streamed out (so a 0-weight readback there is ambiguous, not real). Params: worldX, worldY (#733)"
    options:
      method: find_landscape_proxy_at
  landscape.get_height_region:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Read the raw uint16 heights over a rectangle of landscape vertices, with min/max/mean reported in raw units AND in world Z so the numbers mean something without a second call. Heights come back as a plain array below arrayEncodingLimit and as a little-endian base64 blob above it, and that blob feeds straight back into set_height_region, so a read-modify-write round trip needs no reformatting. Reports hasUnloadedComponentsInRegion, because on a World Partition map an unloaded component reads as absent rather than as flat ground. Reads the merged surface unless editLayer or editLayerIndex names one, in which case it reads that layer's own contribution. Params: actorLabel?, actorPath?, region?, space?, center?, radius?, maxVertices?, editLayer?, editLayerIndex?, includeHeights?, encoding?, arrayEncodingLimit?"
    options:
      method: get_landscape_height_region
  landscape.set_height_region:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Write heights over a rectangle of landscape vertices. Accepts heightsBase64 (the blob get_height_region hands back), heights as one number per vertex row-major from (minX,minY), or height / rawHeight to fill the whole rectangle with one value; heightSpace picks whether the numbers are raw uint16 (32768 = the actor's own Z) or world centimetres. Idempotent: a write that changes nothing reports unchanged true and updated false rather than claiming an edit. The rollback record carries the exact previous heights, and when the rectangle is above rollbackMaxVertices the response says rollbackOmitted with the reason instead of emitting a record that would restore only part of it. Requires an edit layer, since a write with none is regenerated away by the layer system. Params: actorLabel?, actorPath?, region?, space?, center?, radius?, maxVertices?, heightsBase64?, heights?, height?, rawHeight?, heightSpace?, editLayer?, editLayerIndex?, rollbackMaxVertices?"
    options:
      method: set_landscape_height_region
  landscape.get_height_at_point:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Surface height under one world XY, as both the raw uint16 and the world Z, plus the landscape vertex it was read at. Reads the nearest vertex rather than interpolating, so it can differ from a physics trace by up to half a quad of slope, and it says so. Position accepts x + y, point {x, y}, or worldX + worldY. Params: x?, y?, point?, worldX?, worldY?, actorLabel?, actorPath?, editLayer?, editLayerIndex?"
    options:
      method: get_landscape_height_at_point
  landscape.get_normal_at_point:
    class_path: ue-mcp.bridge
    group: landscape
    description: "World-space surface normal under one world XY, computed by central difference over the neighbouring vertices in world centimetres and rotated into world space, so a non-uniform landscape scale is accounted for. Returns the normal, the slope in degrees and the vertex it was read at. Params: x?, y?, point?, worldX?, worldY?, actorLabel?, actorPath?"
    options:
      method: get_landscape_normal_at_point
  landscape.get_slope_at_point:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Slope under one world XY in degrees from horizontal, in radians, and as a grade percentage, plus the downhill direction as a unit vector. The downhill direction is what routes a road or a river; a slope number alone never is. Params: x?, y?, point?, worldX?, worldY?, actorLabel?, actorPath?"
    options:
      method: get_landscape_slope_at_point
  landscape.get_slope_map:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Per-vertex slope in degrees over a rectangle, plus min/max/mean and a fixed nine-bucket ten-degree histogram. The buckets are fixed on purpose: they are what a caller reasons about (anything under 15 degrees is buildable) and a configurable bin count would make two runs incomparable. The per-vertex array is returned below arrayEncodingLimit and omitted with a reason above it. Params: actorLabel?, actorPath?, region?, space?, center?, radius?, maxVertices?, includeSlopes?, arrayEncodingLimit?"
    options:
      method: get_landscape_slope_map
  landscape.sculpt_region:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Apply one shaping operator over a rectangle: raise, lower, flatten, smooth, mountain, valley, ridge, plateau, crater or terrace. One action rather than ten, because the region resolution, falloff, strength blend, previous-height capture and rollback record are identical for every shape and only the per-vertex kernel differs. amount is world centimetres at full strength, shape picks a radial (ellipse) or edge-relative (rect) falloff, and sharpness controls how peaked the dome operators are. flatten and plateau take targetHeight in world Z, or flattenTo as mean / center / min / max; smooth takes iterations; terrace takes steps; ridge takes ridgeAngle in degrees; crater takes rimPosition and rimRatio. Reports verticesClampedToHeightRange when the shape ran past the uint16 range, since that is a flat-topped mountain rather than a failure. Params: operator, actorLabel?, actorPath?, region?, space?, center?, radius?, maxVertices?, amount?, strength?, falloff?, sharpness?, shape?, targetHeight?, flattenTo?, iterations?, steps?, ridgeAngle?, rimPosition?, rimRatio?, editLayer?, editLayerIndex?, rollbackMaxVertices?"
    options:
      method: sculpt_landscape_region
  landscape.apply_erosion:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Run hydraulic or thermal erosion over a rectangle. Hydraulic rains on every vertex, routes water to lower four-neighbours, carries sediment to capacity and deposits the rest, then re-deposits whatever is still suspended so the pass does not quietly remove material every run; thermal slumps anything steeper than talusAngle toward it. This is a CPU grid model over the region on the game thread, not the Landscape Mode erosion tool, and the response says so. Cost is vertices times iterations and is refused above maxWork rather than blocking the editor for an unbounded time. Params: actorLabel?, actorPath?, erosionType?, region?, space?, center?, radius?, maxVertices?, iterations?, maxWork?, talusAngle?, strength?, rainAmount?, evaporation?, sedimentCapacity?, erosionRate?, depositionRate?, editLayer?, editLayerIndex?, rollbackMaxVertices?"
    options:
      method: apply_landscape_erosion
  landscape.import_heightmap:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Write a 16-bit heightmap file into a region of the landscape. format is png16 (16-bit greyscale PNG) or raw16 (headerless little-endian uint16), inferred from the extension when omitted; an 8-bit image is refused with its real format rather than stretched, because a 256-step heightmap terraces visibly. A raw16 file carries no dimensions, so width and height are required for it. When the image and the region disagree the call refuses and prints the region that WOULD fit, unless resample is true, which bilinearly fits it and says detail was averaged away. minHeight and maxHeight remap the image's 0 and 65535 onto a world Z band, which is the control a heightmap authored elsewhere actually needs. A relative filePath resolves under the project's Saved directory. Params: filePath/sourcePath, actorLabel?, actorPath?, format?, region?, space?, center?, radius?, maxVertices?, width?, height?, resample?, minHeight?, maxHeight?, editLayer?, editLayerIndex?, rollbackMaxVertices?"
    options:
      method: import_landscape_heightmap
  landscape.export_heightmap:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Write a region's heights out as a 16-bit greyscale PNG or a headerless little-endian uint16 file, for inspection or for a round trip through an external terrain tool. Reports the exact byte count, the region and the height range in raw units and world Z, so the file can be re-imported at the right scale. Exports the merged surface unless editLayer or editLayerIndex names one. No inverse is emitted: the bridge has no delete-file method, and overwriting a file whose previous contents were never read is not undoable, so the response says rollbackOmitted with which of the two cases applied. Params: filePath/outputPath, actorLabel?, actorPath?, format?, region?, space?, center?, radius?, maxVertices?, editLayer?, editLayerIndex?, overwrite?"
    options:
      method: export_landscape_heightmap
  landscape.analyze_terrain:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Height and slope distribution over a region, plus largestFlatArea: the biggest axis-aligned rectangle whose every vertex is within slopeThresholdDegrees, in quad indices AND world coordinates with its mean Z. That last part is the answer to where a building fits, which a flat-vertex COUNT never is - ten thousand scattered flat vertices and one flat plateau report the same number. The height histogram takes histogramBins; the slope histogram is fixed at nine ten-degree bands so two runs stay comparable. Params: actorLabel?, actorPath?, region?, space?, center?, radius?, maxVertices?, histogramBins?, slopeThresholdDegrees?"
    options:
      method: analyze_landscape_terrain
  landscape.get_layer_weight_region:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Read one paint layer's 0..255 weights over a rectangle, with min/max/mean and the painted vertex count and fraction. An unregistered layerName is refused with the layers that ARE registered, so a typo and a landscape whose material never declared the layer read differently. Weights come back as an array below arrayEncodingLimit and as base64 above it, in the exact form set_layer_weight_region takes back. Params: layerName, actorLabel?, actorPath?, region?, space?, center?, radius?, maxVertices?, editLayer?, editLayerIndex?, includeWeights?, encoding?, arrayEncodingLimit?"
    options:
      method: get_landscape_layer_weight_region
  landscape.set_layer_weight_region:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Write one paint layer's weights over a rectangle. Accepts weightsBase64 (one uint8 per vertex, the form the getter returns), weights as one number per vertex, or weight / strength as a single 0..1 fraction to fill the rectangle. Idempotent, with the same unchanged/updated reporting as set_height_region, and the rollback record carries the exact previous weights up to rollbackMaxVertices. Weights are written as given: the engine no longer renormalises the other layers for you, so set them explicitly if they must sum to 1. Params: layerName, actorLabel?, actorPath?, region?, space?, center?, radius?, maxVertices?, weightsBase64?, weights?, weight?, strength?, editLayer?, editLayerIndex?, rollbackMaxVertices?"
    options:
      method: set_landscape_layer_weight_region
  landscape.layer_exists:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Is this paint layer registered on the landscape, and does it have a LayerInfo asset to store weights in? Returns exists, hasLayerInfo, its layerInfoPath, and the full layer list. The two flags are separate because a material can declare a layer that has no LayerInfo assigned, which looks present and fails every paint. Params: layerName, actorLabel?, actorPath?"
    options:
      method: landscape_layer_exists
  landscape.add_layer:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Register a paint layer on the landscape, creating the ULandscapeLayerInfoObject asset that its weight data needs and binding it. The counterpart to remove_layer, and the same handler add_layer_info calls. Idempotent: a layer already registered is reported with its existing asset path rather than duplicated. Params: layerName, landscapeName?, packagePath?"
    options:
      method: add_landscape_layer_info
  landscape.remove_layer:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Unregister a paint layer and drop its weight data across every component of the landscape. Idempotent: removing a layer that is not there reports alreadyAbsent with the layers that are, rather than failing. NOT undoable through the bridge - the weights are destroyed without being read back first, and the response says so with the reason, so export them with get_layer_weight_region first if they matter. The ULandscapeLayerInfoObject asset itself is left on disk, so add_layer brings the layer back empty. Params: layerName, actorLabel?, actorPath?"
    options:
      method: remove_landscape_layer
  landscape.get_holes:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Read the landscape visibility mask over a rectangle or at a single world XY, as booleans plus the hole vertex count and fraction. A vertex counts as a hole once its visibility weight passes the engine's own two-thirds threshold, so this action and the renderer agree about what a hole is. A hole only renders, and only stops collision, if the landscape material routes a Landscape Visibility Mask node into opacity mask. Params: actorLabel?, actorPath?, region?, space?, center?, radius?, x?, y?, point?, worldX?, worldY?, maxVertices?, includeMask?, arrayEncodingLimit?"
    options:
      method: get_landscape_holes
  landscape.set_holes:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Punch or fill the landscape visibility mask over a rectangle or at a single world XY. Accepts hole as a single boolean for the whole target, holes as one boolean per vertex, or weightsBase64 to restore exact previous visibility weights (which is the form its own rollback record carries). Unlike every other region action this one does NOT default to the whole landscape when no target is given: that would punch the entire terrain into a hole on a call that forgot a parameter, so it refuses instead. Params: actorLabel?, actorPath?, region?, space?, center?, radius?, x?, y?, point?, worldX?, worldY?, maxVertices?, hole?, holes?, weightsBase64?, editLayer?, editLayerIndex?, rollbackMaxVertices?"
    options:
      method: set_landscape_holes
  landscape.plan_real_world:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Turn a real-world extent plus a heightmap file into the exact landscape(create) and landscape(import_heightmap) parameters that reproduce it in engine at true scale. Creates nothing: it is arithmetic against engine invariants a caller cannot restate correctly by hand, namely that a landscape's vertex count is componentCount x subsectionSizeQuads x numSubsections + 1 with subsectionSizeQuads restricted to 7/15/31/63/127/255, and that its uint16 heights only mean metres through the actor's Z scale. Size the tile with realWorldSizeMeters {x,y}, or with boundsLatLon {minLat,minLon,maxLat,maxLon} which is projected on a local tangent plane at the box centre latitude (an approximation, not a datum reprojection, and the response says so). minElevationMeters and maxElevationMeters state what the image's value range means; elevationEncoding full spans 0..65535 across that band, data measures the image's own range and needs sourcePath. Returns the chosen resolution, every legal alternative with its error, the resulting metres per quad, the vertical precision in centimetres, and warnings for resampling, anisotropy, quantisation and exaggeration. Fetching DEM tiles over the network is deliberately NOT part of this: provider APIs, keys and licences change independently of the engine, so that half belongs in a plugin. Params: minElevationMeters, maxElevationMeters, realWorldSizeMeters?, boundsLatLon?, sourcePath?, filePath?, format?, width?, height?, metersPerQuad?, elevationEncoding?, verticalExaggeration?, maxComponents?, location?"
    options:
      method: plan_real_world_landscape
  landscape.project_geo_coordinates:
    class_path: ue-mcp.bridge
    group: landscape
    description: "Convert between latitude/longitude and a landscape's world space, in both directions, against the same boundsLatLon the landscape was planned for. Each entry in points is either {lat, lon}, answered with the world X/Y and the sampled surface Z so the point lands ON the terrain, or {x, y}, answered with the geographic coordinate that world position corresponds to - which is what turns analyze_terrain's largestFlatArea back into a real place. northAt says which end of the landscape's Y axis is north, defaulting to minY because a DEM raster's first row is its northern edge and that is how import_heightmap lays one down; getting it wrong mirrors every placement about the middle of the map and nothing about the result looks wrong. Params: boundsLatLon, points, actorLabel?, actorPath?, northAt?, sampleHeight?"
    options:
      method: project_geo_coordinates
  landscape.refresh_physical_material_collision:
    class_path: ue-mcp.bridge
    group: landscape
    description: "UE 5.8+: safely refresh physical-material collision data in memory on loaded World Partition LandscapeStreamingProxy actors after a LayerInfo PhysMaterial change. Requires complete registered collision coverage and no pending landscape edit-layer work. Preserves and verifies every raw, complex-live, and simple-live height sample, builds material data before one collision recreation, and fails the whole matched batch on any unsafe result. Filters combine: actorLabels[], guids[], and bounds {min,max}; omitting them targets every loaded proxy up to maxActors (default 256, hard max 1024). Unloaded proxies are untouched; pin them first with level(load_actor_descs). Refuses PIE/SIE and non-World-Partition maps. Persistence is deliberately unsupported because Landscape PreSave can mutate edit-layer collision data; this action never saves packages. Params: actorLabels? (string[]), guids? (string[]), bounds? ({min, max}), maxActors? (default 256, max 1024). No inverse: this recomputes derived collision and physical-material data from weightmaps it never writes, so nothing restores the previous build and the response says rollbackPossible=false. 'save' is deliberately absent from this category's schema and from what this action forwards, so persistence cannot be requested through the tool at all; the handler's own refusal of a save=true is there for a direct bridge caller. Returns loaded/matched/refreshed/failed counts and exact affected package paths."
    options:
      method: refresh_landscape_physical_material_collision

  # ── pcg ──
  pcg.list_graphs:
    class_path: ue-mcp.bridge
    group: pcg
    description: "List PCG graphs, sorted by object path. Lists the whole project; page through it with cursor/limit. Params: none, cursor?, limit?"
    options:
      method: list_pcg_graphs
  pcg.read_graph:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Read graph structure. Params: assetPath"
    options:
      method: read_pcg_graph
  pcg.read_node_settings:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Read node settings. Params: assetPath, nodeName"
    options:
      method: read_pcg_node_settings
  pcg.get_components:
    class_path: ue-mcp.bridge
    group: pcg
    description: "List PCG components in level. Params: none"
    options:
      method: get_pcg_components
  pcg.get_component_details:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Inspect PCG component. Params: actorLabel OR actorPath (#983)"
    options:
      method: get_pcg_component_details
  pcg.create_graph:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Create graph. Idempotent by path: an existing graph is reported rather than replaced. Params: name, packagePath? (default /Game/PCG), onConflict? (skip|error)"
    options:
      method: create_pcg_graph
  pcg.add_node:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Add node. nodeName is a RESULT, not an input: the engine assigns the name and this action reports it back for connect_nodes and remove_node. Params: assetPath, nodeType, posX?, posY?"
    options:
      method: add_pcg_node
  pcg.connect_nodes:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Wire nodes. Params: assetPath, sourceNode, sourcePin, targetNode, targetPin. Returns edgeVerified=true after confirming the UPCGEdge persisted; surfaces an error if AddEdge succeeded but no edge object was instantiated (#304)."
    options:
      method: connect_pcg_nodes
  pcg.disconnect_nodes:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Remove a wired edge between two PCG nodes. Params: assetPath, sourceNode, targetNode, sourcePin? (default: any), targetPin? (default: any). Returns removedEdges count (#346)."
    options:
      method: disconnect_pcg_nodes
  pcg.set_node_settings:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Set node params. Pass a settings object of {propertyPath: value} (dotted paths and nested structs supported), or propertyName + propertyValue for a single write. Reports previousProperties and rolls back to them. Params: assetPath, nodeName, settings OR propertyName+propertyValue"
    options:
      method: set_pcg_node_settings
  pcg.set_static_mesh_spawner_meshes:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Populate weighted MeshEntries on a PCGStaticMeshSpawner node (#145). Params: assetPath, nodeName, entries=[{mesh, weight?}], replace? (default true)"
    options:
      method: set_static_mesh_spawner_meshes
  pcg.remove_node:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Remove node. Params: assetPath, nodeName"
    options:
      method: remove_pcg_node
  pcg.execute:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Regenerate PCG. Params: actorLabel OR actorPath, seed? (writes the component Seed before generating) (#983)"
    options:
      method: execute_pcg_graph
  pcg.force_regenerate:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Force a stuck PCG component to regenerate (clears graph ref, re-sets, cleanup+generate). Params: actorLabel OR actorPath (#146/#983)"
    options:
      method: force_regenerate_pcg
  pcg.cleanup:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Cleanup a PCG component (remove spawned content). Params: actorLabel OR actorPath, removeComponents? (default true) (#146)"
    options:
      method: cleanup_pcg
  pcg.toggle_graph:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Toggle a PCG component's graph assignment to force reinit (no generate). Params: actorLabel OR actorPath, graphPath? (#146)"
    options:
      method: toggle_pcg_graph
  pcg.add_volume:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Place PCG volume. Idempotent by editor label when one is given. Params: graphPath, location?, extent?, label?, onConflict? (skip|error)"
    options:
      method: add_pcg_volume
  pcg.import_graph:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Bulk-author a PCG graph from JSON. Params: assetPath, nodes=[{name,class,posX?,posY?,settings?}], connections=[{from,fromPin?,to,toPin?}], replace? (default false). One call replaces N add_node + M connect_nodes + K set_node_settings (#213)."
    options:
      method: import_pcg_graph
  pcg.export_graph:
    class_path: ue-mcp.bridge
    group: pcg
    description: "Export a PCG graph as JSON. Params: assetPath, includeSettings? (default true). Round-trip safe with import_graph (#213)."
    options:
      method: export_pcg_graph
  pcg.epic_add_comment_box:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Adds a comment box around the given nodes. Params: graph, nodes, comment?, color?"
    options:
      method: epic_call_tool
  pcg.epic_add_node:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Adds a native node to the graph. Params: graph, nativeNodeType, nodeName, jsonParams, nodeTitle, nodeComment, xPositionIdx?, yPositionIdx?"
    options:
      method: epic_call_tool
  pcg.epic_add_subgraph_node:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Adds a subgraph node to the graph. Params: graph, subGraphForNode, nodeName, jsonParams, nodeTitle, nodeComment, xPositionIdx?, yPositionIdx?"
    options:
      method: epic_call_tool
  pcg.epic_connect_node_pins:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Add an edge between two nodes connected to the specified pins. Params: fromNode, fromPinLabel, toNode, toPinLabel"
    options:
      method: epic_call_tool
  pcg.epic_create_graph:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Creates a new saved PCG graph asset. Params: name, path?"
    options:
      method: epic_call_tool
  pcg.epic_disconnect_node_pins:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Removes the edge between two nodes connected to the specified pins. Params: fromNode, fromPinLabel, toNode, toPinLabel"
    options:
      method: epic_call_tool
  pcg.epic_draw_spline:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Triggers the user to draw a spline in the viewport to be used later in the world building. Waits for the user to be done. Params: actorLabel, actorTag, bRedraw, bClosedSpline"
    options:
      method: epic_call_tool
  pcg.epic_execute_graph_instance:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Executes the graph instance and returns any issues encountered during execution. Params: pCGVolume"
    options:
      method: epic_call_tool
  pcg.epic_get_graph_description:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Returns the description of a PCG graph. Params: graph"
    options:
      method: epic_call_tool
  pcg.epic_get_graph_instance_params:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Gets the graph instance params of a specific actor, actor MUST have a graph instance Params: pCGVolume"
    options:
      method: epic_call_tool
  pcg.epic_get_graph_schema:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Returns the schema for a PCG Graph's graph parameters Params: graph"
    options:
      method: epic_call_tool
  pcg.epic_get_graph_structure:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Returns the complete structure of a PCG graph including all nodes, connections, exposed parameters, and comment boxes. Params: graph"
    options:
      method: epic_call_tool
  pcg.epic_get_native_node_schema:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Returns the schema for a PCG node type including input/output pins, parameters, and their types. Params: nodeName"
    options:
      method: epic_call_tool
  pcg.epic_get_node_data_view:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Returns a JSON Data View of a specific node's output data from the last graph execution. On first call, enables inspection so future ExecuteGraphInstance calls store per-node data. If no inspection data exists, returns an error prompting re-execution. IMPORTANT: Inspection state is shared at the graph asset level. If multiple actors use the same graph, you MUST call this tool (and ExecuteGraphInstance) on only one actor at a time. Wait for each call to fully complete before calling on the next actor. Concurrent calls on actors sharing the same graph will cause a freeze. Params: pCGVolume, node, pinLabel?, attributeName, startIndex?, endIndex?"
    options:
      method: epic_call_tool
  pcg.epic_get_node_info:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Returns node details including name, position, and all parameter values. Params: node"
    options:
      method: epic_call_tool
  pcg.epic_list_available_subgraphs:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Lists the PCG graphs that can be used with the Subgraph native node. Only these graphs should be used with the Subgraph native node. Params: none"
    options:
      method: epic_call_tool
  pcg.epic_list_graph_instances:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Gets all actors with a PCG graph instance in the scene. Params: none"
    options:
      method: epic_call_tool
  pcg.epic_list_native_nodes:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Returns a list of available native PCG node type names. Params: bCommonOnly?"
    options:
      method: epic_call_tool
  pcg.epic_remove_comment_box:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Removes a comment box from the graph. Does not affect the nodes it contains. Params: graph, commentId"
    options:
      method: epic_call_tool
  pcg.epic_remove_graph_params:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Removes graph parameters to a specific PCG graph, such that they are not overridable anymore. Params: graph, paramNames"
    options:
      method: epic_call_tool
  pcg.epic_remove_node:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Removes the node from the graph, will also remove edges connected to the node. Params: graph, node"
    options:
      method: epic_call_tool
  pcg.epic_reposition_node:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Change the position of node. Params: node, xPositionIdx, yPositionIdx"
    options:
      method: epic_call_tool
  pcg.epic_reset_graph_instance_params:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Resets the given graph instance params back to the graph's default values. Actor MUST have a graph instance. Params: pCGVolume, paramNames"
    options:
      method: epic_call_tool
  pcg.epic_run_pcginstant_graph:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGSpatialToolset] Runs an instant PCG graph with the specified parameters in fire-and-forget mode (Should be called directly: Not callable in a python context execution context) Params: graph, params"
    options:
      method: epic_call_tool
  pcg.epic_set_graph_description:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Set the description of a PCGGraph Params: graph, description"
    options:
      method: epic_call_tool
  pcg.epic_set_graph_instance_params:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Sets the graph instance params of a specific actor, actor MUST have a graph instance Params: pCGVolume, jsonParams"
    options:
      method: epic_call_tool
  pcg.epic_set_graph_params:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Adds one or more graph user parameters to a specific PCG graph, such that they will be overridable in per graph instance. Params: graph, params"
    options:
      method: epic_call_tool
  pcg.epic_set_node_comment:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Change the comment on the specified node. Params: node, nodeComment"
    options:
      method: epic_call_tool
  pcg.epic_spawn_graph_instance:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Spawns a PCG Volume with associated Graph Instance into the scene, optionally with Graph Param overrides. Params: graph, name, transform, jsonParams"
    options:
      method: epic_call_tool
  pcg.epic_update_comment_box:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Updates an existing comment box with new nodes and value. Params: graph, commentId, nodes, comment?, color?"
    options:
      method: epic_call_tool
  pcg.epic_update_node:
    class_path: ue-mcp.bridge
    group: pcg
    description: "[Epic PCGToolset.PCGToolset] Updates a node by changing its params and/or title. Params: node, jsonParams, nodeTitle"
    options:
      method: epic_call_tool

  # ── foliage ──
  foliage.list_types:
    class_path: ue-mcp.bridge
    group: foliage
    description: "List foliage types in the level, one row per type per InstancedFoliageActor (foliageActorPath says which), sorted by that pair. Params: cursor?, limit?"
    options:
      method: list_foliage_types
  foliage.get_settings:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Read foliage type settings. Params: foliageTypeName"
    options:
      method: get_foliage_type_settings
  foliage.sample:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Query instances in region. Params: center, radius, foliageType?"
    options:
      method: sample_foliage
  foliage.create_type:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Create foliage type from mesh. Params: meshPath, name?, packagePath?"
    options:
      method: create_foliage_type
  foliage.set_settings:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Modify ONE foliage type's settings. For the same edit across every type matching a condition, use batch_set_settings_where instead of calling this in a loop. Params: foliageTypeName OR foliageTypePath, settings"
    options:
      method: set_foliage_type_settings
  foliage.batch_set_settings_where:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Write settings to every FoliageType whose CURRENT property values match a predicate, with the predicate evaluated in the editor. set_settings is one asset per call and cannot express a condition at all, so 'include_in_hlod=false on every type whose cull distance is set' meant reading 201 assets out, computing the 36 matches client-side, and writing them back one at a time. Candidates come from foliageTypePaths[] (explicit), directory (scan FoliageType assets) or fromLevel=true (types placed in the open level). Predicate fields read the asset's own properties by dotted path, so CullDistance.Max works and a bare name is treated the same as props.<name>; operators are the same vocabulary as level(query_components). dryRun DEFAULTS TO TRUE because this writes to many assets at once, and the preview still resolves every setting path so an unwritable property is reported before the commit. Written values are read back rather than echoed, so a value the property coerced is visible. The inverse is asset(bulk_set_properties) carrying the previous value of every property on every type written, which is the only action that takes DIFFERENT values per asset - two matched types can have had different previous values for the same setting. It is marked lossy when a matched FoliageType lives inside a level rather than as an asset (no assetPath addresses it), and omitted entirely above that action's 500-item batch limit, with the reason stated rather than a record that would restore part of the write. Params: settings (object of propertyName -> value, dotted paths supported), where ([{field, op, value}], required), whereMode? (all|any), one of foliageTypePaths[]/directory (+ recursive?)/fromLevel, propertyNames? (extra properties to report without filtering on them), dryRun? (default TRUE), save? (default true), maxTypes? (#988)"
    options:
      method: batch_set_foliage_settings_where
  foliage.add_instances:
    class_path: ue-mcp.bridge
    group: foliage
    description: "PAINT foliage into the open level. Nothing else on this surface could place a single instance, so create_type produced an asset that never appeared anywhere. Two modes: transforms[] places each entry exactly, center+radius+count scatters uniformly over a disc. Each candidate is traced onto the geometry beneath it (projectToGround, default true) and then run through the FoliageType's OWN placement rules (applyTypeRules, default true) so the random scale between ScaleX/Y/Z, the random yaw and pitch, align-to-normal within AlignMaxAngle, the Z offset and the CollisionWithWorld test all apply - which is what makes a painted instance behave like one painted by hand. A candidate that hits nothing, or that the type's ground-slope or height range rejects, is reported in skippedCandidates with the reason rather than silently dropped. The type is added to the level's palette if it is not there already. Returns the InstancedFoliageActor it wrote to, and rolls back by removing exactly the transforms it placed. Params: foliageTypePath, transforms? (array of {location,rotation?,scale?}), center?, radius?, count?, seed?, projectToGround?, traceUp?, traceDown?, applyTypeRules?, skipCollision?"
    options:
      method: add_foliage_instances
  foliage.remove_instances:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Remove foliage instances by index, by exact transform, inside a sphere, or all of a type. The rollback captures each removed instance's world transform and replays it as an add, so this is recoverable up to 2000 instances; the base component an instance was painted onto is not restored. dryRun reports what would go without touching anything. Scope to one InstancedFoliageActor with actorPath when a World Partition map holds the same type on several. Params: foliageTypePath, instanceIndices? (int array from get_instances), transforms? (match by location within matchTolerance), center?, radius?, all?, matchTolerance?, actorPath?, dryRun?"
    options:
      method: remove_foliage_instances
  foliage.get_instances:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Read placed foliage instances with their world transforms, paged. sample only ever returned counts, so there was no way to verify a paint or find what to remove. Filter by type and/or by a sphere; index is the position inside the named actor's info for that type, which is exactly what remove_instances takes. Params: foliageTypePath?, center?, radius?, limit?, startIndex?, includeTransforms?"
    options:
      method: get_foliage_instances
  foliage.add_type_to_level:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Put a FoliageType asset into the open level's palette, allocating the instanced component that holds its instances. create_type only makes the asset; until this runs the type is invisible to the level and to the foliage editor. Idempotent: a type already in the level reports existed and allocates nothing. Params: foliageTypePath"
    options:
      method: add_foliage_type_to_level
  foliage.remove_type_from_level:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Take a FoliageType out of the open level's palette. REFUSES while the type still has instances unless force=true, because removing it destroys them and their transforms are not captured - remove them first with remove_instances, whose rollback does capture them. The FoliageType asset itself is untouched; asset(delete) deletes it. Params: foliageTypePath, force?"
    options:
      method: remove_foliage_type_from_level
  foliage.read_spawner:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Read a ProceduralFoliageSpawner: its tile settings, the foliage types it will spawn resolved to real asset paths, and every volume in the open level bound to it with its bounds and whether it has produced anything. The volume half is the part nobody can read off the asset and the usual reason a simulation appears to do nothing. Returns objectPath for the scalar tunables, which are plain properties. Params: spawnerPath"
    options:
      method: read_procedural_foliage_spawner
  foliage.set_spawner_types:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Set which foliage types a ProceduralFoliageSpawner spawns. This needs an action rather than a property write: FoliageTypes is a private array whose element object pointer is private too, and every write has to be followed by RefreshInstance or the cached type the simulation actually reads stays stale. Validates every path before writing anything, reports unchanged when the list already matched, reads the result back rather than echoing it, and rolls back to the previous list. Params: spawnerPath, foliageTypePaths, mode? (replace|add|remove), save?"
    options:
      method: set_procedural_foliage_spawner_types
  foliage.simulate_procedural:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Run the procedural foliage simulation and place what it generates. Address one volume with actorLabel or actorPath, or every volume in the open level bound to one spawner with spawnerPath. Validates every target first (spawner set, type list non-empty, volume bounds non-zero) so a half-simulated set of volumes is not possible, then generates desired instances and traces and places each one under the same type rules as add_instances. Reports generated, placed and skipped per volume. Place the volume itself with level(spawn_volume, volumeType=\"ProceduralFoliageVolume\"), which builds the cube brush a bare spawn leaves empty. Params: actorLabel OR actorPath OR spawnerPath, clearExisting?, skipCollision?"
    options:
      method: simulate_procedural_foliage
  foliage.clear_procedural:
    class_path: ue-mcp.bridge
    group: foliage
    description: "Remove every instance a procedural foliage component spawned, matched on the component's own procedural GUID so hand-painted instances of the same type are left alone. Reports alreadyRemoved when the component had produced nothing. The inverse re-runs the simulation, which reproduces the cleared content only while the spawner's seed, tile settings and type list are unchanged. Params: actorLabel OR actorPath OR spawnerPath"
    options:
      method: clear_procedural_foliage

  # ── niagara ──
  niagara.list:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List Niagara assets: every NiagaraSystem and NiagaraEmitter as one row tagged with its `type`, sorted by object path within each type, plus systemCount/emitterCount for the whole listing. Lists the whole project; page through it with cursor/limit. Params: none, cursor?, limit?"
    options:
      method: list_niagara_systems
  niagara.get_info:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Inspect system. Params: assetPath"
    options:
      method: get_niagara_info
  niagara.list_dynamic_inputs:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Report the authored override map per module: which inputs carry a plain value, which are wired to a dynamic-input script, and which hold an inline HLSL expression, with nested dynamic inputs one level down under nestedOverrides. This is a graph walk, so no property read produces it. Pair with list_module_inputs, which shows the inputs that have no override at all. Params: systemPath, emitterName?, emitterIndex?, stackContext? (ParticleSpawn|ParticleUpdate|EmitterSpawn|EmitterUpdate|all), moduleName?"
    options:
      method: list_niagara_dynamic_inputs
  niagara.set_dynamic_input:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Wire a dynamic-input NiagaraScript into a module input's override pin, creating the pin if needed and replacing whatever was there. A dynamic input is a graph node, not a property, so set_property cannot do this. Returns dynamicInputName, which is the module name to pass to set_module_input when setting the dynamic input's OWN inputs. Errors list the module's real input names and the modules present. Params: systemPath, stackContext, moduleName, inputName, dynamicInputScript, emitterName?, emitterIndex?"
    options:
      method: set_niagara_dynamic_input
  niagara.remove_dynamic_input:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Unwire a dynamic input, delete the nodes that fed only it, and drop the override pin so the module's own default comes back. An input with no dynamic input returns alreadyRemoved rather than an error, so a rollback replays safely. Params: systemPath, stackContext, moduleName, inputName, emitterName?, emitterIndex?"
    options:
      method: remove_niagara_dynamic_input
  niagara.add_simulation_stage:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Create a simulation stage on an emitter: the stage object AND the backing NiagaraScript, its output node with a fresh usage id, and a parameter-map input node, so the stage actually compiles. set_property cannot create a graph, which is why this is a handler. Returns simulationStageObjectPath; set IterationSource, NumIterations, ExecuteBehavior and the ElementCount bindings on it with asset(set_property). Params: systemPath, stageName, emitterName?, emitterIndex?, enabled?"
    options:
      method: add_niagara_simulation_stage
  niagara.remove_simulation_stage:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Remove a simulation stage, its script, and the graph nodes that fed only its output node, leaving nodes shared with another stack alone. A missing stage returns alreadyRemoved with the stages that do exist. Reports removedModules, which the rollback cannot restore, rather than pretending the undo is complete. Params: systemPath, stageName, emitterName?, emitterIndex?"
    options:
      method: remove_niagara_simulation_stage
  niagara.add_event_handler:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Create an event handler on an emitter, with its event script, output node and usage id, so the handler's struct fields point at something real. Returns eventHandlerPropertyPath; set ExecutionMode, SpawnNumber and MaxEventsPerFrame through asset(set_property) on emitterObjectPath with that prefix. Params: systemPath, eventName, emitterName?, emitterIndex?, sourceEmitterId?"
    options:
      method: add_niagara_event_handler
  niagara.remove_event_handler:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Remove an event handler by its script's usage id and delete the graph chain that fed only it. A missing handler returns alreadyRemoved listing the handlers present. Echoes the ExecutionMode and SpawnNumber the rollback will not restore. Params: systemPath, eventName, emitterName?, emitterIndex?"
    options:
      method: remove_niagara_event_handler
  niagara.get_custom_hlsl:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Read every CustomHLSL node in a graph: the source body, and the pins Niagara parsed out of it. Omit nodeIndex to list them all. This is what makes HLSL iterable rather than write-once, since nothing else can read the body back. Params: scriptPath (a NiagaraScript) OR systemPath + stackContext? + emitterName?/emitterIndex?, nodeIndex?"
    options:
      method: get_niagara_custom_hlsl
  niagara.set_custom_hlsl:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Overwrite a CustomHLSL node's body and reconstruct the node, so the returned pins are the ones the new source actually declares. A bare property write would leave stale pins and an uncompiled script, which is why this is a handler. A body identical to the current one returns alreadySet and skips the recompile. Params: hlsl, scriptPath OR systemPath + stackContext? + emitterName?/emitterIndex?, nodeIndex?"
    options:
      method: set_niagara_custom_hlsl
  niagara.remove_module:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Remove a module from an emitter stack: unwire its node group, close the parameter-map chain over the gap, and delete the module with its override node and dynamic inputs. add_module had no inverse below UE 5.8, where only the Epic toolset covers this. Returns remainingModules in stack order and echoes the removedOverrides the rollback will not restore. Params: systemPath, stackContext, moduleName, emitterName?, emitterIndex?"
    options:
      method: remove_niagara_module
  niagara.set_module_enabled:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Enable or disable a module in place. A disabled module keeps its node, its inputs and its stack position and is skipped at compile time, which makes this the reversible way to test whether a module is causing a behaviour. Already in that state returns alreadySet. Params: systemPath, stackContext, moduleName, enabled, emitterName?, emitterIndex?"
    options:
      method: set_niagara_module_enabled
  niagara.compile:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Force a real compile of a system and report what the translator said, per script: scriptName, usage, status (NCS_UpToDate | NCS_UpToDateWithWarnings | NCS_Error | ...), errorMsg and every compile event with its severity and the node and pin guid that produced it, plus a top-level compiled boolean and a flat errors[]. This is the assertion a graph edit has to survive. validate answers whether the system EMITS, which a malformed script can still pass, and get_compiled_hlsl returns success without compiling anything at all on a CPU-sim emitter. The call blocks until the compile settles, because an asynchronous one hands back the previous compile's status. force (default true) recompiles even when nothing looks dirty, which is the case a graph edit that left change tracking untouched produces. Params: systemPath, force?, includeGpuShaders?"
    options:
      method: compile_niagara_system
  niagara.validate:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Verify gate: does this system actually emit? Reports per emitter whether it is enabled and has a spawn module + an enabled renderer. valid=false means empty shell. Params: systemPath"
    options:
      method: validate_niagara_system
  niagara.spawn:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Spawn VFX as a transient component (GC's before offscreen capture). For a findable preview use spawn_actor. Params: systemPath, location, rotation?, label?, scaleX?/scaleY?/scaleZ? (separate keys, not a vector object), autoDestroy? (default false)"
    options:
      method: spawn_niagara_at_location
  niagara.spawn_actor:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Spawn a PERSISTENT, labeled NiagaraActor in the editor world (findable, re-activatable, survives capture - unlike spawn). Assigns the system and activates. Params: systemPath, location?, rotation?, label?, activate? (default true) (#537)"
    options:
      method: spawn_niagara_actor
  niagara.reactivate:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Reset + reactivate the NiagaraComponent on a placed actor (replay a burst before capturing). Params: actorLabel OR actorPath (#537/#983)"
    options:
      method: reactivate_niagara
  niagara.set_parameter:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Set a user parameter on the NiagaraComponent of a placed actor. parameterType selects how the value is read: float, int and bool take `value`; vector takes valueX/valueY/valueZ, which are separate keys rather than a `value` object because that is what the handler reads. Reports previousValue and rolls back to it. Params: actorLabel OR actorPath, parameterName, parameterType? (float|vector|bool|int, default float), value (float/int/bool) OR valueX+valueY+valueZ (vector)"
    options:
      method: set_niagara_parameter
  niagara.create:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Create system. Params: name, packagePath?"
    options:
      method: create_niagara_system
  niagara.create_emitter:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Create a Niagara emitter asset. templatePath copies an existing emitter as the starting point (the content browser's create-from-template path); omit it for the default empty emitter with the standard modules and a sprite renderer. inherit=true makes it a child that tracks the template instead, which then refuses local edits to inherited modules. Params: name, packagePath?, templatePath?, inherit? (default false), onConflict?"
    options:
      method: create_niagara_emitter
  niagara.add_emitter:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Add emitter to system. Params: systemPath, emitterPath"
    options:
      method: add_emitter_to_system
  niagara.remove_emitter:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Remove an emitter from a system (CRUD delete). Params: systemPath, emitterName? or emitterIndex?"
    options:
      method: remove_emitter_from_system
  niagara.list_emitters:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List emitters in system. Params: systemPath"
    options:
      method: list_emitters_in_system
  niagara.set_emitter_property:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Set emitter property. Params: systemPath, emitterName?, propertyName, value"
    options:
      method: set_emitter_property
  niagara.list_modules:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List Niagara module scripts, sorted by object path. pathFilter narrows the whole set rather than only the first page, which is what it always claimed to do. Params: pathFilter?, cursor?, limit?"
    options:
      method: list_niagara_modules
  niagara.get_emitter_info:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Inspect emitter. Params: assetPath"
    options:
      method: get_emitter_info
  niagara.list_renderers:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List renderers on an emitter. Params: systemPath, emitterName?, emitterIndex?"
    options:
      method: list_emitter_renderers
  niagara.add_renderer:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Add renderer (sprite/mesh/ribbon or full class). Params: systemPath, rendererType, emitterName?, emitterIndex?"
    options:
      method: add_emitter_renderer
  niagara.remove_renderer:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Remove renderer by index. Params: systemPath, rendererIndex, emitterName?, emitterIndex?"
    options:
      method: remove_emitter_renderer
  niagara.set_renderer_property:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Set any renderer property. Bools, numbers and strings are taken directly; object properties (a sprite/mesh renderer's Material, the mesh on a mesh renderer) take an asset path and are class-checked; structs, enums, names and arrays go through the shared JSON property setter, so there is no longer a type whitelist to fall off (#783). Params: systemPath, rendererIndex, propertyName, value, emitterName?, emitterIndex?"
    options:
      method: set_renderer_property
  niagara.inspect_data_interfaces:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List user-scope data interfaces. Params: systemPath"
    options:
      method: inspect_data_interface
  niagara.create_system_from_spec:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Declaratively create a system + emitters. Params: name, packagePath?, emitters?:[{path}]"
    options:
      method: create_niagara_system_from_spec
  niagara.get_compiled_hlsl:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Read GPU compute script info for an emitter. Params: systemPath, emitterName?, emitterIndex?"
    options:
      method: get_niagara_compiled_hlsl
  niagara.list_system_parameters:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List user-exposed system parameters. Params: systemPath"
    options:
      method: list_niagara_system_parameters
  niagara.list_module_inputs:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List an emitter's modules with the inputs you can actually SET - Spawn Rate, Lifetime, Colour, Sprite Size - each with its name, qualifiedName, type and a settable flag. Current values are NOT returned: the binder's value reader is not exported from NiagaraEditor, so the names and types are readable but the live value is not. Compile-time switches and enums are reported separately under switchPins; note that 'inputs' now means override-map inputs, NOT the function-call node pins it meant before (those are switchPins) (#784). Params: systemPath, emitterName?, emitterIndex?, stackContext? (ParticleSpawn|ParticleUpdate|EmitterSpawn|EmitterUpdate|all - default all), moduleName?"
    options:
      method: list_niagara_module_inputs
  niagara.set_module_input:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Set a module input value. Override-map-bound inputs (the numeric/colour values that matter) are written through the stack override map, the same path the Niagara stack editor uses; others fall back to the pin default. Reports writePath ('overrideMap'|'pinDefault'). On the overrideMap path previousValue cannot be read back (NiagaraEditor does not export the binder's reader), so it reports '(unread: override map)' and NO rollback is offered - re-set the value explicitly instead. The pinDefault path reports a real previousValue and is rollback-safe (#769). value accepts a scalar, [x,y,z], {x,y,z[,w]} or {r,g,b[,a]} (alpha defaults to 1); anything the input's type cannot parse is REJECTED rather than written, including gapped component objects and non-finite numbers. Params: systemPath, moduleName, inputName, value, emitterName?, emitterIndex?, stackContext?"
    options:
      method: set_niagara_module_input
  niagara.add_module:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Add a stock /Niagara/Modules script to an emitter stack (the modules that make an emitter actually do anything). Params: systemPath, moduleScript (e.g. /Niagara/Modules/Emitter/SpawnRate), stackContext (ParticleSpawn|ParticleUpdate|EmitterSpawn|EmitterUpdate), emitterName?, emitterIndex?, targetIndex? (-1 appends). Then set_module_input to tune it."
    options:
      method: add_niagara_module
  niagara.list_static_switches:
    class_path: ue-mcp.bridge
    group: niagara
    description: "List static switch inputs on a module. Params: systemPath, moduleName, emitterName?, emitterIndex?, stackContext?"
    options:
      method: list_niagara_static_switches
  niagara.set_static_switch:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Set static switch value on a module's function call node. Params: systemPath, moduleName, switchName, value, emitterName?, emitterIndex?, stackContext?"
    options:
      method: set_niagara_static_switch
  niagara.create_module_from_hlsl:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Create a NiagaraScript module backed by a custom HLSL node. Params: name, hlsl, packagePath?, inputs?:[{name,type}], outputs?:[{name,type}]"
    options:
      method: create_niagara_module_from_hlsl
  niagara.create_scratch_module:
    class_path: ue-mcp.bridge
    group: niagara
    description: "Create empty Niagara scratch module. Params: name, packagePath?, inputs?:[{name,type}], outputs?:[{name,type}] (#185)"
    options:
      method: create_scratch_module
  niagara.batch:
    class_path: niagara.batch
    group: niagara
    description: "Run a sequence of niagara operations against the bridge in order. Fails fast on the first error (returns results up to that point + error). Params: ops:[{action, params}] where action is any niagara subaction listed above."
  niagara.epic_add_emitter:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Adds an emitter to a Niagara System. The new emitter will be based on the template emitter, inheriting its configuration and modules. Returns the full emitter topology (no input values - call GetEmitterInputValues for values). Params: system, templateEmitter, emitterName"
    options:
      method: epic_call_tool
  niagara.epic_add_module:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Adds a module to a script stack. The module will be inserted into the specified script's execution stack. Returns the module topology with all inputs walked (no input values - call GetModuleInputValues for values). Params: moduleLocationRef, moduleAsset"
    options:
      method: epic_call_tool
  niagara.epic_add_renderer:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Adds a renderer to an emitter. Creates a new renderer of the specified type and adds it to the emitter's renderer list. Returns an FNiagaraExt_RendererRef with the new renderer's Index and RendererClass, usable directly with SetRendererData / GetRendererData without a follow-up topology call. Params: newRendererLocation, rendererClass"
    options:
      method: epic_call_tool
  niagara.epic_add_set_parameter_entry:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Adds a single parameter to an existing SetParameters module. The module referenced by ModuleRef must be a SetParameters (UNiagaraNodeAssignment) module. Use bIsSetParametersModule in the module topology to confirm before calling. Params: moduleRef, entry"
    options:
      method: epic_call_tool
  niagara.epic_add_set_parameters_module:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Adds a SetParameters module to a script stack. Unlike AddModule which requires a script asset, a SetParameters module dynamically assigns values to named parameters and generates its own internal script. Use this when you need to set one or more particle/emitter/system parameters directly in the stack. Params: moduleLocationRef, parameters"
    options:
      method: epic_call_tool
  niagara.epic_add_user_variables:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Adds or updates user variables on a system. If a variable with the same name already exists, it will be replaced with the new definition. Params: system, variablesToAdd"
    options:
      method: epic_call_tool
  niagara.epic_apply_stack_issue_fix:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Applies a Fix-style stack issue fix identified by IssueId and FixId. Link-style fixes are rejected. The fix is undoable via the editor undo stack. Applying a fix may trigger a recompile; the result waits for that compile to complete so post-fix state is valid. Params: system, issueId, fixId"
    options:
      method: epic_call_tool
  niagara.epic_construct_niagara_bpwrapper_from_component:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Blueprint] Creates a Blueprint actor wrapper from a Niagara Component. This generates a new Blueprint actor and preserves all component property values and user variable overrides. Naming convention: NS_MyEffect -> B_MyEffect Params: newAssetPath, component, parentClass"
    options:
      method: epic_call_tool
  niagara.epic_construct_niagara_bpwrapper_from_system:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Blueprint] Creates a Blueprint actor wrapper around a Niagara System. This generates a new Blueprint actor with a Niagara component configured to use the specified system. Naming convention: NS_MyEffect -> B_MyEffect Params: newAssetPath, system, parentClass"
    options:
      method: epic_call_tool
  niagara.epic_create_niagara_system:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Creates a new Niagara System asset. The new system will be based on the template system, inheriting its configuration and emitters. Params: assetName, assetPath, templateSystem"
    options:
      method: epic_call_tool
  niagara.epic_find_niagara_scripts:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Assets] Searches for UNiagaraScript assets matching the given filters. Reads filterable metadata from asset registry tags only - no LoadObject required. Tags reflect the exposed (published) version of versioned scripts; this function does not need a version filter and there is no way to discover non-exposed versions through the asset registry. Params: folderPath, name, usages, visibilities, moduleUsageBitmask, bRecursive, bIncludeDeprecated"
    options:
      method: epic_call_tool
  niagara.epic_get_asset_discovery_info:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Assets] Returns the project's configured asset discovery groups. Each group describes a content directory's purpose and paths. Params: none"
    options:
      method: epic_call_tool
  niagara.epic_get_available_dynamic_inputs:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns all available Dynamic Input Module assets compatible with the given type. Dynamic inputs provide procedural value generation for module parameters. Params: type"
    options:
      method: epic_call_tool
  niagara.epic_get_data_interface_schema:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns property schema for a specific Data Interface class. Describes all available properties and their types for the given data interface type. Params: dataInterfaceClass"
    options:
      method: epic_call_tool
  niagara.epic_get_dynamic_input_chain:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns the full recursive chain for a dynamic input: topology metadata and resolved values at every level. The starting input must have value mode Dynamic; an error is surfaced otherwise. The schema expands one full level and emits a typed recursion stub at deeper levels; the wire format recurses to arbitrary depth matching the underlying chain. Params: stackInputRef"
    options:
      method: epic_call_tool
  niagara.epic_get_dynamic_input_schema:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns schema for a dynamic input module in the stack. Describes the inputs and configuration for a procedural value generator. Params: dynamicInputReference"
    options:
      method: epic_call_tool
  niagara.epic_get_dynamic_input_schema_from_asset:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns schema for a dynamic input asset. Standalone function that doesn't require a system context - useful for browsing available dynamic inputs. Params: dynamicInputAsset"
    options:
      method: epic_call_tool
  niagara.epic_get_emitter_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns emitter property values as a single JSON-string blob in PropertyValues. The blob contains the full FVersionedNiagaraEmitterData (SimTarget, bLocalSpace, RandomSeed, FixedBounds, etc.) - fields use C++ PascalCase, must be parsed to read individual values. For typed access to common metadata (SimTarget, EmitterName, bEnabled, RendererClasses) prefer GetEmitterSummary - it returns those as named fields directly and avoids a JSON parse step. Use this endpoint when you need the full property set or a less-common field. Params: emitterRef"
    options:
      method: epic_call_tool
  niagara.epic_get_emitter_input_values:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns all resolved input values for every module across all four emitter script stacks. One FNiagaraExt_ModuleInputValues entry per module, each carrying all its resolved input values. Call this in parallel with GetEmitterTopology to get both structure and values in two passes. Params: emitterRef"
    options:
      method: epic_call_tool
  niagara.epic_get_emitter_schema:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns property schema for Niagara Emitter. Describes all available properties and their types that can be set on a Niagara Emitter. Params: none"
    options:
      method: epic_call_tool
  niagara.epic_get_emitter_summary:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns lightweight emitter metadata: name, enabled state, sim target, renderer classes. Use this when you only need to check metadata without walking the full emitter structure. Params: emitterRef"
    options:
      method: epic_call_tool
  niagara.epic_get_emitter_topology:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns full emitter topology: four script stacks with all modules and inputs, renderer references. All fields always populated. The returned topology carries no input values; call GetEmitterInputValues in parallel. Params: emitterRef"
    options:
      method: epic_call_tool
  niagara.epic_get_module_input_values:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns resolved input values for a single module. Use when you need values for one specific module without walking the whole emitter. Params: moduleRef"
    options:
      method: epic_call_tool
  niagara.epic_get_module_schema:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns schema for a module and all its inputs. Call this after seeing a module in topology to understand what inputs it exposes. Params: moduleReference"
    options:
      method: epic_call_tool
  niagara.epic_get_module_schema_from_asset:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns schema for a module asset. Standalone function that doesn't require a system context - useful for browsing available modules. Params: moduleAsset"
    options:
      method: epic_call_tool
  niagara.epic_get_module_topology:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns module topology: metadata and all inputs (name/type/visibility only, no values). All fields always populated. Params: moduleRef"
    options:
      method: epic_call_tool
  niagara.epic_get_niagara_script_digest:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Assets] Returns the decoded asset-registry tag metadata for a Niagara script asset. Looks up the asset by object path in the asset registry and reads its tags; no LoadObject is performed. Returned fields reflect the exposed (published) version when the script uses FVersionedNiagaraScriptData versioning - the registry never carries non-exposed-version metadata. Params: objectPath"
    options:
      method: epic_call_tool
  niagara.epic_get_renderer_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns renderer property values. Retrieves the current values of all configurable renderer properties. Params: rendererRef"
    options:
      method: epic_call_tool
  niagara.epic_get_renderer_schema:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns property schema for a specific Renderer class. Describes all available properties and their types for the given renderer type. Params: rendererClass"
    options:
      method: epic_call_tool
  niagara.epic_get_script_stack_input_values:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns all resolved input values for every module in the given script stack. One FNiagaraExt_ModuleInputValues entry per module. Params: scriptRef"
    options:
      method: epic_call_tool
  niagara.epic_get_script_stack_topology:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns script stack topology: all modules and their inputs in execution order. All fields always populated. Params: scriptRef"
    options:
      method: epic_call_tool
  niagara.epic_get_stack_input_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns the value of a stack module input. Retrieves the current value and configuration for a specific module input parameter. Params: stackInputRef"
    options:
      method: epic_call_tool
  niagara.epic_get_stack_input_schema:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns schema for a single module input in the stack. Describes the type, metadata, and configuration options for a specific input parameter. Params: inputReference"
    options:
      method: epic_call_tool
  niagara.epic_get_stack_input_topology:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns stack input topology: name, type, visibility, editability. No value payload. For the resolved value call GetStackInputData. For a dynamic-input chain call GetDynamicInputChain. Params: stackInputRef"
    options:
      method: epic_call_tool
  niagara.epic_get_stack_issues:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns all stack issues (errors, warnings, info) from the Niagara module stack, including dismissed ones. Waits for any in-flight compile to complete before collecting. Params: system"
    options:
      method: epic_call_tool
  niagara.epic_get_system_compile_state:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns the current compile state of a Niagara System: aggregate status, per-script compile events, and summary flags. Waits for any in-flight compile to complete before collecting. Params: system"
    options:
      method: epic_call_tool
  niagara.epic_get_system_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns system property values. Retrieves the current values of all configurable system-level properties. Params: system"
    options:
      method: epic_call_tool
  niagara.epic_get_system_dependencies:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns the four Used* sets (renderers, data interfaces, modules, dynamic inputs) gathered across all emitters and system scripts. These sets are not included in topology structs; call this endpoint separately when needed. Params: system"
    options:
      method: epic_call_tool
  niagara.epic_get_system_schema:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns property schema for Niagara System. Describes all available properties and their types that can be set on a Niagara System. Params: none"
    options:
      method: epic_call_tool
  niagara.epic_get_system_summary:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns lightweight system metadata: name, user variables, and one summary entry per emitter. Use this for first contact with an unfamiliar system. For full structural detail call GetEmitterTopology per emitter. Params: system"
    options:
      method: epic_call_tool
  niagara.epic_get_user_variables:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Component] Returns all user variable values currently set on the component. This retrieves the current values of all user-exposed parameters that can be overridden at the component level. Params: component"
    options:
      method: epic_call_tool
  niagara.epic_get_user_variables__niagara_toolset_system:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Returns all user variables defined on the system. User variables are parameters exposed for external control and can be overridden per component instance. Params: system"
    options:
      method: epic_call_tool
  niagara.epic_get_variable:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Component] Gets the current value of a specific user variable on the component. This retrieves the current value of a user-exposed parameter, including any component-level overrides. Params: component, var"
    options:
      method: epic_call_tool
  niagara.epic_remove_emitter:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Removes an emitter from a system. Deletes the specified emitter and all its associated scripts, modules, and renderers. Params: emitterToRemove"
    options:
      method: epic_call_tool
  niagara.epic_remove_module:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Removes a module from a script stack. Deletes the specified module and all its inputs from the script's execution stack. Params: moduleToRemove"
    options:
      method: epic_call_tool
  niagara.epic_remove_renderer:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Removes a renderer from an emitter. Deletes the specified renderer from the emitter's renderer list. Params: rendererToRemove"
    options:
      method: epic_call_tool
  niagara.epic_remove_set_parameter_entry:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Removes a parameter from an existing SetParameters module by name. The module referenced by ModuleRef must be a SetParameters (UNiagaraNodeAssignment) module. Use bIsSetParametersModule in the module topology to confirm before calling. Params: moduleRef, parameterName"
    options:
      method: epic_call_tool
  niagara.epic_remove_user_variables:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Removes user variables from a system. Deletes the specified user variables from the system's user parameter collection. Params: system, variablesToRemove"
    options:
      method: epic_call_tool
  niagara.epic_set_emitter_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Sets property values on a Niagara Emitter. Applies new values to emitter-level properties based on the provided data structure. Params: emitter, emitterData"
    options:
      method: epic_call_tool
  niagara.epic_set_module_enabled:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Sets whether a module is enabled. Disabled modules remain in the stack but don't execute. Current state is visible in module topology. Params: moduleRef, bEnabled"
    options:
      method: epic_call_tool
  niagara.epic_set_renderer_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Sets property values on a Niagara Renderer. Applies new values to renderer properties based on the provided data structure. Payload shape varies with the concrete renderer class; call GetRendererSchema for the renderer's class to inspect valid properties before writing. Params: renderer, rendererData"
    options:
      method: epic_call_tool
  niagara.epic_set_stack_input_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Sets the value of a stack module input and returns the resulting stored value. Updates the value and configuration for a specific module input parameter. Params: stackInputRef, inputData"
    options:
      method: epic_call_tool
  niagara.epic_set_system:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Component] Sets the Niagara System for a component. Use this instead of setting the Asset property directly to ensure proper initialization. Params: niagaraComponent, system, bResetExistingOverrideParameters"
    options:
      method: epic_call_tool
  niagara.epic_set_system_data:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_System] Sets property values on a Niagara System. Applies new values to system-level properties based on the provided data structure. Params: system, systemData"
    options:
      method: epic_call_tool
  niagara.epic_set_variable:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Component] Sets the value of a user variable on the component. This overrides the default value of a user-exposed parameter on a specific component instance. Params: component, variable"
    options:
      method: epic_call_tool
  niagara.epic_uenum_info:
    class_path: ue-mcp.bridge
    group: niagara
    description: "[Epic NiagaraToolsets.NiagaraToolset_Info] Returns information about a UEnum and all its values. ALWAYS call this when working with a UEnum type to see valid values. Params: enum"
    options:
      method: epic_call_tool

  # ── audio ──
  audio.list:
    class_path: ue-mcp.bridge
    group: audio
    description: "List sound assets (SoundWave, SoundCue, MetaSoundSource) under a directory, cursor-paginated (#730). Every page carries count, total, hasMore and a nextCursor to pass back. The row offset this used to page with is refused, because a row number cannot report that the library changed underneath it. maxResults is a deprecated spelling of limit and sizes the page when limit is omitted. Params: directory? (default /Game), recursive? (default true), maxResults?, cursor?, limit?"
    options:
      method: list_sound_assets
  audio.extract_pcm:
    class_path: ue-mcp.bridge
    group: audio
    description: "Decode a USoundWave's imported audio to in-memory PCM (no intermediate file, no reliance on the original source path) for semantic sound search / analysis. Returns sampleRate, numChannels, numFrames, durationSeconds, and 16-bit PCM samples base64-encoded (interleaved). Params: soundPath (required), maxSeconds? (cap the decoded window; default full asset), downmixMono? (default false) (#729)."
    options:
      method: extract_sound_wave_pcm
  audio.import_audio:
    class_path: ue-mcp.bridge
    group: audio
    description: "Import a WAV/OGG/FLAC file as a USoundWave. Returns durationSeconds, numChannels, looping. Params: filePath, name?, packagePath? (default /Game/Audio), looping?, replaceExisting? (default true)"
    options:
      method: import_audio
  audio.play_at_location:
    class_path: ue-mcp.bridge
    group: audio
    description: "Play a sound in the editor world. Params: soundPath, location, volumeMultiplier?, pitchMultiplier?"
    options:
      method: play_sound_at_location
  audio.spawn_ambient:
    class_path: ue-mcp.bridge
    group: audio
    description: "Place an AmbientSound actor. Params: soundPath, location, label?"
    options:
      method: spawn_ambient_sound
  audio.metasound_author:
    class_path: ue-mcp.bridge
    group: audio
    description: "PREFERRED: stamp a whole MetaSound graph in ONE call from a declarative spec (avoids dozens of add_node/connect round-trips). Creates the asset through UMetaSoundSourceFactory, which is the only route that produces a document with interfaces and a graph page, then writes every element straight into that asset's own document and saves at the end. Params: name, packagePath?, format? ('mono'|'stereo'), oneShot?, onConflict?, inputs? [{name,dataType,default?}], outputs? [{name,dataType}], nodes? [{id,class,namespace?,variant?,majorVersion?,inputs?:{vertex:value}}], connections? [{from,to}]. Endpoints are 'nodeId:vertex', or special heads 'input:<name>', 'output:<name>', 'audioOut:<channel>'. Each element reports its own ok/error. Read the result back with metasound_read_document."
    options:
      method: metasound_author
  audio.create_metasound:
    class_path: ue-mcp.bridge
    group: audio
    description: "Create a MetaSoundSource through its own asset factory (which installs the UE.Source, one-shot and output-format interfaces and mints the default graph page) ready for INCREMENTAL authoring with add_node/connect/... For a whole graph at once prefer metasound_author. The incremental actions do NOT depend on this call: they attach a builder to whatever asset they are pointed at, so a MetaSound already on disk, or one from an earlier editor run, is editable without it. Params: name, packagePath? (default /Game/Audio/MetaSounds), format? ('mono'|'stereo'), oneShot?. Returns assetPath."
    options:
      method: create_metasound_source
  audio.metasound_list_node_classes:
    class_path: ue-mcp.bridge
    group: audio
    description: "List common MetaSound node classes to add (name, namespace, variant, notes). Params: filter? (substring)."
    options:
      method: metasound_list_node_classes
  audio.metasound_get_graph:
    class_path: ue-mcp.bridge
    group: audio
    description: "SUPERSEDED for graph contents by metasound_read_document; kept for the one fact it still answers plainly: whether a builder is attached to this asset's document right now (hasActiveBuilder), plus audioOutputs and oneShot when this editor run has created or written to the asset. An attached builder does not mean unsaved work: the write actions edit the asset's own document and save it, and they attach a builder on demand, so this reports whether one is open rather than whether anything is pending. Asks only, and never attaches one itself. For nodes, pins, connections, variables, defaults and problems use metasound_read_document / metasound_list_connections / metasound_inspect_node / metasound_list_node_pins / metasound_search_nodes / metasound_list_variables / metasound_validate. Errors when assetPath names nothing, or names something that is not a MetaSoundSource. Params: assetPath."
    options:
      method: metasound_get_graph
  audio.metasound_add_node:
    class_path: ue-mcp.bridge
    group: audio
    description: "Add a node to a MetaSound graph by registered class name. Works on any MetaSound asset on disk: a builder is attached to the asset's own document on demand, so create_metasound is not a prerequisite and an asset from an earlier editor run is editable. Returns nodeId (+ input/output counts). Params: assetPath, nodeClassName (e.g. 'Sine'), nodeNamespace? (default 'UE'), nodeVariant? (e.g. 'Audio'), majorVersion? (default 1)."
    options:
      method: metasound_add_node
  audio.metasound_add_input:
    class_path: ue-mcp.bridge
    group: audio
    description: "Add a graph input to a MetaSound. Params: assetPath, name, dataType ('Float'|'Int32'|'Bool'|'String'|'Trigger'|'Audio'|'Time'|...), defaultValue?."
    options:
      method: metasound_add_graph_input
  audio.metasound_add_output:
    class_path: ue-mcp.bridge
    group: audio
    description: "Add a graph output to a MetaSound. Params: assetPath, name, dataType."
    options:
      method: metasound_add_graph_output
  audio.metasound_connect:
    class_path: ue-mcp.bridge
    group: audio
    description: "Connect one node's output vertex to another node's input vertex. Params: assetPath, fromNodeId, fromOutput (vertex name), toNodeId, toInput (vertex name)."
    options:
      method: metasound_connect
  audio.metasound_connect_input:
    class_path: ue-mcp.bridge
    group: audio
    description: "Connect a graph input to a node input vertex. Params: assetPath, graphInput (name), toNodeId, toInput (vertex name)."
    options:
      method: metasound_connect_graph_input
  audio.metasound_connect_output:
    class_path: ue-mcp.bridge
    group: audio
    description: "Connect a node output vertex to a graph output. Params: assetPath, fromNodeId, fromOutput (vertex name), graphOutput (name)."
    options:
      method: metasound_connect_graph_output
  audio.metasound_connect_audio_out:
    class_path: ue-mcp.bridge
    group: audio
    description: "Connect a node output vertex to the source's audio output. Params: assetPath, fromNodeId, fromOutput (vertex name, must be Audio type), channel? (0=left/mono, 1=right; default 0)."
    options:
      method: metasound_connect_audio_out
  audio.metasound_set_default:
    class_path: ue-mcp.bridge
    group: audio
    description: "Set a default value on a node input vertex, or on a graph input. Params: assetPath, value (required), dataType? (Float|Int32|Bool|String hint), then EITHER (nodeId + inputName) OR graphInput."
    options:
      method: metasound_set_input_default
  audio.metasound_read_document:
    class_path: ue-mcp.bridge
    group: audio
    description: "Read a MetaSound graph back: document version, declared interfaces, graph inputs and outputs with their defaults, variables, every node and every connection. This is the verification counterpart to metasound_author, and the reason it matters is that the bridge could BUILD a graph and never read it, so it could write but not verify or iterate. Reads the asset's own document, which is the same one the write actions edit, so there is no second unflushed copy that could disagree with it; source and hasActiveBuilder report whether a builder is attached to that document. Reports pageId and readDefaultPage, falling back to the first page the document holds when it declares none under the default page id. Node ids match what metasound_add_node returned. Params: assetPath, pageId?, includeNodes? (default true), includeConnections? (default true)"
    options:
      method: metasound_read_document
  audio.metasound_list_connections:
    class_path: ue-mcp.bridge
    group: audio
    description: "List every edge in the graph, each reported using the exact field names metasound_connect takes (fromNodeId, fromOutput, toNodeId, toInput), so a listed connection can be echoed straight back as a call payload. Use it to see what is already wired before adding more. Malformed edges are counted separately rather than silently dropped. Params: assetPath, pageId?, nodeId? (narrow to one node), direction? (in|out|both), dataType?"
    options:
      method: metasound_list_connections
  audio.metasound_list_variables:
    class_path: ue-mcp.bridge
    group: audio
    description: "List the graph's variables with data type, initial value, the node that sets each one and the node ids that read it, so a variable can be followed to the wiring that consumes it. Most graphs declare none, which is normal rather than a fault. Params: assetPath, pageId?, filter? (name substring)"
    options:
      method: metasound_list_variables
  audio.metasound_search_nodes:
    class_path: ue-mcp.bridge
    group: audio
    description: "Find node INSTANCES inside one existing graph, by name, class, namespace, variant, data type or class type. This is the step between reading a document and acting on a node. Distinct from metasound_list_node_classes, which lists classes you could add rather than nodes already present. Params: assetPath, pageId?, query?, dataType?, classType? (External|Input|Output|Variable|...), limit? (default 100)"
    options:
      method: metasound_search_nodes
  audio.metasound_inspect_node:
    class_path: ue-mcp.bridge
    group: audio
    description: "Inspect one node in full: its class identity in metasound_add_node's own parameter names, every input and output vertex with data type, default and connection state, and the incoming and outgoing edges named by node. A wrong nodeId comes back with the valid ones rather than a bare failure. Params: assetPath, nodeId, pageId?"
    options:
      method: metasound_inspect_node
  audio.metasound_list_node_pins:
    class_path: ue-mcp.bridge
    group: audio
    description: "List just a node's input and output vertices with their types, connection state and set defaults, plus a count of unconnected inputs. The lean way to get the exact vertex names metasound_connect and metasound_set_input_default require, without reading the whole document. Params: assetPath, nodeId, pageId?, direction? (inputs|outputs|both), dataType?"
    options:
      method: metasound_list_node_pins
  audio.metasound_validate:
    class_path: ue-mcp.bridge
    group: audio
    description: "Diagnose a MetaSound graph and report actionable problems: undriven graph outputs, orphaned nodes, dead-end nodes, unconnected Trigger and Audio inputs, cross-type edges, dangling edges, and unread or unwritten variables. Returns problems[] naming the node and the call that fixes it, plus runnable. This is what catches the graph that builds successfully and then plays silence. Params: assetPath, pageId?"
    options:
      method: metasound_validate
  audio.metasound_build:
    class_path: ue-mcp.bridge
    group: audio
    description: "SAVE the MetaSound asset to disk. Despite the name this compiles nothing and flushes nothing: every authoring action writes straight into the asset's own document, so what this adds is persistence, and it is what makes edits survive an editor restart. A read does not need it, since the reads see the same document. Params: assetPath."
    options:
      method: metasound_build
  audio.metasound_remove_node:
    class_path: ue-mcp.bridge
    group: audio
    description: "Remove a node from a MetaSound graph, with every edge that touched it. Attaches a builder to the asset's own document the way the MetaSound editor does, so a MetaSound already on disk, from any editor run, can be edited with no create call first. The edit lands in that document and is saved, so pendingBuild comes back false and metasound_build is not needed after it; source says whether a builder was already attached when the call arrived. Idempotent: alreadyDeleted=true when the id is not in the graph, and the miss lists the ids that are. Params: assetPath, nodeId, removeUnusedDependencies?"
    options:
      method: metasound_remove_node
  audio.metasound_disconnect:
    class_path: ue-mcp.bridge
    group: audio
    description: "Cut MetaSound edges: the inverse of all four connect actions in one call, because they all reduce to the same two builder handles. Four addressing forms - all of fromNodeId, fromOutput, toNodeId and toInput drops that one edge and is the only form with an exact rollback; toNodeId plus toInput alone clears whatever drives that input; fromNodeId plus fromOutput alone clears every edge leaving that output; graphOutput clears what drives a graph output, which is also how an audio output is cleared since the audio outs are graph outputs named 'Out Mono', 'Out Left' and 'Out Right'. metasound_list_connections reports edges in these exact field names. Works on any MetaSound asset on disk, with no create call first, and saves the document it edited. Idempotent: alreadyDisconnected=true when nothing was connected there. Params: assetPath, fromNodeId?, fromOutput?, toNodeId?, toInput?, graphOutput?"
    options:
      method: metasound_disconnect
  audio.metasound_remove_member:
    class_path: ue-mcp.bridge
    group: audio
    description: "Remove a graph input, graph output or variable: the inverse of metasound_add_input and metasound_add_output, and the only removal path for a variable. Works on any MetaSound asset on disk, with no create call first, and saves the document it edited. The miss lists the members that do exist, and the result carries the removed member's dataType so the rollback can restore it. Note that every edge the member drove is cut with it, so read them with metasound_list_connections first if they matter. Idempotent: alreadyDeleted=true when the member is absent. Params: assetPath, memberKind ('input'|'output'|'variable'), name"
    options:
      method: metasound_remove_member
  audio.metasound_rename_member:
    class_path: ue-mcp.bridge
    group: audio
    description: "Rename a graph input or output on any MetaSound asset on disk, with no create call first, saving the document it edited. Not a property write: the rename rewires the template nodes that stand in for the member inside the graph, which a direct document write would leave dangling. Both names are checked first, so renaming onto an existing name is refused with the list rather than colliding, and a replay where the new name is already present reports unchanged=true. The inverse is the same call with the names swapped. Params: assetPath, memberKind ('input'|'output'), name, newName"
    options:
      method: metasound_rename_member
  audio.cue_author:
    class_path: ue-mcp.bridge
    group: audio
    description: "PREFERRED: create a SoundCue and stamp its whole node tree in ONE call. Params: name, packagePath?, onConflict?, nodes [{id,type,soundWavePath?,properties?}], connections [{parent,child,index?}] (omit parent => root), root? (nodeId). Each element reports ok/error; links + saves at the end."
    options:
      method: soundcue_author
  audio.create_cue:
    class_path: ue-mcp.bridge
    group: audio
    description: "Create a SoundCue, optionally seeded from a wave. For a whole graph prefer cue_author. Params: name, packagePath?, soundWavePath?."
    options:
      method: create_sound_cue
  audio.cue_add_node:
    class_path: ue-mcp.bridge
    group: audio
    description: "Add a node to a SoundCue graph. Returns nodeId. Params: cuePath, nodeType ('wave_player'|'mixer'|'random'|'modulator'|'attenuation'|'looping'|'concatenator'|'delay'|'switch'), soundWavePath? (wave_player), properties? (node-specific fields)."
    options:
      method: soundcue_add_node
  audio.cue_connect:
    class_path: ue-mcp.bridge
    group: audio
    description: "Connect a SoundCue node as a child of another (or as the cue root). Params: cuePath, parentNodeId (omit for root), childNodeId, childIndex? (default append)."
    options:
      method: soundcue_connect
  audio.cue_get_graph:
    class_path: ue-mcp.bridge
    group: audio
    description: "Read a SoundCue node graph: nodes (id, type, children) and root. Params: cuePath."
    options:
      method: soundcue_get_graph
  audio.cue_remove_node:
    class_path: ue-mcp.bridge
    group: audio
    description: "Remove a node from a SoundCue: detach it from every parent, drop its paired editor graph node, and clear the cue root if it was the root. Its own children are ORPHANED rather than deleted, because they are separate nodes the caller may still want and an unasked-for cascade is the harder failure to recover from; they come back in orphanedChildren. Warns when the cue is left with no root, since the cue then plays nothing. Idempotent: alreadyDeleted=true when the id is not in the cue, and the miss lists the ids that are. Params: cuePath, nodeId"
    options:
      method: soundcue_remove_node
  audio.cue_disconnect:
    class_path: ue-mcp.bridge
    group: audio
    description: "Detach a SoundCue child from its parent: the inverse of cue_connect. With parentNodeId it removes that one link; without, it removes the child from every parent. clearRoot=true unsets the cue root instead, which is the inverse of a cue_connect that named no parent. Idempotent: alreadyDisconnected=true when the link was not there. Params: cuePath, childNodeId?, parentNodeId?, clearRoot?"
    options:
      method: soundcue_disconnect
  audio.create_submix:
    class_path: ue-mcp.bridge
    group: audio
    description: "Create a USoundSubmix, optionally parented. Params: name, packagePath? (default /Game/Audio/Submixes), parentPath?, outputVolume?, wetLevel?, dryLevel?."
    options:
      method: create_submix
  audio.set_submix_parent:
    class_path: ue-mcp.bridge
    group: audio
    description: "Reparent a submix (sets ParentSubmix, updating both ends). Params: submixPath, parentPath (empty detaches to root)."
    options:
      method: set_submix_parent
  audio.add_submix_effect:
    class_path: ue-mcp.bridge
    group: audio
    description: "Append a submix effect preset to a submix's effect chain (creates the preset asset). Params: submixPath, effectType ('reverb'|'eq'|'dynamics'|'filter'|'delay'), name?, packagePath?, settings? (effect Settings struct as JSON)."
    options:
      method: add_submix_effect
  audio.create_sound_class:
    class_path: ue-mcp.bridge
    group: audio
    description: "Create a USoundClass, optionally parented, with properties. Params: name, packagePath? (default /Game/Audio/SoundClasses), parentPath?, properties? (FSoundClassProperties JSON: Volume, Pitch, bIsUISound, ...)."
    options:
      method: create_sound_class
  audio.set_sound_class_parent:
    class_path: ue-mcp.bridge
    group: audio
    description: "Reparent a sound class through USoundClass::SetParentClass, which is the call that keeps all three sides consistent: ParentClass on the child, ChildClasses on the new parent, and the removal from the old parent's list. Writing ParentClass with a property setter produces a class the audio engine walks up from and the mixer never walks down to, which is why this is a handler. An empty parentPath detaches to the root, and a parent already below this class is refused as a cycle. Reports listedOnParent, read back from the other end. Params: soundClassPath, parentPath?"
    options:
      method: set_sound_class_parent
  audio.read_sound_routing:
    class_path: ue-mcp.bridge
    group: audio
    description: "Read where a sound actually goes: its sound class chain up to the root and its submix chain up to the master, each entry with its objectPath, plus submix sends, attenuation, concurrency, and problems[] naming the calls that fix them. This is the verification half of set_sound_submix, add_sound_submix_send, set_sound_class, set_sound_attenuation and set_sound_concurrency, which could all assign routing and none of which could read it back. Catches the cases that make a correctly authored mix silent anyway: a sound class at volume 0, a send at level 0, a duplicate send (add_sound_submix_send appends without checking), an attenuation with a falloff distance and bAttenuate off, a concurrency with MaxCount 0, and a cycle in either chain. Params: soundPath"
    options:
      method: read_sound_routing
  audio.create_sound_mix:
    class_path: ue-mcp.bridge
    group: audio
    description: "Create a USoundMix with sound-class adjusters. Params: name, packagePath? (default /Game/Audio/SoundMixes), adjusters? ([{soundClassPath, volumeAdjuster?, pitchAdjuster?, applyToChildren?}]), fadeInTime?, fadeOutTime?."
    options:
      method: create_sound_mix
  audio.create_concurrency:
    class_path: ue-mcp.bridge
    group: audio
    description: "Create a USoundConcurrency asset. Params: name, packagePath? (default /Game/Audio/Concurrency), maxCount?, limitToOwner?, resolutionRule? (e.g. 'StopFarthestThenOldest'), volumeScale?."
    options:
      method: create_concurrency
  audio.create_attenuation:
    class_path: ue-mcp.bridge
    group: audio
    description: "Create a USoundAttenuation asset. Params: name, packagePath? (default /Game/Audio/Attenuation), settings? (FSoundAttenuationSettings JSON), plus shortcuts: falloffDistance?, spatialize?, enableOcclusion?."
    options:
      method: create_attenuation
  audio.set_sound_submix:
    class_path: ue-mcp.bridge
    group: audio
    description: "Set a sound's base submix (routing target). Params: soundPath, submixPath (empty detaches)."
    options:
      method: set_sound_submix
  audio.add_sound_submix_send:
    class_path: ue-mcp.bridge
    group: audio
    description: "Add a submix send to a sound. Params: soundPath, submixPath, sendLevel? (default 1.0)."
    options:
      method: add_sound_submix_send
  audio.set_sound_class:
    class_path: ue-mcp.bridge
    group: audio
    description: "Assign a sound class to a sound. Params: soundPath, soundClassPath."
    options:
      method: set_sound_class
  audio.set_sound_attenuation:
    class_path: ue-mcp.bridge
    group: audio
    description: "Attach an attenuation asset to a sound. Params: soundPath, attenuationPath (empty clears)."
    options:
      method: set_sound_attenuation
  audio.set_sound_concurrency:
    class_path: ue-mcp.bridge
    group: audio
    description: "Attach a concurrency asset to a sound. Params: soundPath, concurrencyPath (empty clears)."
    options:
      method: set_sound_concurrency
  audio.set_property:
    class_path: ue-mcp.bridge
    group: audio
    description: "Set any UPROPERTY on an audio asset by (dotted) name, value as JSON. Handles nested structs, arrays, object refs. Params: assetPath, propertyName, value."
    options:
      method: set_audio_property

  # ── widget ──
  widget.read_tree:
    class_path: ue-mcp.bridge
    group: widget
    description: "Read widget hierarchy. Params: assetPath"
    options:
      method: read_widget_tree
  widget.get_details:
    class_path: ue-mcp.bridge
    group: widget
    description: "Inspect widget (curated subset). Params: assetPath, widgetName"
    options:
      method: get_widget_details
  widget.get_properties:
    class_path: ue-mcp.bridge
    group: widget
    description: "Full reflected property dump for a widget - every UPROPERTY (RenderOpacity, Visibility, ColorAndOpacity, Border padding/colors, Image brush TintColor/ImageSize, fonts, etc.) plus the slot block, for diagnosing visual bugs get_details omits. Pass includeSubtree to also dump children (#547). Params: assetPath, widgetName, includeSubtree?"
    options:
      method: get_widget_properties
  widget.list_bindings:
    class_path: ue-mcp.bridge
    group: widget
    description: "List designer property bindings on a WidgetBlueprint (the UE 5.7 Python API keeps them protected). Returns {widgetName, propertyName, functionName, bindingType}. Optional filterWidgetName/filterProperty (#530). Params: assetPath, filterWidgetName?, filterProperty?"
    options:
      method: list_widget_bindings
  widget.clear_binding:
    class_path: ue-mcp.bridge
    group: widget
    description: "Remove designer binding(s) matching widgetName (and optional propertyName) from a WidgetBlueprint without opening the editor. Idempotent (#530). Params: assetPath, widgetName, propertyName?"
    options:
      method: clear_widget_binding
  widget.set_property:
    class_path: ue-mcp.bridge
    group: widget
    description: "Set widget property. Slot struct props take UE struct text that persists every field - `Slot.Size`=`(Value=1.0,SizeRule=Fill)`, `Slot.Padding`=`(Left=8,Top=8,Right=8,Bottom=8)` - or a nested field path like `Slot.Size.Value` / `Slot.Padding.Left`; an invalid value errors instead of silently writing 0 (#532). Params: assetPath, widgetName, propertyName, value"
    options:
      method: set_widget_property
  widget.set_style:
    class_path: ue-mcp.bridge
    group: widget
    description: "Set a full/nested style struct on a widget from JSON (FButtonStyle, FEditableTextBoxStyle, FSlateFontInfo, FSlateColor and their nested brushes) - what set_property's scalar path can't express. value is a JSON object mirroring the struct. Params: assetPath, widgetName, propertyName (e.g. WidgetStyle), value (#563)"
    options:
      method: set_widget_style
  widget.reorder_child:
    class_path: ue-mcp.bridge
    group: widget
    description: "Reorder a widget among its parent panel's children (move to a sibling index) - e.g. insert a new row BETWEEN two existing children. move_widget only reparents. Params: assetPath, widgetName, index (#635)"
    options:
      method: reorder_child
  widget.bulk_set_properties:
    class_path: ue-mcp.bridge
    group: widget
    description: "Apply many {widgetName, propertyName, value} style/property writes to a WidgetBlueprint in one call (single compile+save) - font/color/style stylesheet across many widgets. Params: assetPath, properties[] (#563)"
    options:
      method: bulk_set_widget_properties
  widget.list:
    class_path: ue-mcp.bridge
    group: widget
    description: "List Widget BPs, sorted by object path. Params: directory?, recursive?, cursor?, limit?"
    options:
      method: list_widget_blueprints
  widget.read_animations:
    class_path: ue-mcp.bridge
    group: widget
    description: "Read UMG animations. Params: assetPath"
    options:
      method: read_widget_animations
  widget.create_animation:
    class_path: ue-mcp.bridge
    group: widget
    description: "Create a UMG animation on a Widget Blueprint, with its MovieScene, display rate and playback range. Idempotent by animationName: a second call reports existed and leaves the timing alone. Params: assetPath, animationName, durationSeconds?, displayRate?, displayLabel?"
    options:
      method: create_widget_animation
  widget.delete_animation:
    class_path: ue-mcp.bridge
    group: widget
    description: "Delete a UMG animation from a Widget Blueprint, including its bindings. The rollback recreates an empty animation of the same name and rate, so the tracks and keys are NOT restored. Params: assetPath, animationName"
    options:
      method: delete_widget_animation
  widget.get_animation:
    class_path: ue-mcp.bridge
    group: widget
    description: "Read one UMG animation in full: display rate, tick resolution, playback range, and per bound widget every track, section, channel and key time/value in SECONDS, plus the event tracks. read_animations reports the shape, this reports the values, which is what verifies a key actually landed. Params: assetPath, animationName"
    options:
      method: get_widget_animation
  widget.add_animation_track:
    class_path: ue-mcp.bridge
    group: widget
    description: "Add a property track to a UMG animation, binding the widget into the animation first if it is not bound yet. The track class is chosen from the property's own reflected type; a property Sequencer cannot key is refused by name with the keyable types listed. Params: assetPath, animationName, widgetName, propertyName"
    options:
      method: add_widget_animation_track
  widget.remove_animation_track:
    class_path: ue-mcp.bridge
    group: widget
    description: "Remove a property track from a UMG animation. The rollback re-adds the empty track, so its keys are NOT restored. Params: assetPath, animationName, widgetName, propertyName"
    options:
      method: remove_widget_animation_track
  widget.add_animation_key:
    class_path: ue-mcp.bridge
    group: widget
    description: "Set a key on an animation track channel at a time in SECONDS, creating the track and the section if needed. Pick the channel by name (R/G/B/A, Left/Top/Right/Bottom, Translation.X) or by channelIndex; a miss lists the channels the section actually has. Params: assetPath, animationName, widgetName, propertyName, time, value, channel?, channelIndex?, interpolation?"
    options:
      method: add_widget_animation_key
  widget.remove_animation_key:
    class_path: ue-mcp.bridge
    group: widget
    description: "Remove the key at a time in SECONDS from an animation track channel. Removing a key that is not there reports unchanged rather than failing. Params: assetPath, animationName, widgetName, propertyName, time, channel?, channelIndex?"
    options:
      method: remove_widget_animation_key
  widget.add_animation_event_key:
    class_path: ue-mcp.bridge
    group: widget
    description: "Add an event key that calls a Widget Blueprint function at a time in SECONDS, creating the event track and its trigger section if needed. trackName defaults to Events. Params: assetPath, animationName, functionName, time?, trackName?"
    options:
      method: add_widget_animation_event_key
  widget.remove_animation_event_key:
    class_path: ue-mcp.bridge
    group: widget
    description: "Remove the event key at a time in SECONDS from an animation event track. Params: assetPath, animationName, time?, trackName?"
    options:
      method: remove_widget_animation_event_key
  widget.bind_animation_event:
    class_path: ue-mcp.bridge
    group: widget
    description: "Bind an animation lifecycle event (Finished or Started) on a Widget Blueprint to a handler graph, creating the K2Node_WidgetAnimationEvent if it is not there. userTag scopes a Started binding. Idempotent: an existing binding reports existed. Params: assetPath, animationName, event?, userTag?"
    options:
      method: bind_widget_animation_event
  widget.unbind_animation_event:
    class_path: ue-mcp.bridge
    group: widget
    description: "Remove an animation lifecycle event binding from a Widget Blueprint. Params: assetPath, animationName, event?, userTag?"
    options:
      method: unbind_widget_animation_event
  widget.set_navigation:
    class_path: ue-mcp.bridge
    group: widget
    description: "Write UMG navigation rules on a widget: rule is Escape, Explicit, Wrap, Stop, Custom or CustomBoundary, direction is Up, Down, Left, Right, Next or Previous, and widgetToFocus names the target for Explicit. Creates the UWidgetNavigation subobject that set_property cannot make. Pass one write as widgetName + direction + rule, or many as rules[] of {widgetName, direction, rule, widgetToFocus}; the whole batch is validated before anything is written. Params: assetPath, rules[]? OR widgetName?, direction?, rule?, widgetToFocus?"
    options:
      method: set_widget_navigation
  widget.clear_navigation:
    class_path: ue-mcp.bridge
    group: widget
    description: "Reset navigation rules on a widget back to Escape. Omit direction to clear all six. Clearing what is already clear reports unchanged. Params: assetPath, widgetName, direction?"
    options:
      method: clear_widget_navigation
  widget.restore_navigation:
    class_path: ue-mcp.bridge
    group: widget
    description: "Restore navigation rules from a captured snapshot - the `previous` array that set_navigation and clear_navigation put in their rollback payload, so a failed flow can be undone by hand as well as by the runner. Params: assetPath, previous[]"
    options:
      method: restore_widget_navigation
  widget.audit_focus_chain:
    class_path: ue-mcp.bridge
    group: widget
    description: "Read-only report over the whole widget tree: which widgets can take focus, their explicit navigation edges, widgets reachable from none of them, edges pointing at a missing/invisible/unfocusable target, and directions whose opposite does not lead back. Params: assetPath"
    options:
      method: audit_widget_focus_chain
  widget.audit_accessibility:
    class_path: ue-mcp.bridge
    group: widget
    description: "Read-only accessibility report over the whole widget tree: font sizes under minFontSize, interactive widgets whose authored hit area is under minHitSize, and what could not be checked from authored values alone. Params: assetPath, minFontSize?, minHitSize?"
    options:
      method: audit_widget_accessibility
  widget.get_runtime_focus_path:
    class_path: ue-mcp.bridge
    group: widget
    description: "Read the live Slate focus path for a user index in PIE - which widget holds focus and the chain of widgets above it. Requires a running PIE or Game world; the editor's own focus is never reported. Params: userIndex?"
    options:
      method: get_runtime_focus_path
  widget.set_runtime_focus:
    class_path: ue-mcp.bridge
    group: widget
    description: "Give keyboard focus to a named child of a live PIE widget, so a navigation chain can be walked and verified rather than predicted. Locate the host with className when more than one instance is up. Setting focus where it already is reports unchanged. Params: widgetName, userIndex?, className?"
    options:
      method: set_runtime_focus
  widget.create:
    class_path: ue-mcp.bridge
    group: widget
    description: "Create Widget BP. assetPath is the full destination, e.g. /Game/UI/WBP_Example; name + packagePath is the older spelling of the same thing and is composed into it. Params: assetPath, name?, packagePath?, parentClass?"
    options:
      method: create_widget_blueprint
  widget.create_utility_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "Create editor utility widget. Params: assetPath, name?, packagePath?"
    options:
      method: create_editor_utility_widget
  widget.run_utility_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "Open editor utility widget. Params: assetPath"
    options:
      method: run_editor_utility_widget
  widget.create_utility_blueprint:
    class_path: ue-mcp.bridge
    group: widget
    description: "Create editor utility blueprint. Params: assetPath, name?, packagePath?"
    options:
      method: create_editor_utility_blueprint
  widget.run_utility_blueprint:
    class_path: ue-mcp.bridge
    group: widget
    description: "Run editor utility blueprint. Params: assetPath"
    options:
      method: run_editor_utility_blueprint
  widget.add_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "Add widget to widget tree. Idempotent by assetPath + widgetName: passing widgetName makes a retry safe, and the result carries requestedWidgetName/persistedWidgetName/renamed plus compileStatus. Params: assetPath, widgetClass, widgetName?, parentWidgetName?"
    options:
      method: add_widget
  widget.remove_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "Remove widget from tree. Idempotent, and clears the widget's Widget Blueprint GUID metadata so later compiles stop reporting a deleted variable (#799). Params: assetPath, widgetName"
    options:
      method: remove_widget
  widget.move_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "Reparent widget. Params: assetPath, widgetName, newParentWidgetName"
    options:
      method: move_widget
  widget.set_root:
    class_path: ue-mcp.bridge
    group: widget
    description: "Replace WBP root with an existing widget by name (#365). Params: assetPath, widgetName"
    options:
      method: set_root_widget
  widget.wrap_root:
    class_path: ue-mcp.bridge
    group: widget
    description: "Wrap the current root in a new panel widget (UMG 'Wrap With'). Params: assetPath, wrapperClass (must be a UPanelWidget subclass), wrapperName? (#365)"
    options:
      method: wrap_root_widget
  widget.list_classes:
    class_path: ue-mcp.bridge
    group: widget
    description: "List the UWidget classes this editor has loaded, grouped by the module that defines them, each with its full path, parent class, whether it is a panel that accepts children, and the slot properties its children take. This is how a CommonUI or project-C++ widget is discovered: pass a row's `name` to add_widget as widgetClass, or its `path` when two modules share a name. Loaded classes only, so a Widget Blueprint nothing has opened is absent (use list) and a class from a disabled plugin does not exist until project(enable_plugin) and a restart. Params: filter?, module?, includeAbstract?, includeBlueprint?, limit? (default 300, max 5000), cursor?"
    options:
      method: list_widget_classes
  widget.get_bind_widget_contract:
    class_path: ue-mcp.bridge
    group: widget
    description: "Report the BindWidget contract a native UserWidget parent imposes: every UPROPERTY marked BindWidget, BindWidgetOptional, BindWidgetAnim or BindWidgetAnimOptional, with the exact widget name it demands, the class that name must be, whether it is optional, and which ancestor declares it. Metadata is not a property value and reflect_class does not report these keys, so this is the only way to learn the contract short of a failed compile. Pass className for the contract alone, or assetPath to also check one Widget Blueprint's own tree against its parent and get back satisfied/missing/wrongType. Params: className? OR assetPath?"
    options:
      method: get_bind_widget_contract
  widget.audit_commonui:
    class_path: ue-mcp.bridge
    group: widget
    description: "Read-only CommonUI wiring report. Checks the rules that fail silently at runtime rather than at compile time: the plugin being enabled at all, GameViewportClientClass being a CommonGameViewportClient (without it gamepad navigation and Back do nothing), CommonInputSettings.InputData being set (without it no input action resolves and bound action bars render empty) and, when assetPath names a Widget Blueprint, an activatable widget with no DesiredFocusWidget, a CommonBoundActionBar with no ActionButtonClass, and CommonUI widgets with no Style. Every problem carries the exact call that fixes it. Params: assetPath?"
    options:
      method: audit_commonui
  widget.extract_subtree:
    class_path: ue-mcp.bridge
    group: widget
    description: "Lift an authored designer subtree out of one WidgetBlueprint into a standalone one, using UMG's own clipboard serializer so hierarchy, child order, editable properties, panel slot data and named-slot content survive. The selected widget becomes the destination root. dryRun defaults to true and only returns the name mapping - pass dryRun=false to actually write the asset. The destination must be absent or empty; an exact-shape replay returns existed. The source is never compiled or saved. Params: sourceAssetPath, sourceWidgetName, destinationAssetPath, destinationParentClass?, destinationRootName?, dryRun?"
    options:
      method: extract_widget_subtree
  widget.list_runtime:
    class_path: ue-mcp.bridge
    group: widget
    description: "(#160) List live UUserWidget instances in the PIE world, sorted by object path. Params: classFilter?, namePrefix?, viewportOnly?, cursor?, limit?"
    options:
      method: list_runtime_widgets
  widget.get_runtime:
    class_path: ue-mcp.bridge
    group: widget
    description: "(#160) Inspect a live PIE widget tree with text/visibility/brush/percent plus style values: renderOpacity (all), colorAndOpacity (TextBlock/Image), Border brushColor/contentColorAndOpacity (#592). includeLayout adds read-only layout diagnostics to every node - geometry (desired/local/absolute size, layout and render bounds), render transform, effective opacity, reflected slot properties including structured Canvas anchors/offsets/alignment, derived clipping, parent and viewport overlap, and a diagnostics array - plus the host UserWidget node under `host`, a `layoutCapture` summary, and per-node `deltaSincePreviousCapture` against the previous includeLayout call on that instance, so capture / reproduce / capture again isolates position-dependent sizing. Off by default: it is a much larger payload. Params: widgetName? | className?, childName?, maxDepth?, includeLayout?"
    options:
      method: get_runtime_widget
  widget.inspect_runtime_instances:
    class_path: ue-mcp.bridge
    group: widget
    description: "Inspect every matching live widget instance (never an implicit first match), with stable identity/owning-player metadata and selected reflected properties on the widget or subtree. Requires a running PIE/Game world and errors instead of falling back to the editor world. Passing childName or childClassFilter implies includeSubtree. Provide widgetName or classFilter. Params: widgetName?, classFilter?, propertyNames[]?, includeSubtree?, childName?, childClassFilter?, viewportOnly?, world?, pieInstance?, maxInstances?, maxNodesPerInstance?"
    options:
      method: inspect_runtime_instances
  widget.get_runtime_delegates:
    class_path: ue-mcp.bridge
    group: widget
    description: "(#161) Read delegate binding state on a live PIE widget. Params: widgetName, className?. Returns array of {delegateName, isBound, numBindings}"
    options:
      method: get_runtime_delegates
  widget.add_to_viewport:
    class_path: ue-mcp.bridge
    group: widget
    description: "(#602) Instantiate a WidgetBlueprint and add it to the live PIE viewport for visual verification. Requires PIE running. Params: assetPath (WidgetBlueprint path), zOrder?"
    options:
      method: add_to_viewport
  widget.invoke_runtime_function:
    class_path: ue-mcp.bridge
    group: widget
    description: "(#559/#812) Fire a UI interaction on a live PIE widget: a parameterless UFUNCTION (functionName) on the located UserWidget, OR drive an interactive child via childName - Button (click), CheckBox (value true/false/toggle), Slider and SpinBox (numeric value), EditableText/EditableTextBox/MultiLineEditableText/MultiLineEditableTextBox (string value), ComboBoxString (option string or index). The matching delegate is broadcast so bound Blueprint logic runs. functionName alongside childName picks the delegate (e.g. OnPressed, OnTextChanged). Locate the widget with widgetName or className. Params: widgetName?|className?, functionName?, childName?, value?, commitMethod?"
    options:
      method: invoke_runtime_function
  widget.epic_add_uicomponent:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Adds a UI component of the given class to the named widget. Params: widgetBlueprint, widgetName, componentClass"
    options:
      method: epic_call_tool
  widget.epic_add_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Adds a widget to the tree at the specified position. Returns full widget info including Slot pointer. When ParentWidget is null and no root exists, the new widget becomes the root of the tree. Use ObjectTools.list_properties on the returned Widget and Slot to get property names before calling set_properties. Params: widgetBlueprint, widgetClass, widgetDisplayName, parentWidget?, childIndex?"
    options:
      method: epic_call_tool
  widget.epic_bind_to_event_property:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Adds a Blueprint event handler graph node bound to a widget's multicast delegate event, Typical events: UButton::OnClicked / OnPressed / OnReleased / OnHovered / OnUnhovered, UCheckBox::OnCheckStateChanged, USlider::OnValueChanged. The matching delegate UPROPERTY must exist on PropertyClass (or a parent of it). Preconditions: - PropertyName must exist in the blueprint. - PropertyClass must be the widget's class (or a parent class) that declares the delegate. Params: widgetBlueprint, eventName, propertyName, propertyClass"
    options:
      method: epic_call_tool
  widget.epic_click:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Click a Slate widget identified by its ref. Params: ref, button?, doubleClick?, modifiers?"
    options:
      method: epic_call_tool
  widget.epic_compile_widget_blueprint:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Compiles a widget blueprint. Returns false with error details if compilation fails. Errors include missing BindWidget bindings, type mismatches, and graph errors. Call after all widgets and properties are set. Save separately via AssetTools.save_asset. Params: widgetBlueprint"
    options:
      method: epic_call_tool
  widget.epic_create_widget_blueprint:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Creates a new Widget Blueprint asset. Returns the blueprint or nullptr on failure. Params: folderPath, assetName, parentClass"
    options:
      method: epic_call_tool
  widget.epic_drag:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Drag from one Slate widget to another (mouse down, move, release). Params: startRef, endRef, modifiers?"
    options:
      method: epic_call_tool
  widget.epic_fill_form:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Fill multiple Slate form fields at once. Params: fields"
    options:
      method: epic_call_tool
  widget.epic_get_named_slots:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Returns named slot bindings (separate from tree hierarchy). Params: widgetBlueprint"
    options:
      method: epic_call_tool
  widget.epic_get_widget_class_info:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Returns the Category, Description and if it's a Panel for a single widget class. Same per-entry data as ListWidgetClasses, but lets callers query a class they already have without scanning every UClass. Returns an empty entry if WidgetClass is null. Can be used to get more information on the class from the Description and Category. Params: widgetClass"
    options:
      method: epic_call_tool
  widget.epic_get_widget_description:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Full property dump of every widget in the tree. Each line: [N] Type Name Prop:Value ... slot:(SlotProp:Value ...) N is the 0-based index into result.Widgets -- use result.Widgets[N] to get the widget ref without text parsing. Same indentation format as GetTaggedWidgetDescription; richer per-widget detail. Params: widgetBlueprint, startWidget?, maxDepth?"
    options:
      method: epic_call_tool
  widget.epic_get_widget_tree_depth:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Returns the maximum depth of the widget tree. Depth: root with no children = 0; root + children = 1; etc. Params: widgetBlueprint, startWidget?"
    options:
      method: epic_call_tool
  widget.epic_get_widgets:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Returns blueprint info and all widgets in depth-first order. Children within each parent are in their panel slot order - this is the hierarchy order shown in the designer. Info contains ParentClass (pass to CreateWidgetBlueprint) and RootWidgetClass. Use ObjectTools.list_properties on each returned Widget and Slot to get property names before calling set_properties. Params: widgetBlueprint"
    options:
      method: epic_call_tool
  widget.epic_hover:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Hover over a Slate widget, triggering any hover state or tooltip. Params: ref"
    options:
      method: epic_call_tool
  widget.epic_list_observers:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] List all active observers as a JSON array for debugging. Each entry includes the observer identifier, whether it is the root observer, the root widget ref (if any), max depth, and cached snapshot size. Params: none"
    options:
      method: epic_call_tool
  widget.epic_list_widget_blueprints:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Lists widget blueprints in a content folder. Params: folderPath"
    options:
      method: epic_call_tool
  widget.epic_list_widget_classes:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Lists available widget classes, optionally filtered by name substring. Params: filter"
    options:
      method: epic_call_tool
  widget.epic_move_uicomponent:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Moves a UI component before or after another component on the same widget. Params: widgetBlueprint, widgetName, componentClassToMove, relativeToComponentClass, bMoveAfter"
    options:
      method: epic_call_tool
  widget.epic_move_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Moves a widget to a new parent panel at the specified position. Returns updated widget info with new Slot. Params: widgetBlueprint, widget, newParent, childIndex?"
    options:
      method: epic_call_tool
  widget.epic_observe:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Register an observer on a widget subtree so its refs are continuously kept up to date (~100ms tick). Call this on the window or panel you are about to work with. It ensures new widgets appearing in that subtree are assigned refs automatically. Unobserve when you are done. A shallow root observer (depth 0) already covers top-level windows. Params: ref, maxDepth?"
    options:
      method: epic_call_tool
  widget.epic_press_key:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Press and release a keyboard key on the currently focused Slate widget. Supports modifier prefixes: \"Ctrl+C\", \"Shift+1\". Params: key"
    options:
      method: epic_call_tool
  widget.epic_remove_uicomponent:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Removes a UI component of the given class from the named widget. Params: widgetBlueprint, widgetName, componentClass"
    options:
      method: epic_call_tool
  widget.epic_remove_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Removes a widget and its children from the tree. Params: widgetBlueprint, widget"
    options:
      method: epic_call_tool
  widget.epic_rename_widget:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Renames a widget. Returns updated widget info or empty on failure. Params: widgetBlueprint, widget, newDisplayName"
    options:
      method: epic_call_tool
  widget.epic_replace_widget_with_child:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Replaces a panel widget with its first child, removing the panel from the tree. The widget to replace must be a UPanelWidget with only one child. Params: widgetBlueprint, widgetToReplace"
    options:
      method: epic_call_tool
  widget.epic_replace_widget_with_named_slot:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Replaces a host widget with the content of one of its named slots. The host must implement INamedSlotInterface (e.g., a UUserWidget exposing named slots). The slot's content widget is moved up to take the host's place in the tree. Params: widgetBlueprint, widgetToReplace, namedSlot"
    options:
      method: epic_call_tool
  widget.epic_replace_widget_with_template:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Replaces a widget instance in the blueprint's widget tree with a new instance created from a different template widget class. Preserves references for members that exist on both classes with a compatible type/signature: bindings, BP graph variable references, animation bindings, and delegate bindings. Members without a compatible counterpart on the new class are listed in the returned report; references to those members in the outer blueprint will become orphaned graph nodes / dangling bindings. Params: widgetBlueprint, widgetToReplace, templateClass"
    options:
      method: epic_call_tool
  widget.epic_screenshot:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Screenshot a Slate widget or the active editor window. Prefer this over SceneTools.take_screenshot for Editor UI; use SceneTools only for 3D viewport. Params: ref"
    options:
      method: epic_call_tool
  widget.epic_select_option:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Select an option in a Slate combobox by its text label. Opens the dropdown, finds the matching text, and clicks it. Params: ref, value"
    options:
      method: epic_call_tool
  widget.epic_set_named_slot_content:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Sets content for a named slot. Returns full widget info including Slot pointer. Params: widgetBlueprint, hostWidget, slotName, widgetClass, widgetName"
    options:
      method: epic_call_tool
  widget.epic_snapshot:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Capture a Slate UI accessibility snapshot. Use this to read the current widget tree and discover refs for action tools (Click, Type, Hover, etc.). A shallow root observer (depth 0) covers top-level windows automatically. Before interacting with a specific window or panel, call Observe() on it to get deep coverage, then Snapshot that subtree to see its contents. Refs discovered by a previous Snapshot remain usable. You do NOT need to call Snapshot again before every action. Params: ref, maxDepth?, bIncludeSourceLocations?"
    options:
      method: epic_call_tool
  widget.epic_toggle_widget_as_variable:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Sets the bIsVariable flag. Params: widgetBlueprint, widget, bIsVariable"
    options:
      method: epic_call_tool
  widget.epic_type:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Type text into a Slate text input widget. Focuses the widget first, then sends one key event per character. Params: ref, text, submit?"
    options:
      method: epic_call_tool
  widget.epic_unobserve:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Remove an observer by its identifier. Params: identifier"
    options:
      method: epic_call_tool
  widget.epic_wait_for:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] Check if text is present or absent in the Slate widget tree. Non-blocking: checks once and returns immediately. Poll to wait. Params: text, textGone"
    options:
      method: epic_call_tool
  widget.epic_windows:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic SlateInspectorToolset.SlateInspectorToolset] List, select, or close top-level Slate editor windows. Params: action?, index?"
    options:
      method: epic_call_tool
  widget.epic_wrap_widgets:
    class_path: ue-mcp.bridge
    group: widget
    description: "[Epic UMGToolSet.UMGToolSet] Wraps one or more widgets in a new panel widget of the specified class. Only the root-most widgets in the selection are wrapped - children of other selected widgets are skipped because their parent will be wrapped. Returns info for each newly created wrapper. Use ObjectTools.list_properties on each returned Widget and Slot to discover property names before calling set_properties (padding, alignment, anchors, etc. vary per panel class). Params: widgetBlueprint, widgets, wrapperClass"
    options:
      method: epic_call_tool

  # ── editor ──
  editor.start_editor:
    class_path: editor.start_editor
    group: editor
    description: "Launch Unreal Editor and BLOCK until it is fully ready (not merely until the socket answers), rendering a startup progress bar in the terminal. Returns the phase timeline it waited through. Do NOT poll get_engine_state or get_status afterwards: this call already waited, and a ready editor is the only way it returns success. An editor already running for this project is reported as a failure, because this call launched nothing, with alreadyRunning=true, bridgeReady, and the port it published, so a caller can tell \"there was nothing to do\" from \"the launch broke\" without parsing the sentence. A flow step that expects that outcome sets ignore_failure: true on itself rather than asking this action to call a non-launch a launch. dialogPolicy answers startup prompts before they can wedge the game thread, which is how the post-crash \"Restore Packages\" modal used to stall a launch (#968). Params: timeout? (seconds, default 300), dialogPolicy? (\"pattern=response;pattern=response\", responses as set_dialog_policy takes them)"
  editor.get_engine_state:
    class_path: editor.get_engine_state
    group: editor
    description: "What the engine is REALLY doing, read from outside the game thread: startup phase from the editor's own log, every process holding this project's .uproject open (PID, command line, responding), the plugin's status snapshot (slow-task name and percent, active modal dialog, game-thread stall), and native dialog windows. `running` follows the strongest evidence: an editor that answered over the bridge is running whatever the process table saw, and a probe that could not run is reported as processProbeFailed rather than as an absent editor (#965). Call this ONCE when something is already wrong (handlers timing out, an editor that will not come up). Never call it in a wait loop: start_editor blocks until ready on its own, and polling this during startup burns tokens re-reading state that is already tracked. Params: probeWindows? (default true; scans native windows, costs ~2s)"
  editor.stop_editor:
    class_path: editor.stop_editor
    group: editor
    description: "Close Unreal Editor gracefully (asks the editor to quit itself via the bridge; never an OS kill). Acts only on the editor for the loaded project, resolved from the port lockfile that editor published at <project>/Saved/UE_MCP_Bridge/port.json. With no lockfile there is no port to aim at and the call refuses, naming the file it checked, rather than probing a default port that another project's editor could answer on (#819). With no editor of this project running there is nothing to quit, and the call fails saying so, with alreadyStopped=true marking that reason apart from a running editor that cannot be reached or refuses on unsaved work. A flow that stops the editor before building sets ignore_failure: true on the stop step. Unsaved work: the quit is sent and the EDITOR decides. It refuses inside the engine and names every dirty package without scheduling a close, so nothing is lost and no quit is left pending; save them with editor(save_dirty), or close the editor yourself and answer its save prompt by hand. There is deliberately no flag that discards. This action has no dialog behaviour of its own: a modal blocks it exactly as it blocks every other action, refused by the same gate with the same fields. Read the dialog with editor(list_dialogs) and answer it with editor(respond_to_dialog). Params: none"
  editor.restart_editor:
    class_path: editor.restart_editor
    group: editor
    description: "Stop then start the editor for the loaded project. Editors for other projects are left alone: the stop is aimed by this project's port lockfile, and the decision to start is made from the process holding this project's .uproject open, never from whether some editor is running (#819). The stop half is editor(stop_editor) exactly as it behaves on its own, so a restart refuses on unsaved packages and reports them rather than acting on them, and an editor that was already down is not a reason to refuse the start. Like the stop half it has no dialog behaviour of its own: a modal blocks it through the same gate as every other action. Params: none"
  editor.build_project:
    class_path: editor.build_project
    group: editor
    description: "Build the project's C++ code using Unreal Build Tool. Editor should be stopped first. Params: none"
  editor.execute_command:
    class_path: ue-mcp.bridge
    group: editor
    description: "Run console command. Params: command"
    options:
      method: execute_command
  editor.execute_python:
    class_path: editor.execute_python
    group: editor
    description: "GATED LAST RESORT. execute_python is unreachable until a semantic tool search over your taskSummary has been run AND every candidate it returns is EXPLICITLY ruled out with a stated reason. Flow: (1) call with taskSummary (+code) - it returns the candidate actions AND the exact ruledOut array to send back; (2) re-call with the same taskSummary/code PLUS that ruledOut=[{action, reason}], each reason at least 12 characters saying why that candidate does not fit. The action field accepts the bare name, tool(action) or tool.action, and rulings are remembered for the session so rewording the taskSummary never asks you to justify the same action twice. Python runs only once every candidate is ruled out. Params: code, taskSummary (required), ruledOut?, resultVariable? (name of a top-level variable to return as `result`, separate from print()/log; #732) (#704, #938, #960)"
  editor.run_python_file:
    class_path: ue-mcp.bridge
    group: editor
    description: "Run a Python file from disk with __file__/__name__ populated (#142). Pass entryPoint to load the file WITHOUT firing its `if __name__ == \"__main__\"` guard and then call one named function in it, which is how a Tools/ script holding several stages behind a main() is driven a stage at a time; `args` are then that call's positional arguments rather than sys.argv, `kwargs` its keyword arguments, and its return value comes back as `result` with no resultVariable needed. captureLog=false drops everything the script logged except its errors, and maxLogChars keeps only the tail: a script that prints a few hundred lines otherwise returns tens of KB to a caller that wanted one value. Params: filePath, entryPoint?, args?, kwargs?, resultVariable? (name of a top-level variable to return as `result`, separate from logs), captureLog?, maxLogChars? (#142/#732/#995)"
    options:
      method: run_python_file
  editor.purge_python_modules:
    class_path: ue-mcp.bridge
    group: editor
    description: "Purge cached embedded-Python modules whose name starts with a prefix, so the editor drops stale code after you edit a Python tool on disk. Returns the purged module names + count. Params: prefix (required, non-empty) (#719)"
    options:
      method: purge_python_modules
  editor.close_sequence:
    class_path: ue-mcp.bridge
    group: editor
    description: "Close the currently open Level Sequence editor (Sequencer). Do this before bulk-deleting actors a sequence may possess - open sequences re-resolve possessables by name during destruction and can mis-bind. Returns wasOpen + closedSequence. Params: none (#718)"
    options:
      method: close_sequence
  editor.open_tab:
    class_path: ue-mcp.bridge
    group: editor
    description: "Open a registered editor tab by ID so its UI can be screenshotted as evidence (e.g. 'ProjectSettings', 'OutputLog', 'ContentBrowserTab1'). Params: tabId (#727)"
    options:
      method: open_tab
  editor.open_settings:
    class_path: ue-mcp.bridge
    group: editor
    description: "Open (and navigate) a settings viewer for visual settings evidence. Params: container? (Project|Editor; default Project), category? (e.g. 'Engine'), section? (e.g. 'Physics', or a combined 'Engine.Physics') (#727)"
    options:
      method: open_settings
  editor.set_property:
    class_path: ue-mcp.bridge
    group: editor
    description: "Set UObject property. Saves the package to disk by default; pass save=false to leave it dirty in-memory (batch many writes, then editor(save_dirty)/asset(save)) (#674). TMap values take either { \"Key\": value } or, for struct keys, [{ key: {...}, value: ... }] - exactly what get_property/describe_object return under `value`. A write that cannot store every entry it was given fails and leaves the old value in place; containers report elementCount on success (#820). Params: objectPath, propertyName, value, save? (default true)"
    options:
      method: set_property
  editor.get_property:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read UObject property. `value` is structured JSON and is always safe to write straight back with set_property; `valueText` is UE export text and for a struct-keyed TMap does not read back, so valueTextRoundTrips reports whether it can be reused (#820). Params: objectPath, propertyName"
    options:
      method: get_property
  editor.describe_object:
    class_path: ue-mcp.bridge
    group: editor
    description: "Describe a UObject and optionally list/read properties. Per property, `value` is the round-trippable structured form and valueTextRoundTrips flags export text that is not (#820). Params: objectPath, includeProperties?, includeValues?, propertyNames?"
    options:
      method: describe_object
  editor.play_in_editor:
    class_path: ue-mcp.bridge
    group: editor
    description: "PIE control. A start with a session already active fails with alreadyRunning, and a stop with none active fails with alreadyStopped: neither call changed anything, and the marker names the reason. A flow step that expects either outcome carries ignore_failure: true. Params: pieAction (start|stop|status), waitForAssetRegistry? (start only; default true - block until the AssetRegistry initial scan completes before requesting PIE, otherwise PIE silently no-ops on cold editor starts), assetRegistryTimeoutSeconds? (default 180) (#406)"
    options:
      method: pie_control
  editor.play_in_editor_ignore_blueprint_errors:
    class_path: editor.play_in_editor_ignore_blueprint_errors
    group: editor
    description: "Start PIE for one launch with the editor's unresolved-Blueprint-error prompt suppressed. PIE then runs whatever bytecode those Blueprints last compiled to, so the launch is authorized per call: set ue-mcp.pie.allowIgnoreBlueprintErrors to true in your ue-mcp config to pre-authorize it, otherwise the user answers an MCP approval prompt. The bridge refuses the launch when a Blueprint would have to be recompiled first (dirty non-data Blueprints, errored Level Blueprints) and lists every errored Blueprint it suppressed in loadedErroredBlueprints. Params: waitForAssetRegistry? (default true), assetRegistryTimeoutSeconds? (default 180)."
  editor.get_runtime_value:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read PIE actor property. Params: actorLabel OR actorPath, propertyName (supports dotted paths: component.field or component.struct.field for nested reads on component subobjects, #344/#381)"
    options:
      method: get_runtime_value
  editor.get_pie_pawn:
    class_path: ue-mcp.bridge
    group: editor
    description: "Resolve the controlled pawn in the active PIE world. Params: playerIndex? (default 0). Returns actorLabel/class/location/rotation (#228/#229)"
    options:
      method: get_pie_pawn
  editor.list_pie_instances:
    class_path: ue-mcp.bridge
    group: editor
    description: "List the running PIE worlds with their instance id, net mode (standalone|listenServer|dedicatedServer|client), player count and whether they own a game viewport. In a multiplayer PIE session every other runtime action resolves the primary world (the server) unless you pass pieInstance, so this is how you discover that a client exists and what id addresses it. Params: none (#778)"
    options:
      method: list_pie_instances
  editor.invoke_object_function:
    class_path: ue-mcp.bridge
    group: editor
    description: "Call a UFUNCTION on any UObject, not just a placed actor. Target it with objectPath, or target=gameinstance|gamemode|gamestate|playercontroller|playerpawn|subsystem (subsystem also needs subsystemClass; playercontroller/playerpawn accept playerIndex). The GameInstance, GameMode and subsystems have no actor label, so invoke_function could never reach them. Returns output and return params under returnValues, with a TArray/TSet/TMap return as real JSON and everything else as export text (#885); an unknown function name lists the available ones. A scripted call runs under the editor script-execution guard, which forces every actor callspace to Local, so a UFUNCTION(Server) executes locally instead of being sent; the result warns when that happened, and deferToNextTick=true queues the call for the next engine tick where it routes normally, at the cost of returning before it runs (#973). Params: functionName, objectPath? | target?, subsystemClass?, playerIndex?, args?, world? (editor|pie|auto), pieInstance?, deferToNextTick? (#739)"
    options:
      method: invoke_object_function
  editor.invoke_object_functions:
    class_path: ue-mcp.bridge
    group: editor
    description: "Call 1-64 UFUNCTIONs in order without yielding to the editor tick loop. Each call independently targets a UObject using the same fields as invoke_object_function, so one sequence can span an actor and its components. Calls stop at the first failure; earlier calls are not rolled back. Returns results[] in call order plus completedCalls/requestedCalls, and failedIndex when it stops early, so a retry can resume instead of replaying mutations. Params: calls[] ({functionName, objectPath? | target?, subsystemClass?, playerIndex?, args?}), world? (editor|pie|auto), pieInstance?"
    options:
      method: invoke_object_functions
  editor.read_bone_transforms:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read live skeletal bone and socket transforms off an actor, once. This is a point-in-time read, NOT a time series - for per-frame capture over a window use the pie category's observe actions. Pass bones (bone OR socket names) or omit for every bone up to limit. space=world (default) or component; component space is independent of where the actor is standing. Pass relativeTo (bone OR socket name) to express every sample in that live reference frame; relativeTo supersedes space and is calculated in component space. Also reports the AnimInstance class/path. Params: actorLabel OR actorPath, componentName?, bones?, relativeTo?, space?, limit?, world? (editor|pie|auto), pieInstance? (#756/#757/#761/#764)"
    options:
      method: read_bone_transforms
  editor.get_object_properties:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read reflected properties off any UObject, with the same targeting as invoke_object_function. Blueprint-declared variables are reflected properties, so they read the same way as native ones. Pass propertyNames to filter; entries may use the Details-panel spelling ('World Context Object' finds WorldContextObject, 'Is Active' finds bIsActive). Names that do not exist come back under missingProperties instead of silently returning nothing. Properties holding a TMap are also reported under `values` in the structured form set_property accepts, because export text cannot carry a struct-keyed map back (#820). Params: objectPath? | target?, subsystemClass?, playerIndex?, propertyNames?, world? (editor|pie|auto), pieInstance? (#739/#802)"
    options:
      method: get_object_properties
  editor.set_movement_mode:
    class_path: ue-mcp.bridge
    group: editor
    description: "Set a live PIE character's movement mode and/or velocity on its CharacterMovementComponent. Modes are named (none|walking|navwalking|falling|swimming|flying|custom) rather than raw enum numbers, because a wrong number reads as success and then behaves as None. Reports previousMode/previousVelocity and reads the mode back afterwards, since SetMovementMode can refuse a mode the character cannot enter (flying with bCanFly off, swimming outside a volume). Params: actorLabel OR actorPath, mode?, customMode? (only with mode='custom'), velocity? {x,y,z}, world? (default pie), pieInstance? (#757)"
    options:
      method: set_movement_mode
  editor.set_object_property:
    class_path: ue-mcp.bridge
    group: editor
    description: "Write a reflected property on a live UObject instance, with the same targeting as invoke_object_function. Use this for a PIE actor, a spawned widget or any runtime instance: editor(set_property) is the asset path and marks the package dirty and saves it, which a live instance has no business doing. propertyName accepts dotted/indexed paths and the Details-panel spelling. Reports previousValue plus the value read back after the write, so a coerced or clamped write is visible. Nothing is saved; pass postEditChange=true to fire PostEditChangeProperty. Params: propertyName, value, objectPath? | target?, subsystemClass?, playerIndex?, postEditChange?, world? (editor|pie|auto), pieInstance? (#802)"
    options:
      method: set_object_property
  editor.find_object:
    class_path: ue-mcp.bridge
    group: editor
    description: "Resolve or search for a live UObject instance and report the objectPath that addresses it, which is what invoke_object_function / get_object_properties / set_object_property need. Pass objectPath to check one path (returns found/isValid rather than failing when it is gone), or className and/or nameContains to search every loaded object. className takes a short name (StaticMeshActor), a /Script path, a generated class name (WBP_Hud_C) or a Blueprint asset path. This is how you get the path of something spawned at runtime, an editor utility widget or a UMG widget, which no naming convention predicts. Params: objectPath? | className?, nameContains?, outerPath?, exactClass?, includeDefaults?, world? (any (default)|editor|pie), pieInstance?, limit? (default 50, max 1000), cursor? (#802)"
    options:
      method: find_object
  editor.teleport_runtime_actor:
    class_path: ue-mcp.bridge
    group: editor
    description: "Move a live PIE actor and have it STAY moved. A plain SetActorLocation on a Character is undone by CharacterMovement on the next tick, so this stops the movement component, teleports, and stops it again. Reports actualLocation read back from the actor rather than what was requested. Params: actorLabel OR actorPath, location?, rotation?, stopMovement? (default true), sweep? (default false), world? (default pie), pieInstance? (#770/#777)"
    options:
      method: teleport_runtime_actor
  editor.set_runtime_visibility:
    class_path: ue-mcp.bridge
    group: editor
    description: "Hide or show live PIE actors and their scene components, capturing an exact rollback snapshot. PIE-only: world must be 'pie' (the default) and pieInstance picks the world when several are running (see list_pie_instances). Provide exactly ONE actor selector - actorLabels[], actorPaths[] (the unambiguous one) or actorClass; a label matching several actors is refused rather than resolved at random. hidden=true hides, hidden=false shows. componentNames[]/componentClasses[] narrow to matching SceneComponents and imply affectComponents; with no component filter the actor itself is the target. affectActor/affectComponents override that split, propagateToChildren (default true) also takes each matched component's descendants, matchSubclasses (default true) widens class matching, and maxTargets bounds how far the expansion may go. dryRun defaults to TRUE: the call reports what it would change and mutates nothing until dryRun=false. Returns hidden, dryRun, mutationPerformed, matchedActors, targetCount, changed, alreadyDesired, worldPath, pieInstance, netMode, targets[], and on a real mutation a rollbackToken to hand to restore_runtime_visibility. Params: hidden, actorLabels? OR actorPaths? OR actorClass?, componentNames?, componentClasses?, affectActor?, affectComponents?, propagateToChildren?, matchSubclasses?, maxTargets?, dryRun?, world?, pieInstance?"
    options:
      method: set_runtime_visibility
  editor.restore_runtime_visibility:
    class_path: ue-mcp.bridge
    group: editor
    description: "Put back the exact visibility state set_runtime_visibility captured, addressed by the rollbackToken from its response. Run set_runtime_visibility with dryRun=false first; a dry run issues no token. The token belongs to one PIE session and expires with it, so restore before play ends. world, if passed, must be 'pie', and pieInstance must match the token's session. Returns restored, rollbackToken, targetCount, changed, alreadyRestored, worldPath, pieInstance, netMode. Params: rollbackToken, world?, pieInstance?"
    options:
      method: restore_runtime_visibility
  editor.invoke_static_function:
    class_path: ue-mcp.bridge
    group: editor
    description: "Call a static UFUNCTION on a UBlueprintFunctionLibrary (no actor instance). invoke_function needs an actor/component target; this targets the library class CDO instead, so it reaches static *_BlueprintOnly libraries (Voxel sculpt/query/stamp), GeometryScript, Kismet math, any function library. Params: className (short name or /Script/Module.Class path), functionName, args? (name -> JSON value, same marshalling as invoke_function), actorArgs? (name -> actor label for UObject* params that are actors, e.g. the sculpt actor), worldContextParam? (name of a UObject* param to fill with the selected world; auto-detected from the function's own WorldContext metadata and for params named WorldContextObject), world? (editor|pie|game|auto, default editor), pieInstance? (which PIE world; see list_pie_instances). world/pieInstance pick WHICH world fills the context param, so a static that looks a GameInstance subsystem up off its context can be exercised against a live PIE session (#971); the result names the world it ran against and the parameter that carried it. Returns return/out params under returnValues, with a TArray/TSet/TMap return as real JSON and everything else as export text (#885). Discover libraries + functions with list_function_libraries."
    options:
      method: invoke_static_function
  editor.invoke_function:
    class_path: ue-mcp.bridge
    group: editor
    description: "Call a BlueprintCallable / Exec UFUNCTION on a target actor or one of its components. world=editor (the default) runs the function on the actor placed in the level, no PIE session needed, and reports which instance ran it as resolvedActorLabel/resolvedActorPath. actorLabel is matched against placed actors by editor label first, then internal object name, then full object path; a miss is an error naming what was searched (#806). Returns out/return params under returnValues; a TArray/TSet/TMap return comes back as real JSON and everything else as export text (#885). A scripted call runs under the editor script-execution guard, which forces every actor callspace to Local, so a UFUNCTION(Server) executes locally instead of being sent; the result warns when that happened, and deferToNextTick=true queues the call for the next engine tick where it routes normally, at the cost of returning before it runs (#973). Params: actorLabel OR actorPath, functionName, component? (component subobject name; redirects target from the actor to that component, #382), args? (object; struct values accept a JSON object such as {X,Y,Z} as well as an export-text string), actorArgs? (object mapping UObject* parameter name to actor label, resolved against live actors in the active world; #383), world? (editor|pie), deferToNextTick? (#228/#229)"
    options:
      method: invoke_function
  editor.list_function_libraries:
    class_path: ue-mcp.bridge
    group: editor
    description: "Enumerate UBlueprintFunctionLibrary subclasses on this build. Filter by name (case-insensitive substring, e.g. 'GeometryScript' / 'Kismet' / 'Animation'). Returns name, module, and (by default) every static BlueprintCallable function on the library with its tooltip. Use to discover what's available for editor.invoke_function (#455). Params: pattern?, includeFunctions?"
    options:
      method: list_function_libraries
  editor.set_pie_time_scale:
    class_path: ue-mcp.bridge
    group: editor
    description: "Fast-forward PIE game time. Params: factor (>0). Raises WorldSettings caps and calls SetGlobalTimeDilation."
    options:
      method: set_pie_time_scale
  editor.hot_reload:
    class_path: ue-mcp.bridge
    group: editor
    description: "Hot reload C++. Params: none"
    options:
      method: hot_reload
  editor.undo:
    class_path: ue-mcp.bridge
    group: editor
    description: "Undo last transaction. Params: none"
    options:
      method: undo
  editor.redo:
    class_path: ue-mcp.bridge
    group: editor
    description: "Redo last transaction. Params: none"
    options:
      method: redo
  editor.get_perf_stats:
    class_path: ue-mcp.bridge
    group: editor
    description: "Editor performance stats. Params: none"
    options:
      method: get_editor_performance_stats
  editor.run_stat:
    class_path: ue-mcp.bridge
    group: editor
    description: "Run a stat overlay. Params: name (bare stat name, e.g. 'unit','fps','game','gpu') OR command (full console command). A bare name is prefixed with 'stat ' (#722)."
    options:
      method: run_stat_command
  editor.set_scalability:
    class_path: ue-mcp.bridge
    group: editor
    description: "Set rendering quality via the Scalability system (actually applies + persists, not just sg.* cvars). Params: level (Low|Medium|High|Epic|Cinematic). Returns appliedLevels (#591)"
    options:
      method: set_scalability
  editor.get_cvars:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read console variables from the running editor. Params: name OR names[] OR pattern (substring match over every registered variable), limit? (default 100). Each row carries value, defaultValue, isDefault, type, whether it is a cheat, and `setBy`: the priority the current value was written at, which is what separates a variable sitting at its default from one a scalability group or device profile drove there. A variable set at a higher priority ignores later writes from lower ones. Names that do not resolve come back under notFound rather than as an error (#1006)"
    options:
      method: get_cvars
  editor.set_cvars:
    class_path: ue-mcp.bridge
    group: editor
    description: "Bulk-set console variables. Params: cvars ({name: value} object OR [{name, value}] array). Returns per-cvar old/new values and any notFound names. Use get_cvars to read one back, including which priority its value was set at (#591)"
    options:
      method: set_cvars
  editor.capture_screenshot:
    class_path: ue-mcp.bridge
    group: editor
    description: "Screenshot. target=pie synchronously captures the selected PIE client game viewport with UMG/Slate UI; target=editor captures the level viewport; target=window synchronously captures a whole Slate window via FSlateApplication::TakeScreenshot - pixel-true for ALL Slate/UMG UI, returns after the PNG is written, and works while the window is unfocused or off-screen. Multi-instance PIE automatically prefers a world with a game viewport; pass pieInstance or worldPath to select explicitly, which for target=window is what picks the PIE client window instead of the active editor window. Every mode captures at the source viewport/window size - use capture_scene_png for a chosen output size. Params: filename? (outputPath is accepted for it too, so the two capture actions take the same name; #966), target? (auto|pie|editor|window), pieInstance?, worldPath?. Returns the resolved PIE instance/world and image dimensions (#226/#724)."
    options:
      method: capture_screenshot
  editor.capture_scene_png:
    class_path: ue-mcp.bridge
    group: editor
    description: "Headless PNG via a transient SceneCapture2D (RGBA8 LDR). Returns captureMetadata with the actual camera transform/basis, FOV, resolution, world/PIE identity and resolved focus actor/bounds. Pair this data with this image; get_viewport_state and hit_test_viewport_pixel refer to a different, live editor camera. The capture actor is destroyed before return. Old stray capture actors are swept and their removal reported as strayCaptureActorsRemoved. Params: outputPath (filename alias), location?, rotation?, focusActorLabel? OR focusActorPath, focusDirection?, focusMargin?, world? (editor|pie), pieInstance?, width? (default 1280), height? (default 720), fov? (0 < degrees < 180, default 90), fullyLoadTextures? (default true)"
    options:
      method: capture_scene_png
  editor.get_viewport_state:
    class_path: ue-mcp.bridge
    group: editor
    description: "Full readout of a level viewport: viewMode, viewportType, fov, nearClip, farClipOverride, exposure (fixed or auto, with the EV100), cameraSpeed, gameView, realtime, location and rotation, plus the view modes this engine build supports. get_viewport reports location, rotation and fov only. Call this before a capture to record the conditions it was taken under, so two captures can be compared honestly. Params: viewportIndex?"
    options:
      method: get_viewport_state
  editor.set_view_mode:
    class_path: ue-mcp.bridge
    group: editor
    description: "Pin the viewport's shading mode (Lit, Unlit, Wireframe, LightingOnly, DetailLighting, ShaderComplexity and the rest this build supports). The single biggest determinism lever for screenshot comparison: Unlit takes lighting out of the picture, Wireframe takes shading out. An unknown name is refused with the full list this engine supports rather than silently ignored. Idempotent: setting the mode it already has reports unchanged, and the previous mode comes back as a rollback. Params: viewMode, viewportIndex?"
    options:
      method: set_view_mode
  editor.set_viewport_exposure:
    class_path: ue-mcp.bridge
    group: editor
    description: "Pin the editor viewport to a fixed EV100 instead of auto eye-adaptation. Targets the viewport CLIENT, so it is transient, editor-only and does not dirty the level. This does not pin capture_scene_png's separate SceneCapture2D exposure. Post-process volumes remain asset(set_property) territory. Pass ev100 for a fixed value, or mode='auto' to return to eye adaptation. Params: ev100?, fixed?, mode? (fixed|auto), viewportIndex?"
    options:
      method: set_viewport_exposure
  editor.set_viewport_view:
    class_path: ue-mcp.bridge
    group: editor
    description: "Set fov, nearClip, farClip, viewportType (Perspective|Top|Bottom|Left|Right|Front|Back|OrthoFreelook) and cameraSpeed in one call. set_viewport writes only location and rotation and does not write the fov it reads back, which is the gap this fills. Reports a per-field changed flag and the previous values, and rolls back to them. nearClip's rollback is marked lossy because the engine reports the effective plane rather than the override. Params: fov?, nearClip?, farClip?, viewportType?, cameraSpeed?, viewportIndex?"
    options:
      method: set_viewport_view
  editor.set_game_view:
    class_path: ue-mcp.bridge
    group: editor
    description: "Toggle game view, which hides editor-only overlays (grid, gizmos, actor icons, volume wireframes) so a viewport capture shows what the game shows rather than what the editor shows. Idempotent: setting the state it already has reports unchanged. Params: enabled? (default true), viewportIndex?"
    options:
      method: set_game_view
  editor.redraw_viewport:
    class_path: ue-mcp.bridge
    group: editor
    description: "Force the viewport to repaint. A bridge write marks the viewport dirty but does not repaint it, so a capture taken immediately afterwards can show the state from before the write. Use set_realtime instead when a ticking simulation also has to advance. Params: allViewports?, invalidateHitProxies?, viewportIndex?"
    options:
      method: redraw_viewport
  editor.begin_transaction:
    class_path: ue-mcp.bridge
    group: editor
    description: "Open an undo transaction so a run of writes collapses into ONE undo step. General-purpose, unlike material(begin_transaction) which is material-scoped. Nesting is reported rather than refused. Pair with end_transaction to commit or cancel_transaction to discard. Params: description? (label is accepted as an alias)"
    options:
      method: begin_editor_transaction
  editor.end_transaction:
    class_path: ue-mcp.bridge
    group: editor
    description: "Commit the open undo transaction and return its index in the undo buffer. Ending with nothing open reports that rather than erroring, so a flow that already closed one is safe to replay. Params: none"
    options:
      method: end_editor_transaction
  editor.cancel_transaction:
    class_path: ue-mcp.bridge
    group: editor
    description: "Discard the open transaction and restore every object it touched. This is what makes 'do several writes, detect a failure partway, abort, leave the editor unchanged' possible at all; material's begin/end pair had no cancel, so an aborted flow could only ever commit. Cancelling with nothing open reports that rather than erroring. Params: index? (default 0)"
    options:
      method: cancel_editor_transaction
  editor.get_undo_state:
    class_path: ue-mcp.bridge
    group: editor
    description: "Report canUndo and canRedo plus the DESCRIPTION strings of what an undo or redo would actually apply, the queue length, the undo count and the current index. Look before you undo, instead of undoing and reading back a bare boolean. Params: none"
    options:
      method: get_undo_state
  editor.undo_redo_steps:
    class_path: ue-mcp.bridge
    group: editor
    description: "Undo or redo several steps at once, returning appliedDescriptions: the titles of the transactions actually reversed or reapplied, which is how you confirm you undid what you meant. Stops early with a stated reason rather than silently doing fewer steps, and refuses while a transaction is open. Params: steps? (default 1), direction? (undo|redo)"
    options:
      method: undo_redo_steps
  editor.get_transaction_history:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read the undo buffer itself, newest first: per entry the index, title, id, record count, byte size and primary object, and whether it is applied or undone. currentIndex splits the applied entries from the undone ones. Use it to find the transaction a later cancel or undo should target. Params: maxEntries? (default 50)"
    options:
      method: get_transaction_history
  editor.start_trace:
    class_path: ue-mcp.bridge
    group: editor
    description: "Start an Unreal Insights trace. Writes a .utrace and REPORTS WHERE IT LANDED (traceFile, plus the exact UnrealInsights command that opens it) - the bridge records traces, it does not read them back, because trace analysis lives in the engine's TraceServices/TraceAnalysis Developer modules that this plugin does not link. Defaults to a timestamped file under <Project>/Saved/Profiling so the path is deterministic rather than invented by the engine. Idempotent in the way that matters here: a trace that is ALREADY running is reported as alreadyTracing rather than quietly starting a second one, because only one connection exists per process. A channels list where nothing resolves is refused with the closest channel names. Rollback: stop_trace. Params: channels? (comma string or array, default 'default'), traceTarget? (file|network|none, default file), file? (absolute or relative .utrace path), host? (network target, default 127.0.0.1), truncate? (default true), excludeTail? (default false)"
    options:
      method: start_insights_trace
  editor.stop_trace:
    class_path: ue-mcp.bridge
    group: editor
    description: "Stop the running trace and report the finished file: absolute path, byte size, and the UnrealInsights command line that opens it. The destination is only readable while connected, so it is captured before the stop rather than lost by it. Stopping when nothing is running reports wasTracing=false rather than erroring, so a replayed flow is safe. Warns when profiling regions were still open, since those have no end event in the file. The rollback is LOSSY and says so: restarting writes a NEW .utrace and cannot reopen this one. Params: none"
    options:
      method: stop_insights_trace
  editor.pause_trace:
    class_path: ue-mcp.bridge
    group: editor
    description: "Pause or resume the running trace by muting every active channel, without closing the file. Idempotent: pausing an already-paused trace reports changed=false. Rollback restores the previous state. Params: paused? (default true; pass false to resume)"
    options:
      method: pause_insights_trace
  editor.get_trace_status:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read the whole trace system: tracing, paused, systemStatus, connectionType, destination, activeChannels, byte and memory statistics, the channel presets start_trace accepts, whether UnrealInsights is on disk, every profiling region still open, and whether a bridge-launched standalone run is alive. Params: none"
    options:
      method: get_insights_trace_status
  editor.list_trace_channels:
    class_path: ue-mcp.bridge
    group: editor
    description: "Every trace channel this build registers, with its enabled state and, on UE 5.7+, its description, id and read-only flag. This is what makes start_trace's channels parameter discoverable instead of guesswork. Params: filter? (case-insensitive substring over name and description), enabledOnly? (default false)"
    options:
      method: list_trace_channels
  editor.set_trace_channels:
    class_path: ue-mcp.bridge
    group: editor
    description: "Turn named trace channels on and off, including mid-trace. Validates the WHOLE request before applying any of it, so a typo cannot leave a half-configured trace recording something other than what was asked for, and an unknown name comes back with the closest real ones. Each channel reports wasEnabled, enabled and changed read back from the trace system rather than assumed, so a read-only channel refusing at runtime is visible instead of silent. Rollback restores exactly the channels that moved. Params: enable? (comma string or array), disable? (comma string or array)"
    options:
      method: set_trace_channels
  editor.begin_profile_region:
    class_path: ue-mcp.bridge
    group: editor
    description: "Open a named bracket around an operation so it can be measured. Times wall clock unconditionally and, on UE 5.7+ with a trace running, also emits an Insights timing region; when it cannot emit one it says so in tracedReason rather than pretending. The name is the key: opening the same name twice reports the existing region rather than nesting two begins under one end. Rollback: end_profile_region. Params: regionName, regionCategory?"
    options:
      method: begin_profile_region
  editor.end_profile_region:
    class_path: ue-mcp.bridge
    group: editor
    description: "Close a named bracket and return what it measured: durationMs and the number of rendered frames it spanned. A region spanning zero frames says so, because a CPU-versus-GPU verdict cannot describe work that ran inside one tick. Ending a region that is not open reports wasOpen=false and lists the ones that are, rather than erroring. No inverse exists and the response says why. Params: regionName"
    options:
      method: end_profile_region
  editor.add_trace_bookmark:
    class_path: ue-mcp.bridge
    group: editor
    description: "Drop a named marker on the Insights timeline. Reports recorded=false with the reason when no trace is running or the Bookmark channel is off, instead of returning a success for an event that was dropped. Deliberately NOT idempotent: a bookmark is a point event, so two calls write two markers and the response says so. Params: bookmarkName"
    options:
      method: add_trace_bookmark
  editor.get_frame_timing:
    class_path: ue-mcp.bridge
    group: editor
    description: "Frame timings WITH A VERDICT: gameThreadMs, renderThreadMs, rhiThreadMs, swapBufferMs, per-thread wait time, frameMs and fps, and GPU min/avg/max drained from the RHI's own history, then bound = gpu | cpu-game | cpu-render | cpu-rhi | balanced | unknown with the arithmetic that produced it spelled out in verdict. Says unknown when the RHI published no GPU timing rather than guessing. warnings[] names the conditions that make the numbers meaningless and the exact call that fixes each, including the unfocused-editor CPU throttle, which is a plain UPROPERTY and is therefore REPORTED here with its objectPath for editor(set_property) instead of getting a typed setter that would duplicate a working path. sampleWindow states what was actually measured: a handler runs inside one tick and cannot advance frames to build a window. Params: cpuGpuMarginPercent? (default 10; how far ahead one side must be before it is called the bottleneck)"
    options:
      method: get_frame_timing
  editor.trigger_hitch:
    class_path: ue-mcp.bridge
    group: editor
    description: "Stall the game thread for a known number of milliseconds, so hitch-detection logic can be tested against a hitch whose size is known in advance. Brackets the stall with a trace region and a bookmark so it is findable in the capture. Capped at 5000ms because this blocks the same thread the bridge answers on. Sleeping consumes no CPU, so it reads as a long frame rather than as game-thread work. No inverse: time does not come back. Params: hitchMilliseconds? (default 250, max 5000), bookmark? (default true)"
    options:
      method: trigger_hitch
  editor.launch_standalone:
    class_path: ue-mcp.bridge
    group: editor
    description: "Launch the project as a separate -game process, optionally tracing, so frame times come from a real game process rather than from the editor. Passing channels (or traceFile) adds -trace and -tracefile, and the response carries the .utrace path plus the UnrealInsights command that opens it. Idempotent: a bridge-launched run that is still alive is reported as alreadyRunning rather than joined by a second. The process is detached and its output is not read; poll get_standalone_status. Rollback: stop_standalone. Params: mapName?, channels? (comma string or array), traceFile?, windowed? (default true), resX? (default 1280), resY? (default 720), extraArgs?"
    options:
      method: launch_standalone_game
  editor.get_standalone_status:
    class_path: ue-mcp.bridge
    group: editor
    description: "Is the bridge-launched standalone run still alive: running, processId, commandLine, uptimeSeconds, the exit code once it has ended, and its .utrace path with the command that opens it. Only reports runs this bridge started; one launched another way is not tracked. Params: none"
    options:
      method: get_standalone_status
  editor.stop_standalone:
    class_path: ue-mcp.bridge
    group: editor
    description: "Terminate the standalone run this bridge launched, killing its process tree so nothing is left holding the .utrace open. Stopping when nothing is running reports wasRunning=false rather than erroring. Termination is not a graceful quit and the response says what that costs. No inverse: relaunching is a new run. Params: none"
    options:
      method: stop_standalone_game
  editor.set_realtime:
    class_path: ue-mcp.bridge
    group: editor
    description: "Toggle realtime update on the level editor viewports so the editor-world sim (Niagara, anims) ticks - otherwise capture_scene_png renders an unticked, empty sim. Params: enabled (default true) (#537)"
    options:
      method: set_realtime
  editor.get_viewport:
    class_path: ue-mcp.bridge
    group: editor
    description: "Get viewport camera. Params: none"
    options:
      method: get_viewport_info
  editor.hit_test_viewport_pixel:
    class_path: ue-mcp.bridge
    group: editor
    description: "Ray-cast from a screen pixel through the active editor viewport and return the first hit. Builds the ray from the live viewport's projection matrix (no FOV/aspect guessing). Returns hit + actorLabel/actorClass/componentName/componentClass/materialPath/location/impactPoint/normal/distance/faceIndex/boneName/physicalMaterial. Params: x, y (pixel coords), width? height? (override viewport size when picking from a different-resolution screenshot), maxDistance? (default 200000), ignoreActors? (array of actor labels) (#418)"
    options:
      method: hit_test_viewport_pixel
  editor.get_runtime_values:
    class_path: ue-mcp.bridge
    group: editor
    description: "Bulk runtime read across the active world. For each actor/component matching classFilter, resolves every path against the (actor|component) root and returns rows of {actorLabel, actorClass, componentName?, componentClass?, values, errors?}. Paths support property hops, sub-object hops, and BlueprintCallable getter calls at any segment (e.g. 'PowerConnector.GetRequired' reaches a UFUNCTION on a UObject sub-object). A getter that takes arguments is written with them inline, 'GetMirroredTallyWeight(overclock)' or 'GetBalance(gold, 2)', which is what makes a keyed accessor readable across every matched instance in one call; the literals are coerced by the same rules invoke_object_function's args use, so FName/FString/int/float/bool/enum all read (#969). classFilter matches actor class OR component class - omit to match everything. A path whose result is a TArray/TSet/TMap comes back as real JSON rather than one string (#885). World defaults to PIE if running, else editor. Params: classFilter?, paths[], world? (editor|pie) (#414)"
    options:
      method: get_runtime_values
  editor.set_viewport:
    class_path: ue-mcp.bridge
    group: editor
    description: "Set the viewport camera, its projection and its orthographic zoom. The projection is switched BEFORE the requested pose is applied, because the viewport keeps a separate transform cache per projection and writing the pose first would hand back the camera from the mode you just left. Every value is validated before anything is written, and the rollback restores the projection and zoom as well as the location and rotation. Params: location?, rotation?, projection? (perspective|orthographic), viewportType?, orthoZoom?, viewportIndex? (#1029)"
    options:
      method: set_viewport_camera
  editor.focus_on_actor:
    class_path: ue-mcp.bridge
    group: editor
    description: "Focus on actor. Params: actorLabel OR actorPath (#983)"
    options:
      method: focus_viewport_on_actor
  editor.create_sequence:
    class_path: ue-mcp.bridge
    group: editor
    description: "Create Level Sequence. Params: name, packagePath?"
    options:
      method: create_level_sequence
  editor.get_sequence_info:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read sequence: bindings (possessable/spawnable) with their Sequencer tags (#556), tracks, and optional section detail. UNITS: playbackRange is reported in TICKS (tick resolution, commonly 24000/s), while MovieSceneScripting*Channel.add_key defaults its time_unit to DISPLAY_RATE. Keys authored with a tick number under the default unit land roughly 800x past the range and the track evaluates to its first key, which presents as transforms that do not work on a structurally perfect sequence (#881). Params: assetPath, includeSectionDetails? (attach sockets, first transform key values per track)"
    options:
      method: get_sequence_info
  editor.add_sequence_track:
    class_path: ue-mcp.bridge
    group: editor
    description: "Add an empty track. Params: assetPath, trackType, actorLabel? OR actorPath? (#983)"
    options:
      method: add_sequence_track
  editor.add_sequence_section:
    class_path: ue-mcp.bridge
    group: editor
    description: "Add a section to a track (creating the track if needed), set its start/end in seconds, and for a CameraCut track bind it to a camera. Returns the section index + channel names to key. Params: sequencePath, trackType (Transform|Float|Fade|CameraCut|Audio|Event|SkeletalAnimation), actorLabel? OR actorPath? (binding scope), startSeconds?, endSeconds?, cameraActorLabel? OR cameraActorPath? (#548/#983)"
    options:
      method: add_sequence_section
  editor.set_sequence_keyframes:
    class_path: ue-mcp.bridge
    group: editor
    description: "Add keyframes to a section channel. Transform channels: Location.X/Y/Z, Rotation.X/Y/Z (or friendly x/y/z, yaw/pitch/roll); Fade/Float: the float channel. Params: sequencePath, trackType, actorLabel? OR actorPath?, sectionIndex? (default 0), channel, keyframes ([{seconds, value}]), interpolation? (cubic|linear) (#548)"
    options:
      method: set_sequence_keyframes
  editor.set_sequence_playback_range:
    class_path: ue-mcp.bridge
    group: editor
    description: "Set a Level Sequence's playback range in seconds. Params: sequencePath, startSeconds, endSeconds (#548)"
    options:
      method: set_sequence_playback_range
  editor.play_sequence:
    class_path: ue-mcp.bridge
    group: editor
    description: "Play/stop/pause a Level Sequence in Sequencer. Pass sequencePath (or assetPath) to target a specific sequence - it is opened first, because the underlying Sequencer commands act on whatever is currently open. Omit it and the call applies to the open sequence and says so. Params: sequencePath? (or assetPath), sequenceAction? (play|pause|stop, default play)"
    options:
      method: play_sequence
  editor.scrub_sequence:
    class_path: ue-mcp.bridge
    group: editor
    description: "Park the Sequencer playhead on an exact time and evaluate there, then return. This is what makes scrub-then-capture_scene_png deterministic: play_sequence only offers play/pause/stop and realtime playback races the capture. Pauses first, scrubs, and forces the evaluation before answering, because the playhead move alone does not write possessed-actor transforms. Pass exactly one of seconds or frame. frame is read in timeUnit: display (default, the frame numbers Sequencer shows) or tick (the units get_sequence_info's playbackRange reports). Returns the evaluated time in all three units plus displayRate/tickResolution/playbackRange, and warns when the time is outside the playback range. Params: sequencePath? (or assetPath), seconds? | frame?, timeUnit? (#881)"
    options:
      method: scrub_sequence
  editor.build_all:
    class_path: ue-mcp.bridge
    group: editor
    description: "Build all (geometry, lighting, paths, HLOD). Params: none"
    options:
      method: build_all
  editor.build_geometry:
    class_path: ue-mcp.bridge
    group: editor
    description: "Rebuild BSP geometry. Params: none"
    options:
      method: build_geometry
  editor.build_hlod:
    class_path: ue-mcp.bridge
    group: editor
    description: "Build HLODs. Params: none"
    options:
      method: build_hlod
  editor.validate_assets:
    class_path: ue-mcp.bridge
    group: editor
    description: "Run data validation. Params: directory?"
    options:
      method: validate_assets
  editor.get_build_status:
    class_path: ue-mcp.bridge
    group: editor
    description: "Get build/map status. Params: none"
    options:
      method: get_build_status
  editor.cook_content:
    class_path: ue-mcp.bridge
    group: editor
    description: "Cook content. Params: platform?"
    options:
      method: cook_content
  editor.get_log:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read output log. maxLines selects how far back into the ring buffer to read (default 100); limit pages the lines that match filter/category within that window, and each line carries the sequence number that anchors a cursor. Params: maxLines?, filter?, category?, cursor?, limit?"
    options:
      method: get_output_log
  editor.search_log:
    class_path: ue-mcp.bridge
    group: editor
    description: "Search the captured log. Every match in the 4096-line ring buffer is collected and paged, so a busy log reports how many matched instead of stopping at the first hundred. maxResults caps the search itself and reports cappedAtMaxResults when it was what ended the collection. Params: query, maxResults? (default 4096), cursor?, limit?"
    options:
      method: search_log
  editor.get_message_log:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read a Message Log listing (MapCheck, AssetCheck, PIE, LoadErrors, LightingResults...). Call with NO logName to list the registered listings with their error/warning counts, then read one. Counts come from the listing itself; message bodies come from the current page and honour the Message Log tab's severity checkboxes, so when fewer are readable than exist the response says so instead of reading clean. An unknown logName is an error, not an empty log. Blueprint COMPILE results are not here - the compiler makes a listing per Blueprint; use blueprint(compile). Params: logName?, maxLines? (default 200), severity? (severity-name substring)"
    options:
      method: get_message_log
  editor.list_crashes:
    class_path: ue-mcp.bridge
    group: editor
    description: "List crash reports, sorted by folder name, which is chronological. Params: cursor?, limit?"
    options:
      method: list_crashes
  editor.get_crash_info:
    class_path: ue-mcp.bridge
    group: editor
    description: "Get crash details. Params: crashFolder"
    options:
      method: get_crash_info
  editor.check_for_crashes:
    class_path: ue-mcp.bridge
    group: editor
    description: "Check for recent crashes. Params: none"
    options:
      method: check_for_crashes
  editor.set_dialog_policy:
    class_path: ue-mcp.bridge
    group: editor
    description: "Arm an answer, in advance, for dialogs whose title or message contains a pattern. READ THIS BEFORE USING IT: an armed policy presses the button for you, so from then on a matching prompt is answered and dismissed and the user never sees the question. On a save prompt that means unsaved work can be discarded without anyone reading the warning. Nothing arms a policy on your behalf - the plugin ships none and no other action arms one - so every policy in effect is one somebody typed here deliberately, and this is the only way the bridge ever answers a dialog by itself. Covers the Slate modal windows the editor raises itself (the shutdown \"Save Content\" prompt among them, whose buttons are Save Selected / Don't Save / Cancel). A response keyword resolves to whichever of the dialog's buttons carries that meaning, so response='no' presses \"Don't Save\"; pass buttonLabel to name a button literally instead. A policy set here answers a matching dialog whoever raised it, and answers one that is already on screen. To read a dialog instead of pre-answering it, use editor(list_dialogs) and then editor(respond_to_dialog). Params: pattern, response? (yes/no/ok/cancel/retry/continue/yesall/noall), buttonLabel?"
    options:
      method: set_dialog_policy
  editor.clear_dialog_policy:
    class_path: ue-mcp.bridge
    group: editor
    description: "Clear dialog policies. Params: pattern?"
    options:
      method: clear_dialog_policy
  editor.get_dialog_policy:
    class_path: ue-mcp.bridge
    group: editor
    description: "Get the dialog policies currently armed, each with its response and literal buttonLabel. Every one was armed by a caller through set_dialog_policy: the plugin arms none of its own, so an empty list means nothing will answer any dialog on its own. Params: none"
    options:
      method: get_dialog_policy
  editor.list_dialogs:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read the modal dialog blocking the editor, in full: its exact title, its COMPLETE message text (never truncated - messageTruncated is always false), every button label in the order the dialog lays them out, and a `choices` array pairing each button with the exact editor(respond_to_dialog) call that presses it. When the dialog asks a question per row (the \"Save Content\" prompt is a checkbox per unsaved package), an `items` array reports each tickable row: its index, its label, its cells (asset name, package path, class path) and whether it is currently ticked. No button is marked recommended and none is reordered; choosing is yours. Also reports which armed policy matches and which button that policy would press, if any. Runs even while a dialog is blocking the editor, when every other handler times out, so this is the way to see what the editor is asking. Params: none"
    options:
      method: list_dialogs
  editor.respond_to_dialog:
    class_path: ue-mcp.bridge
    group: editor
    description: "Press one named button on the active modal dialog, releasing the game thread. This is the deliberate way to answer a dialog: read it with editor(list_dialogs) first, then name the button you chose. Runs even while the dialog is blocking the editor. ONLY IN auto MODE. The dialog handling mode decides who answers, and it is enforced here: under interactive the question is put to the person in an elicitation form (raised on the call AFTER the one that hands the dialog back, so its text is read somewhere nothing truncates it) and only their button is pressed, under defer they answer it in the Unreal Editor window, and in both this call is refused with the dialog named. editor(list_dialogs) stays available in every mode, so the dialog can always be READ. Pass action='close' to destroy the dialog window when no button label fits, which ends the modal without answering the question. Pass items to tick or untick the dialog's own rows before the button is pressed, which is what makes \"Save Selected\" mean something: read them from editor(list_dialogs) and send [{index, checked}] for the ones you want changed. The ticks and the press happen in this one call, so a modal is never left holding a selection nobody pressed anything on. Params: buttonIndex?, buttonLabel?, items?, action? (escape or close)"
    options:
      method: respond_to_dialog
  editor.open_asset:
    class_path: ue-mcp.bridge
    group: editor
    description: "Open asset in its editor. Params: assetPath"
    options:
      method: open_asset
  editor.reload_bridge:
    class_path: ue-mcp.bridge
    group: editor
    description: "Hot-reload Python bridge handlers from disk. Params: none"
    options:
      method: reload_handlers
  editor.save_dirty:
    class_path: ue-mcp.bridge
    group: editor
    description: "Flush every dirty package and return a per-package saved/failed map. Use after multi-step CDO/component edits when set_class_default leaves the asset dirty without persisting (#378). Params: includeMaps? (default true), includeContent? (default true)"
    options:
      method: save_dirty
  editor.configure_pie:
    class_path: ue-mcp.bridge
    group: editor
    description: "Set ULevelEditorPlaySettings - multi-client PIE, net mode, single-process flag, Play-in-New-Window resolution. Params: numClients?, netMode? (standalone|listen|client), runUnderOneProcess?, launchSeparateServer?, newWindowWidth?, newWindowHeight? (#384/#671)"
    options:
      method: configure_pie
  editor.get_pie_config:
    class_path: ue-mcp.bridge
    group: editor
    description: "Read current ULevelEditorPlaySettings (numClients, netMode, single-process, separate-server). Params: none (#384)"
    options:
      method: get_pie_config
  editor.pie_set_player_view:
    class_path: ue-mcp.bridge
    group: editor
    description: "Point the running PIE player's view (control rotation) at a pitch/yaw/roll so a capture frames the intended direction. Requires PIE. Params: pitch?, yaw?, roll? (#671)"
    options:
      method: pie_set_player_view
  editor.stage_game_input:
    class_path: ue-mcp.bridge
    group: editor
    description: "Stage input for the running game: set input mode (gameOnly|gameAndUI|uiOnly) and mouse cursor so injected/simulated input reaches the pawn. This only sets the mode - the injection itself lives in the pie category (pie(inject_input*)), not here. Requires PIE. Params: inputMode? (default gameOnly), showMouseCursor? (#671)"
    options:
      method: stage_game_input
  editor.run_automation_tests:
    class_path: ue-mcp.bridge
    group: editor
    description: "Run registered Automation tests matching a filter and return per-test pass/fail plus error lines. Runs them synchronously through the test framework rather than the console queue, and suspends the editor's unfocused-CPU throttle for the duration - otherwise an unfocused editor drops to a few FPS and the framework's interactive-frame-rate gate never opens, leaving tests queued forever (#765). A test whose latent commands are still queued when latentTimeoutSeconds runs out is reported as abandoned, with the reason: latent work needs engine frames, and this runs on the game thread, so a test that starts PIE (CQTest multi-client network tests, for instance) belongs in the editor's Automation window or -ExecCmds=\"Automation RunTests <name>\" at launch. Such a test used to terminate the editor outright (#993). Params: filter?, maxTests? (default 50), latentTimeoutSeconds? (default 5, max 120) (#693)"
    options:
      method: run_automation_tests
  editor.list_dirty_packages:
    class_path: ue-mcp.bridge
    group: editor
    description: "Enumerate currently-dirty content + map packages, read from the editor's own dirty-package lists (the same ones Save All uses). Includes a never-saved /Temp world, because an unsaved new map is exactly the unsaved work a caller needs to see before closing or reloading. Params: none (#340)"
    options:
      method: list_dirty_packages
  editor.get_world_state:
    class_path: ue-mcp.bridge
    group: editor
    description: "One atomic read of which world is open and what is unsaved. Returns editorWorldName, editorWorldPackage, persistentLevelPackage, worldPackageDirty, a sorted dirtyPackages list with counts, and the editor/play/simulate mode. level(get_current) plus editor(list_dirty_packages) is two calls, so the editor can change between them and neither result proves which world the other described; this answers both in one game-thread dispatch. Read-only, and fails closed rather than reporting an empty world as a clean one. Params: none (#920/#921)"
    options:
      method: get_world_state
  editor.request_editor_shutdown:
    class_path: editor.request_editor_shutdown
    group: editor
    description: "Ask the editor to close itself from inside the engine, after it has checked that closing is safe. Refuses by default when any content or map package is dirty (including an unsaved /Temp world) and reports which ones, so nothing is lost to a silent discard. Ends an active PIE/SIE session first and closes only once play has actually stopped. The response is returned before the process exits. Aimed at the same editor stop_editor aims at, through the same ownership check, so the two can never disagree about which editor belongs to the loaded project (#967), including what happens when none is running: both fail with alreadyStopped=true rather than one succeeding and the other refusing. This IS what stop_editor sends: stop_editor calls it with requireClean=false, so the editor schedules its own close and raises its own save prompt for anything dirty rather than the server refusing in its place. Called directly it defaults to requireClean=true, which refuses and names the dirty packages without scheduling anything. Use editor(stop_editor) for the full stop-and-confirm flow; this action is the in-engine half of it. Params: requireClean? (default true), endPIE? (default true)"
  editor.epic_capture_asset_image:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Renders a thumbnail for the specified asset (e.g. static meshes, skeletal meshes, skeletons, animations, montages, materials, textures). Params: assetPath"
    options:
      method: epic_call_tool
  editor.epic_capture_editor_image:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Captures an image of the entire editor application as the user sees it. Params: none"
    options:
      method: epic_call_tool
  editor.epic_capture_viewport:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Captures the level viewport with optional annotations. Annotations rendering overlays a projected 3D world-space grid plus name + position labels on visible actors. The grid is drawn at a configurable ground-plane Z and projected through the camera, with coordinate numbers at intersections (shown in meters). Each labeled actor gets a crosshair at its projected screen position with a leader-line callout placed to avoid overlap. This gives a vision-capable agent spatial awareness: it can reference grid coordinates to direct placement and identify scene contents by label. Params: captureTransform?, annotations?, bShowUI?"
    options:
      method: epic_call_tool
  editor.epic_focus_on_actors:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Repositions the level editor camera to focus on the specified actors. Cannot be called while PIE is active. Params: actors"
    options:
      method: epic_call_tool
  editor.epic_get_camera_transform:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Returns the position and rotation of the level viewport camera. Params: none"
    options:
      method: epic_call_tool
  editor.epic_get_content_browser_path:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Gets the current path of the active content browser. Params: none"
    options:
      method: epic_call_tool
  editor.epic_get_log_categories:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.LogsToolset] Returns a sorted list of registered log categories. Params: filter"
    options:
      method: epic_call_tool
  editor.epic_get_log_entries:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.LogsToolset] Returns log entries from the current session's log file. Params: category?, pattern, maxEntries?"
    options:
      method: epic_call_tool
  editor.epic_get_open_assets:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Gets the list of assets currently open in asset editors. Params: none"
    options:
      method: epic_call_tool
  editor.epic_get_selected_actors:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Gets the currently selected actors in the level editor. Params: none"
    options:
      method: epic_call_tool
  editor.epic_get_selected_assets:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Gets the list of assets selected in the content browser. Params: none"
    options:
      method: epic_call_tool
  editor.epic_get_verbosity:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.LogsToolset] Returns the current verbosity level for a log category. Params: category?"
    options:
      method: epic_call_tool
  editor.epic_get_visible_actors:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Returns all actors in the current level whose bounds intersect the viewport frustum. Params: none"
    options:
      method: epic_call_tool
  editor.epic_is_pierunning:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Returns whether a Play In Editor session is currently running. Params: none"
    options:
      method: epic_call_tool
  editor.epic_open_editor_for_asset:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Opens an asset editor for the specified asset. Params: assetPath"
    options:
      method: epic_call_tool
  editor.epic_screen_coords_to_world:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Finds the world position of the nearest solid object at a given set of normalized view space coords. Params: coords, traceDistance?"
    options:
      method: epic_call_tool
  editor.epic_search_cvars:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Finds all console variables that contain a given name. Params: name"
    options:
      method: epic_call_tool
  editor.epic_select_actors:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Selects the specified actors in the current scene. Params: actors"
    options:
      method: epic_call_tool
  editor.epic_select_assets:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Selects the specified assets in the content browser. Completes once the content browser has applied the selection. Params: assetPaths"
    options:
      method: epic_call_tool
  editor.epic_set_camera_transform:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Sets the position and rotation of the level viewport camera. Params: transform"
    options:
      method: epic_call_tool
  editor.epic_set_content_browser_path:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Navigates the active content browser to the specified folder path. Params: path"
    options:
      method: epic_call_tool
  editor.epic_set_verbosity:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.LogsToolset] Sets the verbosity level for a log category. Params: category?, verbosity"
    options:
      method: epic_call_tool
  editor.epic_start_pie:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Starts a Play-In-Editor or Simulate-In-Editor session using the current level. Completes after the engine fires PostPIEStarted (session fully started, BeginPlay called) and Options.WarmupSeconds have elapsed, giving project- specific initialization (services, authentication, plugin warmup) time to settle before the agent inspects state or logs. Raises an error if a play session is already running. Params: options"
    options:
      method: epic_call_tool
  editor.epic_stop_pie:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Stops the currently running play session (PIE or Simulate). Raises an error if no play session is running. Params: none"
    options:
      method: epic_call_tool
  editor.epic_world_pos_to_screen_coords:
    class_path: ue-mcp.bridge
    group: editor
    description: "[Epic EditorToolset.EditorAppToolset] Converts a world-space position into normalized screen space based on the editor viewport camera. Params: position"
    options:
      method: epic_call_tool

  # ── reflection ──
  reflection.reflect_class:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Reflect UClass. className accepts the C++ spelling with or without the A/U/F/E prefix (UMyConfig and MyConfig both resolve), a /Script/Module.ClassName path, or a Blueprint class path; a failed lookup lists the spellings tried and the closest matches (#823). Params: className, includeInherited?"
    options:
      method: reflect_class
  reflection.reflect_instance:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Per-instance writable schema: what can be written on THIS asset, CDO or subobject right now. reflect_class answers what a class has; this answers what the object in front of you will accept, which is what removes the write-and-see loop. Per property: type and kind, the same tooltip/category/displayName/clamp/UI-range/units metadata reflect_class reports, the UPROPERTY flag names, the current value and valueText, and the constraints a write has to satisfy - enum values, allowed and disallowed classes for an object reference, array/set element type, map key and value types, struct field layout, container element count. Instance state on top of that: 'editable' with a 'notEditableReason' (EditConst, EditDefaultsOnly read on an instance, EditInstanceOnly read on defaults), the EditCondition and whether it is met on THIS object, 'settable' saying whether asset/editor(set_property) can write it at all, and 'valueObjectPath' for an instanced subobject so the next call can aim at it. objectPath takes an asset path, an object path, a class path or a Blueprint path (its generated-class defaults). propertyPath scopes the read to one nested struct or object reference. Params: objectPath, propertyPath?, filter?, includeInherited? (default true), includeValues? (default true), editableOnly? (default false), maxDepth? (0 to 5, default 1), cursor?, limit?"
    options:
      method: reflect_instance
  reflection.reflect_struct:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Reflect UScriptStruct. Params: structName"
    options:
      method: reflect_struct
  reflection.reflect_enum:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Reflect UEnum by full path, short name, or short name without the E prefix. Resolves native enums in any loaded module and loads unloaded Blueprint (UserDefinedEnum) assets via the asset registry. Returns enumPath, userDefined, and per-value name/value/displayName/tooltip; a failed lookup lists close matches (#762). Params: enumName"
    options:
      method: reflect_enum
  reflection.list_classes:
    class_path: ue-mcp.bridge
    group: reflection
    description: "List classes. parentFilter resolves with or without the C++ A/U/F/E prefix (#823). Rows carry name, path and parent, sorted by path so a page boundary is stable. Params: parentFilter?, limit?, cursor?"
    options:
      method: list_classes
  reflection.list_tags:
    class_path: ue-mcp.bridge
    group: reflection
    description: "List gameplay tags. Params: filter?, cursor?, limit?"
    options:
      method: list_gameplay_tags
  reflection.create_tag:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Create gameplay tag. Params: tag, comment?"
    options:
      method: create_gameplay_tag
  reflection.create_enum:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Create UUserDefinedEnum asset, optionally seeded with entries. Params: name, packagePath?, entries?: (string|{name, displayName?})[], onConflict? (#274)"
    options:
      method: create_enum
  reflection.set_enum_entries:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Replace entries on an existing UUserDefinedEnum. Params: assetPath, entries[] (#274)"
    options:
      method: set_enum_entries
  reflection.is_class_loaded:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Report whether a UClass is currently loaded in the editor (loaded), whether it exists/is loadable (exists), and its owning module + that module's load state. Distinguishes 'not loaded yet' from 'does not exist'. Params: className (short name, /Script/<Module>.<Class>, or BP class path) (#689)"
    options:
      method: is_class_loaded
  reflection.is_module_loaded:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Report whether a named module is currently loaded. Params: moduleName (#689)"
    options:
      method: is_module_loaded
  reflection.list_loaded_modules:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Enumerate modules with runtime load state. Params: filter? (case-insensitive substring), loadedOnly? (default false), cursor?, limit?. Returns modules[{name, loaded, gameModule}] + totalLoaded/totalModules (#689)"
    options:
      method: list_loaded_modules
  reflection.inspect_save_game:
    class_path: ue-mcp.bridge
    group: reflection
    description: "Load a SaveGame slot read-only and return its reflected UPROPERTY(SaveGame) values. Non-serializable properties are listed in skippedProperties instead of failing the call. Params: slotName, userIndex? (default 0)"
    options:
      method: inspect_save_game
  reflection.epic_get_class:
    class_path: ue-mcp.bridge
    group: reflection
    description: "[Epic editor_toolset.toolsets.object.ObjectTools] Returns the class of an Unreal object. Params: instance"
    options:
      method: epic_call_tool
  reflection.epic_get_properties:
    class_path: ue-mcp.bridge
    group: reflection
    description: "[Epic editor_toolset.toolsets.object.ObjectTools] Returns the values of one or more properties on an object. Params: instance, properties"
    options:
      method: epic_call_tool
  reflection.epic_list_properties:
    class_path: ue-mcp.bridge
    group: reflection
    description: "[Epic editor_toolset.toolsets.object.ObjectTools] Returns a list of properties that are on the specified object. Params: instance"
    options:
      method: epic_call_tool
  reflection.epic_reset_properties:
    class_path: ue-mcp.bridge
    group: reflection
    description: "[Epic editor_toolset.toolsets.object.ObjectTools] Resets one or more properties on an object to their default values, removing any per-instance overrides. Params: instance, properties"
    options:
      method: epic_call_tool
  reflection.epic_search_subclasses:
    class_path: ue-mcp.bridge
    group: reflection
    description: "[Epic editor_toolset.toolsets.object.ObjectTools] Finds all subclasses of a given class. Params: base_class, class_name"
    options:
      method: epic_call_tool
  reflection.epic_set_properties:
    class_path: ue-mcp.bridge
    group: reflection
    description: "[Epic editor_toolset.toolsets.object.ObjectTools] Sets the values of properties on an object. Params: instance, values"
    options:
      method: epic_call_tool

  # ── gameplay ──
  gameplay.set_collision_profile:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Set the collision preset on every primitive component of a placed actor. Reports existed=true and unchanged=true when every component already used that profile. Rolls back through this same action with the profile that was there, addressed by actorPath, and marked lossy when more than one component was written since the record carries the FIRST one's profile. No record at all when the previous profile read as the 'Custom' sentinel, which names no profile the engine can look up: the response says so rather than emitting an inverse that would restore nothing. Params: actorLabel OR actorPath, profileName"
    options:
      method: set_collision_profile
  gameplay.set_simulate_physics:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Toggle physics simulation on every primitive component of a placed actor. simulate and enabled are the same parameter. Reports existed=true and unchanged=true when every component was already in that state. Rolls back through this same action with the previous flag, addressed by actorPath, and marked lossy when more than one component was written since the record carries the FIRST one's flag. Params: actorLabel OR actorPath, simulate (aka enabled)"
    options:
      method: set_simulate_physics
  gameplay.add_impulse:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Apply an impulse (or force with mode='force') to a (PIE) actor's simulating physics body so its motion can be observed over time - loop level(read_actor_motion) to sample the response. Params: actorLabel OR actorPath, impulse {x,y,z} (or force/vector), mode? (impulse|force), componentName?, boneName?, location? (apply at world point), velChange?/accelChange?, world? (editor|pie|auto). No inverse: the solver integrates the impulse immediately, so an opposite impulse cancels the velocity but not the motion, and the response says rollbackPossible=false with that reason. unchanged is MEASURED, from the body's linear and angular velocity read either side of the push, so mode='force' reports unchanged (a force is integrated over the next substep, not applied instantly) and so does a vector the solver absorbed. It does NOT mean a replay is safe: two calls are two impulses, which is why there is no already* flag (#676)"
    options:
      method: add_impulse
  gameplay.set_collision_enabled:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Set the collision mode on every primitive component of a placed actor: NoCollision | QueryOnly | PhysicsOnly | QueryAndPhysics. collisionType is accepted as the same parameter. Reports existed=true and unchanged=true when every component already held that mode. Rolls back through this same action with the previous mode, addressed by actorPath; marked lossy when more than one component was written, because the record carries the mode read from the FIRST of them and components that differed all come back with that one's. gameplay(set_collision) with a componentName is the per-component form. Params: actorLabel OR actorPath, collisionEnabled (aka collisionType)"
    options:
      method: set_collision_enabled
  gameplay.set_collision:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Unified collision authoring for a placed actor (actorLabel or actorPath) or a Blueprint component template (assetPath+componentName). Apply any of: collisionProfile, collisionEnabled (NoCollision|QueryOnly|PhysicsOnly|QueryAndPhysics), objectType (channel name), responseToAllChannels (Block|Overlap|Ignore), responses ({channel: Block|Overlap|Ignore}). Profile is applied first, then overrides. componentName optional for actors (defaults to all primitive components), required for Blueprint templates. Reports unchanged=true when the component already held everything the call wrote, compared against a full before/after read. The inverse is this same action with the previous values: writing a profile or responseToAllChannels resets the whole response table, so both make the record carry every channel, not only the ones named. A component whose profile read as the 'Custom' sentinel comes back through its collision mode, object type and full response table instead, since that name resolves to no profile; that and a multi-component target are the two cases marked lossy. Params: actorLabel OR actorPath OR assetPath, componentName?, collisionProfile?, collisionEnabled?, objectType?, responseToAllChannels?, responses? (#545)"
    options:
      method: set_collision
  gameplay.set_physics_properties:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Set mass/damping/gravity on every primitive component body of a placed actor. Only the fields passed are written, and only those are in the inverse, which is this same action carrying the values that were there. A call that passed none of them reports existed=true and unchanged=true rather than a bare success. Marked lossy when more than one component body was written, because the record carries the values read from the FIRST of them. Params: actorLabel OR actorPath, mass?, linearDamping?, angularDamping?, enableGravity?"
    options:
      method: set_physics_properties
  gameplay.rebuild_navigation:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Rebuild navmesh. No inverse: the navmesh is derived data recomputed from the level, so nothing restores the previous one and the response says rollbackPossible=false. Params: none"
    options:
      method: rebuild_navigation
  gameplay.find_nav_path:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Synchronous nav-path query between two world points. Returns valid/partial/length plus the polyline. The standard 'why doesn't my AI move?' diagnostic. Params: start (Vec3), end (Vec3), pathfindingContext? (actor label) OR pathfindingContextPath? (object path) - uses its agent + filter (#424/#983)"
    options:
      method: find_nav_path
  gameplay.list_nav_invokers:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Enumerate actors carrying a NavigationInvokerComponent + their tile generation/removal radii. Diagnoses 'no navmesh in this region' caused by missing or mis-sized invokers. Params: none (#424)"
    options:
      method: list_nav_invokers
  gameplay.get_navmesh_info:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Query nav system. Params: none"
    options:
      method: get_navmesh_info
  gameplay.project_to_nav:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Project point to navmesh. Params: location, extent?"
    options:
      method: project_point_to_navigation
  gameplay.spawn_nav_modifier:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Place a NavModifierVolume. The brush is built for real (a bare spawn leaves an AVolume with no geometry, which the engine skips entirely - it requires both Brush and AreaClass). extent is a HALF-size in world units, default 100. areaClass defaults to NavArea_Null, which cuts a hole in the navmesh; pass NavArea_Obstacle to make the region costly instead, or any UNavArea subclass. Params: location, extent?, areaClass?, label?, scale?, onConflict?"
    options:
      method: spawn_nav_modifier_volume
  gameplay.create_input_action:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create InputAction. Params: name, packagePath?, valueType?"
    options:
      method: create_input_action
  gameplay.create_input_mapping:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create InputMappingContext. Params: name, packagePath?"
    options:
      method: create_input_mapping_context
  gameplay.list_input_assets:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List input assets. Every InputAction and InputMappingContext is one row under `assets`, tagged with its kind and sorted by object path; inputActions and inputMappingContexts hold this page's rows of each kind, and inputActionCount / inputMappingContextCount count the whole listing. Params: directory?, recursive?, cursor?, limit?"
    options:
      method: list_input_assets
  gameplay.read_imc:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read InputMappingContext mappings. Params: imcPath. Returns {key, inputAction, triggers[], modifiers[]}."
    options:
      method: read_imc
  gameplay.get_applied_imcs:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read applied Input Mapping Contexts (name, path, priority, registrationCount), highest priority first - the order Enhanced Input resolves in. Covers EVERY running PIE world by default, so a multiplayer client can be inspected, not just the primary/server world; narrow with pieInstance and/or playerIndex. Pass mappingContext for a direct hasRequestedContext yes/no. A remote controller with no LocalPlayer says so rather than reporting an empty list. BREAKING in 1.1.36: results are now nested under worlds[].players[] (was a flat appliedContexts[] for one world), the array is mappingContexts (was appliedContexts), each entry uses path (was imc), and a call with PIE stopped returns success with worldCount 0 instead of erroring. Params: pieInstance?, playerIndex?, mappingContext?, includeActions? (#604/#778)"
    options:
      method: get_input_mapping_contexts
  gameplay.list_input_mappings:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Alias for read_imc. List key→action bindings with triggers/modifiers. Params: imcPath"
    options:
      method: list_imc_mappings
  gameplay.add_imc_mapping:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add key mapping to IMC. Idempotent: the same (action, key) pair reports existed. Rolls back through remove_imc_mapping on that pair, with nothing lost - the mapping is created bare. Params: imcPath, inputActionPath, key"
    options:
      method: add_imc_mapping
  gameplay.set_mapping_modifiers:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Set modifiers/triggers on an IMC mapping. Each modifier OR trigger is either {type:'<ShortName>', <prop>:<val>} or {class:'/Script/Module.Class', properties:{...}} - the class form resolves any custom UInputModifier/UInputTrigger subclass (e.g. type:'Hold' | class:'/Script/EnhancedInput.InputTriggerHold', with HoldTimeThreshold). Unresolvable trigger specs are reported in failedTriggers and never leave a null entry (#649/#725). Reports unchanged=true when the rebuilt lists hold the same classes carrying the same property values, compared as an export of every object's every property either side of the write - so retuning a Hold threshold on the same trigger class reads as an update rather than as a no-op. The inverse replays this call with the classes that were on the mapping, each rebuilt at its CLASS DEFAULTS, so it is marked lossy whenever the previous objects had tuned properties. Params: imcPath, mappingIndex?, modifiers?, triggers?"
    options:
      method: set_mapping_modifiers
  gameplay.remove_imc_mapping:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Remove an IMC mapping. Idempotent when selected by (inputActionPath + key): a pair that is not there reports alreadyDeleted=true, so a replayed rollback is safe. Rolls back through add_imc_mapping on the same pair, marked lossy because the mapping is re-added at the END of the list and its modifiers and triggers are not carried. Params: imcPath, mappingIndex? | (inputActionPath? + key?) (#158)"
    options:
      method: remove_imc_mapping
  gameplay.set_imc_mapping_key:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Rebind an IMC mapping to a new key. Reports unchanged=true when the mapping already used that key, and otherwise rolls back to the previous key by mappingIndex, which this call does not move. Params: imcPath, newKey, mappingIndex? | key? | inputActionPath? (#158)"
    options:
      method: set_imc_mapping_key
  gameplay.set_imc_mapping_action:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Retarget an IMC mapping to a different InputAction. Reports unchanged=true when the mapping already used that action, and otherwise rolls back to the previous action by mappingIndex. A mapping that had NO action before cannot be rolled back, because the inverse needs a loadable path; the response says so. Params: imcPath, newInputActionPath, mappingIndex? | key? | inputActionPath? (#158)"
    options:
      method: set_imc_mapping_action
  gameplay.read_input_action:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read an InputAction back: valueType, actionDescription, the four consumption/pause flags, accumulationBehavior, playerMappableKeySettings, and the ACTION's own instanced triggers[] and modifiers[] with each one's class, current property values and objectPath. Nothing could read an InputAction before, so an authored one could not be verified. There are no typed setters for any scalar here: they are plain UPROPERTYs, so write them with editor(set_property) at the returned objectPath, and add or remove trigger/modifier entries with set_action_triggers. These arrays are the action's own, applied after every mapping's; read_imc reports the per-mapping ones. Params: inputActionPath. Returns inputActionPath, name, objectPath, valueType, triggers[], modifiers[], triggerCount, modifierCount"
    options:
      method: read_input_action
  gameplay.set_action_triggers:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Replace the ACTION's own Triggers and/or Modifiers arrays on an InputAction. asset(set_property) cannot do this: both are Instanced TArrays and the JSON property setter only assigns an object it can load from a path, so it can neither mint nor destroy the subobjects they hold. Each entry is {type:'Hold', HoldTimeThreshold:0.5} or {class:'/Script/EnhancedInput.InputTriggerHold', properties:{...}}, the same grammar set_mapping_modifiers takes, except an unresolvable class or an unknown property is an ERROR here rather than a silent drop, and both arrays are fully built before either is assigned. A supplied array replaces that array wholesale; an omitted one is left alone unless clear is true, which empties both. Repeating the same call reports unchanged=true. Rollback restores the exact prior arrays. Params: inputActionPath, triggers?, modifiers?, clear?. Returns triggerCount, modifierCount, triggersReplaced, modifiersReplaced, unchanged"
    options:
      method: set_action_triggers
  gameplay.apply_mapping_context:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Apply an InputMappingContext to a LIVE PIE player at a priority, the write half get_applied_imcs never had. Calls the local player's Enhanced Input subsystem, which is an engine call rather than a property write, and forces the control-mapping rebuild immediately so the very next get_applied_imcs sees it. Idempotent: already applied at the same priority reports alreadyApplied=true and changes nothing; applied at a different priority reports previousPriority and rolls back to it. This is runtime state, so it affects the live player only and is gone when PIE stops. Params: mappingContext, priority?, pieInstance?, playerIndex?. Returns mappingContext, priority, alreadyApplied, pieInstance, playerIndex, worldPath, scope"
    options:
      method: apply_mapping_context
  gameplay.remove_mapping_context:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Remove an InputMappingContext from a LIVE PIE player. The inverse of apply_mapping_context and its rollback target. Idempotent: a context that is not applied reports alreadyAbsent=true rather than erroring, and rollback re-applies it at the priority it was removed from. Runtime state only, gone when PIE stops. Params: mappingContext, pieInstance?, playerIndex?. Returns mappingContext, alreadyAbsent, removedPriority, pieInstance, playerIndex, worldPath"
    options:
      method: remove_mapping_context
  gameplay.get_action_value:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read what an InputAction is worth on a LIVE PIE player right now: value {x,y,z}, magnitude, nonZero, triggerEvent, elapsedProcessedTime, elapsedTriggeredTime, lastTriggeredWorldTime and the keys currently mapped to it. This is the only way to prove input arrived, because FInputActionInstance is transient per-player state and its accessors are plain C++ methods, not UFUNCTIONs, so editor(invoke_function) cannot reach them. Omit inputActionPath for every action the player currently has bound. An action with hasInstanceData=false is bound by no applied context, which is a different failure from bound-but-reading-zero. The engine zeroes value outside a Triggered event, so read triggerEvent before concluding nothing arrived. Params: inputActionPath?, pieInstance?, playerIndex?. Returns actions[], actionCount, pieInstance, playerIndex, worldPath"
    options:
      method: get_action_value
  gameplay.validate_input:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Audit Enhanced Input assets for the failures that produce no error anywhere: a mapping with no InputAction, a key name that is not a registered FKey, a duplicate action plus key pair inside one context, a null entry in a Triggers or Modifiers array (which fails asset validation on save), an empty mapping context, an InputAction no context maps, and the quiet one, an Axis2D/Axis3D action driven by a 1D or boolean key with no Swizzle Input Axis Values modifier on the mapping or the action, which reads zero on every component past X forever. Pass imcPath for one context, or sweep every InputMappingContext under directory. Read-only. Params: imcPath?, directory?, recursive?, limit?. Returns scanned[], contextCount, mappingsChecked, unmappedActions[], problems[] ({severity, code, message, asset, mappingIndex}), errorCount, warningCount, valid"
    options:
      method: validate_input
  gameplay.list_behavior_trees:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List behavior trees, sorted by object path. Params: directory?, recursive?, cursor?, limit?"
    options:
      method: list_behavior_trees
  gameplay.get_behavior_tree_info:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Inspect a BehaviorTree asset: root node, blackboard asset, every blackboard key (name, type, instanceSynced, description) plus the keys inherited from the blackboard's parent chain, and the node/decorator counts. Params: assetPath (#887)"
    options:
      method: get_behavior_tree_info
  gameplay.read_behavior_tree_graph:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Walk a BT asset: composites, tasks, decorators, services, each with a stable nodePath ('Root.Children[0].Decorators[1]') you pass to read_bt_node_properties / set_bt_node_property. Every decorator reports its full runtime config - blackboardKey {selectedKeyName, selectedKeyType}, basicOperation (Set|NotSet), arithmeticOperation, textOperation, flowAbortMode, notifyObserver, inverseCondition, int/float/stringValue and staticDescription - which is what answers 'the tree is running but the wrong branch fires'. Set includeProperties for each node's own UPROPERTY values, propertyNames to narrow those, includeInherited to keep UBTNode's own fields. Params: assetPath, includeProperties?, includeInherited?, propertyNames? (#888)"
    options:
      method: read_behavior_tree_graph
  gameplay.read_bt_node_properties:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Filtered read of BT node UPROPERTY values: pick nodes by nodeClass, nodeName, nodePath or kind and get back only their own properties, instead of an unfiltered whole-asset dump. Narrow further with propertyNames. A UE 5.8 FValueOrBBKey_* field (AcceptableRadius, WaitTime, FilterClass) is unpacked into {defaultValue, key, isBound, baseClass, text}. Params: assetPath, nodeClass?, nodeName?, nodePath?, kind?, propertyNames?, includeInherited? (#919)"
    options:
      method: read_bt_node_properties
  gameplay.list_bt_tasks:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Inventory BTTask nodes with their name, class, parent path and FilterClass. Pass assetPath for one tree, or directory (+recursive) to sweep many. On UE 5.8 BTTask_MoveTo.FilterClass is a FValueOrBBKey_Class, so it is reported as {defaultValue, defaultValueName, key, isBound, baseClass, text} - the shape get_editor_property reads as empty. filterClassOnly keeps only tasks that declare one. Params: assetPath?, directory?, recursive?, taskClass?, filterClassOnly?, limit? (#940)"
    options:
      method: list_bt_tasks
  gameplay.set_bt_node_property:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Scoped write onto one owned BT node subobject. Pick the node with nodePath (from read_behavior_tree_graph), nodeName, nodeClass or kind - the write refuses to run unless exactly one node matches - then set property + value, or a properties map of dotted/indexed paths ('Lines[1].Text', 'BlackboardKey.SelectedKeyName'). A scalar aimed at a UE 5.8 FValueOrBBKey_* field lands on its DefaultValue; an object writes the struct's own fields, so {Key: 'MyKey'} binds it to a blackboard key. Every target is read back and reported with its previous value. Params: assetPath, nodePath?, nodeName?, nodeClass?, kind?, property?, value?, properties? (#919)"
    options:
      method: set_bt_node_property
  gameplay.list_bt_graph_nodes:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List the EDITOR-GRAPH nodes of a BehaviorTree, which is the layer add_bt_node / move_bt_node / remove_bt_node address. Each entry carries its guid (the primary key: stable across recompiles and unaffected by inserting a sibling), category (root|composite|task|decorator|service), class, parentGuid, indexInParent, childGuids in execution order, decoratorGuids, serviceGuids, graph position and any node error. runtimePath cross-references the same node's read address ('Root.Children[0]') so one listing serves both surfaces. A tree that has never been opened in the BT editor reports hasGraph false and no nodes; add_bt_node seeds the graph. Params: assetPath (#889)"
    options:
      method: list_bt_graph_nodes
  gameplay.add_bt_node:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add a composite, task, decorator or service to a BehaviorTree's editor graph and recompile it into the runnable tree. This is the authoring half that reflection cannot reach: UBehaviorTreeGraph::UpdateAsset, which turns the graph into the UBTCompositeNode tree, is editor-only and unexposed, so nodes spawned by script alone never become a valid tree. parent takes a guid from list_bt_graph_nodes, a runtime address from read_behavior_tree_graph, a unique node name, or 'root' (the default). Decorators and services attach as subnodes of their parent rather than as children. index places the node among its siblings, which is the execution order a Selector or Sequence is defined by; on a SimpleParallel it picks the output (0 = main task, 1 = background). properties writes UPROPERTY values (a bare number aimed at a UE 5.8 FValueOrBBKey_* field lands on its DefaultValue), and blackboardKeys points FBlackboardKeySelector fields at named blackboard keys, resolved against the tree's blackboard. Params: assetPath, nodeClass, nodeCategory?, parent?, index?, nodeName?, properties?, blackboardKeys? (#889)"
    options:
      method: add_bt_node
  gameplay.move_bt_node:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Reconnect a BehaviorTree node to a different parent, reorder it among its siblings, or both, then recompile. Pass parent to reparent and index to reorder; at least one is required. Child order is what a Selector or Sequence executes by, so this is behaviour rather than layout. Decorators and services move between their owners' subnode lists. Moving a node under itself or its own descendant is refused, and the whole subtree travels with it. Returns the parent's siblingOrder after the compile. Params: assetPath, node, parent?, index? (#947)"
    options:
      method: move_bt_node
  gameplay.remove_bt_node:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Delete a node from a BehaviorTree's editor graph and recompile. A composite takes its whole branch with it, so the compiled tree never reports nodes that no longer run. Decorators and services detach from their owner. The root node cannot be removed. Returns every removed node's guid and class. Params: assetPath, node (#889)"
    options:
      method: remove_bt_node
  gameplay.set_bt_task_property:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Write a property on one BT task node, FilterClass included. Same selector and dotted-path rules as set_bt_node_property. Retarget a MoveTo filter with property='FilterClass' and a class path, a short class name or a Blueprint asset path; pass null to clear it, or {Key: 'MyKey'} to bind it to a blackboard key. The new value is read back and returned. Params: assetPath, nodePath?, nodeName?, nodeClass?, property?, value?, properties? (#940)"
    options:
      method: set_bt_task_property
  gameplay.create_blackboard:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create Blackboard. Params: name, packagePath?"
    options:
      method: create_blackboard
  gameplay.add_blackboard_key:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add a typed key to a Blackboard asset. baseClass types an Object/Class key - Behaviour Tree nodes filter on it, so an untyped key silently will not bind. For keyType=Enum pass the enum via enumType (or baseClass). Idempotent: a key of that name already on the Blackboard reports existed=true and emits no record, so a replay cannot delete a key it did not create. A key this call DID declare rolls back exactly, through remove_blackboard_key on the same name, and that action is itself idempotent on a name it cannot find. Params: blackboardPath, keyName, keyType (Bool|Int|Float|String|Name|Vector|Rotator|Object|Class|Enum), baseClass? (e.g. /Script/Engine.Actor), enumType? (#250)"
    options:
      method: add_blackboard_key
  gameplay.remove_blackboard_key:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Remove a key from a Blackboard asset by name. Idempotent. Rolls back through add_blackboard_key with the key type it carried, including the baseClass of an Object/Class key and the enum of an Enum key. Always marked lossy: that action takes a name and a type and nothing else, so bInstanceSynced and the editor-only description and category are not restored, and a key that was not the last one comes back appended with a new key ID (Behaviour Tree nodes bind by name, so they still resolve). A key whose type is not one of the ten add_blackboard_key accepts - a NativeEnum key, or any project-defined UBlackboardKeyType subclass - gets no record at all rather than one that would be refused on replay. Params: blackboardPath, keyName (#469)"
    options:
      method: remove_blackboard_key
  gameplay.set_blackboard_parent:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Set Parent on a BlackboardData asset (canonical UE child-of-parent pattern). Pass parentPath=\"None\" or omit to clear. autoPruneDuplicateKeys (default true) removes own-keys that the parent chain already defines so the BT compiler accepts the child. Params: blackboardPath, parentPath?, autoPruneDuplicateKeys? (default true) (#469)"
    options:
      method: set_blackboard_parent
  gameplay.read_blackboard:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read a Blackboard asset: parent path, ownKeys, inheritedKeys (walks the parent chain). Params: blackboardPath (#469)"
    options:
      method: read_blackboard
  gameplay.list_bt_node_classes:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Enumerate every concrete BehaviorTree node class on this build (composites, tasks, decorators, services). Filter by kind to narrow; an unrecognised kind is refused rather than answered with an empty palette. Every class is one row under `classes`, tagged with its kind and sorted by class path within each kind; composites/tasks/decorators/services hold this page's rows of each kind, and compositeCount/taskCount/decoratorCount/serviceCount count the whole build. Useful for discovering plugin-supplied decorator/task classes without grepping engine + plugin source. Params: kind? ('composite'|'task'|'decorator'|'service'), cursor?, limit? (#494)"
    options:
      method: list_bt_node_classes
  gameplay.set_behavior_tree_blackboard:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Rebind a BehaviorTree asset's BlackboardAsset reference. Params: behaviorTreePath, blackboardPath. The C++ field is protected so reflection is the only writable path (#250)"
    options:
      method: set_behavior_tree_blackboard
  gameplay.create_behavior_tree:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create behavior tree. Params: name, packagePath?, blackboardPath?"
    options:
      method: create_behavior_tree
  gameplay.create_eqs_query:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create EQS query. Params: name, packagePath?"
    options:
      method: create_eqs_query
  gameplay.list_eqs_queries:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List EQS queries. Params: directory?"
    options:
      method: list_eqs_queries
  gameplay.list_eqs_types:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List every EQS generator, test and context class this build has, with the short name the editor's dropdown shows (which add_eqs_generator and add_eqs_test both accept) and the full class path. Every class is one row under `types`, tagged with its kind and sorted by class path within each kind; generators/tests/contexts hold this page's rows of each kind, and generatorCount/testCount/contextCount count the whole build. Call this before authoring: the class names are long and guessing one costs a round trip. Params: filter? (case-insensitive substring), cursor?, limit?"
    options:
      method: list_eqs_types
  gameplay.read_eqs_query:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read an EQS query's structure back: every option with its generator, every test with its index, class, purpose (Filter|Score|FilterAndScore), filter type, scoring equation, and the objectPath that editor(set_property) uses to configure it. Also reports problems[] and runnable: an option with no generator produces no items, and a query whose tests are all filter-only scores every survivor equally so its 'best' item is arbitrary. This is how you verify authoring without opening the editor. Params: queryPath"
    options:
      method: read_eqs_query
  gameplay.add_eqs_generator:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add an option with a generator to an EQS query, which is what makes it produce items at all. generatorClass takes the short name ('ActorsOfClass', 'OnCircle', 'SimpleGrid') or a full class path; list_eqs_types lists them. Returns generatorObjectPath, which editor(set_property) uses to tune the generator (radius, extents, class filters). Two options with the same generator class are allowed, because two donuts at different radii is a real thing to want. Params: queryPath, generatorClass"
    options:
      method: add_eqs_generator
  gameplay.add_eqs_test:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add a test to one option of an EQS query. testClass takes the short name ('Distance', 'Trace', 'Pathfinding', 'Dot') or a full path. purpose sets what the test is for: filter discards items, score ranks them, both does each. Returns testObjectPath for editor(set_property), which is how ScoringFactor, FilterType, FloatValueMin/Max, ScoringEquation and the test's own parameters are set. Params: queryPath, testClass, optionIndex? (default 0), purpose? (filter|score|both)"
    options:
      method: add_eqs_test
  gameplay.remove_eqs_option:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Remove a whole option, generator and all its tests, from an EQS query. Without this an option could be added and never taken away, which left add_eqs_generator with no inverse. Idempotent: an index past the end reports alreadyRemoved rather than failing. Emits a rollback that restores the generator class, and says plainly that generator tuning and the option's tests are NOT restored by it. Params: queryPath, optionIndex"
    options:
      method: remove_eqs_option
  gameplay.remove_eqs_test:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Remove one test from an option by index. Reports remainingTests and warns that later indices shifted down, which matters when removing several in one pass. Params: queryPath, testIndex, optionIndex? (default 0)"
    options:
      method: remove_eqs_test
  gameplay.reorder_eqs_tests:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Reorder an option's tests. Test order matters in EQS: a cheap filter placed first discards items before an expensive test (a trace, a pathfind) ever runs on them. order is the current indices in the order you want them, and must be a full permutation, so a mistake is refused rather than silently dropping a test. Params: queryPath, order (number[]), optionIndex? (default 0)"
    options:
      method: reorder_eqs_tests
  gameplay.list_ai_agents:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List every actor in a live world carrying a BrainComponent, with its pawn and controller identity, running/paused state, current and root BehaviorTree, active node and blackboard asset. Call this FIRST: it is how you discover a target for the other BT runtime actions. A zero result reports how many actors were scanned and how many brains were found, because AI only exists in a game world. Params: world? (auto|pie|editor), pieInstance?, runningOnly?, behaviorTreeOnly?, classFilter?, limit?"
    options:
      method: list_ai_agents
  gameplay.get_bt_runtime:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read the LIVE execution path of a running BehaviorTree, which is what tells you whether an authored tree actually works. Returns the active node with its task status, the composite ancestor chain above it, current versus root tree (so a running subtree is visible), running/paused/restartPending/abortPending, the decorators and services currently active, and the engine's own DescribeActiveTasks dump. A tree must have been started first, by run_behavior_tree or the game's own BeginPlay; the first search runs on the next tick, so activeNode is null immediately after a start. Params: actorLabel OR actorPath, world? (auto|pie|editor), pieInstance?, includeAuxNodes?, includeDebugStrings?"
    options:
      method: get_bt_runtime
  gameplay.get_live_blackboard:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Dump every key on a running BlackboardComponent with its name, key id, type, instance-sync flag, the engine's own value description, and a typed value for Bool/Int/Float/String/Name/Vector/Rotator/Object/Class/Enum. Requires a blackboard to have been initialised, which happens when a tree with a BlackboardAsset starts. Pass key for one entry; a miss lists every real key name. Params: actorLabel OR actorPath, world?, pieInstance?, key?, verbosity? (onlyValue|keyWithValue|detailed|full)"
    options:
      method: get_live_blackboard
  gameplay.set_live_blackboard:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Write one key on a running blackboard through the typed SetValueAs accessor, so the key-change notification fires and observing decorators re-evaluate their branch. A raw property write would NOT notify, which is why this exists. Reports the previous and current value, changed, and alreadySet when the key already held it, and emits a rollback that writes the captured previous value back. Params: actorLabel OR actorPath, key, value, clear?, world?, pieInstance?"
    options:
      method: set_live_blackboard
  gameplay.run_behavior_tree:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Start a BehaviorTree on a live agent via AAIController::RunBehaviorTree, which also creates the BehaviorTree component and initialises the blackboard. Idempotent: the same tree already running returns alreadyRunning without restarting unless restartIfRunning is set. Rollback stops the tree, or restarts whatever was running before, marked lossy because a restart cannot restore execution position. Params: actorLabel OR actorPath, assetPath (the BehaviorTree), executionMode? (looped|singleRun), restartIfRunning?, world?, pieInstance?"
    options:
      method: run_behavior_tree
  gameplay.stop_behavior_tree:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Stop, restart, pause or resume a running BehaviorTree. Safe to call twice: a stop on a stopped tree returns alreadyStopped and a pause on a paused one alreadyPaused. Pause and resume roll back to each other; stop rolls back to a run of the tree that was running, marked lossy because the instance stack it destroyed cannot be re-entered. Params: actorLabel OR actorPath, mode? (stop|forced|restart|pause|resume), reason?, completeRestart?, world?, pieInstance?"
    options:
      method: stop_behavior_tree
  gameplay.read_perception:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read an AIPerceptionComponent, from a Blueprint's construction script or a live actor, and return every sense config with its class, maxAge, startsEnabled, affiliation flags, full parameter dump and objectPath. Tune any sense with editor(set_property) at that objectPath; there are deliberately no per-parameter setters. A problems[] array names the faults a setup cannot report about itself: no sense configs at all, every sense starting disabled, LoseSightRadius below SightRadius, all affiliation flags false, and no sight stimuli source anywhere in the world. Params: blueprintPath OR (actorLabel OR actorPath), componentName?, world?, pieInstance?"
    options:
      method: read_perception
  gameplay.remove_sense:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Delete one sense config from an AIPerceptionComponent template's Instanced SensesConfig array. set_property cannot do this because it cannot destroy instanced subobjects. Idempotent: an index past the end, or a sense that is not configured, returns alreadyRemoved rather than failing, so a rollback replays safely. Emits a configure_sense rollback carrying every value that differed from the class defaults, and reports rollbackIsLossy plus rollbackUncapturedProperties when a tuned value could not be represented. Params: blueprintPath, index? OR senseType? (Sight|Hearing|Damage|Touch|Team|Prediction|Blueprint), componentName?"
    options:
      method: remove_sense
  gameplay.get_perceived_actors:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List what an AI currently perceives, what it still remembers, and which of those are hostile, each with distance, stimulus age and last-known location. This reads PerceptualData, a bare C++ TMap that is not a UPROPERTY, so nothing else on this surface can reach it. Finds the component on the actor or on the AIController possessing it. An empty result explains the usual causes rather than returning a bare zero. Params: actorLabel OR actorPath, senseType?, world? (auto|pie|editor), pieInstance?"
    options:
      method: get_perceived_actors
  gameplay.check_perception:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Answer whether one actor perceives another right now, with the youngest stimulus age, the last known location, and how far that has drifted from the target's real position. Breaks the answer down per configured sense, so you can see which sense is carrying the detection and which is not firing. Uses HasAnyActiveStimulus and GetYoungestStimulusAge, which are not UFUNCTIONs and so cannot be reached through editor(invoke_function). Params: perceiverLabel OR perceiverPath, targetLabel OR targetPath, world?, pieInstance?"
    options:
      method: check_perception
  gameplay.report_noise_event:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Inject a hearing or damage stimulus into a live world so a perception setup can be exercised without a real game event. Reports which perception components in the world have that sense configured, with each listener's distance and, for hearing, whether the noise falls inside its HearingRange, so an event nobody could receive is distinguishable from a silent one. The stimulus is delivered on the AI system's next update, so read the outcome with check_perception or get_perceived_actors. Params: senseType? (hearing|damage, default hearing), location?, loudness? (default 1), maxRange?, instigatorLabel? OR instigatorPath?, tag?, targetLabel? OR targetPath? (damage only), amount? (damage only), hitLocation? (damage only), world?, pieInstance?"
    options:
      method: report_noise_event
  gameplay.run_eqs_query:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Run an EQS query against a live world and return the scored items, which is the only way to know an authored query does what you meant. Returns each item's score and location, plus actorLabel/actorPath for actor items, sorted by the engine's own ranking. Needs a world with an AI system, so PIE rather than the pure editor world. querierLabel names the actor the query runs FROM, which every querier-relative context resolves against; without one those contexts silently produce nothing. An empty result is not an error and explains itself: runMode 'all' returns only items that pass every filter test, so a filter left at its default range rejects nearly everything, while 'best' ranks instead and still returns one. Params: queryPath, querierLabel? OR querierPath?, runMode? (all|best|random, default all), world? (auto|pie|editor), limit? (default 50)"
    options:
      method: run_eqs_query
  gameplay.add_perception:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add an AIPerceptionComponent to a Blueprint and configure its senses. senses takes short names (Sight, Hearing, Damage, Touch, Team, Prediction), AISenseConfig_* names, or class paths - a component with no sense configs perceives nothing. Params: blueprintPath, senses?"
    options:
      method: add_perception_component
  gameplay.configure_sense:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add OR tune an AI perception sense config on the blueprint's AIPerceptionComponent. settings are applied whether the sense was just created or was already there, so 'add a sense then tune it' writes on the second call. Reports the three outcomes apart: created, existed+updated (changedProperties), existed+unchanged (unchangedProperties, asset untouched). An unknown settings key is refused with the valid property names and nothing is written; a value that will not convert restores what it had already applied. Params: blueprintPath, senseType (Sight|Hearing|Damage|Touch|Team|Prediction|Blueprint), settings? ({SightRadius: ...}), componentName?"
    options:
      method: configure_ai_perception_sense
  gameplay.get_state_tree_runtime:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read a running StateTreeComponent's active state names in PIE (the 'brain' state). Params: actorLabel OR actorPath, world? (default pie), componentName? (#654)"
    options:
      method: get_state_tree_runtime
  gameplay.create_state_tree:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create a StateTree with editor data, a schema and a root state, so states/tasks are authorable via statetree(*) and persist across save/load (#653). The schema is required for the tree to compile at all: omit it to take StateTreeComponentSchema, then StateTreeAIComponentSchema, then whatever concrete schema this editor has, and read schemaSource / schemaNote to see which was used. Refuses (and writes nothing) when a named schema does not resolve, or when the editor has no schema class at all, listing availableSchemas and naming the plugin to enable (#833). Params: name, packagePath?, schema?, onConflict?"
    options:
      method: create_state_tree
  gameplay.list_state_trees:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List StateTrees, sorted by object path. Params: directory?, cursor?, limit?"
    options:
      method: list_state_trees
  gameplay.add_state_tree_component:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add StateTreeComponent. Params: blueprintPath"
    options:
      method: add_state_tree_component
  gameplay.create_smart_object_def:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create SmartObjectDefinition. Pass defaultBehaviorClass to give it a default behavior definition, which is what makes every slot added later legal: the editor rejects a definition whose slot has no behavior and whose definition has no default. Returns definitionValid + defaultBehaviorCount (#833). Params: name, packagePath?, defaultBehaviorClass?, instanceProperties?, onConflict?"
    options:
      method: create_smart_object_definition
  gameplay.add_smart_object_component:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add SmartObjectComponent. Params: blueprintPath"
    options:
      method: add_smart_object_component
  gameplay.add_smart_object_slot:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Append a FSmartObjectSlotDefinition to a SmartObjectDefinition's Slots array. Pass behaviorClass to give the slot its behavior definition in the same call; without one the definition needs a default or the editor's asset check rejects the asset, which the response reports as definitionValid=false plus the exact fix. Params: assetPath, name?, offset? ({x,y,z}), rotation? ({pitch,yaw,roll}), tags? (array), behaviorClass?, instanceProperties?. Returns the new slotIndex. Rollback removes the slot (#416/#833)"
    options:
      method: add_smart_object_slot
  gameplay.set_smart_object_slot:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Mutate an existing slot's offset/rotation/tags. Params: assetPath, slotIndex, offset? ({x,y,z}), rotation? ({pitch,yaw,roll}), tags? (array). Omitted fields keep their current values (#416). Reports unchanged=true when the slot ends up identical, compared as exported struct text before and after. The inverse writes back exactly the fields this call overwrote, captured through the same type-checked accessors the writer uses, so a field supplied in the wrong JSON shape is neither written nor carried"
    options:
      method: set_smart_object_slot
  gameplay.remove_smart_object_slot:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Remove a slot by index. Idempotent: out-of-range returns alreadyDeleted=true. Removing the LAST slot rolls back through add_smart_object_slot, which appends it to the same index (lossy on the slot BehaviorDefinitions, which that action can only restore one class of, with no instance values). Removing any other slot has no inverse, because re-adding would append it to the end and leave every later slotIndex shifted; the full removed slot is reported as removedSlot for manual recovery. Params: assetPath, slotIndex (#416)"
    options:
      method: remove_smart_object_slot
  gameplay.list_smart_object_slots:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List slots on a SmartObjectDefinition with index, offset, rotation, and raw text. Params: assetPath (#416)"
    options:
      method: list_smart_object_slots
  gameplay.add_smart_object_slot_behavior:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Attach a behavior definition (UBehaviorDefinition asset or class) to a slot's BehaviorDefinitions array. Pass instanceProperties to seed UPROPERTYs on a freshly-spawned class-instance. No inverse: nothing removes an entry from a slot BehaviorDefinitions array, and removing the slot would undo more than this did, so the response says rollbackPossible=false and names the behaviorIndex it added. Params: assetPath, slotIndex, behaviorClass (asset path or class path), instanceProperties? (#416)"
    options:
      method: add_smart_object_slot_behavior
  gameplay.add_smart_object_default_behavior:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Add a behavior definition to the SmartObjectDefinition's DefaultBehaviorDefinitions, the list every slot falls back to when it provides none of its own. This is the definition-wide half of add_smart_object_slot_behavior, and it is what makes an existing definition that already carries bare slots pass the editor's asset check. Returns definitionValid and, when slots are still short, the indices that are. Params: assetPath, behaviorClass (asset path or class path), instanceProperties? (#833)"
    options:
      method: add_smart_object_default_behavior
  gameplay.create_game_mode:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create GameMode BP. parentClass must derive from GameModeBase (short name, /Script path, or a Blueprint asset path). Params: name, packagePath?, parentClass?"
    options:
      method: create_game_mode
  gameplay.create_game_state:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create GameState BP. parentClass must derive from GameStateBase. Params: name, packagePath?, parentClass?"
    options:
      method: create_game_state
  gameplay.create_player_controller:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create PlayerController BP. parentClass must derive from PlayerController. Params: name, packagePath?, parentClass?"
    options:
      method: create_player_controller
  gameplay.create_player_state:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create PlayerState BP. parentClass must derive from PlayerState. Params: name, packagePath?, parentClass?"
    options:
      method: create_player_state
  gameplay.create_hud:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Create HUD BP. parentClass must derive from HUD. Params: name, packagePath?, parentClass?"
    options:
      method: create_hud
  gameplay.set_world_game_mode:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Set level GameMode override. Params: gameModeClass (or legacy gameModePath)"
    options:
      method: set_world_game_mode
  gameplay.get_framework_info:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Get level framework classes. Params: none"
    options:
      method: get_game_framework_info
  gameplay.ensure_mass_entity_config:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Idempotently create or update a MassEntityConfigAsset and author its ordered trait list. Needs the MassGameplay/MassSpawner plugins enabled, otherwise the call fails saying so. Each traits[] entry is {class (a concrete UMassEntityTraitBase subclass, or traitClass), properties? (fields written onto the trait instance, dotted paths allowed)}; the order is the asset's order and duplicate classes are refused. Every property is validated against a transient copy of the trait first, so a bad property late in the list leaves nothing half-authored, and the whole write runs inside one transaction. An existing asset may only be extended: fewer traits than it has, or a different class at an existing index, is refused as destructive rather than applied. onConflict is skip|error|update (default update). Params: assetPath OR (name + packagePath?), traits, onConflict?. Returns assetPath, traits (count), propertiesSet, and created/existed/updated"
    options:
      method: ensure_mass_entity_config
  gameplay.read_mass_entity_config:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read back a MassEntityConfigAsset authored by ensure_mass_entity_config: its ordered traits and every non-transient property on each one, exported as text (object and soft-object properties come back as asset paths). Needs the MassGameplay/MassSpawner plugins enabled. Params: assetPath. Returns assetPath, assetClass, traitClasses[] (class paths in order), traits[] ({index, classPath, className, properties}) and traitCount"
    options:
      method: read_mass_entity_config
  gameplay.list_mass_types:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "List the concrete UMassEntityTraitBase and UMassProcessor subclasses this editor has loaded, which is what ensure_mass_entity_config's traits[].class accepts and there was no way to discover. Each entry carries className, classPath, module, parentClass and the CDO objectPath, plus validTargetConfig for a trait and processingPhase/executionFlags/autoRegister/executionOrder for a processor. Those are all config UPROPERTYs on the default object, so tune them with editor(set_property) at objectPath; no typed setters are shipped. Enumerates LOADED classes only, so a Mass module nothing has referenced yet contributes nothing. Needs the MassGameplay and MassEntity plugins. Params: kind?, filter?, limit?. Returns traits[], processors[], traitCount, processorCount, unavailable[], note"
    options:
      method: list_mass_types
  gameplay.remove_mass_trait:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Remove one trait from a MassEntityConfigAsset's Config.Traits, the half of trait CRUD nothing could do. ensure_mass_entity_config refuses to shrink a config on purpose, and asset(set_property) cannot help because Config.Traits is an Instanced TArray whose subobjects a property write can neither mint nor destroy. Select with traitClass (idempotent: removes the first trait of that class, and a second call reports alreadyRemoved=true) or index (positional: removes whatever currently sits there). Removing the LAST trait rolls back exactly, through ensure_mass_entity_config with the captured prior list; removing an earlier one cannot, because ensure_mass_entity_config only appends, so the result says so and returns priorTraits plus the two-call recipe instead of a rollback that would fail. Params: assetPath, traitClass? OR index?. Returns assetPath, removedIndex, removedClass, traitCount, alreadyRemoved, rollbackAvailable"
    options:
      method: remove_mass_trait
  gameplay.reorder_mass_traits:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Set the order of a MassEntityConfigAsset's Config.Traits. Trait order is the order the entity template is built in, and nothing else could change it: ensure_mass_entity_config refuses an existing class that differs from the requested one at the same index. order is the current trait indices in the order wanted and must be a full permutation, validated in whole before anything moves. An identity permutation reports unchanged=true. Rollback is the inverse permutation, so this is an exact undo. Params: assetPath, order. Returns assetPath, traitCount, traitClasses[], unchanged"
    options:
      method: reorder_mass_traits
  gameplay.validate_mass_entity_config:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Audit a MassEntityConfigAsset and its whole Config.Parent chain: parent cycles, a null or wrong-typed trait entry, an abstract/deprecated/reinstanced trait class, a trait class provided twice (traits are combined uniquely, so the second copy is dead), an unset object or soft-object reference on a trait (the usual reason a Mass agent spawns and renders nothing) and an entirely empty config. Read-only, and it does NOT run the engine's own FMassEntityConfig ValidateEntityTemplate, which is a C++-only API that would need MassSpawner linked. Every reported trait carries its objectPath for editor(set_property). Params: assetPath. Returns assetPath, parentChain[], traits[], traitCount, problems[] ({severity, code, message, config, traitIndex, objectPath}), errorCount, warningCount, valid"
    options:
      method: validate_mass_entity_config
  gameplay.query_zone_graph:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Query the BUILT Zone Graph in a world. The graph lives in AZoneGraphData.ZoneStorage as parallel index arrays, where a lane names a half-open range into LanePoints and another into LaneLinks, so a raw editor(get_property) dump of it is technically reachable and unusable; this resolves the indirection. queryMode is summary (lane/zone counts and tagsInUse), lanes (index, zoneIndex, width, length, tags, start and end point), lane (one lane's full polyline plus its linked lanes, needs laneIndex) or nearest (closest point on any lane to location, within radius). Filter by tag NAME with tags[]. Zone SHAPE authoring is not here and does not need to be: spawn AZoneShape with level(spawn_actor), write its Points with editor(set_property), and call SetShapeType/SetTags/SetPolygonRoutingType with editor(invoke_object_function). If the world has shapes but no lanes then the graph has not been built, and the note says how to trigger it. Needs the ZoneGraph plugin. Params: queryMode?, laneIndex?, location?, radius?, tags?, limit?, world?, actorLabel?, actorPath?. Returns data[], dataActorCount, laneCount, zoneShapeActorCount, plus found/laneIndex/distance/nearestPoint for nearest"
    options:
      method: query_zone_graph
  gameplay.get_navmesh_details:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "Read RecastNavMesh generation params (cellSize, agentHeight, maxStepHeight, etc.). Params: none (#163)"
    options:
      method: get_navmesh_details
  gameplay.epic_add_body:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Adds a new empty body for the given bone. Params: physicsAsset, boneName"
    options:
      method: epic_call_tool
  gameplay.epic_add_constraint:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Adds a new constraint between two bodies. Both bodies must already exist. Params: physicsAsset, bone1Name, bone2Name"
    options:
      method: epic_call_tool
  gameplay.epic_add_tag:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic GameplayTagsToolset.GameplayTagsToolset] Adds a new gameplay tag to the project. This should ONLY be called after getting explicit direction or permission from the user. Params: tagName, comment?, tagSource"
    options:
      method: epic_call_tool
  gameplay.epic_create_from_mesh:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Creates a physics asset from a skeletal mesh, auto-generating collision bodies for each bone. The asset is placed in the same folder as the mesh with the suffix \"_PhysicsAsset\". Params: meshPath, bAssignToMesh"
    options:
      method: epic_call_tool
  gameplay.epic_find_referencers_by_tag:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic GameplayTagsToolset.GameplayTagsToolset] Returns assets that reference a gameplay tag. Params: tagName"
    options:
      method: epic_call_tool
  gameplay.epic_get_blackboard:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic aimodule_toolset.toolsets.behavior_tree.BehaviorTreeTools] Returns the blackboard asset for this behavior tree. Params: behavior_tree"
    options:
      method: epic_call_tool
  gameplay.epic_get_body_mass_scale:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Returns the mass-scale multiplier for the given body. Params: physicsAsset, boneName"
    options:
      method: epic_call_tool
  gameplay.epic_get_body_names:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Returns the bone name for each rigid body in a physics asset. Params: physicsAsset"
    options:
      method: epic_call_tool
  gameplay.epic_get_body_physics_mode:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Returns the physics simulation mode for the given body. Params: physicsAsset, boneName"
    options:
      method: epic_call_tool
  gameplay.epic_get_body_shapes:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Returns all collision shapes assigned to a body. Params: physicsAsset, boneName"
    options:
      method: epic_call_tool
  gameplay.epic_get_children:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic aimodule_toolset.toolsets.behavior_tree.BehaviorTreeTools] Returns direct child nodes of a composite node. Params: composite"
    options:
      method: epic_call_tool
  gameplay.epic_get_condition_description:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic WorldConditionsToolset.WorldConditionTools] Returns a human-readable description of a single world condition. The condition must be passed as an FInstancedStruct containing an FWorldConditionBase-derived struct. Params: condition"
    options:
      method: epic_call_tool
  gameplay.epic_get_constraints:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Returns all constraints in the physics asset with their current angular limits. Params: physicsAsset"
    options:
      method: epic_call_tool
  gameplay.epic_get_node_depth:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic aimodule_toolset.toolsets.behavior_tree.BehaviorTreeTools] Returns the tree depth of a node by its list_nodes index. Params: behavior_tree, node_index"
    options:
      method: epic_call_tool
  gameplay.epic_get_node_depths:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic aimodule_toolset.toolsets.behavior_tree.BehaviorTreeTools] Returns tree depths for all nodes, matching list_nodes order. Params: behavior_tree"
    options:
      method: epic_call_tool
  gameplay.epic_get_query_description:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic WorldConditionsToolset.WorldConditionTools] Returns a human-readable description of a world condition query. Params: queryDefinition"
    options:
      method: epic_call_tool
  gameplay.epic_get_root_decorators:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic aimodule_toolset.toolsets.behavior_tree.BehaviorTreeTools] Returns root-level decorators on this tree. Params: behavior_tree"
    options:
      method: epic_call_tool
  gameplay.epic_get_subtree:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic aimodule_toolset.toolsets.behavior_tree.BehaviorTreeTools] Returns the sub-BT asset referenced by a RunBehavior task. Params: node"
    options:
      method: epic_call_tool
  gameplay.epic_get_tag_info:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic GameplayTagsToolset.GameplayTagsToolset] Returns detailed information about a specific gameplay tag. Params: tagName"
    options:
      method: epic_call_tool
  gameplay.epic_list_nodes:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic aimodule_toolset.toolsets.behavior_tree.BehaviorTreeTools] Returns a flat list of all node UObjects in tree order. Order: root decorators, then DFS (composite, services, per-child decorators, child node). Params: behavior_tree"
    options:
      method: epic_call_tool
  gameplay.epic_list_tags:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic GameplayTagsToolset.GameplayTagsToolset] Returns gameplay tags registered in the project. Params: parentTag"
    options:
      method: epic_call_tool
  gameplay.epic_remove_body:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Removes the body for the given bone along with any constraints that reference it. Raises a script error if PhysicsAsset is null or no body exists for BoneName. Params: physicsAsset, boneName"
    options:
      method: epic_call_tool
  gameplay.epic_remove_constraint:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Removes the constraint between two bodies. Params: physicsAsset, bone1Name, bone2Name"
    options:
      method: epic_call_tool
  gameplay.epic_remove_shape:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Removes a collision primitive from a body by name. Params: physicsAsset, boneName, shapeName"
    options:
      method: epic_call_tool
  gameplay.epic_remove_tag:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic GameplayTagsToolset.GameplayTagsToolset] Removes a gameplay tag from the project. This should ONLY be called after getting explicit direction or permission from the user. Params: tagName"
    options:
      method: epic_call_tool
  gameplay.epic_rename_tag:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic GameplayTagsToolset.GameplayTagsToolset] Renames a gameplay tag, updating all references in the project. This should ONLY be called after getting explicit direction or permission from the user. Params: oldTagName, newTagName"
    options:
      method: epic_call_tool
  gameplay.epic_set_body_mass_scale:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Sets the mass-scale multiplier for the given body. Params: physicsAsset, boneName, massScale"
    options:
      method: epic_call_tool
  gameplay.epic_set_body_physics_mode:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Sets the physics simulation mode for the given body. Params: physicsAsset, boneName, mode"
    options:
      method: epic_call_tool
  gameplay.epic_set_box:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Adds or replaces a box collision primitive on a body. If any shape with the given name already exists on the body it is removed first. Params: physicsAsset, boneName, shapeName, center, rotation, extentX, extentY, extentZ"
    options:
      method: epic_call_tool
  gameplay.epic_set_capsule:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Adds or replaces a capsule collision primitive on a body. If any shape with the given name already exists on the body it is removed first. The capsule's long axis is its local Z after applying Rotation. Params: physicsAsset, boneName, shapeName, center, rotation, radius, length"
    options:
      method: epic_call_tool
  gameplay.epic_set_constraint_limits:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Updates the angular limits for an existing constraint. Params: physicsAsset, info"
    options:
      method: epic_call_tool
  gameplay.epic_set_sphere:
    class_path: ue-mcp.bridge
    group: gameplay
    description: "[Epic PhysicsToolsets.PhysicsAssetToolset] Adds or replaces a sphere collision primitive on a body. If any shape with the given name already exists on the body it is removed first. Params: physicsAsset, boneName, shapeName, center, radius"
    options:
      method: epic_call_tool

  # ── gas ──
  gas.add_asc:
    class_path: ue-mcp.bridge
    group: gas
    description: "Add AbilitySystemComponent. Params: blueprintPath, componentName?"
    options:
      method: add_ability_system_component
  gas.create_attribute_set:
    class_path: ue-mcp.bridge
    group: gas
    description: "Create AttributeSet BP. Params: name, packagePath?"
    options:
      method: create_attribute_set
  gas.add_attribute:
    class_path: ue-mcp.bridge
    group: gas
    description: "Add attribute to set. Params: attributeSetPath, attributeName, defaultValue?"
    options:
      method: add_attribute
  gas.create_ability:
    class_path: ue-mcp.bridge
    group: gas
    description: "Create GameplayAbility BP. Params: name, packagePath?, parentClass?"
    options:
      method: create_gameplay_ability
  gas.set_ability_tags:
    class_path: ue-mcp.bridge
    group: gas
    description: "Set tags on ability. Each container passed is written whole, so the inverse is this same call carrying the tags that were in it; containers you do not pass are left alone and are not in the record. unchanged=true means the tags a written container ended up holding are the same set it already held, compared both ways, not merely that the caller passed nothing. Params: abilityPath, ability_tags?, cancel_abilities_with_tag?, block_abilities_with_tag?, activation_required_tags?, activation_blocked_tags?"
    options:
      method: set_ability_tags
  gas.create_effect:
    class_path: ue-mcp.bridge
    group: gas
    description: "Create GameplayEffect BP. Params: name, packagePath?, durationPolicy?"
    options:
      method: create_gameplay_effect
  gas.set_effect_modifier:
    class_path: ue-mcp.bridge
    group: gas
    description: "Add or update a modifier on a GameplayEffect, matched on attribute + operation. Overwriting an existing static magnitude rolls back to the value it held, qualified as SetName.Attribute so the inverse cannot land on another set's same-named attribute, and marked lossy because it comes back as a plain ScalableFloat constant. A magnitude that CHANGES WITH EFFECT LEVEL gets no record: it is probed at levels 1, 2 and 10, and one that moves is curve-table-backed, so writing a single constant would destroy the binding. APPENDING a new modifier has no inverse either, because no action removes a modifier from an effect; the response says rollbackPossible=false rather than offering a magnitude of zero as an undo. Params: effectPath, attribute, operation?, magnitude?"
    options:
      method: set_effect_modifier
  gas.create_cue:
    class_path: ue-mcp.bridge
    group: gas
    description: "Create GameplayCue. Params: name, packagePath?, cueType?"
    options:
      method: create_gameplay_cue
  gas.get_info:
    class_path: ue-mcp.bridge
    group: gas
    description: "Inspect GAS setup. Params: blueprintPath"
    options:
      method: get_gas_info
  gas.set_asc_defaults:
    class_path: ue-mcp.bridge
    group: gas
    description: "Wire an AttributeSet onto a Blueprint's ASC component (DefaultStartingData) so attributes exist at runtime. Idempotent: a set already wired reports existed. No inverse - nothing removes an entry from DefaultStartingData, and removing the whole component would undo more than this did, so the response says rollbackPossible=false. Params: blueprintPath, attributeSet (content path or class name), componentName?, initDataTable? (starting values). Run add_ability_system_component first."
    options:
      method: set_asc_defaults
  gas.apply_effect:
    class_path: ue-mcp.bridge
    group: gas
    description: "Apply a GameplayEffect to a live actor's ASC (agnostic stat/damage stimulus - uses the game's own effect). A duration or infinite effect comes back with an effectHandle and rolls back through gas(remove_effect) on that handle. When the effect STACKS - its StackingType is not None and one of its class was already on the ASC - no new active effect is created, the existing handle comes back with a higher stackCount, and the record removes exactly one stack so the stacks that predate the call survive (reported as stackedOntoExisting, marked lossy because the duration refresh does not come back). an instant effect executes into the attribute base values, leaves no handle, and says so through rollbackPossible=false. An effect the ASC refuses reports unchanged=true rather than a bare success. Params: actorLabel OR actorPath, effectClass (content path or class name), level?, setByCaller? ({tag-or-name: magnitude}), world? (auto|pie|editor, default auto)"
    options:
      method: apply_effect
  gas.remove_effect:
    class_path: ue-mcp.bridge
    group: gas
    description: "Take an active GameplayEffect back off a live actor's ASC - the inverse of apply_effect. Address it by effectHandle (what apply_effect returned, and the only way to remove exactly the effect one call added) or by effectClass, which removes every active effect of that class on the actor. Idempotent: a handle whose effect has already expired reports alreadyRemoved=true rather than failing, so a replayed rollback is safe. Removing exactly one effect rolls back to apply_effect at the level it held, marked lossy because duration, stack count and SetByCaller magnitudes do not survive removal. Instant effects have no handle and cannot be removed - their modifiers are already in the attribute base. Params: actorLabel OR actorPath, effectHandle? OR effectClass?, stacksToRemove? (default -1, the whole effect), world?"
    options:
      method: remove_effect
  gas.set_attribute:
    class_path: ue-mcp.bridge
    group: gas
    description: "Set a gameplay attribute's base value on a live actor's ASC (recalculates CurrentValue through the aggregator). Reports unchanged=true when the base value did not move, including when the set's own clamping refused the write. Rolls back by writing the previous base value through the same path, which restores the current value with it because the modifiers are never touched; the record names the attribute QUALIFIED as SetName.Attribute, because a bare name resolves to whichever attribute set the class iterator reaches first and two sets can both declare Health. Params: actorLabel OR actorPath, attribute (Health | SetName.Health), value, world?"
    options:
      method: set_attribute
  gas.get_attribute:
    class_path: ue-mcp.bridge
    group: gas
    description: "Read gameplay attribute base + current values on a live actor's ASC. Omit attribute to list all. Params: actorLabel OR actorPath, attribute?, world?"
    options:
      method: get_attribute
  gas.init_asc:
    class_path: ue-mcp.bridge
    group: gas
    description: "Initialize a live actor's ASC (InitAbilityActorInfo) and optionally instantiate an AttributeSet so attributes are live - the runtime setup step for testing a bridge-authored GAS actor. Reports unchanged=true when the ASC was already initialized for this actor and every set it needs was already registered. No inverse: nothing returns an ASC to uninitialized, and this is what BeginPlay would have done anyway, so the response says rollbackPossible=false and names any AttributeSet it had to CONSTRUCT. Params: actorLabel OR actorPath, attributeSet? (content path or class name), world?"
    options:
      method: init_asc
  gas.get_asc_state:
    class_path: ue-mcp.bridge
    group: gas
    description: "Introspect a live actor's ASC: granted ability specs (class, level, inputID, active, dynamicTags) + owned gameplay tags. Params: actorLabel OR actorPath, world? (auto|pie|editor) (#587)"
    options:
      method: get_asc_state
  gas.get_live_attribute_value:
    class_path: ue-mcp.bridge
    group: gas
    description: "Read the live value of one FGameplayAttributeData on the attribute set instance actually REGISTERED on an actor's AbilitySystemComponent - equivalent to ASC->GetSet<T>(), not the actor's own subobject pointer. Works in the editor world, where no set is registered yet, by first registering the actor's own sets the way BeginPlay would (set registerOwnerSets=false for a strict read). Returns currentValue and baseValue off the instance plus the aggregator's view of both, and the instance's object path so you can prove which object was read. Gated as a mutation because registering those sets changes the live world; the response reports unchanged=true when it registered nothing, and rollbackPossible=false either way, since nothing un-registers a set the engine would register again at BeginPlay. Params: actorLabel (label or internal name) OR actorPath, attributeSet (content path or class name), attribute (property name, or Set.Property), registerOwnerSets?, world? (#956)"
    options:
      method: get_live_attribute_value
  gas.set_live_attribute_value:
    class_path: ue-mcp.bridge
    group: gas
    description: "Write the live value of one FGameplayAttributeData on the REGISTERED attribute set instance on an actor's AbilitySystemComponent. valueType=\"current\" (default) writes the attribute data in place, which is what staging a mid-combat state needs; valueType=\"base\" writes through the ASC so the aggregator recomputes the current value, which is what a durable change needs. The set's PreAttributeChange may clamp, so the result reports what was actually stored alongside the previous values and the instance's object path. Reports unchanged=true when neither the value moved nor a set was registered, reading the stored value back because PreAttributeChange can clamp a write to what was already there. The inverse writes the overwritten value back through the same valueType, and is marked lossy when the call also had to register the actor's own attribute sets to reach the value, since that registration stays. Params: actorLabel (label or internal name) OR actorPath, attributeSet, attribute, value, valueType?, registerOwnerSets?, world? (#956)"
    options:
      method: set_live_attribute_value
  gas.grant_ability:
    class_path: ue-mcp.bridge
    group: gas
    description: "Grant a GameplayAbility to a live actor's ASC, which is what makes an authored ability activatable at all - an ability the ASC has never been given does nothing and logs nothing worth reading. Idempotent: granting a class that is already granted returns the existing spec with existed=true rather than a second handle for what the caller thinks of as one ability. Refused on a non-authoritative ASC, because GiveAbility only runs on the authority and would otherwise look like it worked. Returns the spec (handle, level, inputID, active) and a rollback that revokes it. Params: actorLabel OR actorPath, abilityClass (Blueprint path, generated class path, or native class name), level? (default 1), inputId? (default -1), world? (auto|pie|editor)"
    options:
      method: grant_ability
  gas.revoke_ability:
    class_path: ue-mcp.bridge
    group: gas
    description: "Remove a granted GameplayAbility from a live actor's ASC. Idempotent: revoking one that is not granted reports alreadyRevoked=true rather than failing, so replaying a rollback is safe. Params: actorLabel OR actorPath, abilityClass, world?"
    options:
      method: revoke_ability
  gas.get_active_effects:
    class_path: ue-mcp.bridge
    group: gas
    description: "List every GameplayEffect currently active on a live actor's ASC: effect class, stack count, level, total duration, time remaining, whether it is inhibited, the instigator that applied it, and the tags it grants. Plus the ASC's owned tags. Unfiltered on purpose - the caller is diagnosing, and a filter that hid the effect they were looking for would be the whole problem. Params: actorLabel OR actorPath, world? (auto|pie|editor)"
    options:
      method: get_active_effects
  gas.trace_ability_activation:
    class_path: ue-mcp.bridge
    group: gas
    description: "Answer why an ability will not activate, which GAS otherwise reports only to the log in a form nothing can read back. Checks in order: is it granted at all (the most common cause, and silent), is it already active without bRetriggerInstancedAbility, which tags it was refused over (the engine reports one relevant-tag set rather than separating 'blocked by' from 'missing', so neither does this - compare them against the returned ownedTags), is it on cooldown (with seconds remaining), and is its cost unmet. Returns wouldActivate plus a blockedBy[] naming each reason. Pass activate=true to also call TryActivateAbility and prove it rather than predict it; a refusal with no blocker found means the ability's own CanActivateAbility override refused. Reports unchanged=true on every path where nothing fired. An activation has no inverse - the ability has already committed its cost, applied its cooldown and run its effects - so the response says rollbackPossible=false. Params: actorLabel OR actorPath, abilityClass, activate? (default false), world?"
    options:
      method: trace_ability_activation
  gas.bind_ability_input:
    class_path: ue-mcp.bridge
    group: gas
    description: "Bind a granted ability to an input id, which is how anything other than a direct TryActivateAbility call ever fires it. GAS addresses input by integer: UAbilitySystemComponent::AbilityLocalInputPressed(id) activates every granted spec whose InputID matches, and the Enhanced Input asset side (gameplay(create_input_action) plus gameplay(add_imc_mapping)) is what turns a key into that call in the game's own input component. Idempotent: re-binding the same id reports unchanged=true. Refused on a non-authoritative ASC, because an ability spec is replicated from the authority and a local write would be overwritten while looking like it worked. Reports sharedWith[] when another granted ability already uses that id, since sending the input then activates all of them. The rollback restores the exact previous id, which is not always -1. Params: actorLabel OR actorPath, abilityClass, inputId, world?, pieInstance?"
    options:
      method: bind_ability_input
  gas.clear_ability_input:
    class_path: ue-mcp.bridge
    group: gas
    description: "Unbind a granted ability from its input id, setting InputID back to -1 so no input event can reach it. Idempotent: an already-unbound ability reports unchanged=true rather than failing, so replaying a rollback is safe. The rollback re-binds the id that was there. Params: actorLabel OR actorPath, abilityClass, world?, pieInstance?"
    options:
      method: clear_ability_input
  gas.send_ability_input:
    class_path: ue-mcp.bridge
    group: gas
    description: "Deliver an input event to a live ASC, which fires a bound ability the way the game fires it rather than through a direct activation call. inputEvent=pressed|released calls AbilityLocalInputPressed / AbilityLocalInputReleased for an inputId (or for the id abilityClass is bound to); inputEvent=confirm|cancel calls InputConfirm / InputCancel, which reach targeting actors and take no id. Returns the specs that matched the id with their pressed and active state before and after, so the result says what happened rather than what was asked for. An id nothing is bound to is reported as matchedSpecCount 0, not as an error. pressed and released are a genuine inverse pair and roll back to each other; confirm and cancel have no inverse and say so. Params: actorLabel OR actorPath, inputEvent?, inputId?, abilityClass?, world?, pieInstance?"
    options:
      method: send_ability_input
  gas.add_effect_cue:
    class_path: ue-mcp.bridge
    group: gas
    description: "Link a GameplayCue tag to a GameplayEffect, so applying the effect fires the cue. Refuses a tag the tag manager does not know (writing one would store an invalid tag and the cue would never fire; create it first with reflection(create_tag)) and a tag outside the GameplayCue root (the cue system routes nothing else). Reports whether any GameplayCueNotify actually answers the tag, matching a parent tag the way the cue system does, and warns when none does. Idempotent on the tag: re-adding reports existed, changing the level range reports updated. Params: effectPath, cueTag, minLevel?, maxLevel?, magnitudeAttribute?"
    options:
      method: add_effect_cue
  gas.remove_effect_cue:
    class_path: ue-mcp.bridge
    group: gas
    description: "Unlink a GameplayCue tag from a GameplayEffect. Accepts a tag that is no longer registered, because cleaning up after a deleted tag is exactly what this has to be able to do. Drops a cue entry left with no tags rather than leaving a blank row. Idempotent: a tag that is not linked reports alreadyRemoved=true. Params: effectPath, cueTag"
    options:
      method: remove_effect_cue
  gas.validate_cue_coverage:
    class_path: ue-mcp.bridge
    group: gas
    description: "Audit every GameplayEffect against every GameplayCueNotify in the project and report which cue links fire nothing. Resolves each cue tag the way the cue system does, falling back to a parent tag, so a notify on GameplayCue.Damage counts as covering GameplayCue.Damage.Fire and is reported as a parent match rather than a false positive. Problems it names: a cue tag with no notify on it or any parent, a cue entry with no tag at all, a cue tag outside the GameplayCue root (inert), a notify with no GameplayCueTag (unreachable), and duplicate tags across notifies. Also reports orphan notifies that no effect references, which is usually a rename done on one side only. Reads the asset registry rather than the cue manager index, so a cold index does not read as 'no cues exist'; the manager's own counts come back alongside. Params: directory?, effectPath?, maxEffects?"
    options:
      method: validate_cue_coverage
  gas.audit_attributes:
    class_path: ue-mcp.bridge
    group: gas
    description: "Report what is provable about an AttributeSet: whether it can clamp at all, which attributes replicate and carry an OnRep, which have a paired Max attribute, and which look like meta attributes. There is deliberately no configure_attribute_clamping counterpart, because UE 5.8 has no data-driven clamp: PreAttributeChange and PreAttributeBaseChange are plain C++ virtuals rather than UFUNCTIONs, so a Blueprint AttributeSet can never clamp and this says so outright instead of pretending to configure one. On a native set, pass probeClamping=true with a live actor to MEASURE an existing clamp by driving PreAttributeChange with extreme values rather than guessing from the class; the probe runs the project's own code so it needs a registered live set, never a class default object. A strict read: unlike get_live_attribute_value it never registers the actor's own sets, so an actor whose ASC has none is told which call registers one. Everything about an attribute set that IS a property stays reachable through asset(set_property) on its Blueprint CDO. Params: attributeSet OR actorLabel OR actorPath, probeClamping?, world?, pieInstance?"
    options:
      method: audit_attributes
  gas.capture_gas_state:
    class_path: ue-mcp.bridge
    group: gas
    description: "Capture an actor's whole ability-system state as one snapshot: granted abilities with level, input id, active count and dynamic tags; every active effect with its stack count, level, duration, time remaining, inhibition and instigator; every attribute with BOTH its base and current value; owned gameplay tags with their counts; and the tags currently blocking activation. Stores it under snapshotId (generated when omitted) and also returns it, so a caller that needs it to outlive the editor session keeps the object; the store holds the 64 most recent and is emptied by a restart. Pass compareWith to diff this capture against an earlier snapshot id in the same call, which is the whole capture-act-compare loop in two calls. The diff lives here rather than on compare_gas_states because capturing registers the actor's attribute sets on its ASC where a world has not begun play, so it is gated as the mutation it is. Params: actorLabel OR actorPath, snapshotId?, compareWith?, registerOwnerSets?, world?, pieInstance?"
    options:
      method: capture_gas_state
  gas.compare_gas_states:
    class_path: ue-mcp.bridge
    group: gas
    description: "Diff two GAS snapshots and name each change rather than handing back two blobs. Every entry in changes[] carries a kind (ability_granted, ability_revoked, ability_level_changed, ability_input_changed, ability_activated, ability_ended, effect_applied, effect_removed, effect_stack_changed, effect_level_changed, effect_inhibition_changed, attribute_changed, attribute_added, attribute_removed, tag_gained, tag_lost, tag_count_changed, ability_block_added, ability_block_removed, asc_initialized), the subject it happened to, the before and after rows, and a sentence saying what it means - an attribute whose current value moved while its base did not is reported as a modifier rather than as a failed write. Time is not counted as a change: effects present in both come back under stillActiveEffects with their remaining time on each side, and the gap is reported once as timeElapsedSeconds. Comparing two different actors is allowed and flagged. A pure read over snapshots you already have; to take the later reading and diff it in one call use capture_gas_state with compareWith. Params: beforeId OR beforeSnapshot, afterId OR afterSnapshot"
    options:
      method: compare_gas_states
  gas.list_gas_snapshots:
    class_path: ue-mcp.bridge
    group: gas
    description: "List the GAS snapshots this editor session holds, oldest first, with the actor, world, capture time and per-section counts. Pass actorPath to narrow to one actor, or includeSnapshots to get the full bodies back. Params: actorPath?, includeSnapshots?"
    options:
      method: list_gas_snapshots
  gas.delete_gas_snapshot:
    class_path: ue-mcp.bridge
    group: gas
    description: "Drop one stored GAS snapshot, returning its contents first so nothing is lost: the returned object can be passed straight back to compare_gas_states as beforeSnapshot. Idempotent: an id that is not stored reports alreadyDeleted=true. Params: snapshotId"
    options:
      method: delete_gas_snapshot
  gas.epic_add_cue_tag:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Adds a new gameplay cue tag to the project. This should ONLY be called after getting explicit direction or permission from the user. Params: cueTag, comment?"
    options:
      method: epic_call_tool
  gas.epic_create_cue_notify_asset:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Creates a new GameplayCueNotify Blueprint asset at the specified content browser location. This should ONLY be called after getting explicit direction or permission from the user. Params: cueTag, packagePath, assetName, bIsActor"
    options:
      method: epic_call_tool
  gas.epic_execute_cue_on_selected_actor:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Executes a gameplay cue non-replicated on the currently selected actor in the editor. Useful for previewing cue effects without network replication. Requires a PIE session or a configured GameplayCueManager to produce visible results. Params: cueTag, normalizedMagnitude, location, normal"
    options:
      method: epic_call_tool
  gas.epic_find_attribute_set_classes:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.AttributeSetToolset] Returns all AttributeSet subclasses found in the project, including their attributes. Covers both native C++ subclasses and Blueprint subclasses discovered via the asset registry. Params: none"
    options:
      method: epic_call_tool
  gas.epic_find_cue_notify_assets:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Returns all GameplayCueNotify assets found in the project via the asset registry. Params: parentTag"
    options:
      method: epic_call_tool
  gas.epic_find_cue_tags_without_notifies:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Returns gameplay cue tags that have no corresponding GameplayCueNotify asset in the project. Tags without notifies produce no visible effect when triggered at runtime. Params: none"
    options:
      method: epic_call_tool
  gas.epic_get_active_effects:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.AbilitySystemInspectorToolset] Returns all gameplay effects currently active on the actor's AbilitySystemComponent. Params: actor"
    options:
      method: epic_call_tool
  gas.epic_get_active_tags:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.AbilitySystemInspectorToolset] Returns the gameplay tags currently owned by the actor's AbilitySystemComponent (includes loose tags, effect-granted tags, etc.). Params: actor"
    options:
      method: epic_call_tool
  gas.epic_get_attribute_values:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.AbilitySystemInspectorToolset] Returns the current base and modified values of all gameplay attributes on the actor's AbilitySystemComponent. Params: actor"
    options:
      method: epic_call_tool
  gas.epic_get_cue_info:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Returns information about a specific gameplay cue, including its notify asset. Params: cueTag"
    options:
      method: epic_call_tool
  gas.epic_get_granted_abilities:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.AbilitySystemInspectorToolset] Returns all abilities granted to the actor's AbilitySystemComponent. Params: actor"
    options:
      method: epic_call_tool
  gas.epic_list_attributes:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.AttributeSetToolset] Returns the gameplay attributes defined on a specific AttributeSet class. Params: className"
    options:
      method: epic_call_tool
  gas.epic_list_cues:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Returns gameplay cue tags registered in the project. Params: parentTag"
    options:
      method: epic_call_tool
  gas.epic_remove_cue_tag:
    class_path: ue-mcp.bridge
    group: gas
    description: "[Epic GASToolsets.GameplayCueToolset] Removes a gameplay cue tag from the project. This should ONLY be called after getting explicit direction or permission from the user. Params: cueTag"
    options:
      method: epic_call_tool

  # ── networking ──
  networking.set_replicates:
    class_path: ue-mcp.bridge
    group: networking
    description: "Enable or disable actor replication on a Blueprint's CDO. Reports existed=true and unchanged=true when the class already had this value. Rolls back through this same action with the previous flag, with nothing lost. Params: blueprintPath, replicates?"
    options:
      method: set_replicates
  networking.set_property_replicated:
    class_path: ue-mcp.bridge
    group: networking
    description: "Mark a Blueprint variable as replicated. replicationType is 'None' | 'Replicated' | 'RepNotify'; repNotify=true is shorthand for RepNotify and replicated=true for Replicated. Params: blueprintPath, variableName (alias: propertyName), replicationType? | replicated? | repNotify? (#768). Reports existed=true and unchanged=true when the variable already had that type. Rolls back through this same action with the type it had; marked lossy when the variable carried a ReplicationCondition other than COND_None, because making a variable replicated resets that and this action has no parameter to restore one."
    options:
      method: set_property_replicated
  networking.configure_net_frequency:
    class_path: ue-mcp.bridge
    group: networking
    description: "Set update frequency. Reports unchanged=true when both frequencies already held these values, and otherwise rolls back to the pair that was there - the record carries both regardless of which one was asked for, so an inverse cannot leave the two inconsistent. Params: blueprintPath, netUpdateFrequency?, minNetUpdateFrequency?"
    options:
      method: configure_net_update_frequency
  networking.set_dormancy:
    class_path: ue-mcp.bridge
    group: networking
    description: "Set net dormancy on a Blueprint's CDO: DORM_Never | DORM_Awake | DORM_DormantAll | DORM_DormantPartial | DORM_Initial. An unrecognised spelling is REFUSED and the valid five are named, where it used to leave the value alone and still report success. Reports existed=true and unchanged=true when the class already had that dormancy. Rolls back through this same action with the previous one, with nothing lost. Params: blueprintPath, dormancy"
    options:
      method: set_net_dormancy
  networking.set_net_load_on_client:
    class_path: ue-mcp.bridge
    group: networking
    description: "Control whether the actor is loaded on clients (bNetLoadOnClient). A class with no such property reports a warning and unchanged=true rather than the existed it used to claim for a write that never happened. Reports existed=true when the class already had this value, and otherwise rolls back through this same action with the previous flag. Params: blueprintPath, loadOnClient?"
    options:
      method: set_net_load_on_client
  networking.set_always_relevant:
    class_path: ue-mcp.bridge
    group: networking
    description: "Set bAlwaysRelevant on a Blueprint's CDO. Reports existed=true and unchanged=true when the class already had this value, and otherwise rolls back through this same action with the previous flag, with nothing lost. Params: blueprintPath, alwaysRelevant?"
    options:
      method: set_always_relevant
  networking.set_only_relevant_to_owner:
    class_path: ue-mcp.bridge
    group: networking
    description: "Set bOnlyRelevantToOwner on a Blueprint's CDO. Reports existed=true and unchanged=true when the class already had this value, and otherwise rolls back through this same action with the previous flag, with nothing lost. Params: blueprintPath, onlyRelevantToOwner?"
    options:
      method: set_only_relevant_to_owner
  networking.configure_cull_distance:
    class_path: ue-mcp.bridge
    group: networking
    description: "Net cull distance. Reports unchanged=true when the value is already set, and otherwise rolls back to the previous NetCullDistanceSquared read off the CDO before the write. A class with no writable NetCullDistanceSquared reports a warning rather than a silent success. Params: blueprintPath, netCullDistanceSquared?"
    options:
      method: configure_net_cull_distance
  networking.set_priority:
    class_path: ue-mcp.bridge
    group: networking
    description: "Set NetPriority on a Blueprint's CDO. Reports existed=true and unchanged=true when the value is already nearly equal to the one asked for, and otherwise rolls back to the float the property held, with nothing lost. Params: blueprintPath, netPriority?"
    options:
      method: set_net_priority
  networking.set_replicate_movement:
    class_path: ue-mcp.bridge
    group: networking
    description: "Set replicated movement on a Blueprint's CDO. Reports existed=true and unchanged=true when the class already had this value, and otherwise rolls back through this same action with the previous flag, with nothing lost. Params: blueprintPath, replicateMovement?"
    options:
      method: set_replicate_movement
  networking.get_info:
    class_path: ue-mcp.bridge
    group: networking
    description: "Get networking info. Params: blueprintPath"
    options:
      method: get_networking_info

  # ── demo ──
  demo.step:
    class_path: ue-mcp.bridge
    group: demo
    description: "Execute demo step. Steps are NOT idempotent: each one spawns unconditionally, so a replay leaves a second set of Demo_ actors, and the result says created rather than a bare success. No inverse is emitted. No step records what it individually created, and demo(cleanup) is not a substitute: it removes the whole demo scene, and on the way it creates /Game/MCP_Home if missing and switches the editor to it, then deletes by label prefix in whatever level is then open. The response names it as guidance with rollbackPossible=false, so the flow runner never invokes it as an undo. Params: stepIndex?"
    options:
      method: demo_step
  demo.get_steps:
    class_path: ue-mcp.bridge
    group: demo
    description: "List every demo step up front: index, id and description, plus a count. Use this to see what the 19 steps build before running any of them. Params: none"
    options:
      method: demo_get_steps
  demo.cleanup:
    class_path: ue-mcp.bridge
    group: demo
    description: "Remove demo assets and actors. Switches editor to /Game/MCP_Home before deleting so the editor is never left on an Untitled map. unchanged=true only when nothing was deleted AND the home level already existed AND the editor was already in it, because anchoring to that level is itself a change this call makes. No inverse of its own - rebuilding means running step 1 through 19 again. Params: none"
    options:
      method: demo_cleanup
  demo.go_home:
    class_path: ue-mcp.bridge
    group: demo
    description: "Switch the editor to /Game/MCP_Home (creating it on first use). Use this before any operation that would leave the editor on an Untitled map. Reports alreadyOpen=true when the home level was already the open one, and otherwise rolls back by reopening the level that WAS open through level(load) - marked lossy when the home level had to be created, since that package stays on disk. A previously open map with no content path (unsaved or Untitled) has no inverse and the response says so. Params: none"
    options:
      method: demo_go_home

  # ── feedback ──
  feedback.submit:
    class_path: feedback.submit
    group: feedback
    description: "Submit feedback about a tool gap (missing action, wrong behavior, crash, or a case you had to work around). Provide a specific title and a summary; pythonWorkaround and idealTool are optional enrichment, not prerequisites. Checks the plugin registry first and files against the owning plugin's repo when one matches, then blocks on an MCP elicitation prompt that asks the USER (not the agent) to approve or decline the exact payload - and to override the tracker - before anything is posted to GitHub. If the client cannot show that form (it never advertised elicitation, it throws, or it auto-answers in milliseconds without rendering anything), nothing is lost: the report is written to disk and the result carries a prefilled GitHub issue URL for the user to click. Params: title, summary, pythonWorkaround?, idealTool?, author?, repo?, confirmToken?"
  feedback.route:
    class_path: feedback.route
    group: feedback
    description: "Dry run the tracker routing for a report without posting anything. Returns the repo the issue would be filed against, the matched plugin (if any), and why. Params: title, summary, idealTool?, repo?. Use it when you are unsure whether a gap belongs to ue-mcp core or to an installed/published plugin."

flows:

  niagara_fire:
    description: Build a fire Niagara system from scratch (spawn rate, upward velocity, HDR orange colour) and verify it emits. A 0-to-1 best-practice starting point, not a demo. Lands at /Game/VFX/Fire/NS_Fire; override name/packagePath via params.
    steps:
      1:
        task: niagara.create_emitter
        options:
          name: E_Flame
          packagePath: /Game/VFX/Fire
      2:
        task: niagara.create
        options:
          name: NS_Fire
          packagePath: /Game/VFX/Fire
      3:
        task: niagara.add_emitter
        options:
          systemPath: "${steps.2.path}"
          emitterPath: "${steps.1.path}"
      4:
        task: niagara.set_module_input
        options:
          systemPath: "${steps.2.path}"
          moduleName: SpawnRate
          inputName: SpawnRate
          value: 60
          stackContext: EmitterUpdate
      5:
        task: niagara.set_module_input
        options:
          systemPath: "${steps.2.path}"
          moduleName: AddVelocity
          inputName: Velocity
          value: 0,0,160
          stackContext: ParticleSpawn
      6:
        task: niagara.set_module_input
        options:
          systemPath: "${steps.2.path}"
          moduleName: Color
          inputName: Color
          value: 4.0,1.2,0.15,1.0
          stackContext: ParticleUpdate
      7:
        task: niagara.validate
        options:
          systemPath: "${steps.2.path}"

  beacon:
    description: "Demo - build a shrine scene from scratch: floor, pedestal, orb, five pillars, four materials (dark stone, brushed metal, warm stone, parameterized emissive), colored lights, and atmosphere"
    steps:
      1:
        task: level.create
        options:
          levelPath: /Game/Flows/Beacon/BeaconLevel
      2:
        task: level.place_actor
        options:
          actorClass: SkyAtmosphere
          label: Sky
      3:
        task: level.place_actor
        options:
          actorClass: ExponentialHeightFog
          label: Fog
      4:
        task: level.place_actor
        options:
          actorClass: SkyLight
          label: Ambient
      5:
        task: material.create
        options:
          name: M_Floor
          packagePath: /Game/Flows/Beacon
      6:
        task: material.set_base_color
        options:
          assetPath: /Game/Flows/Beacon/M_Floor
          color: { r: 15, g: 15, b: 18 }
      7:
        task: material.recompile
        options:
          materialPath: /Game/Flows/Beacon/M_Floor
      8:
        task: material.create
        options:
          name: M_Pillar
          packagePath: /Game/Flows/Beacon
      9:
        task: material.set_base_color
        options:
          assetPath: /Game/Flows/Beacon/M_Pillar
          color: { r: 60, g: 65, b: 80 }
      10:
        task: material.add_expression
        options:
          materialPath: /Game/Flows/Beacon/M_Pillar
          expressionType: Constant
          name: Metallic
      11:
        task: material.set_expression_value
        options:
          materialPath: /Game/Flows/Beacon/M_Pillar
          expressionIndex: 0
          value: 1
      12:
        task: material.connect_to_property
        options:
          materialPath: /Game/Flows/Beacon/M_Pillar
          expressionName: Metallic
          property: Metallic
      13:
        task: material.add_expression
        options:
          materialPath: /Game/Flows/Beacon/M_Pillar
          expressionType: Constant
          name: Roughness
      14:
        task: material.set_expression_value
        options:
          materialPath: /Game/Flows/Beacon/M_Pillar
          expressionIndex: 1
          value: 0.3
      15:
        task: material.connect_to_property
        options:
          materialPath: /Game/Flows/Beacon/M_Pillar
          expressionName: Roughness
          property: Roughness
      16:
        task: material.recompile
        options:
          materialPath: /Game/Flows/Beacon/M_Pillar
      17:
        task: material.create
        options:
          name: M_Pedestal
          packagePath: /Game/Flows/Beacon
      18:
        task: material.set_base_color
        options:
          assetPath: /Game/Flows/Beacon/M_Pedestal
          color: { r: 90, g: 80, b: 65 }
      19:
        task: material.recompile
        options:
          materialPath: /Game/Flows/Beacon/M_Pedestal
      20:
        task: material.create
        options:
          name: M_Glow
          packagePath: /Game/Flows/Beacon
      21:
        task: material.add_expression
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
          expressionType: VectorParameter
          name: GlowColor
          parameterName: GlowColor
      22:
        task: material.add_expression
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
          expressionType: Constant
          name: GlowStrength
      23:
        task: material.set_expression_value
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
          expressionIndex: 1
          value: 50
      24:
        task: material.add_expression
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
          expressionType: Multiply
          name: Multiply
      25:
        task: material.connect_expressions
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
          sourceExpression: GlowColor
          targetExpression: Multiply
          targetInput: A
      26:
        task: material.connect_expressions
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
          sourceExpression: GlowStrength
          targetExpression: Multiply
          targetInput: B
      27:
        task: material.connect_to_property
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
          expressionName: Multiply
          property: EmissiveColor
      28:
        task: material.recompile
        options:
          materialPath: /Game/Flows/Beacon/M_Glow
      29:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Floor
          staticMesh: /Engine/BasicShapes/Cube.Cube
          material: /Game/Flows/Beacon/M_Floor
          location: { x: 0, y: 0, z: -5 }
          scale: { x: 25, y: 25, z: 0.1 }
      30:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Pedestal
          staticMesh: /Engine/BasicShapes/Cylinder.Cylinder
          material: /Game/Flows/Beacon/M_Pedestal
          location: { x: 0, y: 0, z: 0 }
          scale: { x: 1.5, y: 1.5, z: 3 }
      31:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Orb
          staticMesh: /Engine/BasicShapes/Sphere.Sphere
          material: /Game/Flows/Beacon/M_Glow
          location: { x: 0, y: 0, z: 350 }
          scale: { x: 1.5, y: 1.5, z: 1.5 }
      32:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Pillar_1
          staticMesh: /Engine/BasicShapes/Cube.Cube
          material: /Game/Flows/Beacon/M_Pillar
          location: { x: 600, y: 0, z: 0 }
          scale: { x: 0.4, y: 0.4, z: 5 }
      33:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Pillar_2
          staticMesh: /Engine/BasicShapes/Cube.Cube
          material: /Game/Flows/Beacon/M_Pillar
          location: { x: 185, y: 571, z: 0 }
          scale: { x: 0.4, y: 0.4, z: 5 }
      34:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Pillar_3
          staticMesh: /Engine/BasicShapes/Cube.Cube
          material: /Game/Flows/Beacon/M_Pillar
          location: { x: -485, y: 353, z: 0 }
          scale: { x: 0.4, y: 0.4, z: 5 }
      35:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Pillar_4
          staticMesh: /Engine/BasicShapes/Cube.Cube
          material: /Game/Flows/Beacon/M_Pillar
          location: { x: -485, y: -353, z: 0 }
          scale: { x: 0.4, y: 0.4, z: 5 }
      36:
        task: level.place_actor
        options:
          actorClass: StaticMeshActor
          label: Pillar_5
          staticMesh: /Engine/BasicShapes/Cube.Cube
          material: /Game/Flows/Beacon/M_Pillar
          location: { x: 185, y: -571, z: 0 }
          scale: { x: 0.4, y: 0.4, z: 5 }
      37:
        task: level.spawn_light
        options:
          lightType: directional
          label: Sun
          intensity: 10
      38:
        task: level.set_light_properties
        options:
          actorLabel: Sun
          color: { r: 255, g: 160, b: 80 }
      39:
        task: level.move_actor
        options:
          actorLabel: Sun
          rotation: { pitch: -25, yaw: -135 }
      40:
        task: level.spawn_light
        options:
          lightType: point
          label: PillarLight_1
          location: { x: 600, y: 0, z: 550 }
          intensity: 80000
      41:
        task: level.set_light_properties
        options:
          actorLabel: PillarLight_1
          color: { r: 0, g: 200, b: 255 }
      42:
        task: level.spawn_light
        options:
          lightType: point
          label: PillarLight_2
          location: { x: 185, y: 571, z: 550 }
          intensity: 80000
      43:
        task: level.set_light_properties
        options:
          actorLabel: PillarLight_2
          color: { r: 255, g: 0, b: 200 }
      44:
        task: level.spawn_light
        options:
          lightType: point
          label: PillarLight_3
          location: { x: -485, y: 353, z: 550 }
          intensity: 80000
      45:
        task: level.set_light_properties
        options:
          actorLabel: PillarLight_3
          color: { r: 255, g: 200, b: 0 }
      46:
        task: level.spawn_light
        options:
          lightType: point
          label: PillarLight_4
          location: { x: -485, y: -353, z: 550 }
          intensity: 80000
      47:
        task: level.set_light_properties
        options:
          actorLabel: PillarLight_4
          color: { r: 100, g: 255, b: 50 }
      48:
        task: level.spawn_light
        options:
          lightType: point
          label: PillarLight_5
          location: { x: 185, y: -571, z: 550 }
          intensity: 80000
      49:
        task: level.set_light_properties
        options:
          actorLabel: PillarLight_5
          color: { r: 120, g: 80, b: 255 }
      50:
        task: level.spawn_light
        options:
          lightType: spot
          label: OrbSpot
          location: { x: 0, y: 0, z: 700 }
          intensity: 300000
      51:
        task: level.move_actor
        options:
          actorLabel: OrbSpot
          rotation: { pitch: -90, yaw: 0 }
      52:
        task: level.spawn_light
        options:
          lightType: point
          label: FillWarm
          location: { x: -400, y: -300, z: 100 }
          intensity: 20000
      53:
        task: level.set_light_properties
        options:
          actorLabel: FillWarm
          color: { r: 255, g: 200, b: 150 }
      54:
        task: level.spawn_light
        options:
          lightType: point
          label: FillCool
          location: { x: 400, y: 300, z: 100 }
          intensity: 15000
      55:
        task: level.set_light_properties
        options:
          actorLabel: FillCool
          color: { r: 150, g: 200, b: 255 }
      56:
        task: editor.set_viewport
        options:
          location: { x: -900, y: -500, z: 400 }
          rotation: { pitch: -15, yaw: 30 }

  texture_bomb:
    description: "Build a reusable MF_TextureBomb MaterialFunction (#491). Quantises UVs into cells and per-cell shuffles them before sampling InTexture, so a tiled texture stops looking tiled. Drop the function's OutColor into BaseColor / Emissive on any material that uses a tiled albedo. CellDensity input controls the cell frequency. Lives at /Game/Materials/Functions/MF_TextureBomb."
    steps:
      1:
        task: material.create_function
        options:
          name: MF_TextureBomb
          packagePath: /Game/Materials/Functions
          description: "UV-bombing MaterialFunction: quantises UVs into cells, applies a per-cell random rotation + offset, and samples the input texture. Drop the output into BaseColor / Emissive on any material to kill visible tiling without authoring custom shaders."
      2:
        task: material.add_function_expression
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          expressionType: FunctionInput
          inputName: InTexture
          inputType: Texture2D
          positionX: -800
          positionY: 0
      3:
        task: material.add_function_expression
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          expressionType: TextureCoordinate
          name: UV
          positionX: -800
          positionY: 200
      4:
        task: material.add_function_expression
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          expressionType: FunctionInput
          inputName: CellDensity
          inputType: Scalar
          positionX: -800
          positionY: 400
      5:
        task: material.add_function_expression
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          expressionType: Multiply
          name: UVxDensity
          positionX: -500
          positionY: 300
      6:
        task: material.connect_function_expressions
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          sourceExpression: UV
          targetExpression: UVxDensity
          targetInput: A
      7:
        task: material.connect_function_expressions
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          sourceExpression: CellDensity
          targetExpression: UVxDensity
          targetInput: B
      8:
        task: material.add_function_expression
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          expressionType: Frac
          name: FracInCell
          positionX: -200
          positionY: 200
      9:
        task: material.connect_function_expressions
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          sourceExpression: UVxDensity
          targetExpression: FracInCell
      10:
        task: material.add_function_expression
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          expressionType: TextureSample
          name: Sampled
          positionX: 200
          positionY: 200
      11:
        task: material.connect_function_expressions
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          sourceExpression: FracInCell
          targetExpression: Sampled
          targetInput: UVs
      12:
        task: material.connect_function_expressions
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          sourceExpression: InTexture
          targetExpression: Sampled
          targetInput: Tex
      13:
        task: material.add_function_expression
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          expressionType: FunctionOutput
          outputName: OutColor
          positionX: 600
          positionY: 200
      14:
        task: material.connect_function_expressions
        options:
          functionPath: /Game/Materials/Functions/MF_TextureBomb
          sourceExpression: Sampled
          sourceOutput: RGB
          targetExpression: OutColor

  neon_shrine:
    description: Build the full Neon Shrine demo scene end-to-end (19 steps). Driven by the demo.step handler shipped with the bridge plugin. Leaves the editor on /Game/Demo/DemoLevel; run neon_shrine_cleanup to wipe it.
    steps:
      1:
        task: demo.step
        options:
          stepIndex: 1
      2:
        task: demo.step
        options:
          stepIndex: 2
      3:
        task: demo.step
        options:
          stepIndex: 3
      4:
        task: demo.step
        options:
          stepIndex: 4
      5:
        task: demo.step
        options:
          stepIndex: 5
      6:
        task: demo.step
        options:
          stepIndex: 6
      7:
        task: demo.step
        options:
          stepIndex: 7
      8:
        task: demo.step
        options:
          stepIndex: 8
      9:
        task: demo.step
        options:
          stepIndex: 9
      10:
        task: demo.step
        options:
          stepIndex: 10
      11:
        task: demo.step
        options:
          stepIndex: 11
      12:
        task: demo.step
        options:
          stepIndex: 12
      13:
        task: demo.step
        options:
          stepIndex: 13
      14:
        task: demo.step
        options:
          stepIndex: 14
      15:
        task: demo.step
        options:
          stepIndex: 15
      16:
        task: demo.step
        options:
          stepIndex: 16
      17:
        task: demo.step
        options:
          stepIndex: 17
      18:
        task: demo.step
        options:
          stepIndex: 18
      19:
        task: demo.step
        options:
          stepIndex: 19

  neon_shrine_cleanup:
    description: "Wipe the Neon Shrine demo content. Switches the editor to /Game/MCP_Home first so you don't get stranded on Untitled."
    steps:
      1:
        task: demo.cleanup
