unreal-engine-mcp-server 0.5.1 → 0.5.2

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 (75) hide show
  1. package/.github/workflows/publish-mcp.yml +1 -4
  2. package/.github/workflows/release-drafter.yml +2 -1
  3. package/CHANGELOG.md +38 -0
  4. package/dist/automation/bridge.d.ts +1 -2
  5. package/dist/automation/bridge.js +24 -23
  6. package/dist/automation/connection-manager.d.ts +1 -0
  7. package/dist/automation/connection-manager.js +10 -0
  8. package/dist/automation/message-handler.js +5 -4
  9. package/dist/automation/request-tracker.d.ts +4 -0
  10. package/dist/automation/request-tracker.js +11 -3
  11. package/dist/tools/actors.d.ts +19 -1
  12. package/dist/tools/actors.js +15 -5
  13. package/dist/tools/assets.js +1 -1
  14. package/dist/tools/blueprint.d.ts +12 -0
  15. package/dist/tools/blueprint.js +43 -14
  16. package/dist/tools/consolidated-tool-definitions.js +2 -1
  17. package/dist/tools/editor.js +3 -2
  18. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  19. package/dist/tools/handlers/actor-handlers.js +14 -8
  20. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  21. package/dist/tools/handlers/sequence-handlers.js +24 -13
  22. package/dist/tools/introspection.d.ts +1 -1
  23. package/dist/tools/introspection.js +1 -1
  24. package/dist/tools/level.js +3 -3
  25. package/dist/tools/lighting.d.ts +54 -7
  26. package/dist/tools/lighting.js +4 -4
  27. package/dist/tools/materials.d.ts +1 -1
  28. package/dist/types/tool-types.d.ts +2 -0
  29. package/dist/unreal-bridge.js +4 -4
  30. package/dist/utils/command-validator.js +6 -5
  31. package/dist/utils/error-handler.d.ts +24 -2
  32. package/dist/utils/error-handler.js +58 -23
  33. package/dist/utils/normalize.d.ts +7 -4
  34. package/dist/utils/normalize.js +12 -10
  35. package/dist/utils/response-validator.js +88 -73
  36. package/dist/utils/unreal-command-queue.d.ts +2 -0
  37. package/dist/utils/unreal-command-queue.js +8 -1
  38. package/docs/handler-mapping.md +4 -2
  39. package/package.json +1 -1
  40. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  41. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  42. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  43. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  44. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  45. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  46. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  47. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  48. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  49. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  50. package/server.json +3 -3
  51. package/src/automation/bridge.ts +27 -25
  52. package/src/automation/connection-manager.ts +18 -0
  53. package/src/automation/message-handler.ts +33 -8
  54. package/src/automation/request-tracker.ts +39 -7
  55. package/src/server/tool-registry.ts +3 -3
  56. package/src/tools/actors.ts +44 -19
  57. package/src/tools/assets.ts +3 -3
  58. package/src/tools/blueprint.ts +115 -49
  59. package/src/tools/consolidated-tool-definitions.ts +2 -1
  60. package/src/tools/editor.ts +4 -3
  61. package/src/tools/handlers/actor-handlers.ts +14 -9
  62. package/src/tools/handlers/sequence-handlers.ts +86 -63
  63. package/src/tools/introspection.ts +7 -7
  64. package/src/tools/level.ts +6 -6
  65. package/src/tools/lighting.ts +19 -19
  66. package/src/tools/materials.ts +1 -1
  67. package/src/tools/sequence.ts +1 -1
  68. package/src/tools/ui.ts +1 -1
  69. package/src/types/tool-types.ts +4 -0
  70. package/src/unreal-bridge.ts +71 -26
  71. package/src/utils/command-validator.ts +46 -5
  72. package/src/utils/error-handler.ts +128 -45
  73. package/src/utils/normalize.ts +38 -16
  74. package/src/utils/response-validator.ts +103 -87
  75. package/src/utils/unreal-command-queue.ts +13 -1
@@ -21,91 +21,106 @@ function buildSummaryText(toolName, payload) {
21
21
  return `${toolName} responded`;
22
22
  }
