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.
- 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 +2 -4
- package/.github/workflows/release-drafter.yml +3 -2
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +109 -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/automation/bridge.d.ts +1 -2
- package/dist/automation/bridge.js +24 -23
- package/dist/automation/connection-manager.d.ts +1 -0
- package/dist/automation/connection-manager.js +10 -0
- package/dist/automation/message-handler.js +5 -4
- package/dist/automation/request-tracker.d.ts +4 -0
- package/dist/automation/request-tracker.js +11 -3
- 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 +58 -24
- package/dist/tools/actors.js +22 -6
- 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 +45 -61
- package/dist/tools/blueprint.js +43 -14
- package/dist/tools/consolidated-tool-definitions.js +2 -1
- 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 +11 -2
- 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.d.ts +1 -1
- package/dist/tools/handlers/actor-handlers.js +14 -13
- package/dist/tools/handlers/asset-handlers.js +454 -454
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/sequence-handlers.js +24 -13
- package/dist/tools/introspection.d.ts +1 -1
- package/dist/tools/introspection.js +1 -1
- 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 +26 -18
- package/dist/tools/lighting.d.ts +54 -7
- package/dist/tools/lighting.js +9 -5
- package/dist/tools/materials.d.ts +1 -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/types/tool-types.d.ts +2 -0
- package/dist/unreal-bridge.js +4 -4
- package/dist/utils/command-validator.js +7 -5
- package/dist/utils/error-handler.d.ts +24 -2
- package/dist/utils/error-handler.js +58 -23
- package/dist/utils/normalize.d.ts +7 -4
- package/dist/utils/normalize.js +12 -10
- 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/dist/utils/response-validator.js +88 -73
- package/dist/utils/unreal-command-queue.d.ts +2 -0
- package/dist/utils/unreal-command-queue.js +8 -1
- package/docs/Migration-Guide-v0.5.0.md +1 -9
- package/docs/handler-mapping.md +4 -2
- package/docs/testing-guide.md +2 -2
- package/package.json +12 -6
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
- package/scripts/run-all-tests.mjs +25 -20
- package/server.json +3 -2
- package/src/automation/bridge.ts +27 -25
- package/src/automation/connection-manager.ts +18 -0
- package/src/automation/message-handler.ts +33 -8
- package/src/automation/request-tracker.ts +39 -7
- 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/tool-registry.ts +3 -3
- package/src/server-setup.ts +3 -37
- package/src/tools/actors.ts +77 -44
- package/src/tools/animation.ts +1 -0
- package/src/tools/assets.ts +76 -65
- package/src/tools/base-tool.ts +3 -3
- package/src/tools/blueprint.ts +170 -104
- package/src/tools/consolidated-tool-definitions.ts +2 -1
- package/src/tools/consolidated-tool-handlers.ts +129 -150
- package/src/tools/dynamic-handler-registry.ts +22 -140
- package/src/tools/editor.ts +43 -29
- package/src/tools/environment.ts +21 -27
- package/src/tools/foliage.ts +28 -25
- package/src/tools/handlers/actor-handlers.ts +16 -17
- package/src/tools/handlers/asset-handlers.ts +484 -484
- package/src/tools/handlers/sequence-handlers.ts +85 -62
- package/src/tools/introspection.ts +7 -7
- package/src/tools/landscape.ts +34 -28
- package/src/tools/level.ts +100 -80
- package/src/tools/lighting.ts +25 -20
- package/src/tools/materials.ts +9 -3
- 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 +42 -26
- package/src/tools/ui.ts +1 -3
- 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/types/tool-types.ts +4 -0
- package/src/unreal-bridge.ts +71 -26
- package/src/utils/command-validator.ts +47 -5
- package/src/utils/error-handler.ts +128 -45
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/normalize.ts +38 -16
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +29 -24
- package/src/utils/response-validator.ts +103 -87
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/unreal-command-queue.ts +13 -1
- 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
|
@@ -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 :
|
|
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
|
|
117
|
-
|
|
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)),
|
package/dist/constants.d.ts
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
+
log.warn('Failed to list assets - returning empty set');
|
|
133
151
|
return { assets: [], totalCount: 0 };
|
|
134
152
|
}
|
|
135
153
|
catch (error) {
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
return response.result;
|
|
199
|
+
if (!context.loaders) {
|
|
200
|
+
throw new Error('Loaders not initialized');
|
|
200
201
|
}
|
|
201
|
-
return
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
return actors.actors[0];
|
|
232
|
+
if (!context.loaders) {
|
|
233
|
+
throw new Error('Loaders not initialized');
|
|
234
234
|
}
|
|
235
|
-
return
|
|
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
|
-
|
|
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 {
|
package/dist/graphql/server.js
CHANGED
|
@@ -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),
|
package/dist/graphql/types.d.ts
CHANGED
|
@@ -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();
|
package/dist/server-setup.d.ts
CHANGED
package/dist/server-setup.js
CHANGED
|
@@ -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
|