ue-mcp 1.4.11-beta.7 → 1.4.11-beta.8

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.
@@ -38,6 +38,6 @@
38
38
  },
39
39
  "nativeToolsets": 52,
40
40
  "nativeToolActions": 830,
41
- "generatedAt": "2026-10-26T22:55:52.670Z",
42
- "version": "1.4.11-beta.7"
41
+ "generatedAt": "2026-10-27T00:12:46.925Z",
42
+ "version": "1.4.11-beta.8"
43
43
  }
package/dist/tools/pie.js CHANGED
@@ -51,10 +51,10 @@ export const pieTool = categoryTool("pie", "PIE testing: record, replay and obse
51
51
  session_errors: specBp("read", "Deduped errors and warnings logged during a PIE session, with Blueprint exceptions.", "pie_session_errors"),
52
52
  session_log: specBp("read", "Paged Output Log lines captured during a PIE session, with filters.", "pie_session_log"),
53
53
  perf_summary: specBp("read", "A recording's frametime avg/p50/p99/max, game/render/gpu ms, peak memory and worst hitches.", "pie_perf_summary"),
54
- script_create: specBp("mutate", "Create a test script asset: steps of {instruction, judge auto|manual, arrange, act, expect {value, op decreasing|increasing|eq|gt|lt|visible|exists, target, within_ms}}. arrange takes a scenario's spawn|set|call entries and act takes uat_run's step actions (place, press, inject, hold, tape, console, capture, wait_ms); they run in that order before the expect window, which counts from the end of act. Refuses a step it cannot check, with errors [{step, field, message, candidates}]: an expect.value the level, player pawn and profile do not resolve, an op or target the value's type cannot meet, or an actor, actor class or Input Action arrange and act name that does not exist. resolve_in_pie=true on a step defers a value that only exists in PIE; a script with no auto step needs manual_only=true. map names the level the script plays in (e.g. /Game/Maps/L_Arena) and is refused when no such level exists.", "pie_script_create"),
54
+ script_create: specBp("mutate", "Create a test script asset: steps of {instruction, judge auto|manual, arrange, act, expect {value, op decreasing|increasing|eq|gt|lt|visible|exists, target, within_ms}}. arrange takes a scenario's spawn|set|call entries and act takes uat_run's step actions (place, press, inject, hold, tape, console, capture, wait_ms); they run in that order before the expect window, which counts from the end of act. Always saves, returning what the check finds as warnings [{step, field, message, candidates}]: an expect.value the level, player pawn and profile do not resolve, an op or target the value's type cannot meet, an actor, actor class or Input Action arrange and act name that does not exist, or no auto step without manual_only=true. resolve_in_pie=true on a step defers a value that only exists in PIE. Refused (INVALID_SCRIPT_STEPS, errors in the same shape) only when the script cannot be written: no steps, a step that does not parse, or a map (e.g. /Game/Maps/L_Arena) that is not a level.", "pie_script_create"),
55
55
  script_read: specBp("read", "Read a test script.", "pie_script_read"),
56
- script_update: specBp("mutate", "Update a test script; steps given replace the stored ones. Checks the result like script_create when steps, profile, map or manual_only change.", "pie_script_update"),
57
- script_validate: specBp("read", "Check steps the way script_create does without saving anything: valid, and errors [{step, field, message, candidates}] for every step the observer could not check.", "pie_script_validate"),
56
+ script_update: specBp("mutate", "Update a test script; steps given replace the stored ones. Checks the result like script_create when steps, profile, map or manual_only change, and saves with its findings as warnings; refuses only what cannot be written.", "pie_script_update"),
57
+ script_validate: specBp("read", "Check steps the way script_create does without saving anything: valid, and errors [{step, field, message, candidates}] for every finding script_create would return as a warning or refuse.", "pie_script_validate"),
58
58
  script_values: specBp("read", "What a script can name in the open level: the player pawn's readable value paths with kind and type, level actors, classes the game mode spawns, Widget Blueprints and the profile's tracked values. target adds which actors are of that class and whether it is the player pawn.", "pie_script_values"),
59
59
  script_delete: { ...specBp("mutate", "Delete a test script (confirm=true).", "pie_script_delete"), destructive: true },
60
60
  script_list: specBp("read", "List test scripts, each with map and last_run: its newest run's run_id, state, started_at, ended_at, summary and results_path, or null.", "pie_script_list"),
@@ -1 +1 @@
1
- {"version":3,"file":"pie.js","sourceRoot":"","sources":["../../src/tools/pie.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pie.js","sourceRoot":"","sources":["../../src/tools/pie.ts"],"names":[],"mappings":"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"}
@@ -15464,15 +15464,17 @@ tasks:
15464
15464
  arrange takes a scenario's spawn|set|call entries and act takes uat_run's
15465
15465
  step actions (place, press, inject, hold, tape, console, capture,
15466
15466
  wait_ms); they run in that order before the expect window, which counts
15467
- from the end of act. Refuses a step it cannot check, with errors [{step,
15468
- field, message, candidates}]: an expect.value the level, player pawn and
15469
- profile do not resolve, an op or target the value's type cannot meet, or
15470
- an actor, actor class or Input Action arrange and act name that does not
15471
- exist. resolve_in_pie=true on a step defers a value that only exists in
15472
- PIE; a script with no auto step needs manual_only=true. map names the
15473
- level the script plays in (e.g. /Game/Maps/L_Arena) and is refused when no
15474
- such level exists. Params: name, package_path?, steps, description?,
15475
- profile?, map?, default_within_ms?, manual_only?, onConflict?
15467
+ from the end of act. Always saves, returning what the check finds as
15468
+ warnings [{step, field, message, candidates}]: an expect.value the level,
15469
+ player pawn and profile do not resolve, an op or target the value's type
15470
+ cannot meet, an actor, actor class or Input Action arrange and act name
15471
+ that does not exist, or no auto step without manual_only=true.
15472
+ resolve_in_pie=true on a step defers a value that only exists in PIE.
15473
+ Refused (INVALID_SCRIPT_STEPS, errors in the same shape) only when the
15474
+ script cannot be written: no steps, a step that does not parse, or a map
15475
+ (e.g. /Game/Maps/L_Arena) that is not a level. Params: name,
15476
+ package_path?, steps, description?, profile?, map?, default_within_ms?,
15477
+ manual_only?, onConflict?
15476
15478
  pie.script_read:
15477
15479
  class_path: pie.script_read
15478
15480
  group: pie
@@ -15482,16 +15484,18 @@ tasks:
15482
15484
  group: pie
15483
15485
  description: >-
15484
15486
  Update a test script; steps given replace the stored ones. Checks the
15485
- result like script_create when steps, profile, map or manual_only change.
15486
- Params: path, steps?, description?, profile?, map?, default_within_ms?,
15487
- manual_only?
15487
+ result like script_create when steps, profile, map or manual_only change,
15488
+ and saves with its findings as warnings; refuses only what cannot be
15489
+ written. Params: path, steps?, description?, profile?, map?,
15490
+ default_within_ms?, manual_only?
15488
15491
  pie.script_validate:
15489
15492
  class_path: pie.script_validate
15490
15493
  group: pie
15491
15494
  description: >-
15492
15495
  Check steps the way script_create does without saving anything: valid, and
15493
- errors [{step, field, message, candidates}] for every step the observer
15494
- could not check. Params: steps, profile?, map?, manual_only?
15496
+ errors [{step, field, message, candidates}] for every finding
15497
+ script_create would return as a warning or refuse. Params: steps,
15498
+ profile?, map?, manual_only?
15495
15499
  pie.script_values:
15496
15500
  class_path: pie.script_values
15497
15501
  group: pie
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ue-mcp",
3
- "version": "1.4.11-beta.7",
3
+ "version": "1.4.11-beta.8",
4
4
  "description": "Unreal Engine MCP server - 30 tools, 2111+ actions for AI-driven editor control",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -78,10 +78,10 @@ namespace
78
78
  return Profile.IsEmpty() || LoadObject<UMCPPIEObservationProfile>(nullptr, *Profile) != nullptr;
79
79
  }
80
80
 
81
- // The checks script_create and script_update share: the profile and map exist, and each step is
82
- // one the observer can check against the level and that profile. Issues already found (parse
83
- // failures) come in through Issues. Returns the structured refusal, or null.
84
- TSharedPtr<FJsonValue> CheckTestScript(const TArray<FScriptStepDef>& Steps, const FString& Profile, const FString& Map,
81
+ // The checks script_create, script_update and script_validate share: the profile and map exist,
82
+ // and each step is one the observer can check against the level and that profile. Issues already
83
+ // found (parse failures) come in through Issues, and every finding is added to it.
84
+ void CheckTestScript(const TArray<FScriptStepDef>& Steps, const FString& Profile, const FString& Map,
85
85
  bool bManualOnly, TArray<FScriptStepIssue>& Issues)
86
86
  {
87
87
  TSet<int32> Failed;
@@ -113,6 +113,7 @@ namespace
113
113
  FScriptStepIssue Issue;
114
114
  Issue.Field = TEXT("map");
115
115
  Issue.Message = FString::Printf(TEXT("Map not found: %s is not a level asset"), *MCPMapPackageName(Map));
116
+ Issue.bBlocksSave = true;
116
117
  Issue.Candidates = RankScriptCandidates(MCPMapPackageName(Map), Maps);
117
118
  Issues.Add(MoveTemp(Issue));
118
119
  }
@@ -127,8 +128,18 @@ namespace
127
128
  }
128
129
  ValidateScriptSteps(Steps, bManualOnly, Ctx, Issues, Failed);
129
130
  }
