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.
Files changed (144) hide show
  1. package/.env.production +1 -1
  2. package/.github/copilot-instructions.md +45 -0
  3. package/.github/workflows/publish-mcp.yml +1 -1
  4. package/README.md +22 -7
  5. package/dist/index.js +137 -46
  6. package/dist/prompts/index.d.ts +10 -3
  7. package/dist/prompts/index.js +186 -7
  8. package/dist/resources/actors.d.ts +19 -1
  9. package/dist/resources/actors.js +55 -64
  10. package/dist/resources/assets.d.ts +3 -2
  11. package/dist/resources/assets.js +117 -109
  12. package/dist/resources/levels.d.ts +21 -3
  13. package/dist/resources/levels.js +31 -56
  14. package/dist/tools/actors.d.ts +3 -14
  15. package/dist/tools/actors.js +246 -302
  16. package/dist/tools/animation.d.ts +57 -102
  17. package/dist/tools/animation.js +429 -450
  18. package/dist/tools/assets.d.ts +13 -2
  19. package/dist/tools/assets.js +58 -46
  20. package/dist/tools/audio.d.ts +22 -13
  21. package/dist/tools/audio.js +467 -121
  22. package/dist/tools/blueprint.d.ts +32 -13
  23. package/dist/tools/blueprint.js +699 -448
  24. package/dist/tools/build_environment_advanced.d.ts +0 -1
  25. package/dist/tools/build_environment_advanced.js +236 -87
  26. package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
  27. package/dist/tools/consolidated-tool-definitions.js +124 -255
  28. package/dist/tools/consolidated-tool-handlers.js +749 -766
  29. package/dist/tools/debug.d.ts +72 -10
  30. package/dist/tools/debug.js +170 -36
  31. package/dist/tools/editor.d.ts +9 -2
  32. package/dist/tools/editor.js +30 -44
  33. package/dist/tools/foliage.d.ts +34 -15
  34. package/dist/tools/foliage.js +97 -107
  35. package/dist/tools/introspection.js +19 -21
  36. package/dist/tools/landscape.d.ts +1 -2
  37. package/dist/tools/landscape.js +311 -168
  38. package/dist/tools/level.d.ts +3 -28
  39. package/dist/tools/level.js +642 -192
  40. package/dist/tools/lighting.d.ts +14 -3
  41. package/dist/tools/lighting.js +236 -123
  42. package/dist/tools/materials.d.ts +25 -7
  43. package/dist/tools/materials.js +102 -79
  44. package/dist/tools/niagara.d.ts +10 -12
  45. package/dist/tools/niagara.js +74 -94
  46. package/dist/tools/performance.d.ts +12 -4
  47. package/dist/tools/performance.js +38 -79
  48. package/dist/tools/physics.d.ts +34 -10
  49. package/dist/tools/physics.js +364 -292
  50. package/dist/tools/rc.js +98 -24
  51. package/dist/tools/sequence.d.ts +1 -0
  52. package/dist/tools/sequence.js +146 -24
  53. package/dist/tools/ui.d.ts +31 -4
  54. package/dist/tools/ui.js +83 -66
  55. package/dist/tools/visual.d.ts +11 -0
  56. package/dist/tools/visual.js +245 -30
  57. package/dist/types/tool-types.d.ts +0 -6
  58. package/dist/types/tool-types.js +1 -8
  59. package/dist/unreal-bridge.d.ts +32 -2
  60. package/dist/unreal-bridge.js +621 -127
  61. package/dist/utils/elicitation.d.ts +57 -0
  62. package/dist/utils/elicitation.js +104 -0
  63. package/dist/utils/error-handler.d.ts +0 -33
  64. package/dist/utils/error-handler.js +4 -111
  65. package/dist/utils/http.d.ts +2 -22
  66. package/dist/utils/http.js +12 -75
  67. package/dist/utils/normalize.d.ts +4 -4
  68. package/dist/utils/normalize.js +15 -7
  69. package/dist/utils/python-output.d.ts +18 -0
  70. package/dist/utils/python-output.js +290 -0
  71. package/dist/utils/python.d.ts +2 -0
  72. package/dist/utils/python.js +4 -0
  73. package/dist/utils/response-validator.d.ts +6 -1
  74. package/dist/utils/response-validator.js +66 -13
  75. package/dist/utils/result-helpers.d.ts +27 -0
  76. package/dist/utils/result-helpers.js +147 -0
  77. package/dist/utils/safe-json.d.ts +0 -2
  78. package/dist/utils/safe-json.js +0 -43
  79. package/dist/utils/validation.d.ts +16 -0
  80. package/dist/utils/validation.js +70 -7
  81. package/mcp-config-example.json +2 -2
  82. package/package.json +11 -10
  83. package/server.json +37 -14
  84. package/src/index.ts +146 -50
  85. package/src/prompts/index.ts +211 -13
  86. package/src/resources/actors.ts +59 -44
  87. package/src/resources/assets.ts +123 -102
  88. package/src/resources/levels.ts +37 -47
  89. package/src/tools/actors.ts +269 -313
  90. package/src/tools/animation.ts +556 -539
  91. package/src/tools/assets.ts +59 -45
  92. package/src/tools/audio.ts +507 -113
  93. package/src/tools/blueprint.ts +778 -462
  94. package/src/tools/build_environment_advanced.ts +312 -106
  95. package/src/tools/consolidated-tool-definitions.ts +136 -267
  96. package/src/tools/consolidated-tool-handlers.ts +871 -795
  97. package/src/tools/debug.ts +179 -38
  98. package/src/tools/editor.ts +35 -37
  99. package/src/tools/foliage.ts +110 -104
  100. package/src/tools/introspection.ts +24 -22
  101. package/src/tools/landscape.ts +334 -181
  102. package/src/tools/level.ts +683 -182
  103. package/src/tools/lighting.ts +244 -123
  104. package/src/tools/materials.ts +114 -83
  105. package/src/tools/niagara.ts +87 -81
  106. package/src/tools/performance.ts +49 -88
  107. package/src/tools/physics.ts +393 -299
  108. package/src/tools/rc.ts +103 -25
  109. package/src/tools/sequence.ts +157 -30
  110. package/src/tools/ui.ts +101 -70
  111. package/src/tools/visual.ts +250 -29
  112. package/src/types/tool-types.ts +0 -9
  113. package/src/unreal-bridge.ts +658 -140
  114. package/src/utils/elicitation.ts +129 -0
  115. package/src/utils/error-handler.ts +4 -159
  116. package/src/utils/http.ts +16 -115
  117. package/src/utils/normalize.ts +20 -10
  118. package/src/utils/python-output.ts +351 -0
  119. package/src/utils/python.ts +3 -0
  120. package/src/utils/response-validator.ts +68 -17
  121. package/src/utils/result-helpers.ts +193 -0
  122. package/src/utils/safe-json.ts +0 -50
  123. package/src/utils/validation.ts +94 -7
  124. package/tests/run-unreal-tool-tests.mjs +720 -0
  125. package/tsconfig.json +2 -2
  126. package/dist/python-utils.d.ts +0 -29
  127. package/dist/python-utils.js +0 -54
  128. package/dist/tools/tool-definitions.d.ts +0 -4919
  129. package/dist/tools/tool-definitions.js +0 -1065
  130. package/dist/tools/tool-handlers.d.ts +0 -47
  131. package/dist/tools/tool-handlers.js +0 -863
  132. package/dist/types/index.d.ts +0 -323
  133. package/dist/types/index.js +0 -28
  134. package/dist/utils/cache-manager.d.ts +0 -64
  135. package/dist/utils/cache-manager.js +0 -176
  136. package/dist/utils/errors.d.ts +0 -133
  137. package/dist/utils/errors.js +0 -256
  138. package/src/python/editor_compat.py +0 -181
  139. package/src/python-utils.ts +0 -57
  140. package/src/tools/tool-definitions.ts +0 -1081
  141. package/src/tools/tool-handlers.ts +0 -973
  142. package/src/types/index.ts +0 -414
  143. package/src/utils/cache-manager.ts +0 -213
  144. package/src/utils/errors.ts +0 -312
