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,189 +0,0 @@
1
- import { coerceString } from '../../utils/result-helpers.js';
2
-
3
- // Helper utilities extracted from the large BlueprintTools implementation
4
- // to keep the main file more focused. These are pure helpers and safe to
5
- // reuse across the class methods.
6
-
7
- export type TransformInput = {
8
- location?: unknown;
9
- rotation?: unknown;
10
- scale?: unknown;
11
- };
12
-
13
- export function toFiniteNumber(raw: unknown): number | undefined {
14
- if (typeof raw === 'number' && Number.isFinite(raw)) return raw;
15
- if (typeof raw === 'string') {
16
- const trimmed = raw.trim();
17
- if (trimmed.length === 0) return undefined;
18
- const parsed = Number(trimmed);
19
- if (Number.isFinite(parsed)) return parsed;
20
- }
21
- return undefined;
22
- }
23
-
24
- export function normalizePartialVector(value: unknown, alternateKeys: string[] = ['x', 'y', 'z']): Record<string, number> | undefined {
25
- if (value === undefined || value === null) {
26
- return undefined;
27
- }
28
-
29
- const result: Record<string, number> = {};
30
-
31
- const assignIfPresent = (component: 'x' | 'y' | 'z', raw: unknown) => {
32
- const numberValue = toFiniteNumber(raw);
33
- if (numberValue !== undefined) {
34
- result[component] = numberValue;
35
- }
36
- };
37
-
38
- if (Array.isArray(value)) {
39
- if (value.length > 0) assignIfPresent('x', value[0]);
40
- if (value.length > 1) assignIfPresent('y', value[1]);
41
- if (value.length > 2) assignIfPresent('z', value[2]);
42
- } else if (typeof value === 'object') {
43
- const obj = value as Record<string, unknown>;
44
- assignIfPresent('x', obj.x ?? obj[alternateKeys[0]]);
45
- assignIfPresent('y', obj.y ?? obj[alternateKeys[1]]);
46
- assignIfPresent('z', obj.z ?? obj[alternateKeys[2]]);
47
- } else {
48
- assignIfPresent('x', value);
49
- }
50
-
51
- return Object.keys(result).length > 0 ? result : undefined;
52
- }
53
-
54
- export function normalizeTransformInput(transform: TransformInput | undefined): Record<string, unknown> | undefined {
55
- if (!transform || typeof transform !== 'object') return undefined;
56
- const result: Record<string, unknown> = {};
57
- const location = normalizePartialVector(transform.location);
58
- if (location) result.location = location;
59
- const rotation = normalizePartialVector(transform.rotation, ['pitch', 'yaw', 'roll']);
60
- if (rotation) result.rotation = rotation;
61
- const scale = normalizePartialVector(transform.scale);
62
- if (scale) result.scale = scale;
63
- return Object.keys(result).length > 0 ? result : undefined;
64
- }
65
-
66
- export type BlueprintScsOperationInput = {
67
- type: string;
68
- componentName?: string;
69
- componentClass?: string;
70
- attachTo?: string;
71
- transform?: TransformInput;
72
- properties?: Record<string, unknown>;
73
- };
74
-
75
- export function sanitizeScsOperation(rawOperation: BlueprintScsOperationInput, index: number): { ok: true; operation: Record<string, unknown> } | { ok: false; error: string } {
76
- if (!rawOperation || typeof rawOperation !== 'object') {
77
- return { ok: false, error: `Operation at index ${index} must be an object.` };
78
- }
79
-
80
- const type = (rawOperation.type || '').toString().trim().toLowerCase();
81
- if (!type) return { ok: false, error: `Operation at index ${index} missing type.` };
82
- const operation: Record<string, unknown> = { type };
83
-
84
- // Type-safe access to properties
85
- const op = rawOperation as Record<string, any>;
86
- const componentName = op.componentName ?? op.name;
87
- const componentClass = op.componentClass ?? op.componentType ?? op.class;
88
- const attachTo = op.attachTo ?? op.parent ?? op.attach;
89
- const transform = normalizeTransformInput(op.transform);
90
- const properties = rawOperation.properties && typeof rawOperation.properties === 'object' ? rawOperation.properties : undefined;
91
-
92
- switch (type) {
93
- case 'add_component': {
94
- if (!componentName) return { ok: false, error: `add_component operation at index ${index} requires componentName.` };
95
- if (!componentClass) return { ok: false, error: `add_component operation for ${componentName} missing componentClass.` };
96
- operation.componentName = componentName;
97
- operation.componentClass = componentClass;
98
- if (attachTo) operation.attachTo = attachTo;
99
- if (transform) operation.transform = transform;
100
- if (properties) operation.properties = properties;
101
- break;
102
- }
103
- case 'remove_component': {
104
- if (!componentName) return { ok: false, error: `remove_component operation at index ${index} requires componentName.` };
105
- operation.componentName = componentName;
106
- break;
107
- }
108
- case 'set_component_properties': {
109
- if (!componentName) return { ok: false, error: `set_component_properties operation at index ${index} requires componentName.` };
110
- if (!properties) return { ok: false, error: `set_component_properties operation at index ${index} missing properties object.` };
111
- operation.componentName = componentName;
112
- operation.properties = properties;
113
- if (transform) operation.transform = transform;
114
- break;
115
- }
116
- case 'modify_component': {
117
- if (!componentName) return { ok: false, error: `modify_component operation at index ${index} requires componentName.` };
118
- if (!transform && !properties) return { ok: false, error: `modify_component operation at index ${index} requires transform or properties.` };
119
- operation.componentName = componentName;
120
- if (transform) operation.transform = transform;
121
- if (properties) operation.properties = properties;
122
- break;
123
- }
124
- case 'attach_component': {
125
- const parent = op.parentComponent ?? op.parent;
126
- if (!componentName) return { ok: false, error: `attach_component operation at index ${index} requires componentName.` };
127
- if (!parent) return { ok: false, error: `attach_component operation at index ${index} requires parentComponent.` };
128
- operation.componentName = componentName;
129
- operation.attachTo = parent;
130
- break;
131
- }
132
- default:
133
- return { ok: false, error: `Unknown SCS operation type: ${type}` };
134
- }
135
-
136
- return { ok: true, operation };
137
- }
138
-
139
- export function resolveBlueprintCandidates(rawName: string | undefined): { primary: string | undefined; candidates: string[] } {
140
- const trimmed = coerceString(rawName)?.trim();
141
- if (!trimmed) return { primary: undefined, candidates: [] };
142
-
143
- // Normalize slashes and remove duplicates (global flag fixed)
144
- const normalized = trimmed.replace(/\\/g, '/').replace(/\/+/g, '/');
145
- const withoutLeading = normalized.replace(/^\/+/, '');
146
-
147
- // Build a prioritized list using a Set to handle uniqueness automatically
148
- const candidates = new Set<string>();
149
-
150
- const add = (path: string) => {
151
- if (path && path.trim()) candidates.add(path.replace(/\/+/g, '/'));
152
- };
153
-
154
- if (normalized.includes('/')) {
155
- // Path-like input: try to guess standard content paths first
156
- const basename = withoutLeading.split('/').pop();
157
- if (basename) {
158
- add(`/Game/Blueprints/${basename}`);
159
- add(`/Game/${basename}`);
160
- }
161
- add(normalized);
162
- add(normalized.startsWith('/') ? normalized : `/${withoutLeading}`);
163
- } else {
164
- // Bare name: try standard locations
165
- add(`/Game/Blueprints/${withoutLeading}`);
166
- add(`/Game/${withoutLeading}`);
167
- add(normalized);
168
- add(`/${withoutLeading}`);
169
- }
170
-
171
- const ordered = Array.from(candidates);
172
- return { primary: ordered[0], candidates: ordered };
173
- }
174
-
175
-
176
-
177
- export function inferVariableTypeFromValue(value: unknown): string | undefined {
178
- if (value === null || value === undefined) return undefined;
179
- if (typeof value === 'boolean') return 'Bool';
180
- if (typeof value === 'number') return Number.isInteger(value) ? 'Int' : 'Float';
181
- if (typeof value === 'string') return 'String';
182
- if (Array.isArray(value)) return 'Array';
183
- if (typeof value === 'object') {
184
- const keys = Object.keys(value as Record<string, unknown>);
185
- if (keys.includes('x') && keys.includes('y') && keys.includes('z')) return 'Vector';
186
- return 'Struct';
187
- }
188
- return undefined;
189
- }
@@ -1,35 +0,0 @@
1
- import { UnrealAutomationClient } from '../src/unreal-client.js';
2
- import { runUnrealTests, assert } from './run-unreal-tool-tests.mjs';
3
-
4
- runUnrealTests('manage_blueprint_graph_events', [
5
- {
6
- name: 'Create Event Node',
7
- action: async (client) => {
8
- const blueprintPath = '/Game/Tests/Integration/BP_EventTest';
9
-
10
- // 1. Create a blueprint to test with
11
- await client.sendRequest('blueprint_create', {
12
- path: blueprintPath,
13
- parentClass: 'Actor',
14
- type: 'Normal'
15
- });
16
-
17
- // 2. Add BeginPlay event node
18
- const result = await client.sendRequest('manage_blueprint_graph', {
19
- blueprintPath: blueprintPath,
20
- graphName: 'EventGraph',
21
- subAction: 'create_node',
22
- nodeType: 'Event',
23
- eventName: 'ReceiveBeginPlay',
24
- x: 200,
25
- y: 200
26
- });
27
-
28
- assert(result.success, 'Failed to create Event node');
29
- assert(result.nodeId, 'Missing nodeId');
30
-
31
- // 3. Cleanup
32
- await client.sendRequest('delete_asset', { path: blueprintPath });
33
- }
34
- }
35
- ]);
@@ -1,38 +0,0 @@
1
- import { TestRunner } from './test-runner.mjs';
2
-
3
- const runner = new TestRunner('Extra Tools Tests');
4
-
5
- runner.addStep('Manage Logs - Subscribe', async (tools) => {
6
- const result = await tools.executeTool('manage_logs', {
7
- action: 'subscribe'
8
- });
9
- return result.success;
10
- });
11
-
12
- runner.addStep('Manage Debug - Spawn Category', async (tools) => {
13
- const result = await tools.executeTool('manage_debug', {
14
- action: 'spawn_category',
15
- category: 'AI'
16
- });
17
- return result.success;
18
- });
19
-
20
- runner.addStep('Manage Insights - Start Session', async (tools) => {
21
- const result = await tools.executeTool('manage_insights', {
22
- action: 'start_session',
23
- channels: 'cpu,gpu'
24
- });
25
- // Insights might fail if not configured/running, treat as soft pass for existence
26
- return true;
27
- });
28
-
29
- runner.addStep('Manage UI - Simulate Input', async (tools) => {
30
- const result = await tools.executeTool('manage_ui', {
31
- action: 'simulate_input',
32
- keyName: 'SpaceBar',
33
- eventType: 'Both'
34
- });
35
- return result.success;
36
- });
37
-
38
- runner.run().catch(console.error);
@@ -1,33 +0,0 @@
1
- import { TestRunner } from './test-runner.mjs';
2
-
3
- const runner = new TestRunner('Render Tools Tests');
4
- const timestamp = Date.now();
5
- const rtPath = `/Game/Tests/Render/RT_Test_${timestamp}`;
6
-
7
- runner.addStep('Create Render Target', async (tools) => {
8
- const result = await tools.executeTool('manage_render', {
9
- action: 'create_render_target',
10
- assetPath: rtPath,
11
- width: 256,
12
- height: 256,
13
- format: 'RTF_R8'
14
- });
15
- return result.success;
16
- });
17
-
18
- runner.addStep('Lumen Update Scene', async (tools) => {
19
- const result = await tools.executeTool('manage_render', {
20
- action: 'lumen_update_scene'
21
- });
22
- return result.success;
23
- });
24
-
25
- runner.addStep('Cleanup Render Target', async (tools) => {
26
- await tools.executeTool('manage_asset', {
27
- action: 'delete',
28
- assetPath: rtPath
29
- });
30
- return true;
31
- });
32
-
33
- runner.run().catch(console.error);
@@ -1,66 +0,0 @@
1
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
- import { spawn } from 'child_process';
4
- import path from 'path';
5
- import { fileURLToPath } from 'url';
6
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const SERVER_PATH = path.resolve(__dirname, '../dist/index.js');
9
-
10
- async function runTest() {
11
- const serverProcess = spawn('node', [SERVER_PATH], {
12
- stdio: ['pipe', 'pipe', 'inherit']
13
- });
14
-
15
- const transport = new StdioClientTransport({
16
- command: 'node',
17
- args: [SERVER_PATH]
18
- });
19
-
20
- const client = new Client({
21
- name: 'test-search-assets',
22
- version: '1.0.0'
23
- }, {
24
- capabilities: {}
25
- });
26
-
27
- try {
28
- await client.connect(transport);
29
- console.log('Connected to MCP server');
30
-
31
- // Test 1: Search for StaticMeshes in /Engine/BasicShapes
32
- console.log('\nTest 1: Searching for StaticMeshes in /Engine/BasicShapes...');
33
- const result1 = await client.callTool({
34
- name: 'manage_asset',
35
- arguments: {
36
- action: 'search_assets',
37
- classNames: ['StaticMesh'],
38
- packagePaths: ['/Engine/BasicShapes'],
39
- recursivePaths: true,
40
- limit: 5
41
- }
42
- });
43
- console.log('Result 1:', JSON.stringify(result1, null, 2));
44
-
45
- // Test 2: Search for Blueprints with limit
46
- console.log('\nTest 2: Searching for Blueprints (limit 2)...');
47
- const result2 = await client.callTool({
48
- name: 'manage_asset',
49
- arguments: {
50
- action: 'search_assets',
51
- classNames: ['Blueprint'],
52
- packagePaths: ['/Game'],
53
- limit: 2
54
- }
55
- });
56
- console.log('Result 2:', JSON.stringify(result2, null, 2));
57
-
58
- } catch (error) {
59
- console.error('Test failed:', error);
60
- } finally {
61
- await client.close();
62
- process.exit(0);
63
- }
64
- }
65
-
66
- runTest();