130
- if (Issues.Num() == 0) return nullptr;
131
- return MakeShared<FJsonValueObject>(ScriptIssuesErrorObject(Issues));
131
+ }
132
+
133
+ // script_create and script_update refuse only what cannot be written; every other finding is
134
+ // returned with the save as warnings, since a value may only resolve once PIE runs.
135
+ TSharedPtr<FJsonValue> RefuseUnsavableScript(const TArray<FScriptStepIssue>& Issues, TArray<FScriptStepIssue>& OutWarnings)
136
+ {
137
+ TArray<FScriptStepIssue> Blocking;
138
+ SplitScriptIssues(Issues, Blocking, OutWarnings);
139
+ if (Blocking.Num() == 0) return nullptr;
140
+ TSharedPtr<FJsonObject> Obj = ScriptIssuesErrorObject(Blocking, TEXT("The script cannot be saved as written"));
141
+ Obj->SetArrayField(TEXT("warnings"), ScriptIssuesToJson(OutWarnings));
142
+ return MakeShared<FJsonValueObject>(Obj);
132
143
  }
133
144
 
134
145
  FString ScriptVerdictForStatus(EScriptStepStatus Status)
@@ -402,7 +413,9 @@ TSharedPtr<FJsonValue> FPIEHandlers::PieScriptCreate(const TSharedPtr<FJsonObjec
402
413
  TArray<FScriptStepDef> Steps;
403
414
  TArray<FScriptStepIssue> Issues;
404
415
  ParseScriptStepsJsonIssues(*StepsArr, Steps, Issues);
405
- if (TSharedPtr<FJsonValue> Refusal = CheckTestScript(Steps, Profile, Map, bManualOnly, Issues)) return Refusal;
416
+ CheckTestScript(Steps, Profile, Map, bManualOnly, Issues);
417
+ TArray<FScriptStepIssue> Warnings;
418
+ if (TSharedPtr<FJsonValue> Refusal = RefuseUnsavableScript(Issues, Warnings)) return Refusal;
406
419
 
407
420
  const FString AssetPath = PackagePath / Name;
408
421
  if (OnConflict == TEXT("update") || OnConflict == TEXT("replace"))
@@ -430,6 +443,7 @@ TSharedPtr<FJsonValue> FPIEHandlers::PieScriptCreate(const TSharedPtr<FJsonObjec
430
443
  MCPSetRollback(Result, TEXT("pie_script_update"), Inverse);
431
444
  }
432
445
  TestScriptFieldsToJson(Existing, AssetPath, Result);
446
+ Result->SetArrayField(TEXT("warnings"), ScriptIssuesToJson(Warnings));
433
447
  return MCPResult(Result);
434
448
  }
