ultimate-unreal-engine-mcp 0.1.7 → 0.1.9
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.
|
@@ -177,32 +177,53 @@ export class PluginBridgeClient {
|
|
|
177
177
|
connect() {
|
|
178
178
|
return new Promise((resolve, reject) => {
|
|
179
179
|
const socket = net.createConnection({ port: this.port, host: '127.0.0.1' });
|
|
180
|
-
// JSON
|
|
181
|
-
// and
|
|
182
|
-
//
|
|
183
|
-
//
|
|
180
|
+
// JSON response parser — handles UE's pretty-printed multi-line JSON.
|
|
181
|
+
// UE's FJsonSerializer outputs JSON with \r\n and tabs inside the object,
|
|
182
|
+
// so we can't split on \n (that would break mid-object). Instead we track
|
|
183
|
+
// brace depth: when depth returns to 0, we have a complete JSON object.
|
|
184
184
|
socket.on('data', (chunk) => {
|
|
185
185
|
this.receiveBuffer += chunk.toString('utf8');
|
|
186
|
-
let
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
const response = JSON.parse(line);
|
|
195
|
-
const pending = this.pendingCommands.get(response.correlationId);
|
|
196
|
-
if (pending) {
|
|
197
|
-
this.pendingCommands.delete(response.correlationId);
|
|
198
|
-
pending.resolve(response);
|
|
186
|
+
let startIdx = -1;
|
|
187
|
+
let depth = 0;
|
|
188
|
+
for (let i = 0; i < this.receiveBuffer.length; i++) {
|
|
189
|
+
const ch = this.receiveBuffer[i];
|
|
190
|
+
if (ch === '{') {
|
|
191
|
+
if (depth === 0) {
|
|
192
|
+
startIdx = i;
|
|
199
193
|
}
|
|
200
|
-
|
|
194
|
+
depth++;
|
|
201
195
|
}
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
else if (ch === '}') {
|
|
197
|
+
depth--;
|
|
198
|
+
if (depth === 0 && startIdx >= 0) {
|
|
199
|
+
const jsonStr = this.receiveBuffer.slice(startIdx, i + 1);
|
|
200
|
+
// Remove everything up to and including this object
|
|
201
|
+
this.receiveBuffer = this.receiveBuffer.slice(i + 1);
|
|
202
|
+
try {
|
|
203
|
+
const response = JSON.parse(jsonStr);
|
|
204
|
+
const pending = this.pendingCommands.get(response.correlationId);
|
|
205
|
+
if (pending) {
|
|
206
|
+
this.pendingCommands.delete(response.correlationId);
|
|
207
|
+
pending.resolve(response);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Malformed JSON — ignore (T-07-09 mitigation)
|
|
212
|
+
}
|
|
213
|
+
// Reset loop to scan remaining buffer from the start
|
|
214
|
+
i = -1;
|
|
215
|
+
startIdx = -1;
|
|
216
|
+
}
|
|
204
217
|
}
|
|
205
218
|
}
|
|
219
|
+
// Keep only unprocessed data (partial object) in the buffer
|
|
220
|
+
if (startIdx > 0) {
|
|
221
|
+
this.receiveBuffer = this.receiveBuffer.slice(startIdx);
|
|
222
|
+
}
|
|
223
|
+
else if (depth === 0) {
|
|
224
|
+
// No open braces — discard any whitespace/newlines between objects
|
|
225
|
+
this.receiveBuffer = '';
|
|
226
|
+
}
|
|
206
227
|
});
|
|
207
228
|
socket.once('connect', () => {
|
|
208
229
|
this.socket = socket;
|
|
@@ -93,15 +93,20 @@ export function registerEditorTools(server, bridge) {
|
|
|
93
93
|
y: z.number().describe('Y coordinate in Unreal units (cm)'),
|
|
94
94
|
z: z.number().describe('Z coordinate in Unreal units (cm)'),
|
|
95
95
|
}).describe('World-space location to spawn the actor at'),
|
|
96
|
+
label: z.string().optional().describe('Optional editor display label for the spawned actor'),
|
|
96
97
|
}),
|
|
97
98
|
annotations: {
|
|
98
99
|
readOnlyHint: false,
|
|
99
100
|
destructiveHint: false,
|
|
100
101
|
},
|
|
101
102
|
}, withKnownIssues('ue_spawn_actor', async (args) => {
|
|
103
|
+
const payload = { class_name: args.class_name, location: args.location };
|
|
104
|
+
if (args.label) {
|
|
105
|
+
payload['label'] = args.label;
|
|
106
|
+
}
|
|
102
107
|
return sendOrDisconnect(_bridge, {
|
|
103
108
|
type: 'actor.spawn',
|
|
104
|
-
payload
|
|
109
|
+
payload,
|
|
105
110
|
});
|
|
106
111
|
}));
|
|
107
112
|
// --------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -201,6 +201,13 @@ void RegisterActorCommands(FMCPCommandRouter& Router)
|
|
|
201
201
|
const FVector Location(X, Y, Z);
|
|
202
202
|
const FRotator Rotation(0.0, 0.0, 0.0);
|
|
203
203
|
|
|
204
|
+
// Optional label — sets the editor display name after spawn.
|
|
205
|
+
FString Label;
|
|
206
|
+
if (Payload->TryGetStringField(TEXT("label"), Label) && !Label.IsEmpty())
|
|
207
|
+
{
|
|
208
|
+
Params.Name = FName(*Label);
|
|
209
|
+
}
|
|
210
|
+
|
|
204
211
|
AActor* Spawned = World->SpawnActor<AActor>(ActorClass, Location, Rotation, Params);
|
|
205
212
|
if (!Spawned)
|
|
206
213
|
{
|
|
@@ -208,6 +215,12 @@ void RegisterActorCommands(FMCPCommandRouter& Router)
|
|
|
208
215
|
return;
|
|
209
216
|
}
|
|
210
217
|
|
|
218
|
+
// Apply explicit label if provided (overrides auto-generated label).
|
|
219
|
+
if (!Label.IsEmpty())
|
|
220
|
+
{
|
|
221
|
+
Spawned->SetActorLabel(Label);
|
|
222
|
+
}
|
|
223
|
+
|
|
211
224
|
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
212
225
|
Data->SetStringField(TEXT("label"), Spawned->GetActorLabel());
|
|
213
226
|
Data->SetStringField(TEXT("id"), Spawned->GetName());
|