23
23
  const effectivePayload = { ...payload };
24
- if (isRecord(effectivePayload.data)) {
25
- Object.assign(effectivePayload, effectivePayload.data);
26
- }
27
- if (isRecord(effectivePayload.result)) {
28
- Object.assign(effectivePayload, effectivePayload.result);
29
- }
30
- const parts = [];
31
- const listKeys = ['actors', 'levels', 'assets', 'folders', 'blueprints', 'components', 'pawnClasses', 'foliageTypes', 'nodes', 'tracks', 'bindings', 'keys'];
32
- for (const key of listKeys) {
33
- if (Array.isArray(effectivePayload[key])) {
34
- const arr = effectivePayload[key];
35
- const names = arr.map(i => isRecord(i) ? (i.name || i.path || i.id || i.assetName || i.objectPath || i.packageName || i.nodeName || '<?>') : String(i));
36
- const count = arr.length;
37
- const preview = names.slice(0, 100).join(', ');
38
- const suffix = count > 100 ? `, ... (+${count - 100} more)` : '';
39
- parts.push(`${key}: ${preview}${suffix} (Total: ${count})`);
24
+ const flattenWrappers = (obj, depth = 0) => {
25
+ if (depth > 5)
26
+ return;
27
+ if (isRecord(obj.data)) {
28
+ Object.assign(obj, obj.data);
29
+ delete obj.data;
30
+ flattenWrappers(obj, depth + 1);
40
31
  }
41
- }
42
- if (typeof effectivePayload.actor === 'string' || isRecord(effectivePayload.actor)) {
43
- const a = effectivePayload.actor;
44
- const name = isRecord(a) ? (a.name || a.path) : a;
45
- const loc = isRecord(effectivePayload.location) ? ` at [${effectivePayload.location.x},${effectivePayload.location.y},${effectivePayload.location.z}]` : '';
46
- parts.push(`Actor: ${name}${loc}`);
47
- }
48
- if (typeof effectivePayload.asset === 'string' || isRecord(effectivePayload.asset)) {
49
- const a = effectivePayload.asset;
50
- const path = isRecord(a) ? (a.path || a.name) : a;
51
- parts.push(`Asset: ${path}`);
52
- }
53
- if (typeof effectivePayload.blueprint === 'string' || isRecord(effectivePayload.blueprint)) {
54
- const bp = effectivePayload.blueprint;
55
- const name = isRecord(bp) ? (bp.name || bp.path || effectivePayload.blueprintPath) : bp;
56
- parts.push(`Blueprint: ${name}`);
57
- }
58
- if (typeof effectivePayload.sequence === 'string' || isRecord(effectivePayload.sequence)) {
59
- const seq = effectivePayload.sequence;
60
- const name = isRecord(seq) ? (seq.name || seq.path) : seq;
61
- parts.push(`Sequence: ${name}`);
62
- }
63
- const usefulKeys = [
64
- 'success', 'error', 'message', 'assets', 'folders', 'count', 'totalCount',
65
- 'saved', 'valid', 'issues', 'class', 'skeleton', 'parent',
66
- 'package', 'dependencies', 'graph', 'tags', 'metadata', 'properties'
67
- ];
68
- for (const key of usefulKeys) {
69
- if (effectivePayload[key] !== undefined && effectivePayload[key] !== null) {
70
- const val = effectivePayload[key];
71
- if (typeof val === 'object') {
72
- if (key === 'metadata' || key === 'properties' || key === 'tags') {
73
- const entries = Object.entries(val);
74
- const formatted = entries.map(([k, v]) => `${k}=${v}`);
75
- const limit = 50;
76
- parts.push(`${key}: { ${formatted.slice(0, limit).join(', ')}${formatted.length > limit ? '...' : ''} }`);
77
- continue;
32
+ if (isRecord(obj.result)) {
33
+ Object.assign(obj, obj.result);
34
+ delete obj.result;
35
+ flattenWrappers(obj, depth + 1);
36
+ }
37
+ };
38
+ flattenWrappers(effectivePayload);
39
+ const parts = [];
40
+ const addedKeys = new Set();
41
+ const skipKeys = new Set(['requestId', 'type', 'data', 'result', 'warnings']);
42
+ const formatValue = (val) => {
43
+ if (val === null || val === undefined)
44
+ return '';
45
+ if (typeof val === 'string')
46
+ return val.length > 150 ? val.slice(0, 150) + '...' : val;
47
+ if (typeof val === 'number' || typeof val === 'boolean')
48
+ return String(val);
49
+ if (Array.isArray(val)) {
50
+ if (val.length === 0)
51
+ return '[] (0)';
52
+ const items = val.slice(0, 30).map(v => {
53
+ if (isRecord(v)) {
54
+ return v.name || v.path || v.id || v.nodeId || v.nodeName || v.className ||
55
+ v.displayName || v.type || v.assetPath || v.objectPath ||
56
+ JSON.stringify(v).slice(0, 50);
78
57
  }
79
- continue;
58
+ return String(v);
59
+ });
60
+ const suffix = val.length > 30 ? `, ... (+${val.length - 30} more)` : '';
61
+ return `[${items.join(', ')}${suffix}] (${val.length})`;
62
+ }
63
+ if (isRecord(val)) {
64
+ const keys = Object.keys(val);
65
+ if (keys.some(k => ['x', 'y', 'z', 'pitch', 'yaw', 'roll'].includes(k))) {
66
+ const x = val.x ?? val.pitch ?? 0;
67
+ const y = val.y ?? val.yaw ?? 0;
68
+ const z = val.z ?? val.roll ?? 0;
69
+ return `[${x}, ${y}, ${z}]`;
80
70
  }
81
- const strVal = String(val);
82
- if (strVal.length > 100)
83
- continue;
84
- if (!parts.some(p => p.includes(strVal))) {
85
- parts.push(`${key}: ${strVal}`);
71
+ const entries = Object.entries(val).slice(0, 8);
72
+ const formatted = entries.map(([k, v]) => {
73
+ const vStr = typeof v === 'object' ? JSON.stringify(v).slice(0, 40) : String(v);
74
+ return `${k}=${vStr}`;
75
+ });
76
+ return `{ ${formatted.join(', ')}${keys.length > 8 ? ' ...' : ''} }`;
77
+ }
78
+ return String(val);
79
+ };
80
+ for (const key of ['success', 'error']) {
81
+ if (effectivePayload[key] !== undefined && !addedKeys.has(key)) {
82
+ const formatted = formatValue(effectivePayload[key]);
83
+ if (formatted) {
84
+ parts.push(`${key}: ${formatted}`);
85
+ addedKeys.add(key);
86
86
  }
87
87
  }
88
88
  }
89
- const success = typeof payload.success === 'boolean' ? payload.success : undefined;
90
- const message = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
91
- const error = typeof payload.error === 'string' ? normalizeText(payload.error) : '';
92
- if (parts.length > 0) {
93
- if (message && message.toLowerCase() !== 'success') {
94
- parts.push(message);
89
+ let hasArrays = false;
90
+ for (const [key, val] of Object.entries(effectivePayload)) {
91
+ if (addedKeys.has(key))
92
+ continue;
93
+ if (skipKeys.has(key))
94
+ continue;
95
+ if (val === undefined || val === null)
96
+ continue;
97
+ if (typeof val === 'string' && val.trim() === '')
98
+ continue;
99
+ if (key === 'message')
100
+ continue;
101
+ if (Array.isArray(val) && val.length > 0)
102
+ hasArrays = true;
103
+ if ((key === 'count' || key === 'totalCount') && hasArrays)
104
+ continue;
105
+ const formatted = formatValue(val);
106
+ if (formatted) {
107
+ parts.push(`${key}: ${formatted}`);
108
+ addedKeys.add(key);
95
109
  }
96
110
  }
97
- else {
98
- if (message)
111
+ const message = typeof effectivePayload.message === 'string' ? normalizeText(effectivePayload.message) : '';
112
+ if (message && message.toLowerCase() !== 'success') {
113
+ const isDuplicateInfo = /^(found|listed|retrieved|got|loaded|created|deleted|saved|spawned)\s+\d+/i.test(message) ||
114
+ /Folders:\s*\[/.test(message) ||
115
+ /\d+\s+(assets?|folders?|items?|actors?|components?)\s+(and|in|at)/i.test(message);
116
+ const messageInParts = parts.some(p => p.toLowerCase().includes(message.toLowerCase().slice(0, 30)));
117
+ if (!isDuplicateInfo && !messageInParts) {
99
118
  parts.push(message);
100
- if (error)
101
- parts.push(`Error: ${error}`);
102
- if (parts.length === 0 && success !== undefined) {
103
- parts.push(success ? 'Success' : 'Failed');
104
119
  }
105
120
  }
106
- const warnings = Array.isArray(payload.warnings) ? payload.warnings : [];
121
+ const warnings = Array.isArray(effectivePayload.warnings) ? effectivePayload.warnings : [];
107
122
  if (warnings.length > 0) {
108
- parts.push(`Warnings: ${warnings.length}`);
123
+ parts.push(`Warnings: ${warnings.map((w) => typeof w === 'string' ? w : JSON.stringify(w)).join('; ')}`);
109
124
  }
110
125
  return parts.length > 0 ? parts.join(' | ') : `${toolName} responded`;
111
126
  }
@@ -11,6 +11,7 @@ export declare class UnrealCommandQueue {
11
11
  private isProcessing;
12
12
  private lastCommandTime;
13
13
  private lastStatCommandTime;
14
+ private processorInterval?;
14
15
  private readonly MIN_COMMAND_DELAY;
15
16
  private readonly MAX_COMMAND_DELAY;
16
17
  private readonly STAT_COMMAND_DELAY;
@@ -19,6 +20,7 @@ export declare class UnrealCommandQueue {
19
20
  private processQueue;
20
21
  private calculateDelay;
21
22
  private startProcessor;
23
+ stopProcessor(): void;
22
24
  private delay;
23
25
  }
24
26
  //# sourceMappingURL=unreal-command-queue.d.ts.map
@@ -5,6 +5,7 @@ export class UnrealCommandQueue {
5
5
  isProcessing = false;
6
6
  lastCommandTime = 0;
7
7
  lastStatCommandTime = 0;
8
+ processorInterval;
8
9
  MIN_COMMAND_DELAY = 100;
9
10
  MAX_COMMAND_DELAY = 500;
10
11
  STAT_COMMAND_DELAY = 300;
@@ -107,12 +108,18 @@ export class UnrealCommandQueue {
107
108
  }
108
109
  }
109
110
  startProcessor() {
110
- setInterval(() => {
111
+ this.processorInterval = setInterval(() => {
111
112
  if (!this.isProcessing && this.queue.length > 0) {
112
113
  this.processQueue();
113
114
  }
114
115
  }, 1000);
115
116
  }
117
+ stopProcessor() {
118
+ if (this.processorInterval) {
119
+ clearInterval(this.processorInterval);
120
+ this.processorInterval = undefined;
121
+ }
122
+ }
116
123
  delay(ms) {
117
124
  return new Promise(resolve => setTimeout(resolve, ms));
118
125
  }
@@ -235,8 +235,10 @@ This document maps the TypeScript tool definitions to their corresponding C++ ha
235
235
 
236
236
  | Action | C++ Handler File | C++ Function | Notes |
237
237
  | :--- | :--- | :--- | :--- |
238
- | `create_node` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | |
238
+ | `create_node` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | Dynamic node class resolution |
239
239
  | `delete_node` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | |
240
240
  | `connect_pins` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | |
241
241
  | `break_pin_links` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | |
242
- | `set_node_property` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | |
242
+ | `set_node_property` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | |
243
+ | `list_node_types` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | Lists all UK2Node subclasses |
244
+ | `set_pin_default_value` | `McpAutomationBridge_BlueprintGraphHandlers.cpp` | `HandleBlueprintGraphAction` | Sets default value on input pins |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unreal-engine-mcp-server",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "mcpName": "io.github.ChiR24/unreal-engine-mcp",
5
5
  "description": "A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine via native automation bridge. Built with TypeScript and designed for game development automation.",
6
6
  "type": "module",