435
449
  }
@@ -454,6 +468,7 @@ TSharedPtr<FJsonValue> FPIEHandlers::PieScriptCreate(const TSharedPtr<FJsonObjec
454
468
  auto Result = MCPSuccess();
455
469
  MCPSetCreated(Result);
456
470
  TestScriptFieldsToJson(Script, AssetPath, Result);
471
+ Result->SetArrayField(TEXT("warnings"), ScriptIssuesToJson(Warnings));
457
472
  TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
458
473
  Payload->SetStringField(TEXT("path"), AssetPath);
459
474
  Payload->SetBoolField(TEXT("confirm"), true);
@@ -499,19 +514,18 @@ TSharedPtr<FJsonValue> FPIEHandlers::PieScriptUpdate(const TSharedPtr<FJsonObjec
499
514
  UMCPPIETestScript* Script = LoadTestScriptAsset(Path);
500
515
  if (!Script) return MCPError(FString::Printf(TEXT("Test script not found: %s"), *Path));
501
516
 
502
- // The script as it would stand after the update is checked whenever its steps, profile, map or
517
+ // The script as it will stand after the update is checked whenever its steps, profile, map or
503
518
  // manual_only change; a description or window change alone leaves the steps as they were.
504
519
  TArray<FScriptStepDef> Steps;
520
+ TArray<FScriptStepIssue> Warnings;
505
521
  if (bHasSteps || bHasProfile || bHasMap || bHasManualOnly)
506
522
  {
507
523
  TArray<FScriptStepIssue> Issues;
508
524
  if (bHasSteps) ParseScriptStepsJsonIssues(*StepsArr, Steps, Issues);
509
525
  else ScriptStepsFromAsset(Script, Steps);
510
- if (TSharedPtr<FJsonValue> Refusal = CheckTestScript(Steps, bHasProfile ? Profile : Script->Profile,
511
- bHasMap ? Map : Script->Map, bHasManualOnly ? bManualOnly : Script->bManualOnly, Issues))
512
- {
513
- return Refusal;
514
- }
526
+ CheckTestScript(Steps, bHasProfile ? Profile : Script->Profile,
527
+ bHasMap ? Map : Script->Map, bHasManualOnly ? bManualOnly : Script->bManualOnly, Issues);
528
+ if (TSharedPtr<FJsonValue> Refusal = RefuseUnsavableScript(Issues, Warnings)) return Refusal;
515
529
  }
516
530
 
517
531
  const FString Before = TestScriptStateKey(Script);
@@ -535,6 +549,7 @@ TSharedPtr<FJsonValue> FPIEHandlers::PieScriptUpdate(const TSharedPtr<FJsonObjec
535
549
  MCPSetRollback(Result, TEXT("pie_script_update"), Inverse);
536
550
  }
537
551
  TestScriptFieldsToJson(Script, Path, Result);
552
+ Result->SetArrayField(TEXT("warnings"), ScriptIssuesToJson(Warnings));
538
553
  return MCPResult(Result);
539
554
  }
540
555
 
@@ -551,15 +566,13 @@ TSharedPtr<FJsonValue> FPIEHandlers::PieScriptValidate(const TSharedPtr<FJsonObj
551
566
  TArray<FScriptStepDef> Steps;
552
567
  TArray<FScriptStepIssue> Issues;
553
568
  ParseScriptStepsJsonIssues(*StepsArr, Steps, Issues);
554
- const TSharedPtr<FJsonValue> Refusal = CheckTestScript(Steps, Profile, Map, bManualOnly, Issues);
569
+ CheckTestScript(Steps, Profile, Map, bManualOnly, Issues);
555
570
 
556
- // The same checks script_create runs, answered rather than refused, so a caller can draft in a loop.
571
+ // The same checks script_create runs, every finding reported as an error, so a caller can draft in a loop.
557
572
  auto Result = MCPSuccess();
558
- Result->SetBoolField(TEXT("valid"), !Refusal.IsValid());
559
- TArray<TSharedPtr<FJsonValue>> Errors;
560
- for (const FScriptStepIssue& I : Issues) Errors.Add(MakeShared<FJsonValueObject>(ScriptIssueToJson(I)));
561
- Result->SetArrayField(TEXT("errors"), Errors);
562
- if (Refusal.IsValid()) Result->SetStringField(TEXT("summary"), Refusal->AsObject()->GetStringField(TEXT("error")));
573
+ Result->SetBoolField(TEXT("valid"), Issues.Num() == 0);
574
+ Result->SetArrayField(TEXT("errors"), ScriptIssuesToJson(Issues));
575
+ if (Issues.Num() > 0) Result->SetStringField(TEXT("summary"), ScriptIssuesErrorObject(Issues)->GetStringField(TEXT("error")));
563
576
  Result->SetArrayField(TEXT("steps"), ScriptStepDefsToJson(Steps));
564
577
  return MCPResult(Result);
565
578
  }
@@ -550,7 +550,9 @@ namespace MCPPIE
550
550
  Out.Reset();
551
551
  if (Values.Num() == 0)
552
552
  {
553
- OutIssues.Add(DraftCheckIssue(INDEX_NONE, TEXT("steps"), TEXT("steps must contain at least one step")));
553
+ FScriptStepIssue Issue = DraftCheckIssue(INDEX_NONE, TEXT("steps"), TEXT("steps must contain at least one step"));
554
+ Issue.bBlocksSave = true;
555
+ OutIssues.Add(MoveTemp(Issue));
554
556
  return false;
555
557
  }
556
558
  bool bOk = true;
@@ -565,6 +567,7 @@ namespace MCPPIE
565
567
  }
