unreal-engine-mcp-server 0.5.0 → 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 (188) 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 +2 -4
  6. package/.github/workflows/release-drafter.yml +3 -2
  7. package/.github/workflows/release.yml +3 -3
  8. package/CHANGELOG.md +109 -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/automation/bridge.d.ts +1 -2
  14. package/dist/automation/bridge.js +24 -23
  15. package/dist/automation/connection-manager.d.ts +1 -0
  16. package/dist/automation/connection-manager.js +10 -0
  17. package/dist/automation/message-handler.js +5 -4
  18. package/dist/automation/request-tracker.d.ts +4 -0
  19. package/dist/automation/request-tracker.js +11 -3
  20. package/dist/config.d.ts +0 -1
  21. package/dist/config.js +0 -1
  22. package/dist/constants.d.ts +4 -0
  23. package/dist/constants.js +4 -0
  24. package/dist/graphql/loaders.d.ts +64 -0
  25. package/dist/graphql/loaders.js +117 -0
  26. package/dist/graphql/resolvers.d.ts +3 -3
  27. package/dist/graphql/resolvers.js +33 -30
  28. package/dist/graphql/server.js +3 -1
  29. package/dist/graphql/types.d.ts +2 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +13 -2
  32. package/dist/server-setup.d.ts +0 -1
  33. package/dist/server-setup.js +0 -40
  34. package/dist/tools/actors.d.ts +58 -24
  35. package/dist/tools/actors.js +22 -6
  36. package/dist/tools/assets.d.ts +19 -71
  37. package/dist/tools/assets.js +28 -22
  38. package/dist/tools/base-tool.d.ts +4 -4
  39. package/dist/tools/base-tool.js +1 -1
  40. package/dist/tools/blueprint.d.ts +45 -61
  41. package/dist/tools/blueprint.js +43 -14
  42. package/dist/tools/consolidated-tool-definitions.js +2 -1
  43. package/dist/tools/consolidated-tool-handlers.js +96 -110
  44. package/dist/tools/dynamic-handler-registry.d.ts +11 -9
  45. package/dist/tools/dynamic-handler-registry.js +17 -95
  46. package/dist/tools/editor.d.ts +19 -193
  47. package/dist/tools/editor.js +11 -2
  48. package/dist/tools/environment.d.ts +8 -14
  49. package/dist/tools/foliage.d.ts +18 -143
  50. package/dist/tools/foliage.js +4 -2
  51. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  52. package/dist/tools/handlers/actor-handlers.js +14 -13
  53. package/dist/tools/handlers/asset-handlers.js +454 -454
  54. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  55. package/dist/tools/handlers/sequence-handlers.js +24 -13
  56. package/dist/tools/introspection.d.ts +1 -1
  57. package/dist/tools/introspection.js +1 -1
  58. package/dist/tools/landscape.d.ts +16 -116
  59. package/dist/tools/landscape.js +7 -3
  60. package/dist/tools/level.d.ts +22 -103
  61. package/dist/tools/level.js +26 -18
  62. package/dist/tools/lighting.d.ts +54 -7
  63. package/dist/tools/lighting.js +9 -5
  64. package/dist/tools/materials.d.ts +1 -1
  65. package/dist/tools/materials.js +5 -1
  66. package/dist/tools/niagara.js +37 -2
  67. package/dist/tools/performance.d.ts +0 -1
  68. package/dist/tools/performance.js +0 -1
  69. package/dist/tools/physics.js +5 -1
  70. package/dist/tools/sequence.d.ts +24 -24
  71. package/dist/tools/sequence.js +13 -0
  72. package/dist/tools/ui.d.ts +0 -2
  73. package/dist/types/automation-responses.d.ts +115 -0
  74. package/dist/types/automation-responses.js +2 -0
  75. package/dist/types/responses.d.ts +249 -0
  76. package/dist/types/responses.js +2 -0
  77. package/dist/types/tool-interfaces.d.ts +135 -135
  78. package/dist/types/tool-types.d.ts +2 -0
  79. package/dist/unreal-bridge.js +4 -4
  80. package/dist/utils/command-validator.js +7 -5
  81. package/dist/utils/error-handler.d.ts +24 -2
  82. package/dist/utils/error-handler.js +58 -23
  83. package/dist/utils/normalize.d.ts +7 -4
  84. package/dist/utils/normalize.js +12 -10
  85. package/dist/utils/path-security.d.ts +2 -0
  86. package/dist/utils/path-security.js +24 -0
  87. package/dist/utils/response-factory.d.ts +4 -4
  88. package/dist/utils/response-factory.js +15 -21
  89. package/dist/utils/response-validator.js +88 -73
  90. package/dist/utils/unreal-command-queue.d.ts +2 -0
  91. package/dist/utils/unreal-command-queue.js +8 -1
  92. package/docs/Migration-Guide-v0.5.0.md +1 -9
  93. package/docs/handler-mapping.md +4 -2
  94. package/docs/testing-guide.md +2 -2
  95. package/package.json +12 -6
  96. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  97. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  98. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  99. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  100. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  101. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  102. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  103. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  104. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  105. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  106. package/scripts/run-all-tests.mjs +25 -20
  107. package/server.json +3 -2
  108. package/src/automation/bridge.ts +27 -25
  109. package/src/automation/connection-manager.ts +18 -0
  110. package/src/automation/message-handler.ts +33 -8
  111. package/src/automation/request-tracker.ts +39 -7
  112. package/src/config.ts +1 -1
  113. package/src/constants.ts +7 -0
  114. package/src/graphql/loaders.ts +244 -0
  115. package/src/graphql/resolvers.ts +47 -49
  116. package/src/graphql/server.ts +3 -1
  117. package/src/graphql/types.ts +3 -0
  118. package/src/index.ts +15 -2
  119. package/src/resources/assets.ts +5 -4
  120. package/src/server/tool-registry.ts +3 -3
  121. package/src/server-setup.ts +3 -37
  122. package/src/tools/actors.ts +77 -44
  123. package/src/tools/animation.ts +1 -0
  124. package/src/tools/assets.ts +76 -65
  125. package/src/tools/base-tool.ts +3 -3
  126. package/src/tools/blueprint.ts +170 -104
  127. package/src/tools/consolidated-tool-definitions.ts +2 -1
  128. package/src/tools/consolidated-tool-handlers.ts +129 -150
  129. package/src/tools/dynamic-handler-registry.ts +22 -140
  130. package/src/tools/editor.ts +43 -29
  131. package/src/tools/environment.ts +21 -27
  132. package/src/tools/foliage.ts +28 -25
  133. package/src/tools/handlers/actor-handlers.ts +16 -17
  134. package/src/tools/handlers/asset-handlers.ts +484 -484
  135. package/src/tools/handlers/sequence-handlers.ts +85 -62
  136. package/src/tools/introspection.ts +7 -7
  137. package/src/tools/landscape.ts +34 -28
  138. package/src/tools/level.ts +100 -80
  139. package/src/tools/lighting.ts +25 -20
  140. package/src/tools/materials.ts +9 -3
  141. package/src/tools/niagara.ts +44 -2
  142. package/src/tools/performance.ts +1 -2
  143. package/src/tools/physics.ts +7 -1
  144. package/src/tools/sequence.ts +42 -26
  145. package/src/tools/ui.ts +1 -3
  146. package/src/types/automation-responses.ts +119 -0
  147. package/src/types/responses.ts +355 -0
  148. package/src/types/tool-interfaces.ts +135 -135
  149. package/src/types/tool-types.ts +4 -0
  150. package/src/unreal-bridge.ts +71 -26
  151. package/src/utils/command-validator.ts +47 -5
  152. package/src/utils/error-handler.ts +128 -45
  153. package/src/utils/normalize.test.ts +162 -0
  154. package/src/utils/normalize.ts +38 -16
  155. package/src/utils/path-security.ts +43 -0
  156. package/src/utils/response-factory.ts +29 -24
  157. package/src/utils/response-validator.ts +103 -87
  158. package/src/utils/safe-json.test.ts +90 -0
  159. package/src/utils/unreal-command-queue.ts +13 -1
  160. package/src/utils/validation.test.ts +184 -0
  161. package/tests/test-animation.mjs +358 -33
  162. package/tests/test-asset-graph.mjs +311 -0
  163. package/tests/test-audio.mjs +314 -116
  164. package/tests/test-behavior-tree.mjs +327 -144
  165. package/tests/test-blueprint-graph.mjs +343 -12
  166. package/tests/test-control-editor.mjs +85 -53
  167. package/tests/test-graphql.mjs +58 -8
  168. package/tests/test-input.mjs +349 -0
  169. package/tests/test-inspect.mjs +291 -61
  170. package/tests/test-landscape.mjs +304 -48
  171. package/tests/test-lighting.mjs +428 -0
  172. package/tests/test-manage-level.mjs +70 -51
  173. package/tests/test-performance.mjs +539 -0
  174. package/tests/test-sequence.mjs +82 -46
  175. package/tests/test-system.mjs +72 -33
  176. package/tests/test-wasm.mjs +98 -8
  177. package/vitest.config.ts +35 -0
  178. package/.github/release-drafter.yml +0 -148
  179. package/dist/prompts/index.d.ts +0 -21
  180. package/dist/prompts/index.js +0 -217
  181. package/dist/tools/blueprint/helpers.d.ts +0 -29
  182. package/dist/tools/blueprint/helpers.js +0 -182
  183. package/src/prompts/index.ts +0 -249
  184. package/src/tools/blueprint/helpers.ts +0 -189
  185. package/tests/test-blueprint-events.mjs +0 -35
  186. package/tests/test-extra-tools.mjs +0 -38
  187. package/tests/test-render.mjs +0 -33
  188. package/tests/test-search-assets.mjs +0 -66
