unreal-engine-mcp-server 0.4.6 → 0.4.7
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.
- package/.env.production +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +1 -2
- package/dist/index.js +21 -6
- package/dist/tools/consolidated-tool-definitions.d.ts +167 -0
- package/dist/tools/consolidated-tool-definitions.js +17 -3
- package/dist/tools/consolidated-tool-handlers.js +17 -0
- package/dist/tools/logs.d.ts +45 -0
- package/dist/tools/logs.js +262 -0
- package/dist/types/tool-types.d.ts +21 -1
- package/docs/unreal-tool-test-cases.md +2 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/index.ts +20 -6
- package/src/tools/consolidated-tool-definitions.ts +17 -2
- package/src/tools/consolidated-tool-handlers.ts +18 -1
- package/src/tools/logs.ts +267 -0
- package/src/types/tool-types.ts +16 -1
package/.env.production
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.7] - 2025-11-16
|
|
6
|
+
### Added
|
|
7
|
+
- Output Log reading via `system_control` tool with `read_log` action. Supports filtering by category (comma-separated or array), log level (Error, Warning, Log, Verbose, VeryVerbose, All), line count (up to 2000), specific log path, include prefixes, and exclude categories. Automatically resolves the latest project log under Saved/Logs.
|
|
8
|
+
- New `src/tools/logs.ts` implementing robust log tailing, parsing (timestamp/category/level/message), and UE-specific internal entry filtering (e.g., excludes LogPython RESULT: blocks unless requested).
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `system_control` tool schema: Added `read_log` action with full filter parameters to inputSchema; extended outputSchema with `logPath`, `entries` array, and `filteredCount`.
|
|
12
|
+
- Updated `src/tools/consolidated-tool-handlers.ts` to route `read_log` to LogTools without requiring UE connection (file-based).
|
|
13
|
+
- `src/index.ts`: Instantiates and passes LogTools to consolidated handler.
|
|
14
|
+
- Version bumped to 0.4.7 in package.json, package-lock.json, server.json, .env.production, and runtime config.
|
|
15
|
+
|
|
5
16
|
## [0.4.6] - 2025-10-04
|
|
6
17
|
### Fixed
|
|
7
18
|
- Fixed duplicate response output issue where tool responses were being displayed twice in MCP content
|
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
[](https://github.com/modelcontextprotocol/sdk)
|
|
6
6
|
[](https://www.unrealengine.com/)
|
|
7
7
|
[](https://registry.modelcontextprotocol.io/)
|
|
8
|
-
[](https://smithery.ai/server/@ChiR24/unreal_mcp)
|
|
9
8
|
|
|
10
9
|
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine via Remote Control API. Built with TypeScript and designed for game development automation.
|
|
11
10
|
|
|
@@ -145,7 +144,7 @@ Then enable Python execution in: Edit > Project Settings > Plugins > Remote Cont
|
|
|
145
144
|
| `create_effect` | Particles, Niagara, debug shapes |
|
|
146
145
|
| `manage_blueprint` | Create blueprints, add components |
|
|
147
146
|
| `build_environment` | Landscapes, terrain, foliage |
|
|
148
|
-
| `system_control` | Profiling, quality, UI, screenshots |
|
|
147
|
+
| `system_control` | Profiling, quality, UI, screenshots, Output Log reading |
|
|
149
148
|
| `console_command` | Direct console command execution |
|
|
150
149
|
| `manage_rc` | Remote Control presets |
|
|
151
150
|
| `manage_sequence` | Sequencer/cinematics |
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { SequenceTools } from './tools/sequence.js';
|
|
|
28
28
|
import { IntrospectionTools } from './tools/introspection.js';
|
|
29
29
|
import { VisualTools } from './tools/visual.js';
|
|
30
30
|
import { EngineTools } from './tools/engine.js';
|
|
31
|
+
import { LogTools } from './tools/logs.js';
|
|
31
32
|
import { consolidatedToolDefinitions } from './tools/consolidated-tool-definitions.js';
|
|
32
33
|
import { handleConsolidatedToolCall } from './tools/consolidated-tool-handlers.js';
|
|
33
34
|
import { prompts } from './prompts/index.js';
|
|
@@ -63,7 +64,7 @@ const CONFIG = {
|
|
|
63
64
|
RETRY_DELAY_MS: 2000,
|
|
64
65
|
// Server info
|
|
65
66
|
SERVER_NAME: 'unreal-engine-mcp',
|
|
66
|
-
SERVER_VERSION: '0.4.
|
|
67
|
+
SERVER_VERSION: '0.4.7',
|
|
67
68
|
// Monitoring
|
|
68
69
|
HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
|
|
69
70
|
};
|
|
@@ -206,6 +207,7 @@ export function createServer() {
|
|
|
206
207
|
const introspectionTools = new IntrospectionTools(bridge);
|
|
207
208
|
const visualTools = new VisualTools(bridge);
|
|
208
209
|
const engineTools = new EngineTools(bridge);
|
|
210
|
+
const logTools = new LogTools(bridge);
|
|
209
211
|
const server = new Server({
|
|
210
212
|
name: CONFIG.SERVER_NAME,
|
|
211
213
|
version: CONFIG.SERVER_VERSION
|
|
@@ -430,11 +432,23 @@ export function createServer() {
|
|
|
430
432
|
const { name } = request.params;
|
|
431
433
|
let args = request.params.arguments || {};
|
|
432
434
|
const startTime = Date.now();
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
let requiresEngine = true;
|
|
436
|
+
try {
|
|
437
|
+
const n = String(name);
|
|
438
|
+
if (n === 'system_control') {
|
|
439
|
+
const action = String((args || {}).action || '').trim();
|
|
440
|
+
if (action === 'read_log') {
|
|
441
|
+
requiresEngine = false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch { }
|
|
446
|
+
if (requiresEngine) {
|
|
447
|
+
const connected = await ensureConnectedOnDemand();
|
|
448
|
+
if (!connected) {
|
|
449
|
+
trackPerformance(startTime, false);
|
|
450
|
+
return createNotConnectedResponse(name);
|
|
451
|
+
}
|
|
438
452
|
}
|
|
439
453
|
// Create tools object for handler
|
|
440
454
|
const tools = {
|
|
@@ -460,6 +474,7 @@ export function createServer() {
|
|
|
460
474
|
introspectionTools,
|
|
461
475
|
visualTools,
|
|
462
476
|
engineTools,
|
|
477
|
+
logTools,
|
|
463
478
|
// Elicitation (client-optional)
|
|
464
479
|
elicit: elicitation.elicit,
|
|
465
480
|
supportsElicitation: elicitation.supports,
|
|
@@ -90,6 +90,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
90
90
|
resolution?: undefined;
|
|
91
91
|
projectPath?: undefined;
|
|
92
92
|
editorExe?: undefined;
|
|
93
|
+
filter_category?: undefined;
|
|
94
|
+
filter_level?: undefined;
|
|
95
|
+
lines?: undefined;
|
|
96
|
+
log_path?: undefined;
|
|
97
|
+
include_prefixes?: undefined;
|
|
98
|
+
exclude_categories?: undefined;
|
|
93
99
|
command?: undefined;
|
|
94
100
|
presetPath?: undefined;
|
|
95
101
|
objectPath?: undefined;
|
|
@@ -186,6 +192,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
186
192
|
imagePath?: undefined;
|
|
187
193
|
imageBase64?: undefined;
|
|
188
194
|
pid?: undefined;
|
|
195
|
+
logPath?: undefined;
|
|
196
|
+
entries?: undefined;
|
|
197
|
+
filteredCount?: undefined;
|
|
189
198
|
command?: undefined;
|
|
190
199
|
result?: undefined;
|
|
191
200
|
info?: undefined;
|
|
@@ -340,6 +349,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
340
349
|
resolution?: undefined;
|
|
341
350
|
projectPath?: undefined;
|
|
342
351
|
editorExe?: undefined;
|
|
352
|
+
filter_category?: undefined;
|
|
353
|
+
filter_level?: undefined;
|
|
354
|
+
lines?: undefined;
|
|
355
|
+
log_path?: undefined;
|
|
356
|
+
include_prefixes?: undefined;
|
|
357
|
+
exclude_categories?: undefined;
|
|
343
358
|
command?: undefined;
|
|
344
359
|
presetPath?: undefined;
|
|
345
360
|
objectPath?: undefined;
|
|
@@ -416,6 +431,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
416
431
|
imagePath?: undefined;
|
|
417
432
|
imageBase64?: undefined;
|
|
418
433
|
pid?: undefined;
|
|
434
|
+
logPath?: undefined;
|
|
435
|
+
entries?: undefined;
|
|
436
|
+
filteredCount?: undefined;
|
|
419
437
|
command?: undefined;
|
|
420
438
|
result?: undefined;
|
|
421
439
|
info?: undefined;
|
|
@@ -550,6 +568,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
550
568
|
resolution?: undefined;
|
|
551
569
|
projectPath?: undefined;
|
|
552
570
|
editorExe?: undefined;
|
|
571
|
+
filter_category?: undefined;
|
|
572
|
+
filter_level?: undefined;
|
|
573
|
+
lines?: undefined;
|
|
574
|
+
log_path?: undefined;
|
|
575
|
+
include_prefixes?: undefined;
|
|
576
|
+
exclude_categories?: undefined;
|
|
553
577
|
command?: undefined;
|
|
554
578
|
presetPath?: undefined;
|
|
555
579
|
objectPath?: undefined;
|
|
@@ -632,6 +656,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
632
656
|
imagePath?: undefined;
|
|
633
657
|
imageBase64?: undefined;
|
|
634
658
|
pid?: undefined;
|
|
659
|
+
logPath?: undefined;
|
|
660
|
+
entries?: undefined;
|
|
661
|
+
filteredCount?: undefined;
|
|
635
662
|
command?: undefined;
|
|
636
663
|
result?: undefined;
|
|
637
664
|
info?: undefined;
|
|
@@ -775,6 +802,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
775
802
|
resolution?: undefined;
|
|
776
803
|
projectPath?: undefined;
|
|
777
804
|
editorExe?: undefined;
|
|
805
|
+
filter_category?: undefined;
|
|
806
|
+
filter_level?: undefined;
|
|
807
|
+
lines?: undefined;
|
|
808
|
+
log_path?: undefined;
|
|
809
|
+
include_prefixes?: undefined;
|
|
810
|
+
exclude_categories?: undefined;
|
|
778
811
|
command?: undefined;
|
|
779
812
|
presetPath?: undefined;
|
|
780
813
|
objectPath?: undefined;
|
|
@@ -854,6 +887,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
854
887
|
imagePath?: undefined;
|
|
855
888
|
imageBase64?: undefined;
|
|
856
889
|
pid?: undefined;
|
|
890
|
+
logPath?: undefined;
|
|
891
|
+
entries?: undefined;
|
|
892
|
+
filteredCount?: undefined;
|
|
857
893
|
command?: undefined;
|
|
858
894
|
result?: undefined;
|
|
859
895
|
info?: undefined;
|
|
@@ -978,6 +1014,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
978
1014
|
resolution?: undefined;
|
|
979
1015
|
projectPath?: undefined;
|
|
980
1016
|
editorExe?: undefined;
|
|
1017
|
+
filter_category?: undefined;
|
|
1018
|
+
filter_level?: undefined;
|
|
1019
|
+
lines?: undefined;
|
|
1020
|
+
log_path?: undefined;
|
|
1021
|
+
include_prefixes?: undefined;
|
|
1022
|
+
exclude_categories?: undefined;
|
|
981
1023
|
command?: undefined;
|
|
982
1024
|
presetPath?: undefined;
|
|
983
1025
|
objectPath?: undefined;
|
|
@@ -1054,6 +1096,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1054
1096
|
imagePath?: undefined;
|
|
1055
1097
|
imageBase64?: undefined;
|
|
1056
1098
|
pid?: undefined;
|
|
1099
|
+
logPath?: undefined;
|
|
1100
|
+
entries?: undefined;
|
|
1101
|
+
filteredCount?: undefined;
|
|
1057
1102
|
command?: undefined;
|
|
1058
1103
|
result?: undefined;
|
|
1059
1104
|
info?: undefined;
|
|
@@ -1195,6 +1240,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1195
1240
|
resolution?: undefined;
|
|
1196
1241
|
projectPath?: undefined;
|
|
1197
1242
|
editorExe?: undefined;
|
|
1243
|
+
filter_category?: undefined;
|
|
1244
|
+
filter_level?: undefined;
|
|
1245
|
+
lines?: undefined;
|
|
1246
|
+
log_path?: undefined;
|
|
1247
|
+
include_prefixes?: undefined;
|
|
1248
|
+
exclude_categories?: undefined;
|
|
1198
1249
|
command?: undefined;
|
|
1199
1250
|
presetPath?: undefined;
|
|
1200
1251
|
objectPath?: undefined;
|
|
@@ -1274,6 +1325,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1274
1325
|
imagePath?: undefined;
|
|
1275
1326
|
imageBase64?: undefined;
|
|
1276
1327
|
pid?: undefined;
|
|
1328
|
+
logPath?: undefined;
|
|
1329
|
+
entries?: undefined;
|
|
1330
|
+
filteredCount?: undefined;
|
|
1277
1331
|
command?: undefined;
|
|
1278
1332
|
result?: undefined;
|
|
1279
1333
|
info?: undefined;
|
|
@@ -1386,6 +1440,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1386
1440
|
resolution?: undefined;
|
|
1387
1441
|
projectPath?: undefined;
|
|
1388
1442
|
editorExe?: undefined;
|
|
1443
|
+
filter_category?: undefined;
|
|
1444
|
+
filter_level?: undefined;
|
|
1445
|
+
lines?: undefined;
|
|
1446
|
+
log_path?: undefined;
|
|
1447
|
+
include_prefixes?: undefined;
|
|
1448
|
+
exclude_categories?: undefined;
|
|
1389
1449
|
command?: undefined;
|
|
1390
1450
|
presetPath?: undefined;
|
|
1391
1451
|
objectPath?: undefined;
|
|
@@ -1459,6 +1519,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1459
1519
|
imagePath?: undefined;
|
|
1460
1520
|
imageBase64?: undefined;
|
|
1461
1521
|
pid?: undefined;
|
|
1522
|
+
logPath?: undefined;
|
|
1523
|
+
entries?: undefined;
|
|
1524
|
+
filteredCount?: undefined;
|
|
1462
1525
|
command?: undefined;
|
|
1463
1526
|
result?: undefined;
|
|
1464
1527
|
info?: undefined;
|
|
@@ -1733,6 +1796,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1733
1796
|
resolution?: undefined;
|
|
1734
1797
|
projectPath?: undefined;
|
|
1735
1798
|
editorExe?: undefined;
|
|
1799
|
+
filter_category?: undefined;
|
|
1800
|
+
filter_level?: undefined;
|
|
1801
|
+
lines?: undefined;
|
|
1802
|
+
log_path?: undefined;
|
|
1803
|
+
include_prefixes?: undefined;
|
|
1804
|
+
exclude_categories?: undefined;
|
|
1736
1805
|
command?: undefined;
|
|
1737
1806
|
presetPath?: undefined;
|
|
1738
1807
|
objectPath?: undefined;
|
|
@@ -1806,6 +1875,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1806
1875
|
imagePath?: undefined;
|
|
1807
1876
|
imageBase64?: undefined;
|
|
1808
1877
|
pid?: undefined;
|
|
1878
|
+
logPath?: undefined;
|
|
1879
|
+
entries?: undefined;
|
|
1880
|
+
filteredCount?: undefined;
|
|
1809
1881
|
command?: undefined;
|
|
1810
1882
|
result?: undefined;
|
|
1811
1883
|
info?: undefined;
|
|
@@ -1911,6 +1983,36 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
1911
1983
|
type: string;
|
|
1912
1984
|
description: string;
|
|
1913
1985
|
};
|
|
1986
|
+
filter_category: {
|
|
1987
|
+
description: string;
|
|
1988
|
+
};
|
|
1989
|
+
filter_level: {
|
|
1990
|
+
type: string;
|
|
1991
|
+
enum: string[];
|
|
1992
|
+
description: string;
|
|
1993
|
+
};
|
|
1994
|
+
lines: {
|
|
1995
|
+
type: string;
|
|
1996
|
+
description: string;
|
|
1997
|
+
};
|
|
1998
|
+
log_path: {
|
|
1999
|
+
type: string;
|
|
2000
|
+
description: string;
|
|
2001
|
+
};
|
|
2002
|
+
include_prefixes: {
|
|
2003
|
+
type: string;
|
|
2004
|
+
items: {
|
|
2005
|
+
type: string;
|
|
2006
|
+
};
|
|
2007
|
+
description: string;
|
|
2008
|
+
};
|
|
2009
|
+
exclude_categories: {
|
|
2010
|
+
type: string;
|
|
2011
|
+
items: {
|
|
2012
|
+
type: string;
|
|
2013
|
+
};
|
|
2014
|
+
description: string;
|
|
2015
|
+
};
|
|
1914
2016
|
directory?: undefined;
|
|
1915
2017
|
sourcePath?: undefined;
|
|
1916
2018
|
destinationPath?: undefined;
|
|
@@ -2030,6 +2132,35 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2030
2132
|
type: string;
|
|
2031
2133
|
description: string;
|
|
2032
2134
|
};
|
|
2135
|
+
logPath: {
|
|
2136
|
+
type: string;
|
|
2137
|
+
description: string;
|
|
2138
|
+
};
|
|
2139
|
+
entries: {
|
|
2140
|
+
type: string;
|
|
2141
|
+
items: {
|
|
2142
|
+
type: string;
|
|
2143
|
+
properties: {
|
|
2144
|
+
timestamp: {
|
|
2145
|
+
type: string;
|
|
2146
|
+
};
|
|
2147
|
+
category: {
|
|
2148
|
+
type: string;
|
|
2149
|
+
};
|
|
2150
|
+
level: {
|
|
2151
|
+
type: string;
|
|
2152
|
+
};
|
|
2153
|
+
message: {
|
|
2154
|
+
type: string;
|
|
2155
|
+
};
|
|
2156
|
+
};
|
|
2157
|
+
};
|
|
2158
|
+
description: string;
|
|
2159
|
+
};
|
|
2160
|
+
filteredCount: {
|
|
2161
|
+
type: string;
|
|
2162
|
+
description: string;
|
|
2163
|
+
};
|
|
2033
2164
|
assets?: undefined;
|
|
2034
2165
|
paths?: undefined;
|
|
2035
2166
|
materialPath?: undefined;
|
|
@@ -2153,6 +2284,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2153
2284
|
resolution?: undefined;
|
|
2154
2285
|
projectPath?: undefined;
|
|
2155
2286
|
editorExe?: undefined;
|
|
2287
|
+
filter_category?: undefined;
|
|
2288
|
+
filter_level?: undefined;
|
|
2289
|
+
lines?: undefined;
|
|
2290
|
+
log_path?: undefined;
|
|
2291
|
+
include_prefixes?: undefined;
|
|
2292
|
+
exclude_categories?: undefined;
|
|
2156
2293
|
presetPath?: undefined;
|
|
2157
2294
|
objectPath?: undefined;
|
|
2158
2295
|
propertyName?: undefined;
|
|
@@ -2231,6 +2368,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2231
2368
|
imagePath?: undefined;
|
|
2232
2369
|
imageBase64?: undefined;
|
|
2233
2370
|
pid?: undefined;
|
|
2371
|
+
logPath?: undefined;
|
|
2372
|
+
entries?: undefined;
|
|
2373
|
+
filteredCount?: undefined;
|
|
2234
2374
|
presetPath?: undefined;
|
|
2235
2375
|
fields?: undefined;
|
|
2236
2376
|
value?: undefined;
|
|
@@ -2349,6 +2489,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2349
2489
|
resolution?: undefined;
|
|
2350
2490
|
projectPath?: undefined;
|
|
2351
2491
|
editorExe?: undefined;
|
|
2492
|
+
filter_category?: undefined;
|
|
2493
|
+
filter_level?: undefined;
|
|
2494
|
+
lines?: undefined;
|
|
2495
|
+
log_path?: undefined;
|
|
2496
|
+
include_prefixes?: undefined;
|
|
2497
|
+
exclude_categories?: undefined;
|
|
2352
2498
|
command?: undefined;
|
|
2353
2499
|
actorNames?: undefined;
|
|
2354
2500
|
className?: undefined;
|
|
@@ -2422,6 +2568,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2422
2568
|
imagePath?: undefined;
|
|
2423
2569
|
imageBase64?: undefined;
|
|
2424
2570
|
pid?: undefined;
|
|
2571
|
+
logPath?: undefined;
|
|
2572
|
+
entries?: undefined;
|
|
2573
|
+
filteredCount?: undefined;
|
|
2425
2574
|
command?: undefined;
|
|
2426
2575
|
result?: undefined;
|
|
2427
2576
|
info?: undefined;
|
|
@@ -2565,6 +2714,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2565
2714
|
resolution?: undefined;
|
|
2566
2715
|
projectPath?: undefined;
|
|
2567
2716
|
editorExe?: undefined;
|
|
2717
|
+
filter_category?: undefined;
|
|
2718
|
+
filter_level?: undefined;
|
|
2719
|
+
lines?: undefined;
|
|
2720
|
+
log_path?: undefined;
|
|
2721
|
+
include_prefixes?: undefined;
|
|
2722
|
+
exclude_categories?: undefined;
|
|
2568
2723
|
command?: undefined;
|
|
2569
2724
|
presetPath?: undefined;
|
|
2570
2725
|
objectPath?: undefined;
|
|
@@ -2671,6 +2826,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2671
2826
|
imagePath?: undefined;
|
|
2672
2827
|
imageBase64?: undefined;
|
|
2673
2828
|
pid?: undefined;
|
|
2829
|
+
logPath?: undefined;
|
|
2830
|
+
entries?: undefined;
|
|
2831
|
+
filteredCount?: undefined;
|
|
2674
2832
|
command?: undefined;
|
|
2675
2833
|
result?: undefined;
|
|
2676
2834
|
info?: undefined;
|
|
@@ -2767,6 +2925,12 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2767
2925
|
resolution?: undefined;
|
|
2768
2926
|
projectPath?: undefined;
|
|
2769
2927
|
editorExe?: undefined;
|
|
2928
|
+
filter_category?: undefined;
|
|
2929
|
+
filter_level?: undefined;
|
|
2930
|
+
lines?: undefined;
|
|
2931
|
+
log_path?: undefined;
|
|
2932
|
+
include_prefixes?: undefined;
|
|
2933
|
+
exclude_categories?: undefined;
|
|
2770
2934
|
command?: undefined;
|
|
2771
2935
|
presetPath?: undefined;
|
|
2772
2936
|
actorNames?: undefined;
|
|
@@ -2835,6 +2999,9 @@ export declare const consolidatedToolDefinitions: ({
|
|
|
2835
2999
|
imagePath?: undefined;
|
|
2836
3000
|
imageBase64?: undefined;
|
|
2837
3001
|
pid?: undefined;
|
|
3002
|
+
logPath?: undefined;
|
|
3003
|
+
entries?: undefined;
|
|
3004
|
+
filteredCount?: undefined;
|
|
2838
3005
|
command?: undefined;
|
|
2839
3006
|
result?: undefined;
|
|
2840
3007
|
presetPath?: undefined;
|
|
@@ -528,7 +528,7 @@ Supported actions: profile, show_fps, set_quality, play_sound, create_widget, sh
|
|
|
528
528
|
properties: {
|
|
529
529
|
action: {
|
|
530
530
|
type: 'string',
|
|
531
|
-
enum: ['profile', 'show_fps', 'set_quality', 'play_sound', 'create_widget', 'show_widget', 'screenshot', 'engine_start', 'engine_quit'],
|
|
531
|
+
enum: ['profile', 'show_fps', 'set_quality', 'play_sound', 'create_widget', 'show_widget', 'screenshot', 'engine_start', 'engine_quit', 'read_log'],
|
|
532
532
|
description: 'System action'
|
|
533
533
|
},
|
|
534
534
|
// Performance
|
|
@@ -567,7 +567,14 @@ Supported actions: profile, show_fps, set_quality, play_sound, create_widget, sh
|
|
|
567
567
|
resolution: { type: 'string', description: 'Screenshot resolution in WIDTHxHEIGHT format (e.g., "1920x1080", "3840x2160"). Optional for screenshot action, uses viewport size if not specified.' },
|
|
568
568
|
// Engine lifecycle
|
|
569
569
|
projectPath: { type: 'string', description: 'Absolute path to .uproject file (e.g., "C:/Projects/MyGame/MyGame.uproject"). Required for engine_start unless UE_PROJECT_PATH environment variable is set.' },
|
|
570
|
-
editorExe: { type: 'string', description: 'Absolute path to Unreal Editor executable (e.g., "C:/UnrealEngine/Engine/Binaries/Win64/UnrealEditor.exe"). Required for engine_start unless UE_EDITOR_EXE environment variable is set.' }
|
|
570
|
+
editorExe: { type: 'string', description: 'Absolute path to Unreal Editor executable (e.g., "C:/UnrealEngine/Engine/Binaries/Win64/UnrealEditor.exe"). Required for engine_start unless UE_EDITOR_EXE environment variable is set.' },
|
|
571
|
+
// Log reading
|
|
572
|
+
filter_category: { description: 'Category filter as string or array; comma-separated or array values' },
|
|
573
|
+
filter_level: { type: 'string', enum: ['Error', 'Warning', 'Log', 'Verbose', 'VeryVerbose', 'All'], description: 'Log level filter' },
|
|
574
|
+
lines: { type: 'number', description: 'Number of lines to read from tail' },
|
|
575
|
+
log_path: { type: 'string', description: 'Absolute path to a specific .log file to read' },
|
|
576
|
+
include_prefixes: { type: 'array', items: { type: 'string' }, description: 'Only include categories starting with any of these prefixes' },
|
|
577
|
+
exclude_categories: { type: 'array', items: { type: 'string' }, description: 'Categories to exclude' }
|
|
571
578
|
},
|
|
572
579
|
required: ['action']
|
|
573
580
|
},
|
|
@@ -585,7 +592,14 @@ Supported actions: profile, show_fps, set_quality, play_sound, create_widget, sh
|
|
|
585
592
|
imageBase64: { type: 'string', description: 'Screenshot image base64 (truncated)' },
|
|
586
593
|
pid: { type: 'number', description: 'Process ID for launched editor' },
|
|
587
594
|
message: { type: 'string', description: 'Status message' },
|
|
588
|
-
error: { type: 'string', description: 'Error message if failed' }
|
|
595
|
+
error: { type: 'string', description: 'Error message if failed' },
|
|
596
|
+
logPath: { type: 'string', description: 'Log file path used for read_log' },
|
|
597
|
+
entries: {
|
|
598
|
+
type: 'array',
|
|
599
|
+
items: { type: 'object', properties: { timestamp: { type: 'string' }, category: { type: 'string' }, level: { type: 'string' }, message: { type: 'string' } } },
|
|
600
|
+
description: 'Parsed Output Log entries'
|
|
601
|
+
},
|
|
602
|
+
filteredCount: { type: 'number', description: 'Count of entries after filtering' }
|
|
589
603
|
}
|
|
590
604
|
}
|
|
591
605
|
},
|
|
@@ -705,6 +705,23 @@ print('RESULT:' + json.dumps({'success': exists, 'exists': exists, 'path': path}
|
|
|
705
705
|
// 9. SYSTEM CONTROL
|
|
706
706
|
case 'system_control':
|
|
707
707
|
switch (requireAction(args)) {
|
|
708
|
+
case 'read_log': {
|
|
709
|
+
const filterCategoryRaw = args.filter_category;
|
|
710
|
+
const filterCategory = Array.isArray(filterCategoryRaw)
|
|
711
|
+
? filterCategoryRaw
|
|
712
|
+
: typeof filterCategoryRaw === 'string' && filterCategoryRaw.trim() !== ''
|
|
713
|
+
? filterCategoryRaw.split(',').map((s) => s.trim()).filter(Boolean)
|
|
714
|
+
: undefined;
|
|
715
|
+
const res = await tools.logTools.readOutputLog({
|
|
716
|
+
filterCategory,
|
|
717
|
+
filterLevel: args.filter_level,
|
|
718
|
+
lines: typeof args.lines === 'number' ? args.lines : undefined,
|
|
719
|
+
logPath: typeof args.log_path === 'string' ? args.log_path : undefined,
|
|
720
|
+
includePrefixes: Array.isArray(args.include_prefixes) ? args.include_prefixes : undefined,
|
|
721
|
+
excludeCategories: Array.isArray(args.exclude_categories) ? args.exclude_categories : undefined
|
|
722
|
+
});
|
|
723
|
+
return cleanObject(res);
|
|
724
|
+
}
|
|
708
725
|
case 'profile': {
|
|
709
726
|
const res = await tools.performanceTools.startProfiling({ type: args.profileType, duration: args.duration });
|
|
710
727
|
return cleanObject(res);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
type ReadParams = {
|
|
3
|
+
filterCategory?: string[];
|
|
4
|
+
filterLevel?: 'Error' | 'Warning' | 'Log' | 'Verbose' | 'VeryVerbose' | 'All';
|
|
5
|
+
lines?: number;
|
|
6
|
+
logPath?: string;
|
|
7
|
+
includePrefixes?: string[];
|
|
8
|
+
excludeCategories?: string[];
|
|
9
|
+
};
|
|
10
|
+
type Entry = {
|
|
11
|
+
timestamp?: string;
|
|
12
|
+
category?: string;
|
|
13
|
+
level?: string;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
export declare class LogTools {
|
|
17
|
+
private bridge;
|
|
18
|
+
private env;
|
|
19
|
+
private log;
|
|
20
|
+
private cachedLogPath?;
|
|
21
|
+
constructor(bridge: UnrealBridge);
|
|
22
|
+
readOutputLog(params: ReadParams): Promise<{
|
|
23
|
+
success: boolean;
|
|
24
|
+
error: string;
|
|
25
|
+
logPath?: undefined;
|
|
26
|
+
entries?: undefined;
|
|
27
|
+
filteredCount?: undefined;
|
|
28
|
+
} | {
|
|
29
|
+
success: boolean;
|
|
30
|
+
logPath: string;
|
|
31
|
+
entries: Entry[];
|
|
32
|
+
filteredCount: number;
|
|
33
|
+
error?: undefined;
|
|
34
|
+
}>;
|
|
35
|
+
private resolveLogPath;
|
|
36
|
+
private resolveFromProjectEnv;
|
|
37
|
+
private findLatestLogInDir;
|
|
38
|
+
private fileExists;
|
|
39
|
+
private cacheLogPath;
|
|
40
|
+
private tailFile;
|
|
41
|
+
private parseLine;
|
|
42
|
+
private isInternalLogEntry;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=logs.d.ts.map
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { loadEnv } from '../types/env.js';
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
export class LogTools {
|
|
6
|
+
bridge;
|
|
7
|
+
env = loadEnv();
|
|
8
|
+
log = new Logger('LogTools');
|
|
9
|
+
cachedLogPath;
|
|
10
|
+
constructor(bridge) {
|
|
11
|
+
this.bridge = bridge;
|
|
12
|
+
}
|
|
13
|
+
async readOutputLog(params) {
|
|
14
|
+
const target = await this.resolveLogPath(params.logPath);
|
|
15
|
+
if (!target) {
|
|
16
|
+
return { success: false, error: 'Log file not found' };
|
|
17
|
+
}
|
|
18
|
+
const maxLines = typeof params.lines === 'number' && params.lines > 0 ? Math.min(params.lines, 2000) : 200;
|
|
19
|
+
let text = '';
|
|
20
|
+
try {
|
|
21
|
+
text = await this.tailFile(target, maxLines);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return { success: false, error: String(err?.message || err) };
|
|
25
|
+
}
|
|
26
|
+
const rawLines = text.split(/\r?\n/).filter(l => l.length > 0);
|
|
27
|
+
const parsed = rawLines.map(l => this.parseLine(l));
|
|
28
|
+
const mappedLevel = params.filterLevel || 'All';
|
|
29
|
+
const includeCats = Array.isArray(params.filterCategory) && params.filterCategory.length ? new Set(params.filterCategory) : undefined;
|
|
30
|
+
const includePrefixes = Array.isArray(params.includePrefixes) && params.includePrefixes.length ? params.includePrefixes : undefined;
|
|
31
|
+
const excludeCats = Array.isArray(params.excludeCategories) && params.excludeCategories.length ? new Set(params.excludeCategories) : undefined;
|
|
32
|
+
const filtered = parsed.filter(e => {
|
|
33
|
+
if (!e)
|
|
34
|
+
return false;
|
|
35
|
+
if (mappedLevel && mappedLevel !== 'All') {
|
|
36
|
+
const lv = (e.level || 'Log');
|
|
37
|
+
if (lv === 'Display') {
|
|
38
|
+
if (mappedLevel !== 'Log')
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
else if (lv !== mappedLevel) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (includeCats && e.category && !includeCats.has(e.category))
|
|
46
|
+
return false;
|
|
47
|
+
if (includePrefixes && includePrefixes.length && e.category) {
|
|
48
|
+
if (!includePrefixes.some(p => (e.category ?? '').startsWith(p)))
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (excludeCats && e.category && excludeCats.has(e.category))
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
});
|
|
55
|
+
const includeInternal = Boolean((includeCats && includeCats.has('LogPython')) ||
|
|
56
|
+
(includePrefixes && includePrefixes.some(p => 'LogPython'.startsWith(p))));
|
|
57
|
+
const sanitized = includeInternal ? filtered : filtered.filter(entry => !this.isInternalLogEntry(entry));
|
|
58
|
+
return { success: true, logPath: target.replace(/\\/g, '/'), entries: sanitized, filteredCount: sanitized.length };
|
|
59
|
+
}
|
|
60
|
+
async resolveLogPath(override) {
|
|
61
|
+
if (override && typeof override === 'string' && override.trim()) {
|
|
62
|
+
try {
|
|
63
|
+
const st = await fs.stat(override);
|
|
64
|
+
if (st.isFile()) {
|
|
65
|
+
return this.cacheLogPath(path.resolve(override));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
}
|
|
70
|
+
if (this.cachedLogPath && (await this.fileExists(this.cachedLogPath))) {
|
|
71
|
+
return this.cachedLogPath;
|
|
72
|
+
}
|
|
73
|
+
const envLog = await this.resolveFromProjectEnv();
|
|
74
|
+
if (envLog) {
|
|
75
|
+
return envLog;
|
|
76
|
+
}
|
|
77
|
+
if (this.bridge.isConnected) {
|
|
78
|
+
try {
|
|
79
|
+
const script = `
|
|
80
|
+
import unreal, json, os
|
|
81
|
+
paths = []
|
|
82
|
+
try:
|
|
83
|
+
d = unreal.Paths.project_log_dir()
|
|
84
|
+
if d:
|
|
85
|
+
paths.append(os.path.abspath(d))
|
|
86
|
+
except Exception:
|
|
87
|
+
pass
|
|
88
|
+
try:
|
|
89
|
+
sd = unreal.Paths.project_saved_dir()
|
|
90
|
+
if sd:
|
|
91
|
+
p = os.path.join(sd, 'Logs')
|
|
92
|
+
paths.append(os.path.abspath(p))
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
try:
|
|
96
|
+
pf = unreal.Paths.get_project_file_path()
|
|
97
|
+
if pf:
|
|
98
|
+
pd = os.path.dirname(pf)
|
|
99
|
+
p = os.path.join(pd, 'Saved', 'Logs')
|
|
100
|
+
paths.append(os.path.abspath(p))
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
all_logs = []
|
|
104
|
+
for base in paths:
|
|
105
|
+
try:
|
|
106
|
+
if os.path.isdir(base):
|
|
107
|
+
for name in os.listdir(base):
|
|
108
|
+
if name.lower().endswith('.log'):
|
|
109
|
+
fp = os.path.join(base, name)
|
|
110
|
+
try:
|
|
111
|
+
m = os.path.getmtime(fp)
|
|
112
|
+
all_logs.append({'p': fp, 'm': m})
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
all_logs.sort(key=lambda x: x['m'], reverse=True)
|
|
118
|
+
print('RESULT:' + json.dumps({'dirs': paths, 'logs': all_logs}))
|
|
119
|
+
`.trim();
|
|
120
|
+
const res = await this.bridge.executePythonWithResult(script);
|
|
121
|
+
const logs = Array.isArray(res?.logs) ? res.logs : [];
|
|
122
|
+
for (const entry of logs) {
|
|
123
|
+
const p = typeof entry?.p === 'string' ? entry.p : undefined;
|
|
124
|
+
if (p && p.trim())
|
|
125
|
+
return this.cacheLogPath(p);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch { }
|
|
129
|
+
}
|
|
130
|
+
const fallback = await this.findLatestLogInDir(path.join(process.cwd(), 'Saved', 'Logs'));
|
|
131
|
+
if (fallback) {
|
|
132
|
+
return fallback;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
async resolveFromProjectEnv() {
|
|
137
|
+
const projectPath = this.env.UE_PROJECT_PATH;
|
|
138
|
+
if (projectPath && typeof projectPath === 'string' && projectPath.trim()) {
|
|
139
|
+
const projectDir = path.dirname(projectPath);
|
|
140
|
+
const logsDir = path.join(projectDir, 'Saved', 'Logs');
|
|
141
|
+
const envLog = await this.findLatestLogInDir(logsDir);
|
|
142
|
+
if (envLog) {
|
|
143
|
+
return envLog;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
async findLatestLogInDir(dir) {
|
|
149
|
+
if (!dir)
|
|
150
|
+
return undefined;
|
|
151
|
+
try {
|
|
152
|
+
const entries = await fs.readdir(dir);
|
|
153
|
+
const candidates = [];
|
|
154
|
+
for (const name of entries) {
|
|
155
|
+
if (!name.toLowerCase().endsWith('.log'))
|
|
156
|
+
continue;
|
|
157
|
+
const fp = path.join(dir, name);
|
|
158
|
+
try {
|
|
159
|
+
const st = await fs.stat(fp);
|
|
160
|
+
candidates.push({ p: fp, m: st.mtimeMs });
|
|
161
|
+
}
|
|
162
|
+
catch { }
|
|
163
|
+
}
|
|
164
|
+
if (candidates.length) {
|
|
165
|
+
candidates.sort((a, b) => b.m - a.m);
|
|
166
|
+
return this.cacheLogPath(candidates[0].p);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch { }
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
async fileExists(filePath) {
|
|
173
|
+
try {
|
|
174
|
+
const st = await fs.stat(filePath);
|
|
175
|
+
return st.isFile();
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
cacheLogPath(p) {
|
|
182
|
+
this.cachedLogPath = p;
|
|
183
|
+
return p;
|
|
184
|
+
}
|
|
185
|
+
async tailFile(filePath, maxLines) {
|
|
186
|
+
const handle = await fs.open(filePath, 'r');
|
|
187
|
+
try {
|
|
188
|
+
const stat = await handle.stat();
|
|
189
|
+
const chunkSize = 128 * 1024;
|
|
190
|
+
let position = stat.size;
|
|
191
|
+
let remaining = '';
|
|
192
|
+
const lines = [];
|
|
193
|
+
while (position > 0 && lines.length < maxLines) {
|
|
194
|
+
const readSize = Math.min(chunkSize, position);
|
|
195
|
+
position -= readSize;
|
|
196
|
+
const buf = Buffer.alloc(readSize);
|
|
197
|
+
await handle.read(buf, 0, readSize, position);
|
|
198
|
+
remaining = buf.toString('utf8') + remaining;
|
|
199
|
+
const parts = remaining.split(/\r?\n/);
|
|
200
|
+
remaining = parts.shift() || '';
|
|
201
|
+
while (parts.length) {
|
|
202
|
+
const line = parts.pop();
|
|
203
|
+
if (line === undefined)
|
|
204
|
+
break;
|
|
205
|
+
if (line.length === 0)
|
|
206
|
+
continue;
|
|
207
|
+
lines.unshift(line);
|
|
208
|
+
if (lines.length >= maxLines)
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (lines.length < maxLines && remaining) {
|
|
213
|
+
lines.unshift(remaining);
|
|
214
|
+
}
|
|
215
|
+
return lines.slice(0, maxLines).join('\n');
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
try {
|
|
219
|
+
await handle.close();
|
|
220
|
+
}
|
|
221
|
+
catch { }
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
parseLine(line) {
|
|
225
|
+
const m1 = line.match(/^\[?(\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}\.\d{2}:\d+)\]?\s*\[(.*?)\]\s*(.*)$/);
|
|
226
|
+
if (m1) {
|
|
227
|
+
const rest = m1[3];
|
|
228
|
+
const m2 = rest.match(/^(\w+):\s*(Error|Warning|Display|Log|Verbose|VeryVerbose):\s*(.*)$/);
|
|
229
|
+
if (m2) {
|
|
230
|
+
return { timestamp: m1[1], category: m2[1], level: m2[2] === 'Display' ? 'Log' : m2[2], message: m2[3] };
|
|
231
|
+
}
|
|
232
|
+
const m3 = rest.match(/^(\w+):\s*(.*)$/);
|
|
233
|
+
if (m3) {
|
|
234
|
+
return { timestamp: m1[1], category: m3[1], level: 'Log', message: m3[2] };
|
|
235
|
+
}
|
|
236
|
+
return { timestamp: m1[1], message: rest };
|
|
237
|
+
}
|
|
238
|
+
const m = line.match(/^(\w+):\s*(Error|Warning|Display|Log|Verbose|VeryVerbose):\s*(.*)$/);
|
|
239
|
+
if (m) {
|
|
240
|
+
return { category: m[1], level: m[2] === 'Display' ? 'Log' : m[2], message: m[3] };
|
|
241
|
+
}
|
|
242
|
+
const mAlt = line.match(/^(\w+):\s*(.*)$/);
|
|
243
|
+
if (mAlt) {
|
|
244
|
+
return { category: mAlt[1], level: 'Log', message: mAlt[2] };
|
|
245
|
+
}
|
|
246
|
+
return { message: line };
|
|
247
|
+
}
|
|
248
|
+
isInternalLogEntry(entry) {
|
|
249
|
+
if (!entry)
|
|
250
|
+
return false;
|
|
251
|
+
const category = entry.category?.toLowerCase() || '';
|
|
252
|
+
const message = entry.message?.trim() || '';
|
|
253
|
+
if (category === 'logpython' && message.startsWith('RESULT:')) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (!entry.category && message.startsWith('[') && message.includes('LogPython: RESULT:')) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=logs.js.map
|
|
@@ -75,6 +75,17 @@ export interface SystemControlResponse extends BaseToolResponse {
|
|
|
75
75
|
soundPlaying?: boolean;
|
|
76
76
|
widgetPath?: string;
|
|
77
77
|
widgetVisible?: boolean;
|
|
78
|
+
imagePath?: string;
|
|
79
|
+
imageBase64?: string;
|
|
80
|
+
pid?: number;
|
|
81
|
+
logPath?: string;
|
|
82
|
+
entries?: Array<{
|
|
83
|
+
timestamp?: string;
|
|
84
|
+
category?: string;
|
|
85
|
+
level?: string;
|
|
86
|
+
message: string;
|
|
87
|
+
}>;
|
|
88
|
+
filteredCount?: number;
|
|
78
89
|
}
|
|
79
90
|
export interface ConsoleCommandResponse extends BaseToolResponse {
|
|
80
91
|
command?: string;
|
|
@@ -131,7 +142,7 @@ export type AnimationAction = 'create_animation_bp' | 'play_montage' | 'setup_ra
|
|
|
131
142
|
export type EffectAction = 'particle' | 'niagara' | 'debug_shape';
|
|
132
143
|
export type BlueprintAction = 'create' | 'add_component';
|
|
133
144
|
export type EnvironmentAction = 'create_landscape' | 'sculpt' | 'add_foliage' | 'paint_foliage';
|
|
134
|
-
export type SystemAction = 'profile' | 'show_fps' | 'set_quality' | 'play_sound' | 'create_widget' | 'show_widget';
|
|
145
|
+
export type SystemAction = 'profile' | 'show_fps' | 'set_quality' | 'play_sound' | 'create_widget' | 'show_widget' | 'screenshot' | 'engine_start' | 'engine_quit' | 'read_log';
|
|
135
146
|
export type VerificationAction = 'foliage_type_exists' | 'foliage_instances_near' | 'landscape_exists' | 'quality_level';
|
|
136
147
|
export interface ConsolidatedToolParams {
|
|
137
148
|
manage_asset: {
|
|
@@ -229,6 +240,15 @@ export interface ConsolidatedToolParams {
|
|
|
229
240
|
widgetName?: string;
|
|
230
241
|
widgetType?: string;
|
|
231
242
|
visible?: boolean;
|
|
243
|
+
resolution?: string;
|
|
244
|
+
projectPath?: string;
|
|
245
|
+
editorExe?: string;
|
|
246
|
+
filter_category?: string | string[];
|
|
247
|
+
filter_level?: 'Error' | 'Warning' | 'Log' | 'Verbose' | 'VeryVerbose' | 'All';
|
|
248
|
+
lines?: number;
|
|
249
|
+
log_path?: string;
|
|
250
|
+
include_prefixes?: string[];
|
|
251
|
+
exclude_categories?: string[];
|
|
232
252
|
};
|
|
233
253
|
console_command: {
|
|
234
254
|
command: string;
|
|
@@ -196,6 +196,8 @@ Additional advanced blueprint scenarios remain documented in the legacy matrix f
|
|
|
196
196
|
| 2 | Play sound with missing asset fails | `{"action":"play_sound","soundPath":"/Game/MCP/Audio/Missing"}` | Error message reports that the sound asset could not be found. |
|
|
197
197
|
| 3 | Play sound at location missing asset fails | `{"action":"play_sound","soundPath":"/Game/MCP/Audio/Missing","location":{"x":0,"y":0,"z":0}}` | Error message reports that the sound asset could not be found for the world location playback. |
|
|
198
198
|
| 4 | Play sound with empty path fails validation | `{"action":"play_sound","soundPath":"","volume":1.0}` | Error message explains that the sound asset path could not be resolved. |
|
|
199
|
+
| 5 | Read Output Log tail with defaults | `{"action":"read_log","lines":50,"filter_level":"All"}` | Success response returns recent log entries and the logPath used. |
|
|
200
|
+
| 6 | Read Output Log filtered by custom categories | `{"action":"read_log","filter_category":"LogMyCategory,LogOtherLog","filter_level":"Error","lines":100}` | Success response returns only matching categories at Error level. |
|
|
199
201
|
|
|
200
202
|
---
|
|
201
203
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unreal-engine-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"mcpName": "io.github.ChiR24/unreal-engine-mcp",
|
|
5
5
|
"description": "A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine via Remote Control API. Built with TypeScript and designed for game development automation.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
|
|
3
3
|
"name": "io.github.ChiR24/unreal-engine-mcp",
|
|
4
4
|
"description": "MCP server for Unreal Engine 5 with 13 tools for game development automation.",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.7",
|
|
6
6
|
"packages": [
|
|
7
7
|
{
|
|
8
8
|
"registryType": "npm",
|
|
9
9
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
10
10
|
"identifier": "unreal-engine-mcp-server",
|
|
11
|
-
"version": "0.4.
|
|
11
|
+
"version": "0.4.7",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
},
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { SequenceTools } from './tools/sequence.js';
|
|
|
28
28
|
import { IntrospectionTools } from './tools/introspection.js';
|
|
29
29
|
import { VisualTools } from './tools/visual.js';
|
|
30
30
|
import { EngineTools } from './tools/engine.js';
|
|
31
|
+
import { LogTools } from './tools/logs.js';
|
|
31
32
|
import { consolidatedToolDefinitions } from './tools/consolidated-tool-definitions.js';
|
|
32
33
|
import { handleConsolidatedToolCall } from './tools/consolidated-tool-handlers.js';
|
|
33
34
|
import { prompts } from './prompts/index.js';
|
|
@@ -88,7 +89,7 @@ const CONFIG = {
|
|
|
88
89
|
RETRY_DELAY_MS: 2000,
|
|
89
90
|
// Server info
|
|
90
91
|
SERVER_NAME: 'unreal-engine-mcp',
|
|
91
|
-
SERVER_VERSION: '0.4.
|
|
92
|
+
SERVER_VERSION: '0.4.7',
|
|
92
93
|
// Monitoring
|
|
93
94
|
HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
|
|
94
95
|
};
|
|
@@ -237,6 +238,7 @@ export function createServer() {
|
|
|
237
238
|
const introspectionTools = new IntrospectionTools(bridge);
|
|
238
239
|
const visualTools = new VisualTools(bridge);
|
|
239
240
|
const engineTools = new EngineTools(bridge);
|
|
241
|
+
const logTools = new LogTools(bridge);
|
|
240
242
|
|
|
241
243
|
const server = new Server(
|
|
242
244
|
{
|
|
@@ -474,11 +476,22 @@ export function createServer() {
|
|
|
474
476
|
let args: any = request.params.arguments || {};
|
|
475
477
|
const startTime = Date.now();
|
|
476
478
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
479
|
+
let requiresEngine = true;
|
|
480
|
+
try {
|
|
481
|
+
const n = String(name);
|
|
482
|
+
if (n === 'system_control') {
|
|
483
|
+
const action = String((args || {}).action || '').trim();
|
|
484
|
+
if (action === 'read_log') {
|
|
485
|
+
requiresEngine = false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
} catch {}
|
|
489
|
+
if (requiresEngine) {
|
|
490
|
+
const connected = await ensureConnectedOnDemand();
|
|
491
|
+
if (!connected) {
|
|
492
|
+
trackPerformance(startTime, false);
|
|
493
|
+
return createNotConnectedResponse(name);
|
|
494
|
+
}
|
|
482
495
|
}
|
|
483
496
|
|
|
484
497
|
// Create tools object for handler
|
|
@@ -505,6 +518,7 @@ export function createServer() {
|
|
|
505
518
|
introspectionTools,
|
|
506
519
|
visualTools,
|
|
507
520
|
engineTools,
|
|
521
|
+
logTools,
|
|
508
522
|
// Elicitation (client-optional)
|
|
509
523
|
elicit: elicitation.elicit,
|
|
510
524
|
supportsElicitation: elicitation.supports,
|
|
@@ -537,7 +537,7 @@ Supported actions: profile, show_fps, set_quality, play_sound, create_widget, sh
|
|
|
537
537
|
properties: {
|
|
538
538
|
action: {
|
|
539
539
|
type: 'string',
|
|
540
|
-
enum: ['profile', 'show_fps', 'set_quality', 'play_sound', 'create_widget', 'show_widget', 'screenshot', 'engine_start', 'engine_quit'],
|
|
540
|
+
enum: ['profile', 'show_fps', 'set_quality', 'play_sound', 'create_widget', 'show_widget', 'screenshot', 'engine_start', 'engine_quit', 'read_log'],
|
|
541
541
|
description: 'System action'
|
|
542
542
|
},
|
|
543
543
|
// Performance
|
|
@@ -577,6 +577,14 @@ Supported actions: profile, show_fps, set_quality, play_sound, create_widget, sh
|
|
|
577
577
|
// Engine lifecycle
|
|
578
578
|
projectPath: { type: 'string', description: 'Absolute path to .uproject file (e.g., "C:/Projects/MyGame/MyGame.uproject"). Required for engine_start unless UE_PROJECT_PATH environment variable is set.' },
|
|
579
579
|
editorExe: { type: 'string', description: 'Absolute path to Unreal Editor executable (e.g., "C:/UnrealEngine/Engine/Binaries/Win64/UnrealEditor.exe"). Required for engine_start unless UE_EDITOR_EXE environment variable is set.' }
|
|
580
|
+
,
|
|
581
|
+
// Log reading
|
|
582
|
+
filter_category: { description: 'Category filter as string or array; comma-separated or array values' },
|
|
583
|
+
filter_level: { type: 'string', enum: ['Error','Warning','Log','Verbose','VeryVerbose','All'], description: 'Log level filter' },
|
|
584
|
+
lines: { type: 'number', description: 'Number of lines to read from tail' },
|
|
585
|
+
log_path: { type: 'string', description: 'Absolute path to a specific .log file to read' },
|
|
586
|
+
include_prefixes: { type: 'array', items: { type: 'string' }, description: 'Only include categories starting with any of these prefixes' },
|
|
587
|
+
exclude_categories: { type: 'array', items: { type: 'string' }, description: 'Categories to exclude' }
|
|
580
588
|
},
|
|
581
589
|
required: ['action']
|
|
582
590
|
},
|
|
@@ -594,7 +602,14 @@ Supported actions: profile, show_fps, set_quality, play_sound, create_widget, sh
|
|
|
594
602
|
imageBase64: { type: 'string', description: 'Screenshot image base64 (truncated)' },
|
|
595
603
|
pid: { type: 'number', description: 'Process ID for launched editor' },
|
|
596
604
|
message: { type: 'string', description: 'Status message' },
|
|
597
|
-
error: { type: 'string', description: 'Error message if failed' }
|
|
605
|
+
error: { type: 'string', description: 'Error message if failed' },
|
|
606
|
+
logPath: { type: 'string', description: 'Log file path used for read_log' },
|
|
607
|
+
entries: {
|
|
608
|
+
type: 'array',
|
|
609
|
+
items: { type: 'object', properties: { timestamp: { type: 'string' }, category: { type: 'string' }, level: { type: 'string' }, message: { type: 'string' } } },
|
|
610
|
+
description: 'Parsed Output Log entries'
|
|
611
|
+
},
|
|
612
|
+
filteredCount: { type: 'number', description: 'Count of entries after filtering' }
|
|
598
613
|
}
|
|
599
614
|
}
|
|
600
615
|
},
|
|
@@ -823,8 +823,25 @@ print('RESULT:' + json.dumps({'success': exists, 'exists': exists, 'path': path}
|
|
|
823
823
|
}
|
|
824
824
|
|
|
825
825
|
// 9. SYSTEM CONTROL
|
|
826
|
-
case 'system_control':
|
|
826
|
+
case 'system_control':
|
|
827
827
|
switch (requireAction(args)) {
|
|
828
|
+
case 'read_log': {
|
|
829
|
+
const filterCategoryRaw = args.filter_category;
|
|
830
|
+
const filterCategory = Array.isArray(filterCategoryRaw)
|
|
831
|
+
? filterCategoryRaw
|
|
832
|
+
: typeof filterCategoryRaw === 'string' && filterCategoryRaw.trim() !== ''
|
|
833
|
+
? filterCategoryRaw.split(',').map((s: string) => s.trim()).filter(Boolean)
|
|
834
|
+
: undefined;
|
|
835
|
+
const res = await tools.logTools.readOutputLog({
|
|
836
|
+
filterCategory,
|
|
837
|
+
filterLevel: args.filter_level,
|
|
838
|
+
lines: typeof args.lines === 'number' ? args.lines : undefined,
|
|
839
|
+
logPath: typeof args.log_path === 'string' ? args.log_path : undefined,
|
|
840
|
+
includePrefixes: Array.isArray(args.include_prefixes) ? args.include_prefixes : undefined,
|
|
841
|
+
excludeCategories: Array.isArray(args.exclude_categories) ? args.exclude_categories : undefined
|
|
842
|
+
});
|
|
843
|
+
return cleanObject(res);
|
|
844
|
+
}
|
|
828
845
|
case 'profile': {
|
|
829
846
|
const res = await tools.performanceTools.startProfiling({ type: args.profileType, duration: args.duration });
|
|
830
847
|
return cleanObject(res);
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { loadEnv } from '../types/env.js';
|
|
3
|
+
import { Logger } from '../utils/logger.js';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
type ReadParams = {
|
|
8
|
+
filterCategory?: string[]
|
|
9
|
+
filterLevel?: 'Error' | 'Warning' | 'Log' | 'Verbose' | 'VeryVerbose' | 'All'
|
|
10
|
+
lines?: number
|
|
11
|
+
logPath?: string
|
|
12
|
+
includePrefixes?: string[]
|
|
13
|
+
excludeCategories?: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type Entry = {
|
|
17
|
+
timestamp?: string
|
|
18
|
+
category?: string
|
|
19
|
+
level?: string
|
|
20
|
+
message: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class LogTools {
|
|
24
|
+
private env = loadEnv();
|
|
25
|
+
private log = new Logger('LogTools');
|
|
26
|
+
private cachedLogPath?: string;
|
|
27
|
+
constructor(private bridge: UnrealBridge) {}
|
|
28
|
+
|
|
29
|
+
async readOutputLog(params: ReadParams) {
|
|
30
|
+
const target = await this.resolveLogPath(params.logPath);
|
|
31
|
+
if (!target) {
|
|
32
|
+
return { success: false, error: 'Log file not found' };
|
|
33
|
+
}
|
|
34
|
+
const maxLines = typeof params.lines === 'number' && params.lines > 0 ? Math.min(params.lines, 2000) : 200;
|
|
35
|
+
let text = '';
|
|
36
|
+
try {
|
|
37
|
+
text = await this.tailFile(target, maxLines);
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
return { success: false, error: String(err?.message || err) };
|
|
40
|
+
}
|
|
41
|
+
const rawLines = text.split(/\r?\n/).filter(l => l.length > 0);
|
|
42
|
+
const parsed: Entry[] = rawLines.map(l => this.parseLine(l));
|
|
43
|
+
const mappedLevel = params.filterLevel || 'All';
|
|
44
|
+
const includeCats = Array.isArray(params.filterCategory) && params.filterCategory.length ? new Set(params.filterCategory) : undefined;
|
|
45
|
+
const includePrefixes = Array.isArray(params.includePrefixes) && params.includePrefixes.length ? params.includePrefixes : undefined;
|
|
46
|
+
const excludeCats = Array.isArray(params.excludeCategories) && params.excludeCategories.length ? new Set(params.excludeCategories) : undefined;
|
|
47
|
+
const filtered = parsed.filter(e => {
|
|
48
|
+
if (!e) return false;
|
|
49
|
+
if (mappedLevel && mappedLevel !== 'All') {
|
|
50
|
+
const lv = (e.level || 'Log');
|
|
51
|
+
if (lv === 'Display') {
|
|
52
|
+
if (mappedLevel !== 'Log') return false;
|
|
53
|
+
} else if (lv !== mappedLevel) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (includeCats && e.category && !includeCats.has(e.category)) return false;
|
|
58
|
+
if (includePrefixes && includePrefixes.length && e.category) {
|
|
59
|
+
if (!includePrefixes.some(p => (e.category ?? '').startsWith(p))) return false;
|
|
60
|
+
}
|
|
61
|
+
if (excludeCats && e.category && excludeCats.has(e.category)) return false;
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
const includeInternal = Boolean(
|
|
65
|
+
(includeCats && includeCats.has('LogPython')) ||
|
|
66
|
+
(includePrefixes && includePrefixes.some(p => 'LogPython'.startsWith(p)))
|
|
67
|
+
);
|
|
68
|
+
const sanitized = includeInternal ? filtered : filtered.filter(entry => !this.isInternalLogEntry(entry));
|
|
69
|
+
return { success: true, logPath: target.replace(/\\/g, '/'), entries: sanitized, filteredCount: sanitized.length };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async resolveLogPath(override?: string): Promise<string | undefined> {
|
|
73
|
+
if (override && typeof override === 'string' && override.trim()) {
|
|
74
|
+
try {
|
|
75
|
+
const st = await fs.stat(override);
|
|
76
|
+
if (st.isFile()) {
|
|
77
|
+
return this.cacheLogPath(path.resolve(override));
|
|
78
|
+
}
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (this.cachedLogPath && (await this.fileExists(this.cachedLogPath))) {
|
|
83
|
+
return this.cachedLogPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const envLog = await this.resolveFromProjectEnv();
|
|
87
|
+
if (envLog) {
|
|
88
|
+
return envLog;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.bridge.isConnected) {
|
|
92
|
+
try {
|
|
93
|
+
const script = `
|
|
94
|
+
import unreal, json, os
|
|
95
|
+
paths = []
|
|
96
|
+
try:
|
|
97
|
+
d = unreal.Paths.project_log_dir()
|
|
98
|
+
if d:
|
|
99
|
+
paths.append(os.path.abspath(d))
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
try:
|
|
103
|
+
sd = unreal.Paths.project_saved_dir()
|
|
104
|
+
if sd:
|
|
105
|
+
p = os.path.join(sd, 'Logs')
|
|
106
|
+
paths.append(os.path.abspath(p))
|
|
107
|
+
except Exception:
|
|
108
|
+
pass
|
|
109
|
+
try:
|
|
110
|
+
pf = unreal.Paths.get_project_file_path()
|
|
111
|
+
if pf:
|
|
112
|
+
pd = os.path.dirname(pf)
|
|
113
|
+
p = os.path.join(pd, 'Saved', 'Logs')
|
|
114
|
+
paths.append(os.path.abspath(p))
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
all_logs = []
|
|
118
|
+
for base in paths:
|
|
119
|
+
try:
|
|
120
|
+
if os.path.isdir(base):
|
|
121
|
+
for name in os.listdir(base):
|
|
122
|
+
if name.lower().endswith('.log'):
|
|
123
|
+
fp = os.path.join(base, name)
|
|
124
|
+
try:
|
|
125
|
+
m = os.path.getmtime(fp)
|
|
126
|
+
all_logs.append({'p': fp, 'm': m})
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
all_logs.sort(key=lambda x: x['m'], reverse=True)
|
|
132
|
+
print('RESULT:' + json.dumps({'dirs': paths, 'logs': all_logs}))
|
|
133
|
+
`.trim();
|
|
134
|
+
const res = await this.bridge.executePythonWithResult(script);
|
|
135
|
+
const logs = Array.isArray(res?.logs) ? res.logs : [];
|
|
136
|
+
for (const entry of logs) {
|
|
137
|
+
const p = typeof entry?.p === 'string' ? entry.p : undefined;
|
|
138
|
+
if (p && p.trim()) return this.cacheLogPath(p);
|
|
139
|
+
}
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
142
|
+
const fallback = await this.findLatestLogInDir(path.join(process.cwd(), 'Saved', 'Logs'));
|
|
143
|
+
if (fallback) {
|
|
144
|
+
return fallback;
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private async resolveFromProjectEnv(): Promise<string | undefined> {
|
|
150
|
+
const projectPath = this.env.UE_PROJECT_PATH;
|
|
151
|
+
if (projectPath && typeof projectPath === 'string' && projectPath.trim()) {
|
|
152
|
+
const projectDir = path.dirname(projectPath);
|
|
153
|
+
const logsDir = path.join(projectDir, 'Saved', 'Logs');
|
|
154
|
+
const envLog = await this.findLatestLogInDir(logsDir);
|
|
155
|
+
if (envLog) {
|
|
156
|
+
return envLog;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async findLatestLogInDir(dir: string): Promise<string | undefined> {
|
|
163
|
+
if (!dir) return undefined;
|
|
164
|
+
try {
|
|
165
|
+
const entries = await fs.readdir(dir);
|
|
166
|
+
const candidates: { p: string; m: number }[] = [];
|
|
167
|
+
for (const name of entries) {
|
|
168
|
+
if (!name.toLowerCase().endsWith('.log')) continue;
|
|
169
|
+
const fp = path.join(dir, name);
|
|
170
|
+
try {
|
|
171
|
+
const st = await fs.stat(fp);
|
|
172
|
+
candidates.push({ p: fp, m: st.mtimeMs });
|
|
173
|
+
} catch {}
|
|
174
|
+
}
|
|
175
|
+
if (candidates.length) {
|
|
176
|
+
candidates.sort((a, b) => b.m - a.m);
|
|
177
|
+
return this.cacheLogPath(candidates[0].p);
|
|
178
|
+
}
|
|
179
|
+
} catch {}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async fileExists(filePath: string): Promise<boolean> {
|
|
184
|
+
try {
|
|
185
|
+
const st = await fs.stat(filePath);
|
|
186
|
+
return st.isFile();
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private cacheLogPath(p: string): string {
|
|
193
|
+
this.cachedLogPath = p;
|
|
194
|
+
return p;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private async tailFile(filePath: string, maxLines: number): Promise<string> {
|
|
198
|
+
const handle = await fs.open(filePath, 'r');
|
|
199
|
+
try {
|
|
200
|
+
const stat = await handle.stat();
|
|
201
|
+
const chunkSize = 128 * 1024;
|
|
202
|
+
let position = stat.size;
|
|
203
|
+
let remaining = '';
|
|
204
|
+
const lines: string[] = [];
|
|
205
|
+
while (position > 0 && lines.length < maxLines) {
|
|
206
|
+
const readSize = Math.min(chunkSize, position);
|
|
207
|
+
position -= readSize;
|
|
208
|
+
const buf = Buffer.alloc(readSize);
|
|
209
|
+
await handle.read(buf, 0, readSize, position);
|
|
210
|
+
remaining = buf.toString('utf8') + remaining;
|
|
211
|
+
const parts = remaining.split(/\r?\n/);
|
|
212
|
+
remaining = parts.shift() || '';
|
|
213
|
+
while (parts.length) {
|
|
214
|
+
const line = parts.pop() as string;
|
|
215
|
+
if (line === undefined) break;
|
|
216
|
+
if (line.length === 0) continue;
|
|
217
|
+
lines.unshift(line);
|
|
218
|
+
if (lines.length >= maxLines) break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (lines.length < maxLines && remaining) {
|
|
222
|
+
lines.unshift(remaining);
|
|
223
|
+
}
|
|
224
|
+
return lines.slice(0, maxLines).join('\n');
|
|
225
|
+
} finally {
|
|
226
|
+
try { await handle.close(); } catch {}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private parseLine(line: string): Entry {
|
|
231
|
+
const m1 = line.match(/^\[?(\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}\.\d{2}:\d+)\]?\s*\[(.*?)\]\s*(.*)$/);
|
|
232
|
+
if (m1) {
|
|
233
|
+
const rest = m1[3];
|
|
234
|
+
const m2 = rest.match(/^(\w+):\s*(Error|Warning|Display|Log|Verbose|VeryVerbose):\s*(.*)$/);
|
|
235
|
+
if (m2) {
|
|
236
|
+
return { timestamp: m1[1], category: m2[1], level: m2[2] === 'Display' ? 'Log' : m2[2], message: m2[3] };
|
|
237
|
+
}
|
|
238
|
+
const m3 = rest.match(/^(\w+):\s*(.*)$/);
|
|
239
|
+
if (m3) {
|
|
240
|
+
return { timestamp: m1[1], category: m3[1], level: 'Log', message: m3[2] };
|
|
241
|
+
}
|
|
242
|
+
return { timestamp: m1[1], message: rest };
|
|
243
|
+
}
|
|
244
|
+
const m = line.match(/^(\w+):\s*(Error|Warning|Display|Log|Verbose|VeryVerbose):\s*(.*)$/);
|
|
245
|
+
if (m) {
|
|
246
|
+
return { category: m[1], level: m[2] === 'Display' ? 'Log' : m[2], message: m[3] };
|
|
247
|
+
}
|
|
248
|
+
const mAlt = line.match(/^(\w+):\s*(.*)$/);
|
|
249
|
+
if (mAlt) {
|
|
250
|
+
return { category: mAlt[1], level: 'Log', message: mAlt[2] };
|
|
251
|
+
}
|
|
252
|
+
return { message: line };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private isInternalLogEntry(entry: Entry): boolean {
|
|
256
|
+
if (!entry) return false;
|
|
257
|
+
const category = entry.category?.toLowerCase() || '';
|
|
258
|
+
const message = entry.message?.trim() || '';
|
|
259
|
+
if (category === 'logpython' && message.startsWith('RESULT:')) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
if (!entry.category && message.startsWith('[') && message.includes('LogPython: RESULT:')) {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
package/src/types/tool-types.ts
CHANGED
|
@@ -98,6 +98,12 @@ export interface SystemControlResponse extends BaseToolResponse {
|
|
|
98
98
|
soundPlaying?: boolean;
|
|
99
99
|
widgetPath?: string;
|
|
100
100
|
widgetVisible?: boolean;
|
|
101
|
+
imagePath?: string;
|
|
102
|
+
imageBase64?: string;
|
|
103
|
+
pid?: number;
|
|
104
|
+
logPath?: string;
|
|
105
|
+
entries?: Array<{ timestamp?: string; category?: string; level?: string; message: string }>;
|
|
106
|
+
filteredCount?: number;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
// Console Command Types
|
|
@@ -174,7 +180,7 @@ export type AnimationAction = 'create_animation_bp' | 'play_montage' | 'setup_ra
|
|
|
174
180
|
export type EffectAction = 'particle' | 'niagara' | 'debug_shape';
|
|
175
181
|
export type BlueprintAction = 'create' | 'add_component';
|
|
176
182
|
export type EnvironmentAction = 'create_landscape' | 'sculpt' | 'add_foliage' | 'paint_foliage';
|
|
177
|
-
export type SystemAction = 'profile' | 'show_fps' | 'set_quality' | 'play_sound' | 'create_widget' | 'show_widget';
|
|
183
|
+
export type SystemAction = 'profile' | 'show_fps' | 'set_quality' | 'play_sound' | 'create_widget' | 'show_widget' | 'screenshot' | 'engine_start' | 'engine_quit' | 'read_log';
|
|
178
184
|
export type VerificationAction = 'foliage_type_exists' | 'foliage_instances_near' | 'landscape_exists' | 'quality_level';
|
|
179
185
|
|
|
180
186
|
// Consolidated tool parameter types
|
|
@@ -282,6 +288,15 @@ export interface ConsolidatedToolParams {
|
|
|
282
288
|
widgetName?: string;
|
|
283
289
|
widgetType?: string;
|
|
284
290
|
visible?: boolean;
|
|
291
|
+
resolution?: string;
|
|
292
|
+
projectPath?: string;
|
|
293
|
+
editorExe?: string;
|
|
294
|
+
filter_category?: string | string[];
|
|
295
|
+
filter_level?: 'Error' | 'Warning' | 'Log' | 'Verbose' | 'VeryVerbose' | 'All';
|
|
296
|
+
lines?: number;
|
|
297
|
+
log_path?: string;
|
|
298
|
+
include_prefixes?: string[];
|
|
299
|
+
exclude_categories?: string[];
|
|
285
300
|
};
|
|
286
301
|
|
|
287
302
|
console_command: {
|