unreal-engine-mcp-server 0.5.0 → 0.5.1

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 (139) hide show
  1. package/.env.example +1 -1
  2. package/.github/release-drafter-config.yml +51 -0
  3. package/.github/workflows/greetings.yml +5 -1
  4. package/.github/workflows/labeler.yml +2 -1
  5. package/.github/workflows/publish-mcp.yml +1 -0
  6. package/.github/workflows/release-drafter.yml +1 -1
  7. package/.github/workflows/release.yml +3 -3
  8. package/CHANGELOG.md +71 -0
  9. package/CONTRIBUTING.md +1 -1
  10. package/GEMINI.md +115 -0
  11. package/Public/Plugin_setup_guide.mp4 +0 -0
  12. package/README.md +166 -200
  13. package/dist/config.d.ts +0 -1
  14. package/dist/config.js +0 -1
  15. package/dist/constants.d.ts +4 -0
  16. package/dist/constants.js +4 -0
  17. package/dist/graphql/loaders.d.ts +64 -0
  18. package/dist/graphql/loaders.js +117 -0
  19. package/dist/graphql/resolvers.d.ts +3 -3
  20. package/dist/graphql/resolvers.js +33 -30
  21. package/dist/graphql/server.js +3 -1
  22. package/dist/graphql/types.d.ts +2 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +13 -2
  25. package/dist/server-setup.d.ts +0 -1
  26. package/dist/server-setup.js +0 -40
  27. package/dist/tools/actors.d.ts +40 -24
  28. package/dist/tools/actors.js +8 -2
  29. package/dist/tools/assets.d.ts +19 -71
  30. package/dist/tools/assets.js +28 -22
  31. package/dist/tools/base-tool.d.ts +4 -4
  32. package/dist/tools/base-tool.js +1 -1
  33. package/dist/tools/blueprint.d.ts +33 -61
  34. package/dist/tools/consolidated-tool-handlers.js +96 -110
  35. package/dist/tools/dynamic-handler-registry.d.ts +11 -9
  36. package/dist/tools/dynamic-handler-registry.js +17 -95
  37. package/dist/tools/editor.d.ts +19 -193
  38. package/dist/tools/editor.js +8 -0
  39. package/dist/tools/environment.d.ts +8 -14
  40. package/dist/tools/foliage.d.ts +18 -143
  41. package/dist/tools/foliage.js +4 -2
  42. package/dist/tools/handlers/actor-handlers.js +0 -5
  43. package/dist/tools/handlers/asset-handlers.js +454 -454
  44. package/dist/tools/landscape.d.ts +16 -116
  45. package/dist/tools/landscape.js +7 -3
  46. package/dist/tools/level.d.ts +22 -103
  47. package/dist/tools/level.js +24 -16
  48. package/dist/tools/lighting.js +5 -1
  49. package/dist/tools/materials.js +5 -1
  50. package/dist/tools/niagara.js +37 -2
  51. package/dist/tools/performance.d.ts +0 -1
  52. package/dist/tools/performance.js +0 -1
  53. package/dist/tools/physics.js +5 -1
  54. package/dist/tools/sequence.d.ts +24 -24
  55. package/dist/tools/sequence.js +13 -0
  56. package/dist/tools/ui.d.ts +0 -2
  57. package/dist/types/automation-responses.d.ts +115 -0
  58. package/dist/types/automation-responses.js +2 -0
  59. package/dist/types/responses.d.ts +249 -0
  60. package/dist/types/responses.js +2 -0
  61. package/dist/types/tool-interfaces.d.ts +135 -135
  62. package/dist/utils/command-validator.js +3 -2
  63. package/dist/utils/path-security.d.ts +2 -0
  64. package/dist/utils/path-security.js +24 -0
  65. package/dist/utils/response-factory.d.ts +4 -4
  66. package/dist/utils/response-factory.js +15 -21
  67. package/docs/Migration-Guide-v0.5.0.md +1 -9
  68. package/docs/testing-guide.md +2 -2
  69. package/package.json +12 -6
  70. package/scripts/run-all-tests.mjs +25 -20
  71. package/server.json +3 -2
  72. package/src/config.ts +1 -1
  73. package/src/constants.ts +7 -0
  74. package/src/graphql/loaders.ts +244 -0
  75. package/src/graphql/resolvers.ts +47 -49
  76. package/src/graphql/server.ts +3 -1
  77. package/src/graphql/types.ts +3 -0
  78. package/src/index.ts +15 -2
  79. package/src/resources/assets.ts +5 -4
  80. package/src/server-setup.ts +3 -37
  81. package/src/tools/actors.ts +36 -28
  82. package/src/tools/animation.ts +1 -0
  83. package/src/tools/assets.ts +74 -63
  84. package/src/tools/base-tool.ts +3 -3
  85. package/src/tools/blueprint.ts +59 -59
  86. package/src/tools/consolidated-tool-handlers.ts +129 -150
  87. package/src/tools/dynamic-handler-registry.ts +22 -140
  88. package/src/tools/editor.ts +39 -26
  89. package/src/tools/environment.ts +21 -27
  90. package/src/tools/foliage.ts +28 -25
  91. package/src/tools/handlers/actor-handlers.ts +2 -8
  92. package/src/tools/handlers/asset-handlers.ts +484 -484
  93. package/src/tools/handlers/sequence-handlers.ts +1 -1
  94. package/src/tools/landscape.ts +34 -28
  95. package/src/tools/level.ts +96 -76
  96. package/src/tools/lighting.ts +6 -1
  97. package/src/tools/materials.ts +8 -2
  98. package/src/tools/niagara.ts +44 -2
  99. package/src/tools/performance.ts +1 -2
  100. package/src/tools/physics.ts +7 -1
  101. package/src/tools/sequence.ts +41 -25
  102. package/src/tools/ui.ts +0 -2
  103. package/src/types/automation-responses.ts +119 -0
  104. package/src/types/responses.ts +355 -0
  105. package/src/types/tool-interfaces.ts +135 -135
  106. package/src/utils/command-validator.ts +3 -2
  107. package/src/utils/normalize.test.ts +162 -0
  108. package/src/utils/path-security.ts +43 -0
  109. package/src/utils/response-factory.ts +29 -24
  110. package/src/utils/safe-json.test.ts +90 -0
  111. package/src/utils/validation.test.ts +184 -0
  112. package/tests/test-animation.mjs +358 -33
  113. package/tests/test-asset-graph.mjs +311 -0
  114. package/tests/test-audio.mjs +314 -116
  115. package/tests/test-behavior-tree.mjs +327 -144
  116. package/tests/test-blueprint-graph.mjs +343 -12
  117. package/tests/test-control-editor.mjs +85 -53
  118. package/tests/test-graphql.mjs +58 -8
  119. package/tests/test-input.mjs +349 -0
  120. package/tests/test-inspect.mjs +291 -61
  121. package/tests/test-landscape.mjs +304 -48
  122. package/tests/test-lighting.mjs +428 -0
  123. package/tests/test-manage-level.mjs +70 -51
  124. package/tests/test-performance.mjs +539 -0
  125. package/tests/test-sequence.mjs +82 -46
  126. package/tests/test-system.mjs +72 -33
  127. package/tests/test-wasm.mjs +98 -8
  128. package/vitest.config.ts +35 -0
  129. package/.github/release-drafter.yml +0 -148
  130. package/dist/prompts/index.d.ts +0 -21
  131. package/dist/prompts/index.js +0 -217
  132. package/dist/tools/blueprint/helpers.d.ts +0 -29
  133. package/dist/tools/blueprint/helpers.js +0 -182
  134. package/src/prompts/index.ts +0 -249
  135. package/src/tools/blueprint/helpers.ts +0 -189
  136. package/tests/test-blueprint-events.mjs +0 -35
  137. package/tests/test-extra-tools.mjs +0 -38
  138. package/tests/test-render.mjs +0 -33
  139. package/tests/test-search-assets.mjs +0 -66