@@ -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
- const segments = path.split('/').filter(s => s.length > 0);
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/SKM_Manny',
143
- '/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny',
144
- '/Game/Mannequin/Character/Mesh/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny',
145
- '/Game/Characters/Mannequin_UE4/Meshes/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn',
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
- meshPath = meshPath.replace('/SK_', '/SKM_');
156
- meshPath = meshPath.replace('UE4_Mannequin', 'SKM_Manny');
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
@@ -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
- "UE_RC_WS_PORT": "30010",
9
- "UE_RC_HTTP_PORT": "30020",
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.1",
4
- "description": "Production-ready MCP server for Unreal Engine integration with consolidated and individual tool modes",
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.4.0",
38
- "ajv": "^8.12.0",
39
- "axios": "^1.7.2",
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-07-09/server.schema.json",
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
3
3
  "name": "io.github.ChiR24/unreal-engine-mcp",
4
- "description": "Production-ready MCP server for Unreal Engine with comprehensive game development tools",
5
- "version": "0.3.1",
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
- "registry_type": "npm",
9
- "registry_base_url": "https://registry.npmjs.org",
8
+ "registryType": "npm",
9
+ "registryBaseUrl": "https://registry.npmjs.org",
10
10
  "identifier": "unreal-engine-mcp-server",
