ue-mcp 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/context-cli.js +16 -16
  2. package/dist/engine-observer.js +68 -68
  3. package/dist/instructions.js +130 -130
  4. package/dist/manifest-signature.d.ts +1 -0
  5. package/dist/manifest-signature.js +53 -0
  6. package/dist/manifest-signature.js.map +1 -0
  7. package/dist/pie/schema.d.ts +791 -0
  8. package/dist/pie/schema.js +151 -0
  9. package/dist/pie/schema.js.map +1 -0
  10. package/dist/plugin-cli.js +299 -299
  11. package/dist/tool-counts.json +2 -2
  12. package/dist/watch-cli.js.map +1 -0
  13. package/package.json +6 -3
  14. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +76 -76
  15. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.h +27 -27
  16. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +128 -128
  17. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +63 -63
  18. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +230 -230
  19. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +10 -32
  20. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -21
  21. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +3 -18
  22. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +1 -19
  23. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +323 -323
  24. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +20 -20
  25. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +537 -537
  26. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +77 -77
  27. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +353 -353
  28. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +18 -18
  29. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp +246 -246
  30. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +597 -597
  31. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +51 -51
  32. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +878 -878
  33. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +449 -449
  34. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +275 -275
  35. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +551 -551
  36. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +531 -531
  37. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +28 -28
  38. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +677 -677
  39. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +822 -822
  40. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +22 -22
  41. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +218 -218
  42. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +17 -17
  43. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.cpp +46 -46
  44. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.h +23 -23
  45. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/MCPExternalRegistry.cpp +83 -83
  46. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +30 -0
  47. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -14
  48. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/UE_MCP_BridgeStatusModule.cpp +37 -37
  49. /package/plugin/ue_mcp_bridge/{UE_MCP_Bridge.uplugin → ue_mcp_bridge.uplugin} +0 -0