@@ -92,7 +92,7 @@ export class MessageHandler {
92
92
  const synthetic = {
93
93
  type: 'automation_response',
94
94
  requestId: reqId,
95
- success: evtSuccess !== undefined ? evtSuccess : (baseSuccess !== undefined ? baseSuccess : undefined),
95
+ success: evtSuccess !== undefined ? evtSuccess : baseSuccess,
96
96
  message: typeof evt.result?.message === 'string' ? evt.result.message : (typeof evt.message === 'string' ? evt.message : FStringSafe(evt.event)),
97
97
  error: typeof evt.result?.error === 'string' ? evt.result.error : undefined,
98
98
  result: evt.result ?? evt.payload ?? undefined
@@ -113,8 +113,9 @@ export class MessageHandler {
113
113
  const expected = (expectedAction || '').toString().toLowerCase();
114
114
  const echoed = (() => {
115
115
  const r = response;
116
- const candidate = (typeof r.action === 'string' && r.action) || (typeof r.result?.action === 'string' && r.result.action);
117
- return candidate;
116
+ const resultObj = response.result;
117
+ const candidate = (typeof r.action === 'string' && r.action) || (typeof resultObj?.action === 'string' && resultObj.action);
118
+ return candidate || undefined;
118
119
  })();
119
120
  if (expected && echoed && typeof echoed === 'string') {
120
121
  const got = echoed.toLowerCase();
@@ -141,7 +142,7 @@ export class MessageHandler {
141
142
  }
142
143
  }
143
144
  catch (e) {
144
- this.log.debug('enforceActionMatch check skipped', e);
145
+ this.log.debug('enforceActionMatch check skipped', e instanceof Error ? e.message : String(e));
145
146
  }
146
147
  return response;
147
148
  }
@@ -3,7 +3,11 @@ export declare class RequestTracker {
3
3
  private maxPendingRequests;
4
4
  private pendingRequests;
5
5
  private coalescedRequests;
6
+ private lastRequestSentAt?;
6
7
  constructor(maxPendingRequests: number);
8
+ getMaxPendingRequests(): number;
9
+ getLastRequestSentAt(): Date | undefined;
10
+ updateLastRequestSentAt(): void;
7
11
  createRequest(action: string, payload: Record<string, unknown>, timeoutMs: number): {
8
12
  requestId: string;
9
13
  promise: Promise<AutomationBridgeResponseMessage>;
@@ -1,18 +1,26 @@
1
1
  import { randomUUID, createHash } from 'node:crypto';
2
- const WAIT_FOR_EVENT_ACTIONS = new Set([]);
3
2
  export class RequestTracker {
4
3
  maxPendingRequests;
5
4
  pendingRequests = new Map();
6
5
  coalescedRequests = new Map();
6
+ lastRequestSentAt;
7
7
  constructor(maxPendingRequests) {
8
8
  this.maxPendingRequests = maxPendingRequests;
9
9
  }
10
+ getMaxPendingRequests() {
11
+ return this.maxPendingRequests;
12
+ }
13
+ getLastRequestSentAt() {
14
+ return this.lastRequestSentAt;
15
+ }
16
+ updateLastRequestSentAt() {
17
+ this.lastRequestSentAt = new Date();
18
+ }
10
19
  createRequest(action, payload, timeoutMs) {
11
20
  if (this.pendingRequests.size >= this.maxPendingRequests) {
12
21
  throw new Error(`Max pending requests limit reached (${this.maxPendingRequests})`);
13
22
  }
14
23
  const requestId = randomUUID();
15
- const waitForEvent = WAIT_FOR_EVENT_ACTIONS.has(action);
16
24
  const promise = new Promise((resolve, reject) => {
17
25
  const timeout = setTimeout(() => {
18
26
  if (this.pendingRequests.has(requestId)) {
@@ -27,7 +35,7 @@ export class RequestTracker {
27
35
  action,
28
36
  payload,
29
37
  requestedAt: new Date(),
30
- waitForEvent,
38
+ waitForEvent: false,
31
39
  eventTimeoutMs: timeoutMs
32
40
  });
33
41
  });
package/dist/config.d.ts CHANGED
@@ -14,7 +14,6 @@ export declare const EnvSchema: z.ZodObject<{
14
14
  MCP_ROUTE_STDOUT_LOGS: z.ZodPipe<z.ZodTransform<boolean, unknown>, z.ZodDefault<z.ZodBoolean>>;
15
15
  UE_PROJECT_PATH: z.ZodOptional<z.ZodString>;
16
16
  UE_EDITOR_EXE: z.ZodOptional<z.ZodString>;
17
- UE_SCREENSHOT_DIR: z.ZodOptional<z.ZodString>;
18
17
  MCP_AUTOMATION_PORT: z.ZodPipe<z.ZodTransform<number, unknown>, z.ZodDefault<z.ZodNumber>>;
19
18
  MCP_AUTOMATION_HOST: z.ZodDefault<z.ZodString>;
20
19
  MCP_AUTOMATION_CLIENT_MODE: z.ZodPipe<z.ZodTransform<boolean, unknown>, z.ZodDefault<z.ZodBoolean>>;
package/dist/config.js CHANGED
@@ -34,7 +34,6 @@ export const EnvSchema = z.object({
34
34
  MCP_ROUTE_STDOUT_LOGS: z.preprocess(stringToBoolean, z.boolean().default(true)),
35
35
  UE_PROJECT_PATH: z.string().optional(),
36
36
  UE_EDITOR_EXE: z.string().optional(),
37
- UE_SCREENSHOT_DIR: z.string().optional(),
38
37
  MCP_AUTOMATION_PORT: z.preprocess((v) => stringToNumber(v, 8091), z.number().default(8091)),
39
38
  MCP_AUTOMATION_HOST: z.string().default('127.0.0.1'),
40
39
  MCP_AUTOMATION_CLIENT_MODE: z.preprocess(stringToBoolean, z.boolean().default(false)),
@@ -9,4 +9,8 @@ export declare const DEFAULT_TIME_OF_DAY = 9;
9
9
  export declare const DEFAULT_SUN_INTENSITY = 10000;
10
10
  export declare const DEFAULT_SKYLIGHT_INTENSITY = 1;
11
11
  export declare const DEFAULT_SCREENSHOT_RESOLUTION = "1920x1080";
12
+ export declare const DEFAULT_OPERATION_TIMEOUT_MS = 30000;
13
+ export declare const DEFAULT_ASSET_OP_TIMEOUT_MS = 60000;
14
+ export declare const EXTENDED_ASSET_OP_TIMEOUT_MS = 120000;
15
+ export declare const LONG_RUNNING_OP_TIMEOUT_MS = 300000;
12
16
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -9,4 +9,8 @@ export const DEFAULT_TIME_OF_DAY = 9;
9
9
  export const DEFAULT_SUN_INTENSITY = 10000;
10
10
  export const DEFAULT_SKYLIGHT_INTENSITY = 1;
11
11
  export const DEFAULT_SCREENSHOT_RESOLUTION = '1920x1080';
12
+ export const DEFAULT_OPERATION_TIMEOUT_MS = 30000;
13
+ export const DEFAULT_ASSET_OP_TIMEOUT_MS = 60000;
14
+ export const EXTENDED_ASSET_OP_TIMEOUT_MS = 120000;
15
+ export const LONG_RUNNING_OP_TIMEOUT_MS = 300000;
12
16
  //# sourceMappingURL=constants.js.map
@@ -0,0 +1,64 @@
1
+ import DataLoader from 'dataloader';
2
+ import type { AutomationBridge } from '../automation/index.js';
3
+ export interface Actor {
4
+ name: string;
5
+ label?: string;
6
+ class?: string;
7
+ path?: string;
8
+ location?: {
9
+ x: number;
10
+ y: number;
11
+ z: number;
12
+ };
13
+ rotation?: {
14
+ pitch: number;
15
+ yaw: number;
16
+ roll: number;
17
+ };
18
+ scale?: {
19
+ x: number;
20
+ y: number;
21
+ z: number;
22
+ };
23
+ tags?: string[];
24
+ }
25
+ export interface Asset {
26
+ name: string;
27
+ path: string;
28
+ class?: string;
29
+ packagePath?: string;
30
+ size?: number;
31
+ }
32
+ export interface Blueprint {
33
+ name: string;
34
+ path: string;
35
+ parentClass?: string;
36
+ variables?: Array<{
37
+ name: string;
38
+ type: string;
39
+ defaultValue?: unknown;
40
+ }>;
41
+ functions?: Array<{
42
+ name: string;
43
+ parameters?: Array<{
44
+ name: string;
45
+ type: string;
46
+ }>;
47
+ }>;
48
+ components?: Array<{
49
+ name: string;
50
+ type: string;
51
+ }>;
52
+ }
53
+ export interface GraphQLLoaders {
54
+ actorLoader: DataLoader<string, Actor | null>;
55
+ assetLoader: DataLoader<string, Asset | null>;
56
+ blueprintLoader: DataLoader<string, Blueprint | null>;
57
+ actorComponentsLoader: DataLoader<string, Array<{
58
+ name: string;
59
+ type: string;
60
+ }> | null>;
61
+ }
62
+ export declare function createLoaders(automationBridge: AutomationBridge): GraphQLLoaders;
63
+ export declare function clearLoaders(loaders: GraphQLLoaders): void;
64
+ //# sourceMappingURL=loaders.d.ts.map
@@ -0,0 +1,117 @@
1
+ import DataLoader from 'dataloader';
2
+ import { Logger } from '../utils/logger.js';
3
+ const log = new Logger('GraphQL:Loaders');
4
+ export function createLoaders(automationBridge) {
5
+ return {
6
+ actorLoader: new DataLoader(async (names) => {
7
+ log.debug(`Batching actor fetch for ${names.length} actors`);
8
+ try {
9
+ const result = await automationBridge.sendAutomationRequest('control_actor', {
10
+ action: 'batch_get',
11
+ actorNames: [...names]
12
+ });
13
+ if (result.success && result.actors) {
14
+ return names.map(name => result.actors?.find(a => a.name === name || a.label === name) ?? null);
15
+ }
16
+ }
17
+ catch {
18
+ log.debug('Batch fetch not supported, falling back to individual fetches');
19
+ }
20
+ const results = await Promise.all(names.map(async (name) => {
21
+ try {
22
+ const result = await automationBridge.sendAutomationRequest('control_actor', {
23
+ action: 'find_by_name',
24
+ actorName: name
25
+ });
26
+ return result.success ? (result.actor ?? null) : null;
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }));
32
+ return results;
33
+ }, {
34
+ cache: true,
35
+ maxBatchSize: 50
36
+ }),
37
+ assetLoader: new DataLoader(async (paths) => {
38
+ log.debug(`Batching asset fetch for ${paths.length} assets`);
39
+ try {
40
+ const result = await automationBridge.sendAutomationRequest('manage_asset', {
41
+ action: 'batch_get',
42
+ assetPaths: [...paths]
43
+ });
44
+ if (result.success && result.assets) {
45
+ return paths.map(path => result.assets?.find(a => a.path === path) ?? null);
46
+ }
47
+ }
48
+ catch {
49
+ log.debug('Batch asset fetch not supported');
50
+ }
51
+ const results = await Promise.all(paths.map(async (path) => {
52
+ try {
53
+ const result = await automationBridge.sendAutomationRequest('manage_asset', {
54
+ action: 'exists',
55
+ assetPath: path
56
+ });
57
+ if (result.success && result.exists) {
58
+ return result.asset ?? { name: path.split('/').pop() || '', path };
59
+ }
60
+ return null;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }));
66
+ return results;
67
+ }, {
68
+ cache: true,
69
+ maxBatchSize: 100
70
+ }),
71
+ blueprintLoader: new DataLoader(async (paths) => {
72
+ log.debug(`Batching blueprint fetch for ${paths.length} blueprints`);
73
+ const results = await Promise.all(paths.map(async (path) => {
74
+ try {
75
+ const result = await automationBridge.sendAutomationRequest('manage_blueprint', {
76
+ action: 'get_blueprint',
77
+ blueprintPath: path
78
+ });
79
+ return result.success ? (result.blueprint ?? null) : null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }));
85
+ return results;
86
+ }, {
87
+ cache: true,
88
+ maxBatchSize: 20
89
+ }),
90
+ actorComponentsLoader: new DataLoader(async (actorNames) => {
91
+ log.debug(`Batching component fetch for ${actorNames.length} actors`);
92
+ const results = await Promise.all(actorNames.map(async (actorName) => {
93
+ try {
94
+ const result = await automationBridge.sendAutomationRequest('control_actor', {
95
+ action: 'get_components',
96
+ actorName
97
+ });
98
+ return result.success ? (result.components ?? null) : null;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }));
104
+ return results;
105
+ }, {
106
+ cache: true,
107
+ maxBatchSize: 30
108
+ })
109
+ };
110
+ }
111
+ export function clearLoaders(loaders) {
112
+ loaders.actorLoader.clearAll();
113
+ loaders.assetLoader.clearAll();
114
+ loaders.blueprintLoader.clearAll();
115
+ loaders.actorComponentsLoader.clearAll();
116
+ }
117
+ //# sourceMappingURL=loaders.js.map
@@ -100,7 +100,7 @@ export declare const resolvers: {
100
100
  }>;
101
101
  asset: (_: any, { path }: {
102
102
  path: string;
103
- }, context: GraphQLContext) => Promise<{} | null>;
103
+ }, context: GraphQLContext) => Promise<import("./loaders.js").Asset | null>;
104
104
  actors: (_: any, args: any, context: GraphQLContext) => Promise<{
105
105
  edges: {
106
106
  node: Actor;
@@ -116,7 +116,7 @@ export declare const resolvers: {
116
116
  }>;
117
117
  actor: (_: any, { name }: {
118
118
  name: string;
119
- }, context: GraphQLContext) => Promise<Actor | null>;
119
+ }, context: GraphQLContext) => Promise<import("./loaders.js").Actor | null>;
120
120
  blueprints: (_: any, args: any, context: GraphQLContext) => Promise<{
121
121
  edges: {
122
122
  node: Blueprint;
@@ -141,7 +141,7 @@ export declare const resolvers: {
141
141
  }>;
142
142
  blueprint: (_: any, { path }: {
143
143
  path: string;
144
- }, context: GraphQLContext) => Promise<Blueprint | null>;
144
+ }, context: GraphQLContext) => Promise<import("./loaders.js").Blueprint | null>;
145
145
  levels: (_: any, __: any, context: GraphQLContext) => Promise<any>;
146
146
  currentLevel: (_: any, __: any, context: GraphQLContext) => Promise<{} | null>;
147
147
  materials: (_: any, args: any, context: GraphQLContext) => Promise<{
@@ -87,6 +87,24 @@ export const scalarResolvers = {
87
87
  }
88
88
  }
89
89
  };
90
+ import { Logger } from '../utils/logger.js';
91
+ const log = new Logger('GraphQL:Resolvers');
92
+ class GraphQLResolverError extends Error {
93
+ extensions;
94
+ constructor(message, code = 'UNREAL_ENGINE_ERROR', originalError) {
95
+ super(message);
96
+ this.name = 'GraphQLResolverError';
97
+ this.extensions = {
98
+ code,
99
+ originalError: originalError?.message
100
+ };
101
+ }
102
+ }
103
+ function createResolverError(operation, error) {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ log.error(`${operation} failed:`, message);
106
+ return new GraphQLResolverError(`${operation} failed: ${message}`, 'UNREAL_ENGINE_ERROR', error instanceof Error ? error : undefined);
107
+ }
90
108
  function logAutomationFailure(source, response) {
91
109
  try {
92
110
  if (!response || response.success !== false) {
@@ -96,7 +114,7 @@ function logAutomationFailure(source, response) {
96
114
  if (errorText.length === 0) {
97
115
  return;
98
116
  }
99
- console.error(`[GraphQL] ${source} automation failure:`, errorText);
117
+ log.error(`${source} automation failure:`, errorText);
100
118
  }
101
119
  catch {
102
120
  }
@@ -111,7 +129,7 @@ async function getActorProperties(bridge, actorName) {
111
129
  return result.success ? result.value || {} : {};
112
130
  }
113
131
  catch (error) {
114
- console.error('Failed to get actor properties:', error);
132
+ log.error('Failed to get actor properties:', error);
115
133
  return {};
116
134
  }
117
135
  }
@@ -129,12 +147,12 @@ async function listAssets(automationBridge, filter, pagination) {
129
147
  };
130
148
  }
131
149
  logAutomationFailure('list_assets', response);
132
- console.error('Failed to list assets:', response);
150
+ log.warn('Failed to list assets - returning empty set');
133
151
  return { assets: [], totalCount: 0 };
134
152
  }
135
153
  catch (error) {
136
- console.error('Failed to list assets:', error);
137
- return { assets: [], totalCount: 0 };
154
+ log.error('Failed to list assets:', error);
155
+ throw createResolverError('listAssets', error);
138
156
  }
139
157
  }
140
158
  async function listActors(automationBridge, filter) {
@@ -156,22 +174,6 @@ async function listActors(automationBridge, filter) {
156
174
  return { actors: [] };
157
175
  }
158
176
  }
159
- async function getBlueprint(automationBridge, blueprintPath) {
160
- try {
161
- const response = await automationBridge.sendAutomationRequest('get_blueprint', {
162
- blueprintPath
163
- }, { timeoutMs: 30000 });
164
- if (response.success && response.result) {
165
- return response.result;
166
- }
167
- logAutomationFailure('get_blueprint', response);
168
- return null;
169
- }
170
- catch (error) {
171
- console.error('Failed to get blueprint:', error);
172
- return null;
173
- }
174
- }
175
177
  export const resolvers = {
176
178
  Query: {
177
179
  assets: async (_, args, context) => {
@@ -194,11 +196,10 @@ export const resolvers = {
194
196
  },
195
197
  asset: async (_, { path }, context) => {
196
198
  try {
197
- const response = await context.automationBridge.sendAutomationRequest('get_asset', { assetPath: path }, { timeoutMs: 10000 });
198
- if (response.success && response.result) {
199
- return response.result;
199
+ if (!context.loaders) {
200
+ throw new Error('Loaders not initialized');
200
201
  }
201
- return null;
202
+ return await context.loaders.assetLoader.load(path);
202
203
  }
203
204
  catch (error) {
204
205
  console.error('Failed to get asset:', error);
@@ -228,11 +229,10 @@ export const resolvers = {
228
229
  },
229
230
  actor: async (_, { name }, context) => {
230
231
  try {
231
- const actors = await listActors(context.automationBridge, { tag: name });
232
- if (actors.actors.length > 0) {
233
- return actors.actors[0];
232
+ if (!context.loaders) {
233
+ throw new Error('Loaders not initialized');
234
234
  }
235
- return null;
235
+ return await context.loaders.actorLoader.load(name);
236
236
  }
237
237
  catch (error) {
238
238
  console.error('Failed to get actor:', error);
@@ -279,7 +279,10 @@ export const resolvers = {
279
279
  }
280
280
  },
281
281
  blueprint: async (_, { path }, context) => {
282
- return await getBlueprint(context.automationBridge, path);
282
+ if (!context.loaders) {
283
+ throw new Error('Loaders not initialized');
284
+ }
285
+ return await context.loaders.blueprintLoader.load(path);
283
286
  },
284
287
  levels: async (_, __, context) => {
285
288
  try {
@@ -2,6 +2,7 @@ import { createYoga } from 'graphql-yoga';
2
2
  import { createServer } from 'http';
3
3
  import { Logger } from '../utils/logger.js';
4
4
  import { createGraphQLSchema } from './schema.js';
5
+ import { createLoaders } from './loaders.js';
5
6
  export class GraphQLServer {
6
7
  log = new Logger('GraphQLServer');
7
8
  server = null;
@@ -40,7 +41,8 @@ export class GraphQLServer {
40
41
  },
41
42
  context: () => ({
42
43
  bridge: this.bridge,
43
- automationBridge: this.automationBridge
44
+ automationBridge: this.automationBridge,
45
+ loaders: createLoaders(this.automationBridge)
44
46
  }),
45
47
  logging: {
46
48
  debug: (...args) => this.log.debug('[GraphQL]', ...args),
@@ -1,7 +1,9 @@
1
1
  import type { UnrealBridge } from '../unreal-bridge.js';
2
2
  import { AutomationBridge } from '../automation/index.js';
3
+ import type { GraphQLLoaders } from './loaders.js';
3
4
  export interface GraphQLContext {
4
5
  bridge: UnrealBridge;
5
6
  automationBridge: AutomationBridge;
7
+ loaders?: GraphQLLoaders;
6
8
  }
7
9
  //# sourceMappingURL=types.d.ts.map
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { UnrealBridge } from './unreal-bridge.js';
3
3
  import { AutomationBridge } from './automation/index.js';
4
4
  import { z } from 'zod';
5
+ import { GraphQLServer } from './graphql/server.js';
5
6
  export declare function createServer(): {
6
7
  server: Server<{
7
8
  method: string;
@@ -39,6 +40,7 @@ export declare function createServer(): {
39
40
  }>;
40
41
  bridge: UnrealBridge;
41
42
  automationBridge: AutomationBridge;
43
+ graphqlServer: GraphQLServer;
42
44
  };
43
45
  export declare const configSchema: z.ZodObject<{
44
46
  logLevel: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { HealthMonitor } from './services/health-monitor.js';
12
12
  import { ServerSetup } from './server-setup.js';
13
13
  import { startMetricsServer } from './services/metrics-server.js';
14
14
  import { config } from './config.js';
15
+ import { GraphQLServer } from './graphql/server.js';
15
16
  const require = createRequire(import.meta.url);
16
17
  const packageInfo = (() => {
17
18
  try {
@@ -81,6 +82,10 @@ export function createServer() {
81
82
  log.error('Automation bridge error', error);
82
83
  });
83
84
  startMetricsServer({ healthMonitor, automationBridge, logger: log });
85
+ const graphqlServer = new GraphQLServer(bridge, automationBridge);
86
+ graphqlServer.start().catch((error) => {
87
+ log.warn('GraphQL server failed to start:', error);
88
+ });
84
89
  log.debug('Initializing WebAssembly integration...');
85
90
  initializeWASM().then(() => {
86
91
  log.info('✅ WebAssembly integration initialized (JSON parsing and math operations)');
@@ -109,7 +114,7 @@ export function createServer() {
109
114
  });
110
115
  const serverSetup = new ServerSetup(server, bridge, automationBridge, log, healthMonitor);
111
116
  serverSetup.setup();
112
- return { server, bridge, automationBridge };
117
+ return { server, bridge, automationBridge, graphqlServer };
113
118
  }
114
119
  export const configSchema = z.object({
115
120
  logLevel: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info').describe('Runtime log level'),
@@ -131,7 +136,7 @@ export default function createServerDefault({ config } = {}) {
131
136
  return server;
132
137
  }
133
138
  export async function startStdioServer() {
134
- const { server, automationBridge } = createServer();
139
+ const { server, automationBridge, graphqlServer } = createServer();
135
140
  const transport = new StdioServerTransport();
136
141
  let shuttingDown = false;
137
142
  const handleShutdown = async (signal) => {
@@ -147,6 +152,12 @@ export async function startStdioServer() {
147
152
  catch (error) {
148
153
  log.warn('Failed to stop automation bridge cleanly', error);
149
154
  }
155
+ try {
156
+ await graphqlServer.stop();
157
+ }
158
+ catch (error) {
159
+ log.warn('Failed to stop GraphQL server cleanly', error);
160
+ }
150
161
  try {
151
162
  if (typeof server.close === 'function') {
152
163
  await server.close();
@@ -16,6 +16,5 @@ export declare class ServerSetup {
16
16
  setup(): Promise<void>;
17
17
  private validateEnvironment;
18
18
  private ensureConnectedOnDemand;
19
- private registerPrompts;
20
19
  }
21
20
  //# sourceMappingURL=server-setup.d.ts.map
@@ -1,5 +1,3 @@
1
- import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
- import { prompts } from './prompts/index.js';
3
1
  import { AssetResources } from './resources/assets.js';
4
2
  import { ActorResources } from './resources/actors.js';
5
3
  import { LevelResources } from './resources/levels.js';
@@ -32,7 +30,6 @@ export class ServerSetup {
32
30
  resourceRegistry.register();
33
31
  const toolRegistry = new ToolRegistry(this.server, this.bridge, this.automationBridge, this.logger, this.healthMonitor, this.assetResources, this.actorResources, this.levelResources, ensureConnected);
34
32
  toolRegistry.register();
35
- this.registerPrompts();
36
33
  }
37
34
  validateEnvironment() {
38
35
  const projectPath = process.env.UE_PROJECT_PATH;
@@ -70,42 +67,5 @@ export class ServerSetup {
70
67
  }
71
68
  return ok;
72
69
  }
73
- registerPrompts() {
74
- this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
75
- return {
76
- prompts: prompts.map(p => ({
77
- name: p.name,
78
- description: p.description,
79
- arguments: Object.entries(p.arguments || {}).map(([name, schema]) => {
80
- const meta = {};
81
- if (schema.type)
82
- meta.type = schema.type;
83
- if (schema.enum)
84
- meta.enum = schema.enum;
85
- if (schema.default !== undefined)
86
- meta.default = schema.default;
87
- return {
88
- name,
89
- description: schema.description,
90
- required: schema.required ?? false,
91
- ...(Object.keys(meta).length ? { _meta: meta } : {})
92
- };
93
- })
94
- }))
95
- };
96
- });
97
- this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
98
- const prompt = prompts.find(p => p.name === request.params.name);
99
- if (!prompt) {
100
- throw new Error(`Unknown prompt: ${request.params.name}`);
101
- }
102
- const args = (request.params.arguments || {});
103
- const messages = prompt.build(args);
104
- return {
105
- description: prompt.description,
106
- messages
107
- };
108
- });
109
- }
110
70
  }
111
71
  //# sourceMappingURL=server-setup.js.map