566
568
  bOk = false;
567
569
  FScriptStepIssue Issue = DraftCheckIssue(I, Field, Err);
570
+ Issue.bBlocksSave = true;
568
571
  if (Field == TEXT("judge"))
569
572
  {
570
573
  Issue.Candidates = { TEXT("auto"), TEXT("manual") };
@@ -1125,21 +1128,29 @@ namespace MCPPIE
1125
1128
  return O;
1126
1129
  }
1127
1130
 
1128
- TSharedPtr<FJsonObject> ScriptIssuesErrorObject(const TArray<FScriptStepIssue>& Issues)
1131
+ TArray<TSharedPtr<FJsonValue>> ScriptIssuesToJson(const TArray<FScriptStepIssue>& Issues)
1132
+ {
1133
+ TArray<TSharedPtr<FJsonValue>> Out;
1134
+ for (const FScriptStepIssue& Issue : Issues) Out.Add(MakeShared<FJsonValueObject>(ScriptIssueToJson(Issue)));
1135
+ return Out;
1136
+ }
1137
+
1138
+ TSharedPtr<FJsonObject> ScriptIssuesErrorObject(const TArray<FScriptStepIssue>& Issues, const TCHAR* Lead)
1129
1139
  {
1130
1140
  TArray<FString> Lines;
1131
- TArray<TSharedPtr<FJsonValue>> Errors;
1132
- for (const FScriptStepIssue& Issue : Issues)
1133
- {
1134
- Lines.Add(Issue.Message);
1135
- Errors.Add(MakeShared<FJsonValueObject>(ScriptIssueToJson(Issue)));
1136
- }
1141
+ for (const FScriptStepIssue& Issue : Issues) Lines.Add(Issue.Message);
1137
1142
  TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
1138
1143
  Obj->SetBoolField(TEXT("success"), false);
1139
1144
  Obj->SetStringField(TEXT("errorCode"), TEXT("INVALID_SCRIPT_STEPS"));
1140
- Obj->SetStringField(TEXT("error"), FString::Printf(TEXT("The script cannot be checked as written (%d problem%s): %s"),
1141
- Issues.Num(), Issues.Num() == 1 ? TEXT("") : TEXT("s"), *FString::Join(Lines, TEXT(" | "))));
1142
- Obj->SetArrayField(TEXT("errors"), Errors);
1145
+ Obj->SetStringField(TEXT("error"), FString::Printf(TEXT("%s (%d problem%s): %s"),
1146
+ Lead, Issues.Num(), Issues.Num() == 1 ? TEXT("") : TEXT("s"), *FString::Join(Lines, TEXT(" | "))));
1147
+ Obj->SetArrayField(TEXT("errors"), ScriptIssuesToJson(Issues));
1143
1148
  return Obj;
1144
1149
  }