@@ -1,230 +1,230 @@
1
- // Control Rig RigVM graph inspection (#774).
2
- //
3
- // list_control_rig_variables reported each graph as just a name, class and node
4
- // count, which is not enough to verify solver wiring - you cannot tell what a
5
- // rig actually computes from a node count. Reading the RigVM models, their
6
- // nodes, pins and links required dropping to Python.
7
- //
8
- // Translation-unit partition of FAnimationHandlers; registration lives in
9
- // AnimationHandlers.cpp.
10
-
11
- #include "AnimationHandlers.h"
12
-
13
- #include "HandlerUtils.h"
14
-
15
- #include "EditorAssetLibrary.h"
16
- #include "Engine/Blueprint.h"
17
- #include "UObject/UObjectGlobals.h"
18
- #include "UObject/UObjectHash.h"
19
-
20
- #include "RigVMModel/RigVMGraph.h"
21
- #include "RigVMModel/RigVMLink.h"
22
- #include "RigVMModel/RigVMNode.h"
23
- #include "RigVMModel/RigVMPin.h"
24
-
25
- namespace
26
- {
27
- FString PinDirectionName(ERigVMPinDirection Direction)
28
- {
29
- switch (Direction)
30
- {
31
- case ERigVMPinDirection::Input: return TEXT("Input");
32
- case ERigVMPinDirection::Output: return TEXT("Output");
33
- case ERigVMPinDirection::IO: return TEXT("IO");
34
- case ERigVMPinDirection::Visible: return TEXT("Visible");
35
- case ERigVMPinDirection::Hidden: return TEXT("Hidden");
36
- default: return TEXT("Invalid");
37
- }
38
- }
39
-
40
- TSharedPtr<FJsonObject> PinToJson(URigVMPin* Pin, bool bIncludeDefaults, int32 Depth)
41
- {
42
- TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
43
- Obj->SetStringField(TEXT("name"), Pin->GetName());
44
- Obj->SetStringField(TEXT("pinPath"), Pin->GetPinPath());
45
- Obj->SetStringField(TEXT("cppType"), Pin->GetCPPType());
46
- Obj->SetStringField(TEXT("direction"), PinDirectionName(Pin->GetDirection()));
47
- Obj->SetBoolField(TEXT("isExecute"), Pin->IsExecuteContext());
48
- if (bIncludeDefaults)
49
- {
50
- const FString Default = Pin->GetDefaultValue();
51
- if (!Default.IsEmpty()) Obj->SetStringField(TEXT("defaultValue"), Default);
52
- }
53
-
54
- // Struct pins expand into sub-pins; that nesting IS the wiring for
55
- // things like a transform's individual channels, so keep it, bounded.
56
- const TArray<URigVMPin*>& SubPins = Pin->GetSubPins();
57
- if (SubPins.Num() > 0 && Depth >= 3)
58
- {
59
- // Say so rather than returning a partial pin tree that reads complete.
60
- Obj->SetBoolField(TEXT("subPinsTruncated"), true);
61
- }
62
- if (SubPins.Num() > 0 && Depth < 3)
63
- {
64
- TArray<TSharedPtr<FJsonValue>> SubArray;
65
- for (URigVMPin* Sub : SubPins)
66
- {
67
- if (Sub) SubArray.Add(MakeShared<FJsonValueObject>(PinToJson(Sub, bIncludeDefaults, Depth + 1)));
68
- }
69
- Obj->SetArrayField(TEXT("subPins"), SubArray);
70
- }
71
- return Obj;
72
- }
73
- }
74
-
75
- TSharedPtr<FJsonValue> FAnimationHandlers::ReadControlRigGraph(const TSharedPtr<FJsonObject>& Params)
76
- {
77
- FString AssetPath;
78
- if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
79
-
80
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
81
- UBlueprint* Blueprint = Cast<UBlueprint>(LoadedAsset);
82
- if (!Blueprint)
83
- {
84
- return MCPError(FString::Printf(TEXT("Failed to load Blueprint at '%s'"), *AssetPath));
85
- }
86
-
87
- const FString GraphFilter = OptionalString(Params, TEXT("graphName"));
88
- const bool bIncludePins = OptionalBool(Params, TEXT("includePins"), true);
89
- const bool bIncludeDefaults = OptionalBool(Params, TEXT("includeDefaults"), true);
90
- const bool bIncludeLinks = OptionalBool(Params, TEXT("includeLinks"), true);
91
- const int32 NodeLimit = FMath::Max(1, OptionalInt(Params, TEXT("limit"), 200));
92
-
93
- // ControlRigBlueprint was removed in 5.7 and the client-host interface
94
- // moved, so reach the models by walking the blueprint's own subobjects for
95
- // URigVMGraph. That is version-tolerant and needs no editor-only host API.
96
- TArray<UObject*> Subobjects;
97
- #if UE_MCP_HAS_5_8_API
98
- GetObjectsWithOuter(Blueprint, Subobjects, EGetObjectsFlags::IncludeNestedObjects);
99
- #else
100
- GetObjectsWithOuter(Blueprint, Subobjects, /*bIncludeNestedObjects=*/true);
101
- #endif
102
-
103
- TArray<URigVMGraph*> Models;
104
- for (UObject* Object : Subobjects)
105
- {
106
- if (URigVMGraph* Graph = Cast<URigVMGraph>(Object))
107
- {
108
- // Nested graphs (function library entries, collapsed nodes) are
109
- // reachable from their own outer; keep them all but de-duplicate.
110
- Models.AddUnique(Graph);
111
- }
112
- }
113
-
114
- if (Models.Num() == 0)
115
- {
116
- return MCPError(FString::Printf(
117
- TEXT("'%s' has no RigVM models. Is this a Control Rig / RigVM asset? (loaded class: %s)"),
118
- *AssetPath, *Blueprint->GetClass()->GetName()));
119
- }
120
-
121
- TArray<TSharedPtr<FJsonValue>> GraphsArray;
122
- int32 TotalNodes = 0;
123
- int32 TotalLinks = 0;
124
- bool bTruncatedAny = false;
125
-
126
- for (URigVMGraph* Graph : Models)
127
- {
128
- const FString GraphName = Graph->GetName();
129
- if (!GraphFilter.IsEmpty() && !GraphName.Contains(GraphFilter, ESearchCase::IgnoreCase))
130
- {
131
- continue;
132
- }
133
-
134
- TSharedPtr<FJsonObject> GraphObj = MakeShared<FJsonObject>();
135
- GraphObj->SetStringField(TEXT("name"), GraphName);
136
- GraphObj->SetStringField(TEXT("path"), Graph->GetPathName());
137
-
138
- const TArray<URigVMNode*>& Nodes = Graph->GetNodes();
139
- GraphObj->SetNumberField(TEXT("nodeCount"), Nodes.Num());
140
- TotalNodes += Nodes.Num();
141
-
142
- TArray<TSharedPtr<FJsonValue>> NodesArray;
143
- bool bGraphTruncated = false;
144
- for (URigVMNode* Node : Nodes)
145
- {
146
- if (!Node) continue;
147
- if (NodesArray.Num() >= NodeLimit) { bTruncatedAny = true; bGraphTruncated = true; break; }
148
-
149
- TSharedPtr<FJsonObject> NodeObj = MakeShared<FJsonObject>();
150
- NodeObj->SetStringField(TEXT("name"), Node->GetName());
151
- NodeObj->SetStringField(TEXT("nodePath"), Node->GetNodePath());
152
- NodeObj->SetStringField(TEXT("class"), Node->GetClass()->GetName());
153
-
154
- if (bIncludePins)
155
- {
156
- TArray<TSharedPtr<FJsonValue>> PinsArray;
157
- for (URigVMPin* Pin : Node->GetPins())
158
- {
159
- if (Pin) PinsArray.Add(MakeShared<FJsonValueObject>(PinToJson(Pin, bIncludeDefaults, 0)));
160
- }
161
- NodeObj->SetArrayField(TEXT("pins"), PinsArray);
162
- }
163
- NodesArray.Add(MakeShared<FJsonValueObject>(NodeObj));
164
- }
165
- GraphObj->SetArrayField(TEXT("nodes"), NodesArray);
166
- // Set at the break site: deriving it from counts false-positives on a
167
- // graph that merely contains a null node.
168
- GraphObj->SetBoolField(TEXT("nodesTruncated"), bGraphTruncated);
169
-
170
- if (bIncludeLinks)
171
- {
172
- const TArray<URigVMLink*>& Links = Graph->GetLinks();
173
- TotalLinks += Links.Num();
174
- TArray<TSharedPtr<FJsonValue>> LinksArray;
175
- for (URigVMLink* Link : Links)
176
- {
177
- if (!Link) continue;
178
- URigVMPin* Source = Link->GetSourcePin();
179
- URigVMPin* Target = Link->GetTargetPin();
180
- if (!Source || !Target) continue;
181
- TSharedPtr<FJsonObject> LinkObj = MakeShared<FJsonObject>();
182
- LinkObj->SetStringField(TEXT("source"), Source->GetPinPath(/*bUseNodePath=*/true));
183
- LinkObj->SetStringField(TEXT("target"), Target->GetPinPath(/*bUseNodePath=*/true));
184
- LinksArray.Add(MakeShared<FJsonValueObject>(LinkObj));
185
- }
186
- GraphObj->SetArrayField(TEXT("links"), LinksArray);
187
- GraphObj->SetNumberField(TEXT("linkCount"), LinksArray.Num());
188
- }
189
-
190
- GraphsArray.Add(MakeShared<FJsonValueObject>(GraphObj));
191
- }
192
-
193
- // Member variables with their full metadata, not just a name and category.
194
- TArray<TSharedPtr<FJsonValue>> VariablesArray;
195
- for (const FBPVariableDescription& Var : Blueprint->NewVariables)
196
- {
197
- TSharedPtr<FJsonObject> VarObj = MakeShared<FJsonObject>();
198
- VarObj->SetStringField(TEXT("name"), Var.VarName.ToString());
199
- VarObj->SetStringField(TEXT("type"), Var.VarType.PinCategory.ToString());
200
- VarObj->SetStringField(TEXT("subType"), Var.VarType.PinSubCategory.ToString());
201
- if (Var.VarType.PinSubCategoryObject.IsValid())
202
- {
203
- VarObj->SetStringField(TEXT("subTypeObject"), Var.VarType.PinSubCategoryObject->GetPathName());
204
- }
205
- VarObj->SetBoolField(TEXT("isArray"), Var.VarType.IsArray());
206
- VarObj->SetStringField(TEXT("defaultValue"), Var.DefaultValue);
207
- VarObj->SetBoolField(TEXT("isPublic"), (Var.PropertyFlags & CPF_BlueprintVisible) != 0);
208
- VarObj->SetBoolField(TEXT("readOnly"), (Var.PropertyFlags & CPF_BlueprintReadOnly) != 0);
209
- VariablesArray.Add(MakeShared<FJsonValueObject>(VarObj));
210
- }
211
-
212
- auto Result = MCPSuccess();
213
- Result->SetStringField(TEXT("assetPath"), AssetPath);
214
- Result->SetStringField(TEXT("name"), Blueprint->GetName());
215
- if (Blueprint->ParentClass)
216
- {
217
- Result->SetStringField(TEXT("parentClass"), Blueprint->ParentClass->GetName());
218
- }
219
- Result->SetArrayField(TEXT("models"), GraphsArray);
220
- Result->SetNumberField(TEXT("modelCount"), GraphsArray.Num());
221
- Result->SetNumberField(TEXT("totalNodes"), TotalNodes);
222
- Result->SetNumberField(TEXT("totalLinks"), TotalLinks);
223
- Result->SetArrayField(TEXT("variables"), VariablesArray);
224
- Result->SetNumberField(TEXT("variableCount"), VariablesArray.Num());
225
- if (bTruncatedAny)
226
- {
227
- Result->SetStringField(TEXT("note"), TEXT("Some graphs hit the node limit; raise 'limit' or narrow with 'graphName'."));
228
- }
229
- return MCPResult(Result);
230
- }
1
+ // Control Rig RigVM graph inspection (#774).
2
+ //
3
+ // list_control_rig_variables reported each graph as just a name, class and node
4
+ // count, which is not enough to verify solver wiring - you cannot tell what a
5
+ // rig actually computes from a node count. Reading the RigVM models, their
6
+ // nodes, pins and links required dropping to Python.
7
+ //
8
+ // Translation-unit partition of FAnimationHandlers; registration lives in
9
+ // AnimationHandlers.cpp.
10
+
11
+ #include "AnimationHandlers.h"
12
+
13
+ #include "HandlerUtils.h"
14
+
15
+ #include "EditorAssetLibrary.h"
16
+ #include "Engine/Blueprint.h"
17
+ #include "UObject/UObjectGlobals.h"
18
+ #include "UObject/UObjectHash.h"
19
+
20
+ #include "RigVMModel/RigVMGraph.h"
21
+ #include "RigVMModel/RigVMLink.h"
22
+ #include "RigVMModel/RigVMNode.h"
23
+ #include "RigVMModel/RigVMPin.h"
24
+
25
+ namespace
26
+ {
27
+ FString PinDirectionName(ERigVMPinDirection Direction)
28
+ {
29
+ switch (Direction)
30
+ {
31
+ case ERigVMPinDirection::Input: return TEXT("Input");
32
+ case ERigVMPinDirection::Output: return TEXT("Output");
33
+ case ERigVMPinDirection::IO: return TEXT("IO");
34
+ case ERigVMPinDirection::Visible: return TEXT("Visible");
35
+ case ERigVMPinDirection::Hidden: return TEXT("Hidden");
36
+ default: return TEXT("Invalid");
37
+ }
38
+ }
39
+
40
+ TSharedPtr<FJsonObject> PinToJson(URigVMPin* Pin, bool bIncludeDefaults, int32 Depth)
41
+ {
42
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
43
+ Obj->SetStringField(TEXT("name"), Pin->GetName());
44
+ Obj->SetStringField(TEXT("pinPath"), Pin->GetPinPath());
45
+ Obj->SetStringField(TEXT("cppType"), Pin->GetCPPType());
46
+ Obj->SetStringField(TEXT("direction"), PinDirectionName(Pin->GetDirection()));
47
+ Obj->SetBoolField(TEXT("isExecute"), Pin->IsExecuteContext());
48
+ if (bIncludeDefaults)
49
+ {
50
+ const FString Default = Pin->GetDefaultValue();
51
+ if (!Default.IsEmpty()) Obj->SetStringField(TEXT("defaultValue"), Default);
52
+ }
53
+
54
+ // Struct pins expand into sub-pins; that nesting IS the wiring for
55
+ // things like a transform's individual channels, so keep it, bounded.
56
+ const TArray<URigVMPin*>& SubPins = Pin->GetSubPins();
57
+ if (SubPins.Num() > 0 && Depth >= 3)
58
+ {
59
+ // Say so rather than returning a partial pin tree that reads complete.
60
+ Obj->SetBoolField(TEXT("subPinsTruncated"), true);
61
+ }
62
+ if (SubPins.Num() > 0 && Depth < 3)
63
+ {
64
+ TArray<TSharedPtr<FJsonValue>> SubArray;
65
+ for (URigVMPin* Sub : SubPins)
66
+ {
67
+ if (Sub) SubArray.Add(MakeShared<FJsonValueObject>(PinToJson(Sub, bIncludeDefaults, Depth + 1)));
68
+ }
69
+ Obj->SetArrayField(TEXT("subPins"), SubArray);
70
+ }
71
+ return Obj;
72
+ }
73
+ }
74
+
75
+ TSharedPtr<FJsonValue> FAnimationHandlers::ReadControlRigGraph(const TSharedPtr<FJsonObject>& Params)
76
+ {
77
+ FString AssetPath;
78
+ if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
79
+
80
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
81
+ UBlueprint* Blueprint = Cast<UBlueprint>(LoadedAsset);
82
+ if (!Blueprint)
83
+ {
84
+ return MCPError(FString::Printf(TEXT("Failed to load Blueprint at '%s'"), *AssetPath));
85
+ }
86
+
87
+ const FString GraphFilter = OptionalString(Params, TEXT("graphName"));
88
+ const bool bIncludePins = OptionalBool(Params, TEXT("includePins"), true);
89
+ const bool bIncludeDefaults = OptionalBool(Params, TEXT("includeDefaults"), true);
90
+ const bool bIncludeLinks = OptionalBool(Params, TEXT("includeLinks"), true);
91
+ const int32 NodeLimit = FMath::Max(1, OptionalInt(Params, TEXT("limit"), 200));
92
+
93
+ // ControlRigBlueprint was removed in 5.7 and the client-host interface
94
+ // moved, so reach the models by walking the blueprint's own subobjects for
95
+ // URigVMGraph. That is version-tolerant and needs no editor-only host API.
96
+ TArray<UObject*> Subobjects;
97
+ #if UE_MCP_HAS_5_8_API
98
+ GetObjectsWithOuter(Blueprint, Subobjects, EGetObjectsFlags::IncludeNestedObjects);
99
+ #else
100
+ GetObjectsWithOuter(Blueprint, Subobjects, /*bIncludeNestedObjects=*/true);
101
+ #endif
102
+
103
+ TArray<URigVMGraph*> Models;
104
+ for (UObject* Object : Subobjects)
105
+ {
106
+ if (URigVMGraph* Graph = Cast<URigVMGraph>(Object))
107
+ {
108
+ // Nested graphs (function library entries, collapsed nodes) are
109
+ // reachable from their own outer; keep them all but de-duplicate.
110
+ Models.AddUnique(Graph);
111
+ }
112
+ }
113
+
114
+ if (Models.Num() == 0)
115
+ {
116
+ return MCPError(FString::Printf(
117
+ TEXT("'%s' has no RigVM models. Is this a Control Rig / RigVM asset? (loaded class: %s)"),
118
+ *AssetPath, *Blueprint->GetClass()->GetName()));
119
+ }
120
+
121
+ TArray<TSharedPtr<FJsonValue>> GraphsArray;
122
+ int32 TotalNodes = 0;
123
+ int32 TotalLinks = 0;
124
+ bool bTruncatedAny = false;
125
+
126
+ for (URigVMGraph* Graph : Models)
127
+ {
128
+ const FString GraphName = Graph->GetName();
129
+ if (!GraphFilter.IsEmpty() && !GraphName.Contains(GraphFilter, ESearchCase::IgnoreCase))
130
+ {
131
+ continue;
132
+ }
133
+
134
+ TSharedPtr<FJsonObject> GraphObj = MakeShared<FJsonObject>();
135
+ GraphObj->SetStringField(TEXT("name"), GraphName);
136
+ GraphObj->SetStringField(TEXT("path"), Graph->GetPathName());
137
+
138
+ const TArray<URigVMNode*>& Nodes = Graph->GetNodes();
139
+ GraphObj->SetNumberField(TEXT("nodeCount"), Nodes.Num());
140
+ TotalNodes += Nodes.Num();
141
+
142
+ TArray<TSharedPtr<FJsonValue>> NodesArray;
143
+ bool bGraphTruncated = false;
144
+ for (URigVMNode* Node : Nodes)
145
+ {
146
+ if (!Node) continue;
147
+ if (NodesArray.Num() >= NodeLimit) { bTruncatedAny = true; bGraphTruncated = true; break; }
148
+
149
+ TSharedPtr<FJsonObject> NodeObj = MakeShared<FJsonObject>();
150
+ NodeObj->SetStringField(TEXT("name"), Node->GetName());
151
+ NodeObj->SetStringField(TEXT("nodePath"), Node->GetNodePath());
152
+ NodeObj->SetStringField(TEXT("class"), Node->GetClass()->GetName());
153
+
154
+ if (bIncludePins)
155
+ {
156
+ TArray<TSharedPtr<FJsonValue>> PinsArray;
157
+ for (URigVMPin* Pin : Node->GetPins())
158
+ {
159
+ if (Pin) PinsArray.Add(MakeShared<FJsonValueObject>(PinToJson(Pin, bIncludeDefaults, 0)));
160
+ }
161
+ NodeObj->SetArrayField(TEXT("pins"), PinsArray);
162
+ }
163
+ NodesArray.Add(MakeShared<FJsonValueObject>(NodeObj));
164
+ }
165
+ GraphObj->SetArrayField(TEXT("nodes"), NodesArray);
166
+ // Set at the break site: deriving it from counts false-positives on a
167
+ // graph that merely contains a null node.
168
+ GraphObj->SetBoolField(TEXT("nodesTruncated"), bGraphTruncated);
169
+
170
+ if (bIncludeLinks)
171
+ {
172
+ const TArray<URigVMLink*>& Links = Graph->GetLinks();
173
+ TotalLinks += Links.Num();
174
+ TArray<TSharedPtr<FJsonValue>> LinksArray;
175
+ for (URigVMLink* Link : Links)
176
+ {
177
+ if (!Link) continue;
178
+ URigVMPin* Source = Link->GetSourcePin();
179
+ URigVMPin* Target = Link->GetTargetPin();
180
+ if (!Source || !Target) continue;
181
+ TSharedPtr<FJsonObject> LinkObj = MakeShared<FJsonObject>();
182
+ LinkObj->SetStringField(TEXT("source"), Source->GetPinPath(/*bUseNodePath=*/true));
183
+ LinkObj->SetStringField(TEXT("target"), Target->GetPinPath(/*bUseNodePath=*/true));
184
+ LinksArray.Add(MakeShared<FJsonValueObject>(LinkObj));
185
+ }
186
+ GraphObj->SetArrayField(TEXT("links"), LinksArray);
187
+ GraphObj->SetNumberField(TEXT("linkCount"), LinksArray.Num());
188
+ }
189
+
190
+ GraphsArray.Add(MakeShared<FJsonValueObject>(GraphObj));
191
+ }
192
+
193
+ // Member variables with their full metadata, not just a name and category.
194
+ TArray<TSharedPtr<FJsonValue>> VariablesArray;
195
+ for (const FBPVariableDescription& Var : Blueprint->NewVariables)
196
+ {
197
+ TSharedPtr<FJsonObject> VarObj = MakeShared<FJsonObject>();
198
+ VarObj->SetStringField(TEXT("name"), Var.VarName.ToString());
199
+ VarObj->SetStringField(TEXT("type"), Var.VarType.PinCategory.ToString());
200
+ VarObj->SetStringField(TEXT("subType"), Var.VarType.PinSubCategory.ToString());
201
+ if (Var.VarType.PinSubCategoryObject.IsValid())
202
+ {
203
+ VarObj->SetStringField(TEXT("subTypeObject"), Var.VarType.PinSubCategoryObject->GetPathName());
204
+ }
205
+ VarObj->SetBoolField(TEXT("isArray"), Var.VarType.IsArray());
206
+ VarObj->SetStringField(TEXT("defaultValue"), Var.DefaultValue);
207
+ VarObj->SetBoolField(TEXT("isPublic"), (Var.PropertyFlags & CPF_BlueprintVisible) != 0);
208
+ VarObj->SetBoolField(TEXT("readOnly"), (Var.PropertyFlags & CPF_BlueprintReadOnly) != 0);
209
+ VariablesArray.Add(MakeShared<FJsonValueObject>(VarObj));
210
+ }
211
+
212
+ auto Result = MCPSuccess();
213
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
214
+ Result->SetStringField(TEXT("name"), Blueprint->GetName());
215
+ if (Blueprint->ParentClass)
216
+ {
217
+ Result->SetStringField(TEXT("parentClass"), Blueprint->ParentClass->GetName());
218
+ }
219
+ Result->SetArrayField(TEXT("models"), GraphsArray);
220
+ Result->SetNumberField(TEXT("modelCount"), GraphsArray.Num());
221
+ Result->SetNumberField(TEXT("totalNodes"), TotalNodes);
222
+ Result->SetNumberField(TEXT("totalLinks"), TotalLinks);
223
+ Result->SetArrayField(TEXT("variables"), VariablesArray);
224
+ Result->SetNumberField(TEXT("variableCount"), VariablesArray.Num());
225
+ if (bTruncatedAny)
226
+ {
227
+ Result->SetStringField(TEXT("note"), TEXT("Some graphs hit the node limit; raise 'limit' or narrow with 'graphName'."));
228
+ }
229
+ return MCPResult(Result);
230
+ }
@@ -75,32 +75,10 @@
75
75
  #include "AI/Navigation/NavCollisionBase.h"
