unreal-engine-mcp-server 0.3.1 → 0.4.3
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/.github/copilot-instructions.md +45 -0
- package/.github/workflows/publish-mcp.yml +1 -1
- package/README.md +22 -7
- package/dist/index.js +137 -46
- package/dist/prompts/index.d.ts +10 -3
- package/dist/prompts/index.js +186 -7
- package/dist/resources/actors.d.ts +19 -1
- package/dist/resources/actors.js +55 -64
- package/dist/resources/assets.d.ts +3 -2
- package/dist/resources/assets.js +117 -109
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +31 -56
- package/dist/tools/actors.d.ts +3 -14
- package/dist/tools/actors.js +246 -302
- package/dist/tools/animation.d.ts +57 -102
- package/dist/tools/animation.js +429 -450
- package/dist/tools/assets.d.ts +13 -2
- package/dist/tools/assets.js +58 -46
- package/dist/tools/audio.d.ts +22 -13
- package/dist/tools/audio.js +467 -121
- package/dist/tools/blueprint.d.ts +32 -13
- package/dist/tools/blueprint.js +699 -448
- package/dist/tools/build_environment_advanced.d.ts +0 -1
- package/dist/tools/build_environment_advanced.js +236 -87
- package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
- package/dist/tools/consolidated-tool-definitions.js +124 -255
- package/dist/tools/consolidated-tool-handlers.js +749 -766
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +170 -36
- package/dist/tools/editor.d.ts +9 -2
- package/dist/tools/editor.js +30 -44
- package/dist/tools/foliage.d.ts +34 -15
- package/dist/tools/foliage.js +97 -107
- package/dist/tools/introspection.js +19 -21
- package/dist/tools/landscape.d.ts +1 -2
- package/dist/tools/landscape.js +311 -168
- package/dist/tools/level.d.ts +3 -28
- package/dist/tools/level.js +642 -192
- package/dist/tools/lighting.d.ts +14 -3
- package/dist/tools/lighting.js +236 -123
- package/dist/tools/materials.d.ts +25 -7
- package/dist/tools/materials.js +102 -79
- package/dist/tools/niagara.d.ts +10 -12
- package/dist/tools/niagara.js +74 -94
- package/dist/tools/performance.d.ts +12 -4
- package/dist/tools/performance.js +38 -79
- package/dist/tools/physics.d.ts +34 -10
- package/dist/tools/physics.js +364 -292
- package/dist/tools/rc.js +98 -24
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +146 -24
- package/dist/tools/ui.d.ts +31 -4
- package/dist/tools/ui.js +83 -66
- package/dist/tools/visual.d.ts +11 -0
- package/dist/tools/visual.js +245 -30
- package/dist/types/tool-types.d.ts +0 -6
- package/dist/types/tool-types.js +1 -8
- package/dist/unreal-bridge.d.ts +32 -2
- package/dist/unreal-bridge.js +621 -127
- package/dist/utils/elicitation.d.ts +57 -0
- package/dist/utils/elicitation.js +104 -0
- package/dist/utils/error-handler.d.ts +0 -33
- package/dist/utils/error-handler.js +4 -111
- package/dist/utils/http.d.ts +2 -22
- package/dist/utils/http.js +12 -75
- package/dist/utils/normalize.d.ts +4 -4
- package/dist/utils/normalize.js +15 -7
- package/dist/utils/python-output.d.ts +18 -0
- package/dist/utils/python-output.js +290 -0
- package/dist/utils/python.d.ts +2 -0
- package/dist/utils/python.js +4 -0
- package/dist/utils/response-validator.d.ts +6 -1
- package/dist/utils/response-validator.js +66 -13
- package/dist/utils/result-helpers.d.ts +27 -0
- package/dist/utils/result-helpers.js +147 -0
- package/dist/utils/safe-json.d.ts +0 -2
- package/dist/utils/safe-json.js +0 -43
- package/dist/utils/validation.d.ts +16 -0
- package/dist/utils/validation.js +70 -7
- package/mcp-config-example.json +2 -2
- package/package.json +11 -10
- package/server.json +37 -14
- package/src/index.ts +146 -50
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +123 -102
- package/src/resources/levels.ts +37 -47
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +59 -45
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +312 -106
- package/src/tools/consolidated-tool-definitions.ts +136 -267
- package/src/tools/consolidated-tool-handlers.ts +871 -795
- package/src/tools/debug.ts +179 -38
- package/src/tools/editor.ts +35 -37
- package/src/tools/foliage.ts +110 -104
- package/src/tools/introspection.ts +24 -22
- package/src/tools/landscape.ts +334 -181
- package/src/tools/level.ts +683 -182
- package/src/tools/lighting.ts +244 -123
- package/src/tools/materials.ts +114 -83
- package/src/tools/niagara.ts +87 -81
- package/src/tools/performance.ts +49 -88
- package/src/tools/physics.ts +393 -299
- package/src/tools/rc.ts +103 -25
- package/src/tools/sequence.ts +157 -30
- package/src/tools/ui.ts +101 -70
- package/src/tools/visual.ts +250 -29
- package/src/types/tool-types.ts +0 -9
- package/src/unreal-bridge.ts +658 -140
- package/src/utils/elicitation.ts +129 -0
- package/src/utils/error-handler.ts +4 -159
- package/src/utils/http.ts +16 -115
- package/src/utils/normalize.ts +20 -10
- package/src/utils/python-output.ts +351 -0
- package/src/utils/python.ts +3 -0
- package/src/utils/response-validator.ts +68 -17
- package/src/utils/result-helpers.ts +193 -0
- package/src/utils/safe-json.ts +0 -50
- package/src/utils/validation.ts +94 -7
- package/tests/run-unreal-tool-tests.mjs +720 -0
- package/tsconfig.json +2 -2
- package/dist/python-utils.d.ts +0 -29
- package/dist/python-utils.js +0 -54
- package/dist/tools/tool-definitions.d.ts +0 -4919
- package/dist/tools/tool-definitions.js +0 -1065
- package/dist/tools/tool-handlers.d.ts +0 -47
- package/dist/tools/tool-handlers.js +0 -863
- package/dist/types/index.d.ts +0 -323
- package/dist/types/index.js +0 -28
- package/dist/utils/cache-manager.d.ts +0 -64
- package/dist/utils/cache-manager.js +0 -176
- package/dist/utils/errors.d.ts +0 -133
- package/dist/utils/errors.js +0 -256
- package/src/python/editor_compat.py +0 -181
- package/src/python-utils.ts +0 -57
- package/src/tools/tool-definitions.ts +0 -1081
- package/src/tools/tool-handlers.ts +0 -973
- package/src/types/index.ts +0 -414
- package/src/utils/cache-manager.ts +0 -213
- package/src/utils/errors.ts +0 -312
package/dist/utils/validation.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validation and sanitization utilities for Unreal Engine assets
|
|
3
3
|
*/
|
|
4
|
+
import { toRotTuple, toVec3Tuple } from './normalize.js';
|
|
4
5
|
/**
|
|
5
6
|
* Maximum path length allowed in Unreal Engine
|
|
6
7
|
*/
|
|
@@ -63,12 +64,22 @@ export function sanitizePath(path) {
|
|
|
63
64
|
if (!path || typeof path !== 'string') {
|
|
64
65
|
return '/Game';
|
|
65
66
|
}
|
|
67
|
+
// Normalize slashes
|
|
68
|
+
path = path.replace(/\\/g, '/');
|
|
66
69
|
// Ensure path starts with /
|
|
67
70
|
if (!path.startsWith('/')) {
|
|
68
71
|
path = `/${path}`;
|
|
69
72
|
}
|
|
70
73
|
// Split path into segments and sanitize each
|
|
71
|
-
|
|
74
|
+
let segments = path.split('/').filter(s => s.length > 0);
|
|
75
|
+
if (segments.length === 0) {
|
|
76
|
+
return '/Game';
|
|
77
|
+
}
|
|
78
|
+
// Ensure the first segment is a valid root (Game, Engine, Script, Temp)
|
|
79
|
+
const ROOTS = new Set(['Game', 'Engine', 'Script', 'Temp']);
|
|
80
|
+
if (!ROOTS.has(segments[0])) {
|
|
81
|
+
segments = ['Game', ...segments];
|
|
82
|
+
}
|
|
72
83
|
const sanitizedSegments = segments.map(segment => {
|
|
73
84
|
// Don't sanitize Game, Engine, or other root folders
|
|
74
85
|
if (['Game', 'Engine', 'Script', 'Temp'].includes(segment)) {
|
|
@@ -139,10 +150,14 @@ export function resolveSkeletalMeshPath(input) {
|
|
|
139
150
|
}
|
|
140
151
|
// Common skeleton to mesh mappings
|
|
141
152
|
const skeletonToMeshMap = {
|
|
142
|
-
'/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/
|
|
143
|
-
'/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/
|
|
144
|
-
'/Game/Mannequin/Character/Mesh/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/
|
|
145
|
-
'/Game/Characters/Mannequin_UE4/Meshes/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/
|
|
153
|
+
'/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
154
|
+
'/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
155
|
+
'/Game/Mannequin/Character/Mesh/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
156
|
+
'/Game/Characters/Mannequin_UE4/Meshes/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
|
|
157
|
+
'/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
158
|
+
'/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
|
|
159
|
+
'/Game/Characters/Mannequins/Skeletons/UE5_Manny_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
160
|
+
'/Game/Characters/Mannequins/Skeletons/UE5_Quinn_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
|
|
146
161
|
};
|
|
147
162
|
// Check if this is a known skeleton path
|
|
148
163
|
if (skeletonToMeshMap[input]) {
|
|
@@ -152,8 +167,16 @@ export function resolveSkeletalMeshPath(input) {
|
|
|
152
167
|
if (input.includes('_Skeleton')) {
|
|
153
168
|
// Try common replacements
|
|
154
169
|
let meshPath = input.replace('_Skeleton', '');
|
|
155
|
-
|
|
156
|
-
|
|
170
|
+
// Mapping for replacements
|
|
171
|
+
const replacements = {
|
|
172
|
+
'/SK_': '/SKM_',
|
|
173
|
+
'UE4_Mannequin': 'SKM_Manny',
|
|
174
|
+
'UE5_Mannequin': 'SKM_Manny',
|
|
175
|
+
'UE5_Manny': 'SKM_Manny',
|
|
176
|
+
'UE5_Quinn': 'SKM_Quinn'
|
|
177
|
+
};
|
|
178
|
+
// Apply all replacements using regex
|
|
179
|
+
meshPath = meshPath.replace(new RegExp(Object.keys(replacements).join('|'), 'g'), match => replacements[match]);
|
|
157
180
|
return meshPath;
|
|
158
181
|
}
|
|
159
182
|
// If it starts with SK_ (skeleton prefix), try SKM_ (skeletal mesh prefix)
|
|
@@ -170,4 +193,44 @@ export function resolveSkeletalMeshPath(input) {
|
|
|
170
193
|
export async function concurrencyDelay(ms = 100) {
|
|
171
194
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
172
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Ensure the provided value is a finite number within optional bounds.
|
|
198
|
+
* @throws if the value is not a finite number or violates bounds
|
|
199
|
+
*/
|
|
200
|
+
export function validateNumber(value, label, { min, max, allowZero = true } = {}) {
|
|
201
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
202
|
+
throw new Error(`Invalid ${label}: expected a finite number`);
|
|
203
|
+
}
|
|
204
|
+
if (!allowZero && value === 0) {
|
|
205
|
+
throw new Error(`Invalid ${label}: zero is not allowed`);
|
|
206
|
+
}
|
|
207
|
+
if (typeof min === 'number' && value < min) {
|
|
208
|
+
throw new Error(`Invalid ${label}: must be >= ${min}`);
|
|
209
|
+
}
|
|
210
|
+
if (typeof max === 'number' && value > max) {
|
|
211
|
+
throw new Error(`Invalid ${label}: must be <= ${max}`);
|
|
212
|
+
}
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Validate an array (tuple) of finite numbers, preserving the original shape.
|
|
217
|
+
* @throws if the tuple has the wrong length or contains invalid values
|
|
218
|
+
*/
|
|
219
|
+
export function ensureVector3(value, label) {
|
|
220
|
+
const tuple = toVec3Tuple(value);
|
|
221
|
+
if (!tuple) {
|
|
222
|
+
throw new Error(`Invalid ${label}: expected an object with x,y,z or an array of 3 numbers`);
|
|
223
|
+
}
|
|
224
|
+
return tuple;
|
|
225
|
+
}
|
|
226
|
+
export function ensureColorRGB(value, label) {
|
|
227
|
+
return ensureVector3(value, label);
|
|
228
|
+
}
|
|
229
|
+
export function ensureRotation(value, label) {
|
|
230
|
+
const tuple = toRotTuple(value);
|
|
231
|
+
if (!tuple) {
|
|
232
|
+
throw new Error(`Invalid ${label}: expected an object with pitch,yaw,roll or an array of 3 numbers`);
|
|
233
|
+
}
|
|
234
|
+
return tuple;
|
|
235
|
+
}
|
|
173
236
|
//# sourceMappingURL=validation.js.map
|
package/mcp-config-example.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"args": ["PATH_TO/unreal-engine-mcp-server/dist/cli.js"],
|
|
6
6
|
"env": {
|
|
7
7
|
"UE_HOST": "127.0.0.1",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"UE_RC_HTTP_PORT": "30010",
|
|
9
|
+
"UE_RC_WS_PORT": "30020",
|
|
10
10
|
"UE_PROJECT_PATH": "C:/Users/YourName/Documents/Unreal Projects/YourProject"
|
|
11
11
|
}
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unreal-engine-mcp-server",
|
|
3
|
-
"version": "0.3
|
|
4
|
-
"
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"mcpName": "io.github.ChiR24/unreal-engine-mcp",
|
|
5
|
+
"description": "Production-ready MCP server for Unreal Engine integration with 13 tools",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "dist/index.js",
|
|
7
8
|
"types": "dist/index.d.ts",
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
"lint": "eslint . --ext .ts",
|
|
16
17
|
"lint:fix": "eslint . --ext .ts --fix",
|
|
17
18
|
"clean": "rimraf dist",
|
|
18
|
-
"prepare": "npm run build"
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"test:tools": "node tests/run-unreal-tool-tests.mjs"
|
|
19
21
|
},
|
|
20
22
|
"engines": {
|
|
21
23
|
"node": ">=18"
|
|
@@ -32,22 +34,21 @@
|
|
|
32
34
|
],
|
|
33
35
|
"author": "Unreal Engine MCP Team",
|
|
34
36
|
"license": "MIT",
|
|
35
|
-
"mcpName": "io.github.ChiR24/unreal-engine-mcp",
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
38
|
-
"ajv": "^8.
|
|
39
|
-
"axios": "^1.
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.18.2",
|
|
39
|
+
"ajv": "^8.17.1",
|
|
40
|
+
"axios": "^1.12.2",
|
|
40
41
|
"dotenv": "^16.4.5",
|
|
42
|
+
"json5": "^2.2.3",
|
|
41
43
|
"ws": "^8.18.0",
|
|
42
|
-
"yargs": "^17.7.2"
|
|
43
|
-
"zod": "^3.22.4"
|
|
44
|
+
"yargs": "^17.7.2"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"@types/json5": "^0.0.30",
|
|
46
48
|
"@types/node": "^20.12.7",
|
|
47
49
|
"@types/ws": "^8.5.10",
|
|
48
50
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
49
51
|
"@typescript-eslint/parser": "^8.43.0",
|
|
50
|
-
"cross-env": "^10.0.0",
|
|
51
52
|
"eslint": "^8.57.0",
|
|
52
53
|
"rimraf": "^6.0.1",
|
|
53
54
|
"ts-node": "^10.9.2",
|
package/server.json
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
|
|
3
3
|
"name": "io.github.ChiR24/unreal-engine-mcp",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "0.3
|
|
4
|
+
"description": "MCP server for Unreal Engine 5 with 13 tools for game development automation.",
|
|
5
|
+
"version": "0.4.3",
|
|
6
6
|
"packages": [
|
|
7
7
|
{
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"registryType": "npm",
|
|
9
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
10
10
|
"identifier": "unreal-engine-mcp-server",
|
|
11
|
-
"version": "0.3
|
|
11
|
+
"version": "0.4.3",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
},
|
|
15
|
-
"
|
|
15
|
+
"environmentVariables": [
|
|
16
16
|
{
|
|
17
17
|
"name": "UE_HOST",
|
|
18
|
-
"description": "Unreal Engine host address",
|
|
19
|
-
"
|
|
18
|
+
"description": "Unreal Engine host address (default: 127.0.0.1)",
|
|
19
|
+
"isRequired": false,
|
|
20
20
|
"value": "127.0.0.1"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"name": "UE_RC_HTTP_PORT",
|
|
24
|
-
"description": "Remote Control HTTP port",
|
|
25
|
-
"
|
|
24
|
+
"description": "Remote Control HTTP port (default: 30010)",
|
|
25
|
+
"isRequired": false,
|
|
26
26
|
"value": "30010"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"name": "UE_RC_WS_PORT",
|
|
30
|
-
"description": "Remote Control WebSocket port",
|
|
31
|
-
"
|
|
30
|
+
"description": "Remote Control WebSocket port (default: 30020)",
|
|
31
|
+
"isRequired": false,
|
|
32
32
|
"value": "30020"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "LOG_LEVEL",
|
|
36
|
+
"description": "Logging level: debug, info, warn, error (default: info)",
|
|
37
|
+
"isRequired": false,
|
|
38
|
+
"value": "info"
|
|
33
39
|
}
|
|
34
40
|
]
|
|
35
41
|
}
|
|
@@ -40,12 +46,26 @@
|
|
|
40
46
|
"modelcontextprotocol",
|
|
41
47
|
"unreal-engine",
|
|
42
48
|
"ue5",
|
|
49
|
+
"ue6",
|
|
43
50
|
"game-development",
|
|
44
51
|
"remote-control",
|
|
45
52
|
"automation",
|
|
46
53
|
"3d",
|
|
47
54
|
"gamedev",
|
|
48
|
-
"cinematics"
|
|
55
|
+
"cinematics",
|
|
56
|
+
"level-design",
|
|
57
|
+
"animation",
|
|
58
|
+
"physics",
|
|
59
|
+
"niagara",
|
|
60
|
+
"vfx",
|
|
61
|
+
"blueprint",
|
|
62
|
+
"lighting",
|
|
63
|
+
"rendering",
|
|
64
|
+
"play-in-editor",
|
|
65
|
+
"asset-management",
|
|
66
|
+
"actor-control",
|
|
67
|
+
"sequencer",
|
|
68
|
+
"production"
|
|
49
69
|
],
|
|
50
70
|
"author": "ChiR24",
|
|
51
71
|
"homepage": "https://github.com/ChiR24/Unreal_mcp",
|
|
@@ -54,6 +74,9 @@
|
|
|
54
74
|
"url": "https://github.com/ChiR24/Unreal_mcp.git",
|
|
55
75
|
"source": "github"
|
|
56
76
|
},
|
|
77
|
+
"bugs": {
|
|
78
|
+
"url": "https://github.com/ChiR24/Unreal_mcp/issues"
|
|
79
|
+
},
|
|
57
80
|
"engines": {
|
|
58
81
|
"node": ">=18"
|
|
59
82
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { BlueprintTools } from './tools/blueprint.js';
|
|
|
17
17
|
import { LevelTools } from './tools/level.js';
|
|
18
18
|
import { LightingTools } from './tools/lighting.js';
|
|
19
19
|
import { LandscapeTools } from './tools/landscape.js';
|
|
20
|
+
import { BuildEnvironmentAdvanced } from './tools/build_environment_advanced.js';
|
|
20
21
|
import { FoliageTools } from './tools/foliage.js';
|
|
21
22
|
import { DebugVisualizationTools } from './tools/debug.js';
|
|
22
23
|
import { PerformanceTools } from './tools/performance.js';
|
|
@@ -27,8 +28,6 @@ import { SequenceTools } from './tools/sequence.js';
|
|
|
27
28
|
import { IntrospectionTools } from './tools/introspection.js';
|
|
28
29
|
import { VisualTools } from './tools/visual.js';
|
|
29
30
|
import { EngineTools } from './tools/engine.js';
|
|
30
|
-
import { toolDefinitions } from './tools/tool-definitions.js';
|
|
31
|
-
import { handleToolCall } from './tools/tool-handlers.js';
|
|
32
31
|
import { consolidatedToolDefinitions } from './tools/consolidated-tool-definitions.js';
|
|
33
32
|
import { handleConsolidatedToolCall } from './tools/consolidated-tool-handlers.js';
|
|
34
33
|
import { prompts } from './prompts/index.js';
|
|
@@ -44,6 +43,7 @@ import { responseValidator } from './utils/response-validator.js';
|
|
|
44
43
|
import { ErrorHandler } from './utils/error-handler.js';
|
|
45
44
|
import { routeStdoutLogsToStderr } from './utils/stdio-redirect.js';
|
|
46
45
|
import { cleanObject } from './utils/safe-json.js';
|
|
46
|
+
import { createElicitationHelper } from './utils/elicitation.js';
|
|
47
47
|
|
|
48
48
|
const log = new Logger('UE-MCP');
|
|
49
49
|
|
|
@@ -81,14 +81,13 @@ let lastHealthSuccessAt = 0;
|
|
|
81
81
|
|
|
82
82
|
// Configuration
|
|
83
83
|
const CONFIG = {
|
|
84
|
-
//
|
|
85
|
-
USE_CONSOLIDATED_TOOLS: process.env.USE_CONSOLIDATED_TOOLS !== 'false',
|
|
84
|
+
// Tooling: use consolidated tools only (13 tools)
|
|
86
85
|
// Connection retry settings
|
|
87
86
|
MAX_RETRY_ATTEMPTS: 3,
|
|
88
87
|
RETRY_DELAY_MS: 2000,
|
|
89
88
|
// Server info
|
|
90
89
|
SERVER_NAME: 'unreal-engine-mcp',
|
|
91
|
-
SERVER_VERSION: '0.3
|
|
90
|
+
SERVER_VERSION: '0.4.3',
|
|
92
91
|
// Monitoring
|
|
93
92
|
HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
|
|
94
93
|
};
|
|
@@ -149,9 +148,9 @@ export async function createServer() {
|
|
|
149
148
|
// Disable auto-reconnect loops; connect only on-demand
|
|
150
149
|
bridge.setAutoReconnectEnabled(false);
|
|
151
150
|
|
|
152
|
-
|
|
151
|
+
// Initialize response validation with schemas
|
|
153
152
|
log.debug('Initializing response validation...');
|
|
154
|
-
const toolDefs =
|
|
153
|
+
const toolDefs = consolidatedToolDefinitions;
|
|
155
154
|
toolDefs.forEach((tool: any) => {
|
|
156
155
|
if (tool.outputSchema) {
|
|
157
156
|
responseValidator.registerSchema(tool.name, tool.outputSchema);
|
|
@@ -227,6 +226,7 @@ export async function createServer() {
|
|
|
227
226
|
const lightingTools = new LightingTools(bridge);
|
|
228
227
|
const landscapeTools = new LandscapeTools(bridge);
|
|
229
228
|
const foliageTools = new FoliageTools(bridge);
|
|
229
|
+
const buildEnvAdvanced = new BuildEnvironmentAdvanced(bridge);
|
|
230
230
|
const debugTools = new DebugVisualizationTools(bridge);
|
|
231
231
|
const performanceTools = new PerformanceTools(bridge);
|
|
232
232
|
const audioTools = new AudioTools(bridge);
|
|
@@ -246,12 +246,39 @@ export async function createServer() {
|
|
|
246
246
|
capabilities: {
|
|
247
247
|
resources: {},
|
|
248
248
|
tools: {},
|
|
249
|
-
prompts: {
|
|
250
|
-
|
|
249
|
+
prompts: {
|
|
250
|
+
listChanged: false
|
|
251
|
+
},
|
|
252
|
+
logging: {},
|
|
253
|
+
elicitation: {}
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
);
|
|
254
257
|
|
|
258
|
+
// Optional elicitation helper – used only if client supports it.
|
|
259
|
+
const elicitation = createElicitationHelper(server as any, log);
|
|
260
|
+
const defaultElicitationTimeoutMs = elicitation.getDefaultTimeoutMs();
|
|
261
|
+
|
|
262
|
+
const createNotConnectedResponse = (toolName: string) => {
|
|
263
|
+
const payload = {
|
|
264
|
+
success: false,
|
|
265
|
+
error: 'UE_NOT_CONNECTED',
|
|
266
|
+
message: 'Unreal Engine is not connected (after 3 attempts). Please open UE and try again.',
|
|
267
|
+
retriable: false,
|
|
268
|
+
scope: `tool-call/${toolName}`
|
|
269
|
+
} as const;
|
|
270
|
+
|
|
271
|
+
return responseValidator.wrapResponse(toolName, {
|
|
272
|
+
...payload,
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: 'text',
|
|
276
|
+
text: JSON.stringify(payload, null, 2)
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
|
|
255
282
|
// Handle resource listing
|
|
256
283
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
257
284
|
return {
|
|
@@ -433,34 +460,28 @@ export async function createServer() {
|
|
|
433
460
|
throw new Error(`Unknown resource: ${uri}`);
|
|
434
461
|
});
|
|
435
462
|
|
|
436
|
-
|
|
463
|
+
// Handle tool listing - consolidated tools only
|
|
437
464
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
438
|
-
log.info(
|
|
465
|
+
log.info('Serving consolidated tools');
|
|
439
466
|
return {
|
|
440
|
-
tools:
|
|
467
|
+
tools: consolidatedToolDefinitions
|
|
441
468
|
};
|
|
442
469
|
});
|
|
443
470
|
|
|
444
|
-
// Handle tool calls -
|
|
471
|
+
// Handle tool calls - consolidated tools only (13)
|
|
445
472
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
446
|
-
const { name
|
|
473
|
+
const { name } = request.params;
|
|
474
|
+
let args: any = request.params.arguments || {};
|
|
447
475
|
const startTime = Date.now();
|
|
448
476
|
|
|
449
477
|
// Ensure connection only when needed, with 3 attempts
|
|
450
478
|
const connected = await ensureConnectedOnDemand();
|
|
451
479
|
if (!connected) {
|
|
452
|
-
const notConnected = {
|
|
453
|
-
content: [{ type: 'text', text: 'Unreal Engine is not connected (after 3 attempts). Please open UE and try again.' }],
|
|
454
|
-
success: false,
|
|
455
|
-
error: 'UE_NOT_CONNECTED',
|
|
456
|
-
retriable: false,
|
|
457
|
-
scope: `tool-call/${name}`
|
|
458
|
-
} as any;
|
|
459
480
|
trackPerformance(startTime, false);
|
|
460
|
-
return
|
|
481
|
+
return createNotConnectedResponse(name);
|
|
461
482
|
}
|
|
462
483
|
|
|
463
|
-
|
|
484
|
+
// Create tools object for handler
|
|
464
485
|
const tools = {
|
|
465
486
|
actorTools,
|
|
466
487
|
assetTools,
|
|
@@ -474,6 +495,7 @@ export async function createServer() {
|
|
|
474
495
|
lightingTools,
|
|
475
496
|
landscapeTools,
|
|
476
497
|
foliageTools,
|
|
498
|
+
buildEnvAdvanced,
|
|
477
499
|
debugTools,
|
|
478
500
|
performanceTools,
|
|
479
501
|
audioTools,
|
|
@@ -483,19 +505,81 @@ export async function createServer() {
|
|
|
483
505
|
introspectionTools,
|
|
484
506
|
visualTools,
|
|
485
507
|
engineTools,
|
|
508
|
+
// Elicitation (client-optional)
|
|
509
|
+
elicit: elicitation.elicit,
|
|
510
|
+
supportsElicitation: elicitation.supports,
|
|
511
|
+
elicitationTimeoutMs: defaultElicitationTimeoutMs,
|
|
512
|
+
// Resources for listing and info
|
|
513
|
+
assetResources,
|
|
514
|
+
actorResources,
|
|
515
|
+
levelResources,
|
|
486
516
|
bridge
|
|
487
517
|
};
|
|
488
518
|
|
|
489
|
-
|
|
519
|
+
// Execute consolidated tool handler
|
|
490
520
|
try {
|
|
491
521
|
log.debug(`Executing tool: ${name}`);
|
|
492
522
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
523
|
+
// Opportunistic generic elicitation for missing primitive required fields
|
|
524
|
+
try {
|
|
525
|
+
const toolDef: any = (consolidatedToolDefinitions as any[]).find(t => t.name === name);
|
|
526
|
+
const inputSchema: any = toolDef?.inputSchema;
|
|
527
|
+
const elicitFn: any = (tools as any).elicit;
|
|
528
|
+
if (inputSchema && typeof elicitFn === 'function') {
|
|
529
|
+
const props = inputSchema.properties || {};
|
|
530
|
+
const required: string[] = Array.isArray(inputSchema.required) ? inputSchema.required : [];
|
|
531
|
+
const missing = required.filter((k: string) => {
|
|
532
|
+
const v = (args as any)[k];
|
|
533
|
+
if (v === undefined || v === null) return true;
|
|
534
|
+
if (typeof v === 'string' && v.trim() === '') return true;
|
|
535
|
+
return false;
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Build a flat primitive-only schema subset per MCP Elicitation rules
|
|
539
|
+
const primitiveProps: any = {};
|
|
540
|
+
for (const k of missing) {
|
|
541
|
+
const p = props[k];
|
|
542
|
+
if (!p || typeof p !== 'object') continue;
|
|
543
|
+
const t = (p.type || '').toString();
|
|
544
|
+
const isEnum = Array.isArray(p.enum);
|
|
545
|
+
if (t === 'string' || t === 'number' || t === 'integer' || t === 'boolean' || isEnum) {
|
|
546
|
+
primitiveProps[k] = {
|
|
547
|
+
type: t || (isEnum ? 'string' : undefined),
|
|
548
|
+
title: p.title,
|
|
549
|
+
description: p.description,
|
|
550
|
+
enum: p.enum,
|
|
551
|
+
enumNames: p.enumNames,
|
|
552
|
+
minimum: p.minimum,
|
|
553
|
+
maximum: p.maximum,
|
|
554
|
+
minLength: p.minLength,
|
|
555
|
+
maxLength: p.maxLength,
|
|
556
|
+
pattern: p.pattern,
|
|
557
|
+
format: p.format,
|
|
558
|
+
default: p.default
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (Object.keys(primitiveProps).length > 0) {
|
|
564
|
+
const elicitOptions: any = { fallback: async () => ({ ok: false, error: 'missing-params' }) };
|
|
565
|
+
if (typeof (tools as any).elicitationTimeoutMs === 'number' && Number.isFinite((tools as any).elicitationTimeoutMs)) {
|
|
566
|
+
elicitOptions.timeoutMs = (tools as any).elicitationTimeoutMs;
|
|
567
|
+
}
|
|
568
|
+
const elicitRes = await elicitFn(
|
|
569
|
+
`Provide missing parameters for ${name}`,
|
|
570
|
+
{ type: 'object', properties: primitiveProps, required: Object.keys(primitiveProps) },
|
|
571
|
+
elicitOptions
|
|
572
|
+
);
|
|
573
|
+
if (elicitRes && elicitRes.ok && elicitRes.value) {
|
|
574
|
+
args = { ...args, ...elicitRes.value };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} catch (e) {
|
|
579
|
+
log.debug('Generic elicitation prefill skipped', { err: (e as any)?.message || String(e) });
|
|
498
580
|
}
|
|
581
|
+
|
|
582
|
+
let result = await handleConsolidatedToolCall(name, args, tools);
|
|
499
583
|
|
|
500
584
|
log.debug(`Tool ${name} returned result`);
|
|
501
585
|
|
|
@@ -531,14 +615,25 @@ export async function createServer() {
|
|
|
531
615
|
});
|
|
532
616
|
if (metrics.recentErrors.length > 20) metrics.recentErrors.splice(0, metrics.recentErrors.length - 20);
|
|
533
617
|
} catch {}
|
|
534
|
-
|
|
535
|
-
|
|
618
|
+
|
|
619
|
+
const sanitizedError = cleanObject(errorResponse);
|
|
620
|
+
let errorText = '';
|
|
621
|
+
try {
|
|
622
|
+
errorText = JSON.stringify(sanitizedError, null, 2);
|
|
623
|
+
} catch {
|
|
624
|
+
errorText = sanitizedError.message || errorResponse.message || `Failed to execute ${name}`;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const wrappedError = {
|
|
628
|
+
...sanitizedError,
|
|
629
|
+
isError: true,
|
|
536
630
|
content: [{
|
|
537
631
|
type: 'text',
|
|
538
|
-
text:
|
|
539
|
-
}]
|
|
540
|
-
...errorResponse
|
|
632
|
+
text: errorText
|
|
633
|
+
}]
|
|
541
634
|
};
|
|
635
|
+
|
|
636
|
+
return responseValidator.wrapResponse(name, wrappedError);
|
|
542
637
|
}
|
|
543
638
|
});
|
|
544
639
|
|
|
@@ -548,11 +643,18 @@ export async function createServer() {
|
|
|
548
643
|
prompts: prompts.map(p => ({
|
|
549
644
|
name: p.name,
|
|
550
645
|
description: p.description,
|
|
551
|
-
arguments: Object.entries(p.arguments || {}).map(([name, schema]) =>
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
646
|
+
arguments: Object.entries(p.arguments || {}).map(([name, schema]) => {
|
|
647
|
+
const meta: Record<string, unknown> = {};
|
|
648
|
+
if (schema.type) meta.type = schema.type;
|
|
649
|
+
if (schema.enum) meta.enum = schema.enum;
|
|
650
|
+
if (schema.default !== undefined) meta.default = schema.default;
|
|
651
|
+
return {
|
|
652
|
+
name,
|
|
653
|
+
description: schema.description,
|
|
654
|
+
required: schema.required ?? false,
|
|
655
|
+
...(Object.keys(meta).length ? { _meta: meta } : {})
|
|
656
|
+
};
|
|
657
|
+
})
|
|
556
658
|
}))
|
|
557
659
|
};
|
|
558
660
|
});
|
|
@@ -563,18 +665,12 @@ export async function createServer() {
|
|
|
563
665
|
if (!prompt) {
|
|
564
666
|
throw new Error(`Unknown prompt: ${request.params.name}`);
|
|
565
667
|
}
|
|
566
|
-
|
|
567
|
-
|
|
668
|
+
|
|
669
|
+
const args = (request.params.arguments || {}) as Record<string, unknown>;
|
|
670
|
+
const messages = prompt.build(args);
|
|
568
671
|
return {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
role: 'user',
|
|
572
|
-
content: {
|
|
573
|
-
type: 'text',
|
|
574
|
-
text: `Set up three-point lighting with ${request.params.arguments?.intensity || 'medium'} intensity`
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
]
|
|
672
|
+
description: prompt.description,
|
|
673
|
+
messages
|
|
578
674
|
};
|
|
579
675
|
});
|
|
580
676
|
|