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.
Files changed (135) hide show
  1. package/.env.production +1 -1
  2. package/.github/copilot-instructions.md +45 -0
  3. package/.github/workflows/publish-mcp.yml +3 -2
  4. package/README.md +21 -5
  5. package/dist/index.js +124 -31
  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.js +46 -62
  11. package/dist/resources/levels.d.ts +21 -3
  12. package/dist/resources/levels.js +29 -54
  13. package/dist/tools/actors.d.ts +3 -14
  14. package/dist/tools/actors.js +246 -302
  15. package/dist/tools/animation.d.ts +57 -102
  16. package/dist/tools/animation.js +429 -450
  17. package/dist/tools/assets.d.ts +13 -2
  18. package/dist/tools/assets.js +52 -44
  19. package/dist/tools/audio.d.ts +22 -13
  20. package/dist/tools/audio.js +467 -121
  21. package/dist/tools/blueprint.d.ts +32 -13
  22. package/dist/tools/blueprint.js +699 -448
  23. package/dist/tools/build_environment_advanced.d.ts +0 -1
  24. package/dist/tools/build_environment_advanced.js +190 -45
  25. package/dist/tools/consolidated-tool-definitions.js +78 -252
  26. package/dist/tools/consolidated-tool-handlers.js +506 -133
  27. package/dist/tools/debug.d.ts +72 -10
  28. package/dist/tools/debug.js +167 -31
  29. package/dist/tools/editor.d.ts +9 -2
  30. package/dist/tools/editor.js +30 -44
  31. package/dist/tools/foliage.d.ts +34 -15
  32. package/dist/tools/foliage.js +97 -107
  33. package/dist/tools/introspection.js +19 -21
  34. package/dist/tools/landscape.d.ts +1 -2
  35. package/dist/tools/landscape.js +311 -168
  36. package/dist/tools/level.d.ts +3 -28
  37. package/dist/tools/level.js +642 -192
  38. package/dist/tools/lighting.d.ts +14 -3
  39. package/dist/tools/lighting.js +236 -123
  40. package/dist/tools/materials.d.ts +25 -7
  41. package/dist/tools/materials.js +102 -79
  42. package/dist/tools/niagara.d.ts +10 -12
  43. package/dist/tools/niagara.js +74 -94
  44. package/dist/tools/performance.d.ts +12 -4
  45. package/dist/tools/performance.js +38 -79
  46. package/dist/tools/physics.d.ts +34 -10
  47. package/dist/tools/physics.js +364 -292
  48. package/dist/tools/rc.js +97 -23
  49. package/dist/tools/sequence.d.ts +1 -0
  50. package/dist/tools/sequence.js +125 -22
  51. package/dist/tools/ui.d.ts +31 -4
  52. package/dist/tools/ui.js +83 -66
  53. package/dist/tools/visual.d.ts +11 -0
  54. package/dist/tools/visual.js +245 -30
  55. package/dist/types/tool-types.d.ts +0 -6
  56. package/dist/types/tool-types.js +1 -8
  57. package/dist/unreal-bridge.d.ts +32 -2
  58. package/dist/unreal-bridge.js +621 -127
  59. package/dist/utils/elicitation.d.ts +57 -0
  60. package/dist/utils/elicitation.js +104 -0
  61. package/dist/utils/error-handler.d.ts +0 -33
  62. package/dist/utils/error-handler.js +4 -111
  63. package/dist/utils/http.d.ts +2 -22
  64. package/dist/utils/http.js +12 -75
  65. package/dist/utils/normalize.d.ts +4 -4
  66. package/dist/utils/normalize.js +15 -7
  67. package/dist/utils/python-output.d.ts +18 -0
  68. package/dist/utils/python-output.js +290 -0
  69. package/dist/utils/python.d.ts +2 -0
  70. package/dist/utils/python.js +4 -0
  71. package/dist/utils/response-validator.js +28 -2
  72. package/dist/utils/result-helpers.d.ts +27 -0
  73. package/dist/utils/result-helpers.js +147 -0
  74. package/dist/utils/safe-json.d.ts +0 -2
  75. package/dist/utils/safe-json.js +0 -43
  76. package/dist/utils/validation.d.ts +16 -0
  77. package/dist/utils/validation.js +70 -7
  78. package/mcp-config-example.json +2 -2
  79. package/package.json +10 -9
  80. package/server.json +37 -14
  81. package/src/index.ts +130 -33
  82. package/src/prompts/index.ts +211 -13
  83. package/src/resources/actors.ts +59 -44
  84. package/src/resources/assets.ts +48 -51
  85. package/src/resources/levels.ts +35 -45
  86. package/src/tools/actors.ts +269 -313
  87. package/src/tools/animation.ts +556 -539
  88. package/src/tools/assets.ts +53 -43
  89. package/src/tools/audio.ts +507 -113
  90. package/src/tools/blueprint.ts +778 -462
  91. package/src/tools/build_environment_advanced.ts +266 -64
  92. package/src/tools/consolidated-tool-definitions.ts +90 -264
  93. package/src/tools/consolidated-tool-handlers.ts +630 -121
  94. package/src/tools/debug.ts +176 -33
  95. package/src/tools/editor.ts +35 -37
  96. package/src/tools/foliage.ts +110 -104
  97. package/src/tools/introspection.ts +24 -22
  98. package/src/tools/landscape.ts +334 -181
  99. package/src/tools/level.ts +683 -182
  100. package/src/tools/lighting.ts +244 -123
  101. package/src/tools/materials.ts +114 -83
  102. package/src/tools/niagara.ts +87 -81
  103. package/src/tools/performance.ts +49 -88
  104. package/src/tools/physics.ts +393 -299
  105. package/src/tools/rc.ts +102 -24
  106. package/src/tools/sequence.ts +136 -28
  107. package/src/tools/ui.ts +101 -70
  108. package/src/tools/visual.ts +250 -29
  109. package/src/types/tool-types.ts +0 -9
  110. package/src/unreal-bridge.ts +658 -140
  111. package/src/utils/elicitation.ts +129 -0
  112. package/src/utils/error-handler.ts +4 -159
  113. package/src/utils/http.ts +16 -115
  114. package/src/utils/normalize.ts +20 -10
  115. package/src/utils/python-output.ts +351 -0
  116. package/src/utils/python.ts +3 -0
  117. package/src/utils/response-validator.ts +25 -2
  118. package/src/utils/result-helpers.ts +193 -0
  119. package/src/utils/safe-json.ts +0 -50
  120. package/src/utils/validation.ts +94 -7
  121. package/tests/run-unreal-tool-tests.mjs +720 -0
  122. package/tsconfig.json +2 -2
  123. package/dist/python-utils.d.ts +0 -29
  124. package/dist/python-utils.js +0 -54
  125. package/dist/types/index.d.ts +0 -323
  126. package/dist/types/index.js +0 -28
  127. package/dist/utils/cache-manager.d.ts +0 -64
  128. package/dist/utils/cache-manager.js +0 -176
  129. package/dist/utils/errors.d.ts +0 -133
  130. package/dist/utils/errors.js +0 -256
  131. package/src/python/editor_compat.py +0 -181
  132. package/src/python-utils.ts +0 -57
  133. package/src/types/index.ts +0 -414
  134. package/src/utils/cache-manager.ts +0 -213
  135. 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.4.0",
4
- "description": "Production-ready MCP server for Unreal Engine integration using consolidated tools only",
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.1",
38
- "ajv": "^8.12.0",
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-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.4.0",
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
- "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.4.0",
11
+ "version": "0.4.4",
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
@@ -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.0',
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
- logging: {}
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, arguments: args } = request.params;
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 notConnected;
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
- 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,
535
630
  content: [{
536
631
  type: 'text',
537
- text: errorResponse.message || `Failed to execute ${name}`
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
- name,
552
- description: schema.description,
553
- required: schema.required || false
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
- // Return a template for the lighting setup
668
+
669
+ const args = (request.params.arguments || {}) as Record<string, unknown>;
670
+ const messages = prompt.build(args);
567
671
  return {
568
- messages: [
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