unreal-engine-mcp-server 0.5.2 → 0.5.4
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/CHANGELOG.md +195 -0
- package/README.md +9 -6
- package/dist/automation/bridge.d.ts +1 -0
- package/dist/automation/bridge.js +62 -4
- package/dist/automation/types.d.ts +1 -0
- package/dist/config/class-aliases.d.ts +5 -0
- package/dist/config/class-aliases.js +30 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/graphql/server.d.ts +0 -1
- package/dist/graphql/server.js +15 -16
- package/dist/index.js +1 -1
- package/dist/services/metrics-server.d.ts +2 -1
- package/dist/services/metrics-server.js +29 -4
- package/dist/tools/consolidated-tool-definitions.js +3 -3
- package/dist/tools/debug.d.ts +5 -0
- package/dist/tools/debug.js +7 -0
- package/dist/tools/handlers/actor-handlers.js +4 -27
- package/dist/tools/handlers/asset-handlers.js +13 -1
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -1
- package/dist/tools/handlers/common-handlers.d.ts +11 -11
- package/dist/tools/handlers/common-handlers.js +6 -4
- package/dist/tools/handlers/editor-handlers.d.ts +2 -1
- package/dist/tools/handlers/editor-handlers.js +6 -6
- package/dist/tools/handlers/effect-handlers.js +3 -0
- package/dist/tools/handlers/graph-handlers.d.ts +2 -1
- package/dist/tools/handlers/graph-handlers.js +1 -1
- package/dist/tools/handlers/input-handlers.d.ts +5 -1
- package/dist/tools/handlers/level-handlers.d.ts +2 -1
- package/dist/tools/handlers/level-handlers.js +3 -3
- package/dist/tools/handlers/lighting-handlers.d.ts +2 -1
- package/dist/tools/handlers/lighting-handlers.js +3 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +2 -1
- package/dist/tools/handlers/pipeline-handlers.js +64 -10
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/system-handlers.d.ts +1 -1
- package/dist/tools/input.d.ts +5 -1
- package/dist/tools/input.js +37 -1
- package/dist/tools/lighting.d.ts +1 -0
- package/dist/tools/lighting.js +7 -0
- package/dist/tools/physics.d.ts +1 -1
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +7 -0
- package/dist/types/handler-types.d.ts +343 -0
- package/dist/types/handler-types.js +2 -0
- package/dist/unreal-bridge.d.ts +1 -1
- package/dist/unreal-bridge.js +8 -6
- package/dist/utils/command-validator.d.ts +1 -0
- package/dist/utils/command-validator.js +11 -1
- package/dist/utils/error-handler.js +3 -1
- package/dist/utils/response-validator.js +2 -2
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.js +3 -6
- package/dist/utils/unreal-command-queue.js +1 -1
- package/dist/utils/validation.js +6 -2
- package/docs/handler-mapping.md +6 -1
- package/package.json +2 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +25 -1
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +40 -58
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +27 -46
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +16 -1
- package/server.json +2 -2
- package/src/automation/bridge.ts +80 -10
- package/src/automation/types.ts +1 -0
- package/src/config/class-aliases.ts +65 -0
- package/src/constants.ts +10 -0
- package/src/graphql/server.ts +23 -23
- package/src/index.ts +1 -1
- package/src/services/metrics-server.ts +40 -6
- package/src/tools/consolidated-tool-definitions.ts +3 -3
- package/src/tools/debug.ts +8 -0
- package/src/tools/handlers/actor-handlers.ts +5 -31
- package/src/tools/handlers/asset-handlers.ts +19 -1
- package/src/tools/handlers/blueprint-handlers.ts +1 -1
- package/src/tools/handlers/common-handlers.ts +32 -11
- package/src/tools/handlers/editor-handlers.ts +8 -7
- package/src/tools/handlers/effect-handlers.ts +4 -0
- package/src/tools/handlers/graph-handlers.ts +7 -6
- package/src/tools/handlers/level-handlers.ts +5 -4
- package/src/tools/handlers/lighting-handlers.ts +5 -1
- package/src/tools/handlers/pipeline-handlers.ts +83 -16
- package/src/tools/input.ts +60 -1
- package/src/tools/lighting.ts +11 -0
- package/src/tools/physics.ts +1 -1
- package/src/tools/sequence.ts +11 -0
- package/src/types/handler-types.ts +442 -0
- package/src/unreal-bridge.ts +8 -6
- package/src/utils/command-validator.ts +23 -1
- package/src/utils/error-handler.ts +4 -1
- package/src/utils/response-validator.ts +7 -9
- package/src/utils/safe-json.ts +20 -15
- package/src/utils/unreal-command-queue.ts +3 -1
- package/src/utils/validation.test.ts +3 -3
- package/src/utils/validation.ts +36 -26
- package/tests/test-console-command.mjs +1 -1
- package/tests/test-runner.mjs +63 -3
- package/tests/run-unreal-tool-tests.mjs +0 -948
- package/tests/test-asset-errors.mjs +0 -35
|
@@ -1,11 +1,73 @@
|
|
|
1
1
|
import { cleanObject } from '../../utils/safe-json.js';
|
|
2
2
|
import { ITools } from '../../types/tool-interfaces.js';
|
|
3
|
+
import type { PipelineArgs } from '../../types/handler-types.js';
|
|
3
4
|
import { executeAutomationRequest } from './common-handlers.js';
|
|
4
5
|
import { spawn } from 'child_process';
|
|
5
6
|
import path from 'path';
|
|
6
7
|
import fs from 'fs';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
function validateUbtArgumentsString(extraArgs: string): void {
|
|
10
|
+
if (!extraArgs || typeof extraArgs !== 'string') {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const forbiddenChars = ['\n', '\r', ';', '|', '`', '&&', '||', '>', '<'];
|
|
15
|
+
for (const char of forbiddenChars) {
|
|
16
|
+
if (extraArgs.includes(char)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`UBT arguments contain forbidden character(s) and are blocked for safety. Blocked: ${JSON.stringify(char)}.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function tokenizeArgs(extraArgs: string): string[] {
|
|
25
|
+
if (!extraArgs) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const args: string[] = [];
|
|
30
|
+
let current = '';
|
|
31
|
+
let inQuotes = false;
|
|
32
|
+
let escapeNext = false;
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < extraArgs.length; i++) {
|
|
35
|
+
const ch = extraArgs[i];
|
|
36
|
+
|
|
37
|
+
if (escapeNext) {
|
|
38
|
+
current += ch;
|
|
39
|
+
escapeNext = false;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (ch === '\\') {
|
|
44
|
+
escapeNext = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (ch === '"') {
|
|
49
|
+
inQuotes = !inQuotes;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!inQuotes && /\s/.test(ch)) {
|
|
54
|
+
if (current.length > 0) {
|
|
55
|
+
args.push(current);
|
|
56
|
+
current = '';
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
current += ch;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (current.length > 0) {
|
|
64
|
+
args.push(current);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return args;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function handlePipelineTools(action: string, args: PipelineArgs, tools: ITools) {
|
|
9
71
|
switch (action) {
|
|
10
72
|
case 'run_ubt': {
|
|
11
73
|
const target = args.target;
|
|
@@ -14,11 +76,12 @@ export async function handlePipelineTools(action: string, args: any, tools: IToo
|
|
|
14
76
|
const extraArgs = args.arguments || '';
|
|
15
77
|
|
|
16
78
|
if (!target) {
|
|
17
|
-
|
|
79
|
+
throw new Error('Target is required for run_ubt');
|
|
18
80
|
}
|
|
19
81
|
|
|
20
|
-
|
|
21
|
-
|
|
82
|
+
validateUbtArgumentsString(extraArgs);
|
|
83
|
+
|
|
84
|
+
let ubtPath = 'UnrealBuildTool';
|
|
22
85
|
const enginePath = process.env.UE_ENGINE_PATH || process.env.UNREAL_ENGINE_PATH;
|
|
23
86
|
|
|
24
87
|
if (enginePath) {
|
|
@@ -34,13 +97,11 @@ export async function handlePipelineTools(action: string, args: any, tools: IToo
|
|
|
34
97
|
}
|
|
35
98
|
|
|
36
99
|
if (!projectPath) {
|
|
37
|
-
|
|
100
|
+
throw new Error('UE_PROJECT_PATH environment variable is not set and no projectPath argument was provided.');
|
|
38
101
|
}
|
|
39
102
|
|
|
40
|
-
// If projectPath points to a .uproject file, use it. If it's a directory, look for a .uproject file.
|
|
41
103
|
let uprojectFile = projectPath;
|
|
42
104
|
if (!uprojectFile.endsWith('.uproject')) {
|
|
43
|
-
// Find first .uproject in the directory
|
|
44
105
|
try {
|
|
45
106
|
const files = fs.readdirSync(projectPath);
|
|
46
107
|
const found = files.find(f => f.endsWith('.uproject'));
|
|
@@ -48,20 +109,23 @@ export async function handlePipelineTools(action: string, args: any, tools: IToo
|
|
|
48
109
|
uprojectFile = path.join(projectPath, found);
|
|
49
110
|
}
|
|
50
111
|
} catch (_e) {
|
|
51
|
-
|
|
112
|
+
throw new Error(`Could not read project directory: ${projectPath}`);
|
|
52
113
|
}
|
|
53
114
|
}
|
|
54
115
|
|
|
116
|
+
const projectArg = `-Project="${uprojectFile}"`;
|
|
117
|
+
const extraTokens = tokenizeArgs(extraArgs);
|
|
118
|
+
|
|
55
119
|
const cmdArgs = [
|
|
56
120
|
target,
|
|
57
121
|
platform,
|
|
58
122
|
configuration,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
]
|
|
123
|
+
projectArg,
|
|
124
|
+
...extraTokens
|
|
125
|
+
];
|
|
62
126
|
|
|
63
127
|
return new Promise((resolve) => {
|
|
64
|
-
const child = spawn(ubtPath, cmdArgs, { shell:
|
|
128
|
+
const child = spawn(ubtPath, cmdArgs, { shell: false });
|
|
65
129
|
|
|
66
130
|
const MAX_OUTPUT_SIZE = 20 * 1024; // 20KB cap
|
|
67
131
|
let stdout = '';
|
|
@@ -90,12 +154,14 @@ export async function handlePipelineTools(action: string, args: any, tools: IToo
|
|
|
90
154
|
? '\n[Output truncated for response payload]'
|
|
91
155
|
: '';
|
|
92
156
|
|
|
157
|
+
const quotedArgs = cmdArgs.map(arg => arg.includes(' ') ? `"${arg}"` : arg);
|
|
158
|
+
|
|
93
159
|
if (code === 0) {
|
|
94
160
|
resolve({
|
|
95
161
|
success: true,
|
|
96
162
|
message: 'UnrealBuildTool finished successfully',
|
|
97
163
|
output: stdout + truncatedNote,
|
|
98
|
-
command: `${ubtPath} ${
|
|
164
|
+
command: `${ubtPath} ${quotedArgs.join(' ')}`
|
|
99
165
|
});
|
|
100
166
|
} else {
|
|
101
167
|
resolve({
|
|
@@ -104,23 +170,24 @@ export async function handlePipelineTools(action: string, args: any, tools: IToo
|
|
|
104
170
|
message: `UnrealBuildTool failed with code ${code}`,
|
|
105
171
|
output: stdout + truncatedNote,
|
|
106
172
|
errorOutput: stderr + truncatedNote,
|
|
107
|
-
command: `${ubtPath} ${
|
|
173
|
+
command: `${ubtPath} ${quotedArgs.join(' ')}`
|
|
108
174
|
});
|
|
109
175
|
}
|
|
110
176
|
});
|
|
111
177
|
|
|
112
178
|
child.on('error', (err) => {
|
|
179
|
+
const quotedArgs = cmdArgs.map(arg => arg.includes(' ') ? `"${arg}"` : arg);
|
|
180
|
+
|
|
113
181
|
resolve({
|
|
114
182
|
success: false,
|
|
115
183
|
error: 'SPAWN_FAILED',
|
|
116
184
|
message: `Failed to spawn UnrealBuildTool: ${err.message}`,
|
|
117
|
-
command: `${ubtPath} ${
|
|
185
|
+
command: `${ubtPath} ${quotedArgs.join(' ')}`
|
|
118
186
|
});
|
|
119
187
|
});
|
|
120
188
|
});
|
|
121
189
|
}
|
|
122
190
|
default:
|
|
123
|
-
// Fallback to automation bridge if we add more actions later that are bridge-supported
|
|
124
191
|
const res = await executeAutomationRequest(tools, 'manage_pipeline', { ...args, subAction: action }, 'Automation bridge not available for manage_pipeline');
|
|
125
192
|
return cleanObject(res);
|
|
126
193
|
}
|
package/src/tools/input.ts
CHANGED
|
@@ -8,6 +8,42 @@ interface ToolDefinition {
|
|
|
8
8
|
outputSchema: object;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// Common valid key names for UE5 Enhanced Input (not exhaustive, but covers primary cases)
|
|
12
|
+
const VALID_KEY_NAMES = new Set([
|
|
13
|
+
// Keyboard - Letters
|
|
14
|
+
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
15
|
+
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
16
|
+
// Keyboard - Numbers
|
|
17
|
+
'Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine',
|
|
18
|
+
// Keyboard - Function keys
|
|
19
|
+
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
|
|
20
|
+
// Keyboard - Special
|
|
21
|
+
'SpaceBar', 'Enter', 'Escape', 'Tab', 'BackSpace', 'CapsLock',
|
|
22
|
+
'LeftShift', 'RightShift', 'LeftControl', 'RightControl', 'LeftAlt', 'RightAlt',
|
|
23
|
+
'LeftCommand', 'RightCommand', 'Insert', 'Delete', 'Home', 'End', 'PageUp', 'PageDown',
|
|
24
|
+
'Up', 'Down', 'Left', 'Right',
|
|
25
|
+
// Keyboard - Punctuation
|
|
26
|
+
'Semicolon', 'Equals', 'Comma', 'Hyphen', 'Underscore', 'Period', 'Slash', 'Tilde',
|
|
27
|
+
'LeftBracket', 'Backslash', 'RightBracket', 'Apostrophe', 'Quote',
|
|
28
|
+
// Mouse - Buttons
|
|
29
|
+
'LeftMouseButton', 'RightMouseButton', 'MiddleMouseButton', 'ThumbMouseButton', 'ThumbMouseButton2',
|
|
30
|
+
// Mouse - Axes (must be mapped separately, not as composite 2D)
|
|
31
|
+
'MouseX', 'MouseY', 'MouseWheelAxis', 'MouseScrollUp', 'MouseScrollDown',
|
|
32
|
+
// Gamepad - Face Buttons
|
|
33
|
+
'Gamepad_FaceButton_Bottom', 'Gamepad_FaceButton_Right', 'Gamepad_FaceButton_Left', 'Gamepad_FaceButton_Top',
|
|
34
|
+
// Gamepad - Shoulder/Trigger
|
|
35
|
+
'Gamepad_LeftShoulder', 'Gamepad_RightShoulder', 'Gamepad_LeftTrigger', 'Gamepad_RightTrigger',
|
|
36
|
+
'Gamepad_LeftTriggerAxis', 'Gamepad_RightTriggerAxis',
|
|
37
|
+
// Gamepad - Sticks
|
|
38
|
+
'Gamepad_LeftThumbstick', 'Gamepad_RightThumbstick',
|
|
39
|
+
'Gamepad_LeftStick_Up', 'Gamepad_LeftStick_Down', 'Gamepad_LeftStick_Left', 'Gamepad_LeftStick_Right',
|
|
40
|
+
'Gamepad_RightStick_Up', 'Gamepad_RightStick_Down', 'Gamepad_RightStick_Left', 'Gamepad_RightStick_Right',
|
|
41
|
+
// Gamepad - D-Pad
|
|
42
|
+
'Gamepad_DPad_Up', 'Gamepad_DPad_Down', 'Gamepad_DPad_Left', 'Gamepad_DPad_Right',
|
|
43
|
+
// Gamepad - Special
|
|
44
|
+
'Gamepad_Special_Left', 'Gamepad_Special_Right'
|
|
45
|
+
]);
|
|
46
|
+
|
|
11
47
|
export class InputTools {
|
|
12
48
|
private automationBridge: AutomationBridge | null = null;
|
|
13
49
|
|
|
@@ -37,11 +73,33 @@ export class InputTools {
|
|
|
37
73
|
|
|
38
74
|
async addMapping(contextPath: string, actionPath: string, key: string) {
|
|
39
75
|
if (!this.automationBridge) throw new Error('Automation bridge not set');
|
|
76
|
+
|
|
77
|
+
// Validate key name
|
|
78
|
+
if (!key || typeof key !== 'string' || key.trim().length === 0) {
|
|
79
|
+
return { success: false, error: 'INVALID_ARGUMENT', message: 'Key name is required.' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const trimmedKey = key.trim();
|
|
83
|
+
|
|
84
|
+
// Check for common mistakes (composite 2D axis names)
|
|
85
|
+
if (trimmedKey === 'MouseXY2D' || trimmedKey === 'Mouse2D' || trimmedKey === 'MouseXY') {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: 'INVALID_ARGUMENT',
|
|
89
|
+
message: `Invalid key name '${trimmedKey}'. For mouse axis input, use separate mappings with 'MouseX' and 'MouseY' keys instead of composite 2D axis names.`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Warn if key is not in our known list (but still attempt the mapping)
|
|
94
|
+
if (!VALID_KEY_NAMES.has(trimmedKey)) {
|
|
95
|
+
console.warn(`[InputTools] Key '${trimmedKey}' is not in the standard key list. Attempting mapping anyway.`);
|
|
96
|
+
}
|
|
97
|
+
|
|
40
98
|
return this.automationBridge.sendAutomationRequest('manage_input', {
|
|
41
99
|
action: 'add_mapping',
|
|
42
100
|
contextPath,
|
|
43
101
|
actionPath,
|
|
44
|
-
key
|
|
102
|
+
key: trimmedKey
|
|
45
103
|
});
|
|
46
104
|
}
|
|
47
105
|
|
|
@@ -55,6 +113,7 @@ export class InputTools {
|
|
|
55
113
|
}
|
|
56
114
|
}
|
|
57
115
|
|
|
116
|
+
|
|
58
117
|
export const inputTools: ToolDefinition = {
|
|
59
118
|
name: 'manage_input',
|
|
60
119
|
description: `Enhanced Input management.
|
package/src/tools/lighting.ts
CHANGED
|
@@ -29,6 +29,17 @@ export class LightingTools {
|
|
|
29
29
|
return `Light_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* List available light types (classes)
|
|
34
|
+
*/
|
|
35
|
+
async listLightTypes() {
|
|
36
|
+
if (!this.automationBridge) {
|
|
37
|
+
throw new Error('Automation Bridge required to list light types');
|
|
38
|
+
}
|
|
39
|
+
const response = await this.automationBridge.sendAutomationRequest('list_light_types', {});
|
|
40
|
+
return response;
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
/**
|
|
33
44
|
* Spawn a light actor using the Automation Bridge.
|
|
34
45
|
* @param lightClass The Unreal light class name (e.g. 'DirectionalLight', 'PointLight')
|
package/src/tools/physics.ts
CHANGED
package/src/tools/sequence.ts
CHANGED
|
@@ -361,6 +361,17 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
|
|
|
361
361
|
return resp;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
/**
|
|
365
|
+
* List available track types
|
|
366
|
+
*/
|
|
367
|
+
async listTrackTypes(): Promise<StandardActionResponse> {
|
|
368
|
+
const resp = await this.sendAction('list_track_types', {});
|
|
369
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
370
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement list_track_types' } as const;
|
|
371
|
+
}
|
|
372
|
+
return resp;
|
|
373
|
+
}
|
|
374
|
+
|
|
364
375
|
/**
|
|
365
376
|
* Set playback work range
|
|
366
377
|
*/
|