1150
+
1151
+ void SplitScriptIssues(const TArray<FScriptStepIssue>& Issues, TArray<FScriptStepIssue>& OutBlocking,
1152
+ TArray<FScriptStepIssue>& OutWarnings)
1153
+ {
1154
+ for (const FScriptStepIssue& Issue : Issues) (Issue.bBlocksSave ? OutBlocking : OutWarnings).Add(Issue);
1155
+ }
1145
1156
  }
@@ -10,9 +10,10 @@ class UClass;
10
10
  class UObject;
11
11
 
12
12
  /**
13
- * Draft-time checks for test scripts: pie_script_create and pie_script_update
14
- * refuse a step the observer could never read, or whose op and target cannot
15
- * hold for the value's type, before the script is saved rather than when it runs.
13
+ * Draft-time checks for test scripts: a step the observer could never read, or
14
+ * whose op and target cannot hold for the value's type. pie_script_validate
15
+ * reports them; pie_script_create and pie_script_update save anyway and return
16
+ * them as warnings, refusing only what cannot be written (bBlocksSave).
16
17
  *
17
18
  * Values resolve the way the observer resolves them at run time: a profile
18
19
  * tracked value (exact path or last segment), else a dotted reflection path on
@@ -34,6 +35,9 @@ namespace MCPPIE
34
35
  FString Field;
35
36
  FString Message;
36
37
  TArray<FString> Candidates;
38
+ // The script cannot be written with this issue (a step that does not parse, a missing map).
39
+ // Every other issue is a warning: script_create and script_update save with it.
40
+ bool bBlocksSave = false;
37
41
  };
38
42
 
39
43
  enum class EScriptValueKind : uint8 { Number, Integer, Boolean, Enum };
@@ -101,6 +105,13 @@ namespace MCPPIE
101
105
  TSharedRef<FJsonObject> ScriptWatchableValuesToJson(const FString& TargetClass, const FString& ProfilePath, int32 Limit);
102
106
 
103
107
  TSharedRef<FJsonObject> ScriptIssueToJson(const FScriptStepIssue& Issue);
108
+ TArray<TSharedPtr<FJsonValue>> ScriptIssuesToJson(const TArray<FScriptStepIssue>& Issues);
104
109
  // { success: false, errorCode: INVALID_SCRIPT_STEPS, error, errors: [{ step, field, message, candidates? }] }.
105
- TSharedPtr<FJsonObject> ScriptIssuesErrorObject(const TArray<FScriptStepIssue>& Issues);
110
+ // Lead opens the error text, ahead of the count and the messages.
111
+ TSharedPtr<FJsonObject> ScriptIssuesErrorObject(const TArray<FScriptStepIssue>& Issues,
112
+ const TCHAR* Lead = TEXT("The script cannot be checked as written"));
113
+
114
+ // Issues that block a save, and the rest (warnings), each in their original order.
115
+ void SplitScriptIssues(const TArray<FScriptStepIssue>& Issues, TArray<FScriptStepIssue>& OutBlocking,
116
+ TArray<FScriptStepIssue>& OutWarnings);
106
117
  }
@@ -0,0 +1,163 @@
1
+ // pie_script_create and pie_script_update save a script whose steps the static
2
+ // check cannot resolve, returning the findings as warnings, and refuse only a
3
+ // script that cannot be written.
4
+
5
+ #include "Misc/AutomationTest.h"
6
+
7
+ #if WITH_DEV_AUTOMATION_TESTS
8
+
9
+ #include "HandlerRegistry.h"
10
+ #include "HandlerUtils.h"
11
+ #include "Handlers/PIE/PIEHandlers.h"
12
+ #include "Dom/JsonObject.h"
13
+ #include "EditorAssetLibrary.h"
14
+ #include "Misc/Guid.h"
15
+
16
+ namespace MCPPIEScriptSaveWarningsTests
17
+ {
18
+ const TCHAR* const Dir = TEXT("/Game/UEMCPTests/ScriptWarnings");
19
+ // A value only a running game has: the pawn's PlayerState is empty before PIE.
20
+ const TCHAR* const PIEOnlyValue = TEXT("PlayerState.SigilAttributes.Sigil.CurrentValue");
21
+
22
+ TSharedPtr<FJsonObject> Run(FMCPHandlerRegistry& Registry, const TCHAR* Method, const TSharedPtr<FJsonObject>& Request)
23
+ {
24
+ const TSharedPtr<FJsonValue> Response = Registry.ExecuteHandler(Method, Request);
25
+ return Response.IsValid() && Response->Type == EJson::Object ? Response->AsObject() : MakeShared<FJsonObject>();
26
+ }
27
+
28
+ bool Succeeded(const TSharedPtr<FJsonObject>& Response)
29
+ {
30
+ bool bSuccess = false;
31
+ return Response->TryGetBoolField(TEXT("success"), bSuccess) && bSuccess;
32
+ }
33
+
34
+ FString Field(const TSharedPtr<FJsonObject>& Response, const TCHAR* Name)
35
+ {
36
+ FString Value;
37
+ Response->TryGetStringField(Name, Value);
38
+ return Value;
39
+ }
40
+
41
+ TArray<TSharedPtr<FJsonValue>> Array(const TSharedPtr<FJsonObject>& Response, const TCHAR* Name)
42
+ {
43
+ const TArray<TSharedPtr<FJsonValue>>* Values = nullptr;
44
+ return Response->TryGetArrayField(Name, Values) && Values ? *Values : TArray<TSharedPtr<FJsonValue>>();
45
+ }
46
+
47
+ TSharedPtr<FJsonValue> AutoStep(const FString& Value, const FString& Op)
48
+ {
49
+ TSharedPtr<FJsonObject> Expect = MakeShared<FJsonObject>();
50
+ Expect->SetStringField(TEXT("value"), Value);
51
+ Expect->SetStringField(TEXT("op"), Op);
52
+ Expect->SetNumberField(TEXT("target"), 0);
53
+ TSharedPtr<FJsonObject> Step = MakeShared<FJsonObject>();
54
+ Step->SetStringField(TEXT("instruction"), TEXT("The sigil charges"));
55
+ Step->SetObjectField(TEXT("expect"), Expect);
56
+ return MakeShared<FJsonValueObject>(Step);
57
+ }
58
+
59
+ TSharedPtr<FJsonValue> ManualStep()
60
+ {
61
+ TSharedPtr<FJsonObject> Step = MakeShared<FJsonObject>();
62
+ Step->SetStringField(TEXT("instruction"), TEXT("The sigil glows"));
63
+ Step->SetStringField(TEXT("judge"), TEXT("manual"));
64
+ return MakeShared<FJsonValueObject>(Step);
65
+ }
66
+
67
+ // The step and field of the first warning, or an empty pair.
68
+ TPair<int32, FString> FirstWarning(const TSharedPtr<FJsonObject>& Response)
69
+ {
70
+ const TArray<TSharedPtr<FJsonValue>> Warnings = Array(Response, TEXT("warnings"));
71
+ if (Warnings.Num() == 0 || !Warnings[0]->AsObject().IsValid()) return { INDEX_NONE, FString() };
72
+ const TSharedPtr<FJsonObject> W = Warnings[0]->AsObject();
73
+ double Step = -1;
74
+ W->TryGetNumberField(TEXT("step"), Step);
75
+ return { static_cast<int32>(Step), Field(W, TEXT("field")) };
76
+ }
77
+
78
+ int32 StoredStepCount(FMCPHandlerRegistry& Registry, const FString& Path)
79
+ {
80
+ TSharedPtr<FJsonObject> Read = MakeShared<FJsonObject>();
81
+ Read->SetStringField(TEXT("path"), Path);
82
+ return Array(Run(Registry, TEXT("pie_script_read"), Read), TEXT("steps")).Num();
83
+ }
84
+ }
85
+
86
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
87
+ FPIEScriptSaveWarningsTest,
88
+ "UE.MCP.PIE.Script.SaveWithWarnings",
89
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
90
+
91
+ bool FPIEScriptSaveWarningsTest::RunTest(const FString& /*Parameters*/)
92
+ {
93
+ using namespace MCPPIEScriptSaveWarningsTests;
94
+
95
+ FMCPHandlerRegistry Registry;
96
+ FPIEHandlers::RegisterHandlers(Registry);
97
+ const FString Name = TEXT("S_SaveWarnings_") + FGuid::NewGuid().ToString(EGuidFormats::Digits).Left(8);
98
+ const FString Path = FString(Dir) / Name;
99
+
100
+ // A step the check cannot resolve before PIE is saved, with the finding as a warning.
101
+ TSharedPtr<FJsonObject> Create = MakeShared<FJsonObject>();
102
+ Create->SetStringField(TEXT("name"), Name);
103
+ Create->SetStringField(TEXT("package_path"), Dir);
104
+ Create->SetArrayField(TEXT("steps"), { AutoStep(PIEOnlyValue, TEXT("gt")) });
105
+ const TSharedPtr<FJsonObject> Created = Run(Registry, TEXT("pie_script_create"), Create);
106
+ TestTrue(FString::Printf(TEXT("script_create saves despite the finding: %s"), *Field(Created, TEXT("error"))), Succeeded(Created));
107
+ TestTrue(TEXT("script_create reports created"), Created->HasTypedField<EJson::Boolean>(TEXT("created")));
108
+ TestEqual(TEXT("the warning names step 0"), FirstWarning(Created).Key, 0);
109
+ TestEqual(TEXT("the warning names expect.value"), FirstWarning(Created).Value, FString(TEXT("expect.value")));
110
+ TestTrue(TEXT("the asset exists"), UEditorAssetLibrary::DoesAssetExist(Path));
111
+
112
+ TSharedPtr<FJsonObject> Read = MakeShared<FJsonObject>();
113
+ Read->SetStringField(TEXT("path"), Path);
114
+ const TArray<TSharedPtr<FJsonValue>> Stored = Array(Run(Registry, TEXT("pie_script_read"), Read), TEXT("steps"));
115
+ if (TestEqual(TEXT("one step stored"), Stored.Num(), 1))
116
+ {
117
+ const TSharedPtr<FJsonObject>* Expect = nullptr;
118
+ Stored[0]->AsObject()->TryGetObjectField(TEXT("expect"), Expect);
119
+ TestEqual(TEXT("the unresolved value is stored as written"), Expect ? Field(*Expect, TEXT("value")) : FString(), FString(PIEOnlyValue));
120
+ }
121
+
122
+ // Editing a script that still has the finding saves, and keeps warning.
123
+ TSharedPtr<FJsonObject> Grow = MakeShared<FJsonObject>();
124
+ Grow->SetStringField(TEXT("path"), Path);
125
+ Grow->SetArrayField(TEXT("steps"), { AutoStep(PIEOnlyValue, TEXT("gt")), ManualStep() });
126
+ const TSharedPtr<FJsonObject> Grown = Run(Registry, TEXT("pie_script_update"), Grow);
127
+ TestTrue(FString::Printf(TEXT("script_update saves despite the finding: %s"), *Field(Grown, TEXT("error"))), Succeeded(Grown));
128
+ TestEqual(TEXT("script_update warns about step 0"), FirstWarning(Grown).Key, 0);
129
+ TestEqual(TEXT("two steps stored"), StoredStepCount(Registry, Path), 2);
130
+
131
+ // Deleting a step saves too.
132
+ TSharedPtr<FJsonObject> Shrink = MakeShared<FJsonObject>();
133
+ Shrink->SetStringField(TEXT("path"), Path);
134
+ Shrink->SetArrayField(TEXT("steps"), { AutoStep(PIEOnlyValue, TEXT("gt")) });
135
+ TestTrue(TEXT("script_update removing a step saves"), Succeeded(Run(Registry, TEXT("pie_script_update"), Shrink)));
136
+ TestEqual(TEXT("one step stored after the removal"), StoredStepCount(Registry, Path), 1);
137
+
138
+ // A step that does not parse cannot be written: refused, nothing stored changes.
139
+ TSharedPtr<FJsonObject> Broken = MakeShared<FJsonObject>();
140
+ Broken->SetStringField(TEXT("path"), Path);
141
+ Broken->SetArrayField(TEXT("steps"), { AutoStep(PIEOnlyValue, TEXT("gt")), AutoStep(TEXT("BaseEyeHeight"), TEXT("sideways")) });
142
+ const TSharedPtr<FJsonObject> Refused = Run(Registry, TEXT("pie_script_update"), Broken);
143
+ TestFalse(TEXT("an unparseable step is refused"), Succeeded(Refused));
144
+ TestEqual(TEXT("refusal code"), Field(Refused, TEXT("errorCode")), FString(TEXT("INVALID_SCRIPT_STEPS")));
145
+ const TArray<TSharedPtr<FJsonValue>> Errors = Array(Refused, TEXT("errors"));
146
+ if (TestEqual(TEXT("one blocking error"), Errors.Num(), 1))
147
+ {
148
+ TestEqual(TEXT("the error names expect.op"), Field(Errors[0]->AsObject(), TEXT("field")), FString(TEXT("expect.op")));
149
+ }
150
+ TestEqual(TEXT("the refusal still lists the warning"), FirstWarning(Refused).Key, 0);
151
+ TestEqual(TEXT("the refused update stored nothing"), StoredStepCount(Registry, Path), 1);
152
+
153
+ // Nothing to save at all is refused.
154
+ TSharedPtr<FJsonObject> Empty = MakeShared<FJsonObject>();
155
+ Empty->SetStringField(TEXT("path"), Path);
156
+ Empty->SetArrayField(TEXT("steps"), {});
157
+ TestFalse(TEXT("an empty steps list is refused"), Succeeded(Run(Registry, TEXT("pie_script_update"), Empty)));
158
+
159
+ UEditorAssetLibrary::DeleteAsset(Path);
160
+ return true;
161
+ }
162
+
163
+ #endif // WITH_DEV_AUTOMATION_TESTS
@@ -126,6 +126,8 @@ bool FPIEScriptDraftParseTest::RunTest(const FString& /*Parameters*/)
126
126
  "{\"instruction\":\"x\",\"resolve_in_pie\":true},"