76
76
 
77
77
  // ─── Protected mount guardrail ──────────────────────────────────────────
78
- // Engine-shipped content (/Engine/, /Script/, /Memory/, /Temp/) and Verse
79
- // runtime classes must never be mutated through the bridge. UE's
80
- // UEditorAssetLibrary::DeleteAsset will happily destroy files under
81
- // <engineRoot>/Engine/Content/ if not stopped - verified the hard way.
82
- // Apply this check to every handler that deletes, moves, or renames an
83
- // asset. Plugin content roots (mounted under /<PluginName>/) are NOT
84
- // protected here; per-project plugin content is expected to be writable.
78
+ // The rule itself is MCPIsProtectedAssetPath in HandlerUtils.h, shared by
79
+ // every asset translation unit so the write paths cannot drift apart.
85
80
  namespace
86
81
  {
87
- bool IsProtectedAssetPath(const FString& Path)
88
- {
89
- FString P = Path;
90
- P.TrimStartAndEndInline();
91
- if (P.IsEmpty()) return false;
92
- // Tolerate leading whitespace and the surface form (no leading slash).
93
- if (!P.StartsWith(TEXT("/"))) P = TEXT("/") + P;
94
- const FString L = P.ToLower();
95
- if (L.StartsWith(TEXT("/engine/"))) return true;
96
- if (L.StartsWith(TEXT("/script/"))) return true;
97
- if (L.StartsWith(TEXT("/memory/"))) return true;
98
- if (L.StartsWith(TEXT("/temp/"))) return true;
99
- // Verse runtime objects surface as /Script/CoreUObject.* etc.
100
- if (L.Contains(TEXT("/script/"))) return true;
101
- return false;
102
- }
103
-
104
82
  TSharedPtr<FJsonValue> MakeProtectedPathError(const FString& Path)
105
83
  {
106
84
  return MCPError(FString::Printf(
@@ -1488,8 +1466,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::RenameAsset(const TSharedPtr<FJsonObject>
1488
1466
  return MCPError(TEXT("Missing 'sourcePath'+'destinationPath' or 'assetPath'+'newName'"));
1489
1467
  }
1490
1468
 
1491
- if (IsProtectedAssetPath(SourcePath)) return MakeProtectedPathError(SourcePath);
1492
- if (IsProtectedAssetPath(DestPath)) return MakeProtectedPathError(DestPath);
1469
+ if (MCPIsProtectedAssetPath(SourcePath)) return MakeProtectedPathError(SourcePath);
1470
+ if (MCPIsProtectedAssetPath(DestPath)) return MakeProtectedPathError(DestPath);
1493
1471
 
1494
1472
  // Idempotency: if already at destination, no-op.
1495
1473
  if (SourcePath == DestPath)
@@ -1697,7 +1675,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::DeleteAsset(const TSharedPtr<FJsonObject>
1697
1675
  FString AssetPath;
1698
1676
  if (auto Err = RequireStringAlt(Params, TEXT("path"), TEXT("assetPath"), AssetPath)) return Err;
1699
1677
 
1700
- if (IsProtectedAssetPath(AssetPath)) return MakeProtectedPathError(AssetPath);
1678
+ if (MCPIsProtectedAssetPath(AssetPath)) return MakeProtectedPathError(AssetPath);
1701
1679
 
1702
1680
  const bool bForce = OptionalBool(Params, TEXT("force"), false);
1703
1681
 
@@ -1763,7 +1741,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::DeleteAssetBatch(const TSharedPtr<FJsonOb
1763
1741
  TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1764
1742
  Entry->SetStringField(TEXT("path"), Path);
1765
1743
 
1766
- if (IsProtectedAssetPath(Path))
1744
+ if (MCPIsProtectedAssetPath(Path))
1767
1745
  {
1768
1746
  Entry->SetStringField(TEXT("status"), TEXT("protected"));
1769
1747
  Entry->SetStringField(TEXT("reason"), TEXT("Engine/Script/Memory/Temp mounts are read-only via the bridge"));
@@ -1892,7 +1870,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::BulkRename(const TSharedPtr<FJsonObject>&
1892
1870
  continue;
1893
1871
  }
1894
1872
 
1895
- if (IsProtectedAssetPath(SourcePath) || IsProtectedAssetPath(NewPackagePath))
1873
+ if (MCPIsProtectedAssetPath(SourcePath) || MCPIsProtectedAssetPath(NewPackagePath))
1896
1874
  {
1897
1875
  Record->SetStringField(TEXT("status"), TEXT("protected"));
1898
1876
  Record->SetStringField(TEXT("reason"), TEXT("Engine/Script/Memory/Temp mounts are read-only via the bridge"));
@@ -2555,8 +2533,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::MoveFolder(const TSharedPtr<FJsonObject>&
2555
2533
  SourcePath.RemoveFromEnd(TEXT("/"));
2556
2534
  DestinationPath.RemoveFromEnd(TEXT("/"));
2557
2535
 
2558
- if (IsProtectedAssetPath(SourcePath)) return MakeProtectedPathError(SourcePath);
2559
- if (IsProtectedAssetPath(DestinationPath)) return MakeProtectedPathError(DestinationPath);
2536
+ if (MCPIsProtectedAssetPath(SourcePath)) return MakeProtectedPathError(SourcePath);
2537
+ if (MCPIsProtectedAssetPath(DestinationPath)) return MakeProtectedPathError(DestinationPath);
2560
2538
 
2561
2539
  // Scan source path to discover all assets
2562
2540
  IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
@@ -2738,7 +2716,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::DeleteFolder(const TSharedPtr<FJsonObject
2738
2716
  continue;
2739
2717
  }
2740
2718
 
2741
- if (IsProtectedAssetPath(Norm))
2719
+ if (MCPIsProtectedAssetPath(Norm))
2742
2720
  {
2743
2721
  Entry->SetStringField(TEXT("status"), TEXT("failed"));
2744
2722
  Entry->SetStringField(TEXT("reason"), TEXT("protected_path"));
@@ -64,26 +64,6 @@ namespace
64
64
  bool PassedPreflight() const { return Error.IsEmpty(); }
65
65
  };
66
66
 
67
- // Mirrors IsProtectedAssetPath in AssetHandlers.cpp, which is file-local to
68
- // that translation unit. Keep the two rule sets identical: a write path that
69
- // enforces a weaker rule than its neighbours is how engine content gets
70
- // mutated through the bridge.
71
- bool IsProtectedBulkAssetPath(const FString& Path)
72
- {
73
- FString Normalized = Path;
74
- Normalized.TrimStartAndEndInline();
75
- if (Normalized.IsEmpty()) return false;
76
- if (!Normalized.StartsWith(TEXT("/"))) Normalized = TEXT("/") + Normalized;
77
- const FString Lower = Normalized.ToLower();
78
- if (Lower.StartsWith(TEXT("/engine/"))) return true;
79
- if (Lower.StartsWith(TEXT("/script/"))) return true;
80
- if (Lower.StartsWith(TEXT("/memory/"))) return true;
81
- if (Lower.StartsWith(TEXT("/temp/"))) return true;
82
- // Verse runtime objects surface as /Script/CoreUObject.* etc.
83
- if (Lower.Contains(TEXT("/script/"))) return true;
84
- return false;
85
- }
86
-
87
67
  TSharedPtr<FJsonObject> MakePropertyReadback(
88
68
  const FPreparedPropertyWrite& Prepared,
89
69
  const TSharedPtr<FJsonValue>& ActualValue,
@@ -198,7 +178,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::BulkSetAssetProperties(const TSharedPtr<F
198
178
  }
199
179
  PreparedAsset.AssetPath = AssetPath;
200
180
 
201
- if (IsProtectedBulkAssetPath(AssetPath))
181
+ if (MCPIsProtectedAssetPath(AssetPath))
202
182
  {
203
183
  RejectItem(BulkStatusProtected, FString::Printf(
204
184
  TEXT("Refusing to mutate protected mount: %s. Engine, /Script/, /Memory/, /Temp/ are read-only via the bridge."),
@@ -50,21 +50,6 @@ struct FPreparedUpsertItem
50
50
  TArray<FPreparedProperty> Properties;
51
51
  };
52
52
 
53
- bool IsProtectedAssetPath(const FString& Path)
54
- {
55
- FString Normalized = Path;
56
- Normalized.TrimStartAndEndInline();
57
- if (!Normalized.StartsWith(TEXT("/")))
58
- {
59
- Normalized = TEXT("/") + Normalized;
60
- }
61
- const FString Lower = Normalized.ToLower();
62
- return Lower.StartsWith(TEXT("/engine/"))
63
- || Lower.StartsWith(TEXT("/script/"))
64
- || Lower.StartsWith(TEXT("/memory/"))
65
- || Lower.StartsWith(TEXT("/temp/"));
66
- }
67
-
68
53
  UClass* ResolveDataAssetClass(const FString& ClassName)
69
54
  {
70
55
  UClass* DataClass = nullptr;
@@ -144,7 +129,7 @@ bool ParseAssetIdentity(
144
129
  }
145
130
 
146
131
  const FString LongPackageName = OutItem.PackagePath + TEXT("/") + OutItem.Name;
147
- if (IsProtectedAssetPath(LongPackageName))
132
+ if (MCPIsProtectedAssetPath(LongPackageName))
148
133
  {
149
134
  OutError = FString::Printf(TEXT("Refusing to mutate protected package '%s'"), *LongPackageName);
150
135
  return false;
@@ -357,7 +342,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::BulkRestoreDataAssets(const TSharedPtr<FJ
357
342
  FString AssetPath;
358
343
  const TSharedPtr<FJsonObject>* Properties = nullptr;
359
344
  if (!(*Item)->TryGetStringField(TEXT("assetPath"), AssetPath)
360
- || IsProtectedAssetPath(AssetPath)
345
+ || MCPIsProtectedAssetPath(AssetPath)
361
346
  || !(*Item)->TryGetObjectField(TEXT("properties"), Properties)
362
347
  || !Properties
363
348
  || !Properties->IsValid())
@@ -414,7 +399,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::BulkRestoreDataAssets(const TSharedPtr<FJ
414
399
  FString AssetPath;
415
400
  if (!(*CreatedAssetPaths)[PathIndex].IsValid()
416
401
  || !(*CreatedAssetPaths)[PathIndex]->TryGetString(AssetPath)
417
- || IsProtectedAssetPath(AssetPath))
402
+ || MCPIsProtectedAssetPath(AssetPath))
418
403
  {
419
404
  Errors.Add(MakeShared<FJsonValueString>(FString::Printf(TEXT("createdAssetPaths[%d] is invalid"), PathIndex)));
420
405
  continue;