unreal-engine-mcp-server 0.4.0 → 0.4.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/.env.production +1 -1
- package/.github/copilot-instructions.md +45 -0
- package/.github/workflows/publish-mcp.yml +3 -2
- package/README.md +21 -5
- package/dist/index.js +124 -31
- 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.js +46 -62
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +29 -54
- 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 +52 -44
- 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 +190 -45
- package/dist/tools/consolidated-tool-definitions.js +78 -252
- package/dist/tools/consolidated-tool-handlers.js +506 -133
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +167 -31
- 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 +97 -23
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +125 -22
- 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.js +28 -2
- 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 +10 -9
- package/server.json +37 -14
- package/src/index.ts +130 -33
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +48 -51
- package/src/resources/levels.ts +35 -45
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +53 -43
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +266 -64
- package/src/tools/consolidated-tool-definitions.ts +90 -264
- package/src/tools/consolidated-tool-handlers.ts +630 -121
- package/src/tools/debug.ts +176 -33
- 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 +102 -24
- package/src/tools/sequence.ts +136 -28
- 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 +25 -2
- 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/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/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.4.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.4.4",
|
|
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.18.
|
|
38
|
-
"ajv": "^8.
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.18.2",
|
|
39
|
+
"ajv": "^8.17.1",
|
|
39
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.4.
|
|
4
|
+
"description": "MCP server for Unreal Engine 5 with 13 tools for game development automation.",
|
|
5
|
+
"version": "0.4.4",
|
|
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.4.
|
|
11
|
+
"version": "0.4.4",
|
|
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
|
@@ -43,6 +43,7 @@ import { responseValidator } from './utils/response-validator.js';
|
|
|
43
43
|
import { ErrorHandler } from './utils/error-handler.js';
|
|
44
44
|
import { routeStdoutLogsToStderr } from './utils/stdio-redirect.js';
|
|
45
45
|
import { cleanObject } from './utils/safe-json.js';
|
|
46
|
+
import { createElicitationHelper } from './utils/elicitation.js';
|
|
46
47
|
|
|
47
48
|
const log = new Logger('UE-MCP');
|
|
48
49
|
|
|
@@ -86,7 +87,7 @@ const CONFIG = {
|
|
|
86
87
|
RETRY_DELAY_MS: 2000,
|
|
87
88
|
// Server info
|
|
88
89
|
SERVER_NAME: 'unreal-engine-mcp',
|
|
89
|
-
SERVER_VERSION: '0.4.
|
|
90
|
+
SERVER_VERSION: '0.4.4',
|
|
90
91
|
// Monitoring
|
|
91
92
|
HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
|
|
92
93
|
};
|
|
@@ -245,12 +246,39 @@ export async function createServer() {
|
|
|
245
246
|
capabilities: {
|
|
246
247
|
resources: {},
|
|
247
248
|
tools: {},
|
|
248
|
-
prompts: {
|
|
249
|
-
|
|
249
|
+
prompts: {
|
|
250
|
+
listChanged: false
|
|
251
|
+
},
|
|
252
|
+
logging: {},
|
|
253
|
+
elicitation: {}
|
|
250
254
|
}
|
|
251
255
|
}
|
|
252
256
|
);
|
|
253
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
|
+
|
|
254
282
|
// Handle resource listing
|
|
255
283
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
256
284
|
return {
|
|
@@ -442,21 +470,15 @@ export async function createServer() {
|
|
|
442
470
|
|
|
443
471
|
// Handle tool calls - consolidated tools only (13)
|
|
444
472
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
445
|
-
const { name
|
|
473
|
+
const { name } = request.params;
|
|
474
|
+
let args: any = request.params.arguments || {};
|
|
446
475
|
const startTime = Date.now();
|
|
447
476
|
|
|
448
477
|
// Ensure connection only when needed, with 3 attempts
|
|
449
478
|
const connected = await ensureConnectedOnDemand();
|
|
450
479
|
if (!connected) {
|
|
451
|
-
const notConnected = {
|
|
452
|
-
content: [{ type: 'text', text: 'Unreal Engine is not connected (after 3 attempts). Please open UE and try again.' }],
|
|
453
|
-
success: false,
|
|
454
|
-
error: 'UE_NOT_CONNECTED',
|
|
455
|
-
retriable: false,
|
|
456
|
-
scope: `tool-call/${name}`
|
|
457
|
-
} as any;
|
|
458
480
|
trackPerformance(startTime, false);
|
|
459
|
-
return
|
|
481
|
+
return createNotConnectedResponse(name);
|
|
460
482
|
}
|
|
461
483
|
|
|
462
484
|
// Create tools object for handler
|
|
@@ -483,6 +505,10 @@ 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,
|
|
486
512
|
// Resources for listing and info
|
|
487
513
|
assetResources,
|
|
488
514
|
actorResources,
|
|
@@ -494,6 +520,65 @@ export async function createServer() {
|
|
|
494
520
|
try {
|
|
495
521
|
log.debug(`Executing tool: ${name}`);
|
|
496
522
|
|
|
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) });
|
|
580
|
+
}
|
|
581
|
+
|
|
497
582
|
let result = await handleConsolidatedToolCall(name, args, tools);
|
|
498
583
|
|
|
499
584
|
log.debug(`Tool ${name} returned result`);
|
|
@@ -530,14 +615,25 @@ export async function createServer() {
|
|
|
530
615
|
});
|
|
531
616
|
if (metrics.recentErrors.length > 20) metrics.recentErrors.splice(0, metrics.recentErrors.length - 20);
|
|
532
617
|
} catch {}
|
|
533
|
-
|
|
534
|
-
|
|
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,
|
|
535
630
|
content: [{
|
|
536
631
|
type: 'text',
|
|
537
|
-
text:
|
|
538
|
-
}]
|
|
539
|
-
...errorResponse
|
|
632
|
+
text: errorText
|
|
633
|
+
}]
|
|
540
634
|
};
|
|
635
|
+
|
|
636
|
+
return responseValidator.wrapResponse(name, wrappedError);
|
|
541
637
|
}
|
|
542
638
|
});
|
|
543
639
|
|
|
@@ -547,11 +643,18 @@ export async function createServer() {
|
|
|
547
643
|
prompts: prompts.map(p => ({
|
|
548
644
|
name: p.name,
|
|
549
645
|
description: p.description,
|
|
550
|
-
arguments: Object.entries(p.arguments || {}).map(([name, schema]) =>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
+
})
|
|
555
658
|
}))
|
|
556
659
|
};
|
|
557
660
|
});
|
|
@@ -562,18 +665,12 @@ export async function createServer() {
|
|
|
562
665
|
if (!prompt) {
|
|
563
666
|
throw new Error(`Unknown prompt: ${request.params.name}`);
|
|
564
667
|
}
|
|
565
|
-
|
|
566
|
-
|
|
668
|
+
|
|
669
|
+
const args = (request.params.arguments || {}) as Record<string, unknown>;
|
|
670
|
+
const messages = prompt.build(args);
|
|
567
671
|
return {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
role: 'user',
|
|
571
|
-
content: {
|
|
572
|
-
type: 'text',
|
|
573
|
-
text: `Set up three-point lighting with ${request.params.arguments?.intensity || 'medium'} intensity`
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
]
|
|
672
|
+
description: prompt.description,
|
|
673
|
+
messages
|
|
577
674
|
};
|
|
578
675
|
});
|
|
579
676
|
|