127
127
  "{\"instruction\":\"x\",\"resolve_in_pie\":\"yes\",\"expect\":{\"value\":\"A\",\"op\":\"exists\"}}]")), Steps, Issues);
128
128
  TestFalse(TEXT("bad steps refused"), bOk);
129
+ TestTrue(TEXT("a step that does not parse blocks a save"),
130
+ Issues.Num() > 0 && Issues.FilterByPredicate([](const FScriptStepIssue& I) { return !I.bBlocksSave; }).Num() == 0);
129
131
  TestEqual(TEXT("indices line up with the input"), Steps.Num(), 5);
130
132
  TestEqual(TEXT("one issue per bad step"), Issues.Num(), 4);
131
133
  TestTrue(TEXT("unknown op names its field"), HasIssue(Issues, 1, TEXT("expect.op")));
@@ -138,6 +140,7 @@ bool FPIEScriptDraftParseTest::RunTest(const FString& /*Parameters*/)
138
140
  Issues.Reset();
139
141
  TestFalse(TEXT("empty steps refused"), ParseScriptStepsJsonIssues(TArray<TSharedPtr<FJsonValue>>(), Steps, Issues));
140
142
  TestTrue(TEXT("empty steps is a script-level issue"), HasIssue(Issues, INDEX_NONE, TEXT("steps")));
