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.
- package/.env.example +1 -1
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/greetings.yml +5 -1
- package/.github/workflows/labeler.yml +2 -1
- package/.github/workflows/publish-mcp.yml +1 -0
- package/.github/workflows/release-drafter.yml +1 -1
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +71 -0
- package/CONTRIBUTING.md +1 -1
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +166 -200
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +3 -3
- package/dist/graphql/resolvers.js +33 -30
- package/dist/graphql/server.js +3 -1
- package/dist/graphql/types.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -2
- package/dist/server-setup.d.ts +0 -1
- package/dist/server-setup.js +0 -40
- package/dist/tools/actors.d.ts +40 -24
- package/dist/tools/actors.js +8 -2
- package/dist/tools/assets.d.ts +19 -71
- package/dist/tools/assets.js +28 -22
- package/dist/tools/base-tool.d.ts +4 -4
- package/dist/tools/base-tool.js +1 -1
- package/dist/tools/blueprint.d.ts +33 -61
- package/dist/tools/consolidated-tool-handlers.js +96 -110
- package/dist/tools/dynamic-handler-registry.d.ts +11 -9
- package/dist/tools/dynamic-handler-registry.js +17 -95
- package/dist/tools/editor.d.ts +19 -193
- package/dist/tools/editor.js +8 -0
- package/dist/tools/environment.d.ts +8 -14
- package/dist/tools/foliage.d.ts +18 -143
- package/dist/tools/foliage.js +4 -2
- package/dist/tools/handlers/actor-handlers.js +0 -5
- package/dist/tools/handlers/asset-handlers.js +454 -454
- package/dist/tools/landscape.d.ts +16 -116
- package/dist/tools/landscape.js +7 -3
- package/dist/tools/level.d.ts +22 -103
- package/dist/tools/level.js +24 -16
- package/dist/tools/lighting.js +5 -1
- package/dist/tools/materials.js +5 -1
- package/dist/tools/niagara.js +37 -2
- package/dist/tools/performance.d.ts +0 -1
- package/dist/tools/performance.js +0 -1
- package/dist/tools/physics.js +5 -1
- package/dist/tools/sequence.d.ts +24 -24
- package/dist/tools/sequence.js +13 -0
- package/dist/tools/ui.d.ts +0 -2
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +135 -135
- package/dist/utils/command-validator.js +3 -2
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +4 -4
- package/dist/utils/response-factory.js +15 -21
- package/docs/Migration-Guide-v0.5.0.md +1 -9
- package/docs/testing-guide.md +2 -2
- package/package.json +12 -6
- package/scripts/run-all-tests.mjs +25 -20
- package/server.json +3 -2
- package/src/config.ts +1 -1
- package/src/constants.ts +7 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +47 -49
- package/src/graphql/server.ts +3 -1
- package/src/graphql/types.ts +3 -0
- package/src/index.ts +15 -2
- package/src/resources/assets.ts +5 -4
- package/src/server-setup.ts +3 -37
- package/src/tools/actors.ts +36 -28
- package/src/tools/animation.ts +1 -0
- package/src/tools/assets.ts +74 -63
- package/src/tools/base-tool.ts +3 -3
- package/src/tools/blueprint.ts +59 -59
- package/src/tools/consolidated-tool-handlers.ts +129 -150
- package/src/tools/dynamic-handler-registry.ts +22 -140
- package/src/tools/editor.ts +39 -26
- package/src/tools/environment.ts +21 -27
- package/src/tools/foliage.ts +28 -25
- package/src/tools/handlers/actor-handlers.ts +2 -8
- package/src/tools/handlers/asset-handlers.ts +484 -484
- package/src/tools/handlers/sequence-handlers.ts +1 -1
- package/src/tools/landscape.ts +34 -28
- package/src/tools/level.ts +96 -76
- package/src/tools/lighting.ts +6 -1
- package/src/tools/materials.ts +8 -2
- package/src/tools/niagara.ts +44 -2
- package/src/tools/performance.ts +1 -2
- package/src/tools/physics.ts +7 -1
- package/src/tools/sequence.ts +41 -25
- package/src/tools/ui.ts +0 -2
- package/src/types/automation-responses.ts +119 -0
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +135 -135
- package/src/utils/command-validator.ts +3 -2
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +29 -24
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/validation.test.ts +184 -0
- package/tests/test-animation.mjs +358 -33
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +314 -116
- package/tests/test-behavior-tree.mjs +327 -144
- package/tests/test-blueprint-graph.mjs +343 -12
- package/tests/test-control-editor.mjs +85 -53
- package/tests/test-graphql.mjs +58 -8
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +291 -61
- package/tests/test-landscape.mjs +304 -48
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-level.mjs +70 -51
- package/tests/test-performance.mjs +539 -0
- package/tests/test-sequence.mjs +82 -46
- package/tests/test-system.mjs +72 -33
- package/tests/test-wasm.mjs +98 -8
- package/vitest.config.ts +35 -0
- package/.github/release-drafter.yml +0 -148
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/blueprint/helpers.d.ts +0 -29
- package/dist/tools/blueprint/helpers.js +0 -182
- package/src/prompts/index.ts +0 -249
- package/src/tools/blueprint/helpers.ts +0 -189
- package/tests/test-blueprint-events.mjs +0 -35
- package/tests/test-extra-tools.mjs +0 -38
- package/tests/test-render.mjs +0 -33
- package/tests/test-search-assets.mjs +0 -66
|
@@ -1,39 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StandardActionResponse } from '../types/tool-interfaces.js';
|
|
2
|
+
import { cleanObject } from './safe-json.js';
|
|
2
3
|
|
|
3
4
|
export class ResponseFactory {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
16
|
-
|
|
10
|
+
success: true,
|
|
11
|
+
message,
|
|
12
|
+
data: cleanObject(data)
|
|
17
13
|
};
|
|
18
14
|
}
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
28
|
+
success: false,
|
|
29
|
+
message: errorMessage,
|
|
30
|
+
data: null
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Create a validation error response
|
|
36
|
+
*/
|
|
37
|
+
static validationError(message: string): StandardActionResponse {
|
|
31
38
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
});
|