@@ -1,39 +1,44 @@
1
- import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
1
+ import { StandardActionResponse } from '../types/tool-interfaces.js';
2
+ import { cleanObject } from './safe-json.js';
2
3
 
3
4
  export class ResponseFactory {
4
- static success(message: string, data?: unknown): CallToolResult {
5
- const content: any[] = [{ type: 'text', text: message }];
6
-
7
- if (data) {
8
- content.push({
9
- type: 'text',
10
- text: JSON.stringify(data, null, 2)
11
- });
12
- }
13
-
5
+ /**
6
+ * Create a standard success response
7
+ */
8
+ static success(data: any, message: string = 'Operation successful'): StandardActionResponse {
14
9
  return {
15
- content,
16
- isError: false
10
+ success: true,
11
+ message,
12
+ data: cleanObject(data)
17
13
  };
18
14
  }
19
15
 
20
- static error(message: string, error?: unknown): CallToolResult {
21
- const errorText = error instanceof Error ? error.message : String(error);
22
- const fullMessage = error ? `${message}: ${errorText}` : message;
16
+ /**
17
+ * Create a standard error response
18
+ * @param error The error object or message
19
+ * @param defaultMessage Fallback message if error is not an Error object
20
+ */
21
+ static error(error: any, defaultMessage: string = 'Operation failed'): StandardActionResponse {
22
+ const errorMessage = error instanceof Error ? error.message : String(error || defaultMessage);
23
+
24
+ // Log the full error for debugging (internal logs) but return a clean message to the client
25
+ console.error('[ResponseFactory] Error:', error);
23
26
 
24
27
  return {
25
- content: [{ type: 'text', text: fullMessage }],
26
- isError: true
28
+ success: false,
29
+ message: errorMessage,
30
+ data: null
27
31
  };
28
32
  }
29
33
 
30
- static json(data: unknown): CallToolResult {
34
+ /**
35
+ * Create a validation error response
36
+ */
37
+ static validationError(message: string): StandardActionResponse {
31
38
  return {
32
- content: [{
33
- type: 'text',
34
- text: JSON.stringify(data, null, 2)
35
- }],
36
- isError: false
39
+ success: false,
40
+ message: `Validation Error: ${message}`,
41
+ data: null
37
42
  };
38
43
  }
39
44
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Unit tests for safe-json utility
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { cleanObject } from './safe-json.js';
6
+
7
+ describe('cleanObject', () => {
8
+ it('removes undefined values', () => {
9
+ const input = { a: 1, b: undefined, c: 3 };
10
+ const result = cleanObject(input);
11
+ expect(result).toEqual({ a: 1, c: 3 });
12
+ expect('b' in result).toBe(false);
13
+ });
14
+
15
+ it('preserves null values (only removes undefined)', () => {
16
+ const input = { a: 1, b: null, c: 3 };
17
+ const result = cleanObject(input);
18
+ // cleanObject preserves null, only removes undefined
19
+ expect(result).toEqual({ a: 1, b: null, c: 3 });
20
+ });
21
+
22
+ it('handles nested objects', () => {
23
+ const input = { a: 1, nested: { b: 2, c: undefined } };
24
+ const result = cleanObject(input);
25
+ expect(result.nested).toEqual({ b: 2 });
26
+ });
27
+
28
+ it('handles arrays with undefined (preserves null)', () => {
29
+ const input = { arr: [1, undefined, 3, null] };
30
+ const result = cleanObject(input);
31
+ // Arrays preserve their structure, undefined becomes undefined in array
32
+ expect(result.arr).toEqual([1, undefined, 3, null]);
33
+ });
34
+
35
+ it('preserves falsy values that are not null/undefined', () => {
36
+ const input = { a: 0, b: '', c: false };
37
+ const result = cleanObject(input);
38
+ expect(result).toEqual({ a: 0, b: '', c: false });
39
+ });
40
+
41
+ it('handles empty objects', () => {
42
+ expect(cleanObject({})).toEqual({});
43
+ });
44
+
45
+ it('handles primitive inputs', () => {
46
+ expect(cleanObject('string')).toBe('string');
47
+ expect(cleanObject(42)).toBe(42);
48
+ expect(cleanObject(true)).toBe(true);
49
+ });
50
+
51
+ it('respects max depth limit', () => {
52
+ // Create deeply nested object
53
+ let deep: any = { value: 'deep' };
54
+ for (let i = 0; i < 15; i++) {
55
+ deep = { nested: deep };
56
+ }
57
+
58
+ // Should not throw when hitting depth limit
59
+ expect(() => cleanObject(deep, 10)).not.toThrow();
60
+ });
61
+
62
+ it('handles circular reference prevention at max depth', () => {
63
+ const obj: any = { a: 1 };
64
+ obj.self = obj; // circular reference
65
+
66
+ // Should not throw - depth limiting should prevent infinite recursion
67
+ expect(() => cleanObject(obj, 5)).not.toThrow();
68
+ });
69
+
70
+ it('handles Date objects (converts to empty object)', () => {
71
+ const date = new Date('2024-01-01');
72
+ const input = { created: date };
73
+ const result = cleanObject(input);
74
+ // Date objects are processed as objects using Object.keys, which returns []
75
+ expect(result.created).toEqual({});
76
+ });
77
+
78
+ it('handles empty arrays', () => {
79
+ const input = { arr: [] };
80
+ const result = cleanObject(input);
81
+ expect(result.arr).toEqual([]);
82
+ });
83
+
84
+ it('handles NaN values', () => {
85
+ const input = { a: 1, b: NaN };
86
+ const result = cleanObject(input);
87
+ // NaN handling depends on implementation
88
+ expect(result.a).toBe(1);
89
+ });
90
+ });
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Unit tests for validation utility functions
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import {
6
+ sanitizeAssetName,
7
+ sanitizePath,
8
+ validatePathLength,
9
+ validateAssetParams,
10
+ validateNumber,
11
+ ensureVector3,
12
+ ensureRotation
13
+ } from './validation.js';
14
+
15
+ describe('sanitizeAssetName', () => {
16
+ it('removes invalid characters', () => {
17
+ // ! is replaced with _, then trailing _ is stripped
18
+ expect(sanitizeAssetName('My Asset!')).toBe('My_Asset');
19
+ });
20
+
21
+ it('removes leading/trailing whitespace', () => {
22
+ expect(sanitizeAssetName(' MyAsset ')).toBe('MyAsset');
23
+ });
24
+
25
+ it('preserves valid names', () => {
26
+ expect(sanitizeAssetName('ValidName_123')).toBe('ValidName_123');
27
+ });
28
+
29
+ it('replaces spaces with underscores', () => {
30
+ expect(sanitizeAssetName('My Cool Asset')).toBe('My_Cool_Asset');
31
+ });
32
+
33
+ it('handles empty strings', () => {
34
+ const result = sanitizeAssetName('');
35
+ expect(result.length).toBeGreaterThan(0);
36
+ });
37
+
38
+ it('removes consecutive underscores', () => {
39
+ const result = sanitizeAssetName('My__Asset');
40
+ expect(result).not.toContain('__');
41
+ });
42
+ });
43
+
44
+ describe('sanitizePath', () => {
45
+ it('normalizes forward slashes', () => {
46
+ const result = sanitizePath('/Game/MyAsset');
47
+ expect(result).toContain('/');
48
+ expect(result).not.toContain('\\\\');
49
+ });
50
+
51
+ it('removes double slashes', () => {
52
+ const result = sanitizePath('/Game//MyAsset');
53
+ expect(result).not.toContain('//');
54
+ });
55
+
56
+ it('handles backslashes', () => {
57
+ const result = sanitizePath('\\Game\\MyAsset');
58
+ expect(result).toContain('/');
59
+ });
60
+
61
+ it('sanitizes path segments with dots', () => {
62
+ const result = sanitizePath('/Game/../MyAsset');
63
+ // Each segment is sanitized, .. becomes a segment that gets sanitized
64
+ expect(result).toContain('/');
65
+ });
66
+ });
67
+
68
+ describe('validatePathLength', () => {
69
+ it('accepts valid short paths', () => {
70
+ const result = validatePathLength('/Game/MyAsset');
71
+ expect(result.valid).toBe(true);
72
+ });
73
+
74
+ it('accepts empty paths (length 0 < 260)', () => {
75
+ const result = validatePathLength('');
76
+ // Empty string has length 0, which is < 260, so it's valid
77
+ expect(result.valid).toBe(true);
78
+ });
79
+
80
+ it('rejects excessively long paths', () => {
81
+ const longPath = '/Game/' + 'a'.repeat(300);
82
+ const result = validatePathLength(longPath);
83
+ expect(result.valid).toBe(false);
84
+ expect(result.error).toBeDefined();
85
+ });
86
+ });
87
+
88
+ describe('validateAssetParams', () => {
89
+ it('validates correct create params', () => {
90
+ const result = validateAssetParams({
91
+ action: 'create',
92
+ name: 'MyAsset',
93
+ path: '/Game/Assets'
94
+ });
95
+ expect(result.valid).toBe(true);
96
+ });
97
+
98
+ it('handles names that need sanitization', () => {
99
+ const result = validateAssetParams({
100
+ name: '',
101
+ savePath: '/Game'
102
+ });
103
+ // Empty name gets sanitized to 'Asset', so this should be valid
104
+ expect(result.valid).toBe(true);
105
+ });
106
+
107
+ it('validates path params', () => {
108
+ const result = validateAssetParams({
109
+ name: 'Target',
110
+ savePath: '/Game/Source'
111
+ });
112
+ expect(result.valid).toBe(true);
113
+ });
114
+ });
115
+
116
+ describe('validateNumber', () => {
117
+ it('accepts valid finite numbers', () => {
118
+ expect(() => validateNumber(42, 'test')).not.toThrow();
119
+ });
120
+
121
+ it('rejects NaN', () => {
122
+ expect(() => validateNumber(NaN, 'test')).toThrow('expected a finite number');
123
+ });
124
+
125
+ it('rejects Infinity', () => {
126
+ expect(() => validateNumber(Infinity, 'test')).toThrow();
127
+ });
128
+
129
+ it('respects minimum constraint', () => {
130
+ expect(() => validateNumber(5, 'test', { min: 10 })).toThrow('must be >=');
131
+ });
132
+
133
+ it('respects maximum constraint', () => {
134
+ expect(() => validateNumber(15, 'test', { max: 10 })).toThrow('must be <=');
135
+ });
136
+
137
+ it('allows zero by default', () => {
138
+ expect(() => validateNumber(0, 'test')).not.toThrow();
139
+ });
140
+
141
+ it('rejects zero when allowZero is false', () => {
142
+ expect(() => validateNumber(0, 'test', { allowZero: false })).toThrow('zero is not allowed');
143
+ });
144
+ });
145
+
146
+ describe('ensureVector3', () => {
147
+ it('accepts object format with x, y, z', () => {
148
+ const result = ensureVector3({ x: 1, y: 2, z: 3 }, 'location');
149
+ expect(result).toEqual([1, 2, 3]);
150
+ });
151
+
152
+ it('accepts array format', () => {
153
+ const result = ensureVector3([1, 2, 3], 'location');
154
+ expect(result).toEqual([1, 2, 3]);
155
+ });
156
+
157
+ it('throws on invalid object', () => {
158
+ expect(() => ensureVector3({ x: 1 }, 'location')).toThrow();
159
+ });
160
+
161
+ it('throws on wrong array length', () => {
162
+ expect(() => ensureVector3([1, 2], 'location')).toThrow();
163
+ });
164
+
165
+ it('throws on non-number values', () => {
166
+ expect(() => ensureVector3({ x: 'a', y: 2, z: 3 }, 'location')).toThrow();
167
+ });
168
+ });
169
+
170
+ describe('ensureRotation', () => {
171
+ it('accepts object format with pitch, yaw, roll', () => {
172
+ const result = ensureRotation({ pitch: 0, yaw: 90, roll: 0 }, 'rotation');
173
+ expect(result).toEqual([0, 90, 0]);
174
+ });
175
+
176
+ it('accepts array format', () => {
177
+ const result = ensureRotation([0, 90, 0], 'rotation');
178
+ expect(result).toEqual([0, 90, 0]);
179
+ });
180
+
181
+ it('throws on invalid input', () => {
182
+ expect(() => ensureRotation('invalid', 'rotation')).toThrow();
183
+ });
184
+ });