143
+ TestTrue(TEXT("empty steps blocks a save"), Issues.Num() > 0 && Issues.Last().bBlocksSave);
141
144
 
142
145
  // resolve_in_pie and a boolean target survive a round trip through JSON.
143
146
  Issues.Reset();
@@ -273,6 +276,10 @@ bool FPIEScriptDraftRefusalTest::RunTest(const FString& /*Parameters*/)
273
276
 
274
277
  TArray<FScriptStepIssue> Issues = Check({ Auto(EScriptExpectOp::Gt, TEXT("Helth"), true, 0.0) });
275
278
  Issues.Append(Check({ Manual() }));
279
+ TArray<FScriptStepIssue> Blocking, Warnings;
280
+ SplitScriptIssues(Issues, Blocking, Warnings);
281
+ TestEqual(TEXT("an unresolved value and no auto step do not block a save"), Blocking.Num(), 0);
282
+ TestEqual(TEXT("both are warnings"), Warnings.Num(), 2);
276
283
  const TSharedPtr<FJsonObject> Obj = ScriptIssuesErrorObject(Issues);
277
284
  bool bSuccess = true;
278
285
  TestTrue(TEXT("success false"), Obj->TryGetBoolField(TEXT("success"), bSuccess) && !bSuccess);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "FileVersion": 3,
3
3
  "Version": 1,
4
- "VersionName": "1.4.11-beta.7",
4
+ "VersionName": "1.4.11-beta.8",
5
5
  "FriendlyName": "UE MCP Bridge",
6
6
  "Description": "C++ WebSocket bridge for UE-MCP, providing 2042+ editor actions over the MCP protocol",
7
7
  "Category": "Developer Tools",