11
- "version": "0.3.1",
11
+ "version": "0.4.3",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  },
15
- "environment_variables": [
15
+ "environmentVariables": [
16
16
  {
17
17
  "name": "UE_HOST",
18
- "description": "Unreal Engine host address",
19
- "is_required": false,
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
- "is_required": false,
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
- "is_required": false,
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
- // Tool mode: true = consolidated (13 tools), false = individual (36+ tools)
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.1',
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
- // Initialize response validation with schemas
151
+ // Initialize response validation with schemas
153
152
  log.debug('Initializing response validation...');
154
- const toolDefs = CONFIG.USE_CONSOLIDATED_TOOLS ? consolidatedToolDefinitions : toolDefinitions;
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
- logging: {}
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
- // Handle tool listing - switch between consolidated (13) or individual (36) tools
463
+ // Handle tool listing - consolidated tools only
437
464
  server.setRequestHandler(ListToolsRequestSchema, async () => {
438
- log.info(`Serving ${CONFIG.USE_CONSOLIDATED_TOOLS ? 'consolidated' : 'individual'} tools`);
465
+ log.info('Serving consolidated tools');
439
466
  return {
440
- tools: CONFIG.USE_CONSOLIDATED_TOOLS ? consolidatedToolDefinitions : toolDefinitions
467
+ tools: consolidatedToolDefinitions
441
468
  };
442
469
  });
443
470
 
444
- // Handle tool calls - switch between consolidated (13) or individual (36) tools
471
+ // Handle tool calls - consolidated tools only (13)
445
472
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
446
- const { name, arguments: args } = request.params;
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 notConnected;
481
+ return createNotConnectedResponse(name);
461
482
  }
462
483
 
463
- // Create tools object for handler
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
- // Use consolidated or individual handler based on configuration
519
+ // Execute consolidated tool handler
490
520
  try {
491
521
  log.debug(`Executing tool: ${name}`);
492
522
 
493
- let result;
494
- if (CONFIG.USE_CONSOLIDATED_TOOLS) {
495
- result = await handleConsolidatedToolCall(name, args, tools);
496
- } else {
497
- result = await handleToolCall(name, args, tools);
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
- return {
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: errorResponse.message || `Failed to execute ${name}`
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
- name,
553
- description: schema.description,
554
- required: schema.required || false
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
- // Return a template for the lighting setup
668
+
669
+ const args = (request.params.arguments || {}) as Record<string, unknown>;
670
+ const messages = prompt.build(args);
568
671
  return {
569
- messages: [
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