unreal-engine-mcp-server 0.4.0 → 0.4.4
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.production +1 -1
- package/.github/copilot-instructions.md +45 -0
- package/.github/workflows/publish-mcp.yml +3 -2
- package/README.md +21 -5
- package/dist/index.js +124 -31
- package/dist/prompts/index.d.ts +10 -3
- package/dist/prompts/index.js +186 -7
- package/dist/resources/actors.d.ts +19 -1
- package/dist/resources/actors.js +55 -64
- package/dist/resources/assets.js +46 -62
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +29 -54
- package/dist/tools/actors.d.ts +3 -14
- package/dist/tools/actors.js +246 -302
- package/dist/tools/animation.d.ts +57 -102
- package/dist/tools/animation.js +429 -450
- package/dist/tools/assets.d.ts +13 -2
- package/dist/tools/assets.js +52 -44
- package/dist/tools/audio.d.ts +22 -13
- package/dist/tools/audio.js +467 -121
- package/dist/tools/blueprint.d.ts +32 -13
- package/dist/tools/blueprint.js +699 -448
- package/dist/tools/build_environment_advanced.d.ts +0 -1
- package/dist/tools/build_environment_advanced.js +190 -45
- package/dist/tools/consolidated-tool-definitions.js +78 -252
- package/dist/tools/consolidated-tool-handlers.js +506 -133
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +167 -31
- package/dist/tools/editor.d.ts +9 -2
- package/dist/tools/editor.js +30 -44
- package/dist/tools/foliage.d.ts +34 -15
- package/dist/tools/foliage.js +97 -107
- package/dist/tools/introspection.js +19 -21
- package/dist/tools/landscape.d.ts +1 -2
- package/dist/tools/landscape.js +311 -168
- package/dist/tools/level.d.ts +3 -28
- package/dist/tools/level.js +642 -192
- package/dist/tools/lighting.d.ts +14 -3
- package/dist/tools/lighting.js +236 -123
- package/dist/tools/materials.d.ts +25 -7
- package/dist/tools/materials.js +102 -79
- package/dist/tools/niagara.d.ts +10 -12
- package/dist/tools/niagara.js +74 -94
- package/dist/tools/performance.d.ts +12 -4
- package/dist/tools/performance.js +38 -79
- package/dist/tools/physics.d.ts +34 -10
- package/dist/tools/physics.js +364 -292
- package/dist/tools/rc.js +97 -23
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +125 -22
- package/dist/tools/ui.d.ts +31 -4
- package/dist/tools/ui.js +83 -66
- package/dist/tools/visual.d.ts +11 -0
- package/dist/tools/visual.js +245 -30
- package/dist/types/tool-types.d.ts +0 -6
- package/dist/types/tool-types.js +1 -8
- package/dist/unreal-bridge.d.ts +32 -2
- package/dist/unreal-bridge.js +621 -127
- package/dist/utils/elicitation.d.ts +57 -0
- package/dist/utils/elicitation.js +104 -0
- package/dist/utils/error-handler.d.ts +0 -33
- package/dist/utils/error-handler.js +4 -111
- package/dist/utils/http.d.ts +2 -22
- package/dist/utils/http.js +12 -75
- package/dist/utils/normalize.d.ts +4 -4
- package/dist/utils/normalize.js +15 -7
- package/dist/utils/python-output.d.ts +18 -0
- package/dist/utils/python-output.js +290 -0
- package/dist/utils/python.d.ts +2 -0
- package/dist/utils/python.js +4 -0
- package/dist/utils/response-validator.js +28 -2
- package/dist/utils/result-helpers.d.ts +27 -0
- package/dist/utils/result-helpers.js +147 -0
- package/dist/utils/safe-json.d.ts +0 -2
- package/dist/utils/safe-json.js +0 -43
- package/dist/utils/validation.d.ts +16 -0
- package/dist/utils/validation.js +70 -7
- package/mcp-config-example.json +2 -2
- package/package.json +10 -9
- package/server.json +37 -14
- package/src/index.ts +130 -33
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +48 -51
- package/src/resources/levels.ts +35 -45
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +53 -43
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +266 -64
- package/src/tools/consolidated-tool-definitions.ts +90 -264
- package/src/tools/consolidated-tool-handlers.ts +630 -121
- package/src/tools/debug.ts +176 -33
- package/src/tools/editor.ts +35 -37
- package/src/tools/foliage.ts +110 -104
- package/src/tools/introspection.ts +24 -22
- package/src/tools/landscape.ts +334 -181
- package/src/tools/level.ts +683 -182
- package/src/tools/lighting.ts +244 -123
- package/src/tools/materials.ts +114 -83
- package/src/tools/niagara.ts +87 -81
- package/src/tools/performance.ts +49 -88
- package/src/tools/physics.ts +393 -299
- package/src/tools/rc.ts +102 -24
- package/src/tools/sequence.ts +136 -28
- package/src/tools/ui.ts +101 -70
- package/src/tools/visual.ts +250 -29
- package/src/types/tool-types.ts +0 -9
- package/src/unreal-bridge.ts +658 -140
- package/src/utils/elicitation.ts +129 -0
- package/src/utils/error-handler.ts +4 -159
- package/src/utils/http.ts +16 -115
- package/src/utils/normalize.ts +20 -10
- package/src/utils/python-output.ts +351 -0
- package/src/utils/python.ts +3 -0
- package/src/utils/response-validator.ts +25 -2
- package/src/utils/result-helpers.ts +193 -0
- package/src/utils/safe-json.ts +0 -50
- package/src/utils/validation.ts +94 -7
- package/tests/run-unreal-tool-tests.mjs +720 -0
- package/tsconfig.json +2 -2
- package/dist/python-utils.d.ts +0 -29
- package/dist/python-utils.js +0 -54
- package/dist/types/index.d.ts +0 -323
- package/dist/types/index.js +0 -28
- package/dist/utils/cache-manager.d.ts +0 -64
- package/dist/utils/cache-manager.js +0 -176
- package/dist/utils/errors.d.ts +0 -133
- package/dist/utils/errors.js +0 -256
- package/src/python/editor_compat.py +0 -181
- package/src/python-utils.ts +0 -57
- package/src/types/index.ts +0 -414
- package/src/utils/cache-manager.ts +0 -213
- package/src/utils/errors.ts +0 -312
package/.env.production
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Unreal MCP – AI Agent Guide
|
|
2
|
+
|
|
3
|
+
## Architecture essentials
|
|
4
|
+
- `src/index.ts` boots the Model Context Protocol server, registers output schemas, and connects to Unreal only when a tool/resource call requires it via `ensureConnectedOnDemand`.
|
|
5
|
+
- Consolidated tool routing lives in `src/tools/consolidated-tool-definitions.ts` and `src/tools/consolidated-tool-handlers.ts`, shrinking the surface to 13 multi-action tools that wrap the specialized classes in `src/tools/*`.
|
|
6
|
+
- The Unreal bridge (`src/unreal-bridge.ts`) owns HTTP/WS transport, command throttling, health checks, and graceful fallbacks so callers never talk to Remote Control directly.
|
|
7
|
+
- Health/metrics (connection status, latency, recent errors) are tracked in `index.ts` and exposed through the `ue://health` resource for quick diagnostics.
|
|
8
|
+
|
|
9
|
+
## Key directories
|
|
10
|
+
- `src/tools/` – domain-specific helpers (actors, landscapes, audio, niagara, etc.) that emit Python scripts or console commands.
|
|
11
|
+
- `src/resources/` – read-only listings (assets, actors, levels) with caching and path normalization utilities.
|
|
12
|
+
- `src/utils/` – shared helpers: validation/coercion, `escapePythonString`, result interpretation, AJV-powered response validation, stdout redirection.
|
|
13
|
+
- `docs/unreal-tool-test-cases.md` + `tests/run-unreal-tool-tests.mjs` – declarative test matrix consumed by the automated harness; reports land in `tests/reports/` with time-stamped JSON.
|
|
14
|
+
|
|
15
|
+
## Tool workflow expectations
|
|
16
|
+
- New tool actions must be declared in the consolidated definitions (input/output schema) and wired in the handler switch before delegating to the relevant class in `src/tools/*`.
|
|
17
|
+
- Always return plain JS objects with `success`, `message`, `error`, and optional `warnings` arrays; let `responseValidator.wrapResponse` handle MCP formatting.
|
|
18
|
+
- Shared helpers like `interpretStandardResult` and `cleanObject` keep payloads JSON-safe—use them instead of ad-hoc parsing.
|
|
19
|
+
- When validating inputs, reuse `ensureVector3`, `ensureRotation`, and other utilities from `src/utils/validation.ts` to keep error messaging consistent.
|
|
20
|
+
|
|
21
|
+
## Unreal bridge & Python usage
|
|
22
|
+
- Prefer `bridge.executePythonWithResult` for multi-line scripts; it captures stdout, parses the last `RESULT:` block, and falls back to the console `py` command when plugins are missing.
|
|
23
|
+
- Python snippets should print a single `RESULT:` JSON payload and sanitize inputs with `escapePythonString` or typed coercion helpers.
|
|
24
|
+
- `UnrealBridge.httpCall` enforces timeouts, queues commands, and blocks dangerous console strings (`buildpaths`, `rebuildnavigation`, etc.); avoid bypassing it with raw HTTP.
|
|
25
|
+
- Auto-reconnect is disabled by default—tool handlers should call `ensureConnectedOnDemand()` instead of assuming a live session.
|
|
26
|
+
|
|
27
|
+
## Response & validation conventions
|
|
28
|
+
- Every consolidated tool has an AJV schema; mismatches surface as `_validation` warnings in responses and appear in stderr logs.
|
|
29
|
+
- For Python-driven tools, use the `interpretResult`/`interpretStandardResult` helpers to turn raw bridge output into the normalized `{ success, message, error, warnings }` shape.
|
|
30
|
+
- Keep warning text short and user-facing—the test harness searches response strings for keywords to grade scenarios.
|
|
31
|
+
|
|
32
|
+
## Build & test workflow
|
|
33
|
+
- `npm run build` compiles TypeScript to `dist/`; `npm run lint` enforces the ESLint rules shipped in `.eslintrc.json`.
|
|
34
|
+
- `npm run test:tools` launches the compiled server via stdio, iterates the Markdown-defined cases, and writes a JSON run summary under `tests/reports/`.
|
|
35
|
+
- Override harness behavior with env vars like `UNREAL_MCP_SERVER_CMD`, `UNREAL_MCP_SERVER_ARGS`, `UNREAL_MCP_TEST_DOC`, or `UNREAL_MCP_FBX_FILE` when Unreal lives elsewhere.
|
|
36
|
+
|
|
37
|
+
## Unreal environment & configuration
|
|
38
|
+
- Ensure the project enables: Remote Control API, Remote Control Web Interface, Python Editor Script Plugin, Editor Scripting Utilities, Sequencer, and Level Sequence Editor before invoking automation.
|
|
39
|
+
- Default connection settings come from `UE_HOST`, `UE_RC_HTTP_PORT`, and `UE_RC_WS_PORT` (see README for the `DefaultEngine.ini` snippet that unlocks remote execution).
|
|
40
|
+
- Tools defensively return `UE_NOT_CONNECTED` or asset-not-found errors; keep that contract when extending behavior so clients can retry intelligently.
|
|
41
|
+
|
|
42
|
+
## Debugging & monitoring
|
|
43
|
+
- Logs are routed to stderr via `routeStdoutLogsToStderr()` to keep MCP stdout JSON-only—check the terminal for detailed stack traces or validation warnings.
|
|
44
|
+
- Use the `ue://health` and `ue://version` resources to confirm bridge connectivity, last health-check timestamp, and detected engine version.
|
|
45
|
+
- The command queue in `UnrealBridge` spaces out console/Python traffic; heavy operations may need explicit `__callTimeoutMs` in the payload to extend HTTP timeouts.
|
|
@@ -49,7 +49,7 @@ jobs:
|
|
|
49
49
|
- name: Update server.json version
|
|
50
50
|
run: |
|
|
51
51
|
VERSION=${{ steps.version.outputs.version }}
|
|
52
|
-
jq --arg v "$VERSION" '.version = $v | .packages[] |= if .
|
|
52
|
+
jq --arg v "$VERSION" '.version = $v | .packages[] |= if .registryType == "npm" then .version = $v else . end' server.json > tmp && mv tmp server.json
|
|
53
53
|
echo "Updated server.json:"
|
|
54
54
|
cat server.json
|
|
55
55
|
|
|
@@ -60,7 +60,8 @@ jobs:
|
|
|
60
60
|
|
|
61
61
|
- name: Install MCP Publisher
|
|
62
62
|
run: |
|
|
63
|
-
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.
|
|
63
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.1.0/mcp-publisher_1.1.0_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
64
|
+
chmod +x mcp-publisher
|
|
64
65
|
|
|
65
66
|
- name: Login to MCP Registry
|
|
66
67
|
run: ./mcp-publisher login github-oidc
|
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/unreal-engine-mcp-server)
|
|
5
5
|
[](https://github.com/modelcontextprotocol/sdk)
|
|
6
6
|
[](https://www.unrealengine.com/)
|
|
7
|
+
[](https://registry.modelcontextprotocol.io/)
|
|
7
8
|
|
|
8
9
|
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine via Remote Control API. Built with TypeScript and designed for game development automation.
|
|
9
10
|
|
|
@@ -24,11 +25,26 @@ A comprehensive Model Context Protocol (MCP) server that enables AI assistants t
|
|
|
24
25
|
### Prerequisites
|
|
25
26
|
- Node.js 18+
|
|
26
27
|
- Unreal Engine 5.0-5.6
|
|
27
|
-
- Required UE Plugins:
|
|
28
|
-
- Remote Control
|
|
29
|
-
- Web
|
|
30
|
-
- Python Script Plugin
|
|
31
|
-
- Editor Scripting Utilities
|
|
28
|
+
- Required UE Plugins (enable via **Edit ▸ Plugins**):
|
|
29
|
+
- **Remote Control API** – core Remote Control HTTP/WS endpoints
|
|
30
|
+
- **Remote Control Web Interface** – enables WebSocket bridge used by this server
|
|
31
|
+
- **Python Editor Script Plugin** – exposes Python runtime for automation
|
|
32
|
+
- **Editor Scripting Utilities** – unlocks Editor Actor/Asset subsystems used throughout the tools
|
|
33
|
+
- **Sequencer** *(built-in)* – keep enabled for cinematic tools
|
|
34
|
+
- **Level Sequence Editor** – required for `manage_sequence` operations
|
|
35
|
+
|
|
36
|
+
> 💡 After toggling any plugin, restart the editor to finalize activation. Keep `Editor Scripting Utilities` and `Python Editor Script Plugin` enabled prior to connecting, otherwise many subsystem-based tools (actor spawning, audio, foliage, UI widgets) will refuse to run for safety.
|
|
37
|
+
|
|
38
|
+
### Plugin feature map
|
|
39
|
+
|
|
40
|
+
| Plugin | Location | Used By | Notes |
|
|
41
|
+
|--------|----------|---------|-------|
|
|
42
|
+
| Remote Control API | Developer Tools ▸ Remote Control | All tools | Provides HTTP/WS endpoints consumed by the MCP bridge |
|
|
43
|
+
| Remote Control Web Interface | Developer Tools ▸ Remote Control | All tools | Enables persistent WebSocket session |
|
|
44
|
+
| Python Editor Script Plugin | Scripting | Landscapes, lighting, audio, physics, sequences, UI | Required for every Python execution path |
|
|
45
|
+
| Editor Scripting Utilities | Scripting | Actors, foliage, assets, landscapes, UI | Supplies Editor Actor/Asset subsystems in UE5.6 |
|
|
46
|
+
| Sequencer | Built-in | Sequencer tools | Ensure not disabled in project settings |
|
|
47
|
+
| Level Sequence Editor | Animation | Sequencer tools | Activate before calling `manage_sequence` operations |
|
|
32
48
|
|
|
33
49
|
### Installation
|
|
34
50
|
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import { responseValidator } from './utils/response-validator.js';
|
|
|
36
36
|
import { ErrorHandler } from './utils/error-handler.js';
|
|
37
37
|
import { routeStdoutLogsToStderr } from './utils/stdio-redirect.js';
|
|
38
38
|
import { cleanObject } from './utils/safe-json.js';
|
|
39
|
+
import { createElicitationHelper } from './utils/elicitation.js';
|
|
39
40
|
const log = new Logger('UE-MCP');
|
|
40
41
|
// Ensure stdout remains JSON-only for MCP by routing logs to stderr unless opted out.
|
|
41
42
|
routeStdoutLogsToStderr();
|
|
@@ -61,7 +62,7 @@ const CONFIG = {
|
|
|
61
62
|
RETRY_DELAY_MS: 2000,
|
|
62
63
|
// Server info
|
|
63
64
|
SERVER_NAME: 'unreal-engine-mcp',
|
|
64
|
-
SERVER_VERSION: '0.4.
|
|
65
|
+
SERVER_VERSION: '0.4.4',
|
|
65
66
|
// Monitoring
|
|
66
67
|
HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
|
|
67
68
|
};
|
|
@@ -211,10 +212,34 @@ export async function createServer() {
|
|
|
211
212
|
capabilities: {
|
|
212
213
|
resources: {},
|
|
213
214
|
tools: {},
|
|
214
|
-
prompts: {
|
|
215
|
-
|
|
215
|
+
prompts: {
|
|
216
|
+
listChanged: false
|
|
217
|
+
},
|
|
218
|
+
logging: {},
|
|
219
|
+
elicitation: {}
|
|
216
220
|
}
|
|
217
221
|
});
|
|
222
|
+
// Optional elicitation helper – used only if client supports it.
|
|
223
|
+
const elicitation = createElicitationHelper(server, log);
|
|
224
|
+
const defaultElicitationTimeoutMs = elicitation.getDefaultTimeoutMs();
|
|
225
|
+
const createNotConnectedResponse = (toolName) => {
|
|
226
|
+
const payload = {
|
|
227
|
+
success: false,
|
|
228
|
+
error: 'UE_NOT_CONNECTED',
|
|
229
|
+
message: 'Unreal Engine is not connected (after 3 attempts). Please open UE and try again.',
|
|
230
|
+
retriable: false,
|
|
231
|
+
scope: `tool-call/${toolName}`
|
|
232
|
+
};
|
|
233
|
+
return responseValidator.wrapResponse(toolName, {
|
|
234
|
+
...payload,
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: 'text',
|
|
238
|
+
text: JSON.stringify(payload, null, 2)
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
});
|
|
242
|
+
};
|
|
218
243
|
// Handle resource listing
|
|
219
244
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
220
245
|
return {
|
|
@@ -402,20 +427,14 @@ export async function createServer() {
|
|
|
402
427
|
});
|
|
403
428
|
// Handle tool calls - consolidated tools only (13)
|
|
404
429
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
405
|
-
const { name
|
|
430
|
+
const { name } = request.params;
|
|
431
|
+
let args = request.params.arguments || {};
|
|
406
432
|
const startTime = Date.now();
|
|
407
433
|
// Ensure connection only when needed, with 3 attempts
|
|
408
434
|
const connected = await ensureConnectedOnDemand();
|
|
409
435
|
if (!connected) {
|
|
410
|
-
const notConnected = {
|
|
411
|
-
content: [{ type: 'text', text: 'Unreal Engine is not connected (after 3 attempts). Please open UE and try again.' }],
|
|
412
|
-
success: false,
|
|
413
|
-
error: 'UE_NOT_CONNECTED',
|
|
414
|
-
retriable: false,
|
|
415
|
-
scope: `tool-call/${name}`
|
|
416
|
-
};
|
|
417
436
|
trackPerformance(startTime, false);
|
|
418
|
-
return
|
|
437
|
+
return createNotConnectedResponse(name);
|
|
419
438
|
}
|
|
420
439
|
// Create tools object for handler
|
|
421
440
|
const tools = {
|
|
@@ -441,6 +460,10 @@ export async function createServer() {
|
|
|
441
460
|
introspectionTools,
|
|
442
461
|
visualTools,
|
|
443
462
|
engineTools,
|
|
463
|
+
// Elicitation (client-optional)
|
|
464
|
+
elicit: elicitation.elicit,
|
|
465
|
+
supportsElicitation: elicitation.supports,
|
|
466
|
+
elicitationTimeoutMs: defaultElicitationTimeoutMs,
|
|
444
467
|
// Resources for listing and info
|
|
445
468
|
assetResources,
|
|
446
469
|
actorResources,
|
|
@@ -450,6 +473,62 @@ export async function createServer() {
|
|
|
450
473
|
// Execute consolidated tool handler
|
|
451
474
|
try {
|
|
452
475
|
log.debug(`Executing tool: ${name}`);
|
|
476
|
+
// Opportunistic generic elicitation for missing primitive required fields
|
|
477
|
+
try {
|
|
478
|
+
const toolDef = consolidatedToolDefinitions.find(t => t.name === name);
|
|
479
|
+
const inputSchema = toolDef?.inputSchema;
|
|
480
|
+
const elicitFn = tools.elicit;
|
|
481
|
+
if (inputSchema && typeof elicitFn === 'function') {
|
|
482
|
+
const props = inputSchema.properties || {};
|
|
483
|
+
const required = Array.isArray(inputSchema.required) ? inputSchema.required : [];
|
|
484
|
+
const missing = required.filter((k) => {
|
|
485
|
+
const v = args[k];
|
|
486
|
+
if (v === undefined || v === null)
|
|
487
|
+
return true;
|
|
488
|
+
if (typeof v === 'string' && v.trim() === '')
|
|
489
|
+
return true;
|
|
490
|
+
return false;
|
|
491
|
+
});
|
|
492
|
+
// Build a flat primitive-only schema subset per MCP Elicitation rules
|
|
493
|
+
const primitiveProps = {};
|
|
494
|
+
for (const k of missing) {
|
|
495
|
+
const p = props[k];
|
|
496
|
+
if (!p || typeof p !== 'object')
|
|
497
|
+
continue;
|
|
498
|
+
const t = (p.type || '').toString();
|
|
499
|
+
const isEnum = Array.isArray(p.enum);
|
|
500
|
+
if (t === 'string' || t === 'number' || t === 'integer' || t === 'boolean' || isEnum) {
|
|
501
|
+
primitiveProps[k] = {
|
|
502
|
+
type: t || (isEnum ? 'string' : undefined),
|
|
503
|
+
title: p.title,
|
|
504
|
+
description: p.description,
|
|
505
|
+
enum: p.enum,
|
|
506
|
+
enumNames: p.enumNames,
|
|
507
|
+
minimum: p.minimum,
|
|
508
|
+
maximum: p.maximum,
|
|
509
|
+
minLength: p.minLength,
|
|
510
|
+
maxLength: p.maxLength,
|
|
511
|
+
pattern: p.pattern,
|
|
512
|
+
format: p.format,
|
|
513
|
+
default: p.default
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (Object.keys(primitiveProps).length > 0) {
|
|
518
|
+
const elicitOptions = { fallback: async () => ({ ok: false, error: 'missing-params' }) };
|
|
519
|
+
if (typeof tools.elicitationTimeoutMs === 'number' && Number.isFinite(tools.elicitationTimeoutMs)) {
|
|
520
|
+
elicitOptions.timeoutMs = tools.elicitationTimeoutMs;
|
|
521
|
+
}
|
|
522
|
+
const elicitRes = await elicitFn(`Provide missing parameters for ${name}`, { type: 'object', properties: primitiveProps, required: Object.keys(primitiveProps) }, elicitOptions);
|
|
523
|
+
if (elicitRes && elicitRes.ok && elicitRes.value) {
|
|
524
|
+
args = { ...args, ...elicitRes.value };
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
log.debug('Generic elicitation prefill skipped', { err: e?.message || String(e) });
|
|
531
|
+
}
|
|
453
532
|
let result = await handleConsolidatedToolCall(name, args, tools);
|
|
454
533
|
log.debug(`Tool ${name} returned result`);
|
|
455
534
|
// Clean the result to remove circular references
|
|
@@ -481,13 +560,23 @@ export async function createServer() {
|
|
|
481
560
|
metrics.recentErrors.splice(0, metrics.recentErrors.length - 20);
|
|
482
561
|
}
|
|
483
562
|
catch { }
|
|
484
|
-
|
|
563
|
+
const sanitizedError = cleanObject(errorResponse);
|
|
564
|
+
let errorText = '';
|
|
565
|
+
try {
|
|
566
|
+
errorText = JSON.stringify(sanitizedError, null, 2);
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
errorText = sanitizedError.message || errorResponse.message || `Failed to execute ${name}`;
|
|
570
|
+
}
|
|
571
|
+
const wrappedError = {
|
|
572
|
+
...sanitizedError,
|
|
573
|
+
isError: true,
|
|
485
574
|
content: [{
|
|
486
575
|
type: 'text',
|
|
487
|
-
text:
|
|
488
|
-
}]
|
|
489
|
-
...errorResponse
|
|
576
|
+
text: errorText
|
|
577
|
+
}]
|
|
490
578
|
};
|
|
579
|
+
return responseValidator.wrapResponse(name, wrappedError);
|
|
491
580
|
}
|
|
492
581
|
});
|
|
493
582
|
// Handle prompt listing
|
|
@@ -496,11 +585,21 @@ export async function createServer() {
|
|
|
496
585
|
prompts: prompts.map(p => ({
|
|
497
586
|
name: p.name,
|
|
498
587
|
description: p.description,
|
|
499
|
-
arguments: Object.entries(p.arguments || {}).map(([name, schema]) =>
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
588
|
+
arguments: Object.entries(p.arguments || {}).map(([name, schema]) => {
|
|
589
|
+
const meta = {};
|
|
590
|
+
if (schema.type)
|
|
591
|
+
meta.type = schema.type;
|
|
592
|
+
if (schema.enum)
|
|
593
|
+
meta.enum = schema.enum;
|
|
594
|
+
if (schema.default !== undefined)
|
|
595
|
+
meta.default = schema.default;
|
|
596
|
+
return {
|
|
597
|
+
name,
|
|
598
|
+
description: schema.description,
|
|
599
|
+
required: schema.required ?? false,
|
|
600
|
+
...(Object.keys(meta).length ? { _meta: meta } : {})
|
|
601
|
+
};
|
|
602
|
+
})
|
|
504
603
|
}))
|
|
505
604
|
};
|
|
506
605
|
});
|
|
@@ -510,17 +609,11 @@ export async function createServer() {
|
|
|
510
609
|
if (!prompt) {
|
|
511
610
|
throw new Error(`Unknown prompt: ${request.params.name}`);
|
|
512
611
|
}
|
|
513
|
-
|
|
612
|
+
const args = (request.params.arguments || {});
|
|
613
|
+
const messages = prompt.build(args);
|
|
514
614
|
return {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
role: 'user',
|
|
518
|
-
content: {
|
|
519
|
-
type: 'text',
|
|
520
|
-
text: `Set up three-point lighting with ${request.params.arguments?.intensity || 'medium'} intensity`
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
]
|
|
615
|
+
description: prompt.description,
|
|
616
|
+
messages
|
|
524
617
|
};
|
|
525
618
|
});
|
|
526
619
|
return { server, bridge };
|
package/dist/prompts/index.d.ts
CHANGED
|
@@ -2,13 +2,20 @@ export interface PromptArgument {
|
|
|
2
2
|
type: string;
|
|
3
3
|
description?: string;
|
|
4
4
|
enum?: string[];
|
|
5
|
-
default?:
|
|
5
|
+
default?: unknown;
|
|
6
6
|
required?: boolean;
|
|
7
7
|
}
|
|
8
|
-
export interface
|
|
8
|
+
export interface PromptTemplate {
|
|
9
9
|
name: string;
|
|
10
10
|
description: string;
|
|
11
11
|
arguments?: Record<string, PromptArgument>;
|
|
12
|
+
build: (args: Record<string, unknown>) => Array<{
|
|
13
|
+
role: 'user' | 'assistant';
|
|
14
|
+
content: {
|
|
15
|
+
type: 'text';
|
|
16
|
+
text: string;
|
|
17
|
+
};
|
|
18
|
+
}>;
|
|
12
19
|
}
|
|
13
|
-
export declare const prompts:
|
|
20
|
+
export declare const prompts: PromptTemplate[];
|
|
14
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/prompts/index.js
CHANGED
|
@@ -1,37 +1,216 @@
|
|
|
1
|
+
function clampChoice(value, choices, fallback) {
|
|
2
|
+
if (typeof value === 'string') {
|
|
3
|
+
const normalized = value.toLowerCase();
|
|
4
|
+
if (choices.includes(normalized)) {
|
|
5
|
+
return normalized;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return fallback;
|
|
9
|
+
}
|
|
10
|
+
function coerceNumber(value, fallback, min, max) {
|
|
11
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
12
|
+
if (!Number.isFinite(num)) {
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
if (min !== undefined && num < min) {
|
|
16
|
+
return min;
|
|
17
|
+
}
|
|
18
|
+
if (max !== undefined && num > max) {
|
|
19
|
+
return max;
|
|
20
|
+
}
|
|
21
|
+
return num;
|
|
22
|
+
}
|
|
23
|
+
function formatVector(value) {
|
|
24
|
+
if (!value || typeof value !== 'object') {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const vector = value;
|
|
28
|
+
const x = typeof vector.x === 'number' ? vector.x : Number(vector.x);
|
|
29
|
+
const y = typeof vector.y === 'number' ? vector.y : Number(vector.y);
|
|
30
|
+
const z = typeof vector.z === 'number' ? vector.z : Number(vector.z);
|
|
31
|
+
if ([x, y, z].some((component) => !Number.isFinite(component))) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return `${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)}`;
|
|
35
|
+
}
|
|
1
36
|
export const prompts = [
|
|
2
37
|
{
|
|
3
38
|
name: 'setup_three_point_lighting',
|
|
4
|
-
description: '
|
|
39
|
+
description: 'Author a cinematic three-point lighting rig aligned to the active camera focus.',
|
|
5
40
|
arguments: {
|
|
6
41
|
intensity: {
|
|
7
42
|
type: 'string',
|
|
8
43
|
enum: ['low', 'medium', 'high'],
|
|
9
44
|
default: 'medium',
|
|
10
|
-
description: '
|
|
45
|
+
description: 'Overall lighting mood. Low = dramatic contrast, high = bright key light.'
|
|
11
46
|
}
|
|
47
|
+
},
|
|
48
|
+
build: (args) => {
|
|
49
|
+
const intensity = clampChoice(args.intensity, ['low', 'medium', 'high'], 'medium');
|
|
50
|
+
const moodHints = {
|
|
51
|
+
low: 'gentle key with strong contrast and subtle rim highlights',
|
|
52
|
+
medium: 'balanced key/fill ratio for natural coverage',
|
|
53
|
+
high: 'bright key with energetic fill and crisp rim separation'
|
|
54
|
+
};
|
|
55
|
+
const text = `Configure a three-point lighting rig around the current cinematic focus.
|
|
56
|
+
|
|
57
|
+
Tasks:
|
|
58
|
+
- Position a key light roughly 45° off-axis at eye level. Target the subject center and tune intensity for ${intensity} output (${moodHints[intensity]}).
|
|
59
|
+
- Add a fill light on the opposite side with wider spread and softened shadows to control contrast.
|
|
60
|
+
- Place a rim/back light to outline silhouettes and separate the subject from the background.
|
|
61
|
+
- Ensure all lights use physically plausible color temperature, enable shadow casting where helpful, and adjust attenuation to avoid spill.
|
|
62
|
+
- Once balanced, report the final intensity values, color temperatures, and any blockers encountered.`;
|
|
63
|
+
return [{
|
|
64
|
+
role: 'user',
|
|
65
|
+
content: { type: 'text', text }
|
|
66
|
+
}];
|
|
12
67
|
}
|
|
13
68
|
},
|
|
14
69
|
{
|
|
15
70
|
name: 'create_fps_controller',
|
|
16
|
-
description: '
|
|
71
|
+
description: 'Spin up a first-person controller blueprint with input mappings, collision, and starter movement.',
|
|
17
72
|
arguments: {
|
|
18
73
|
spawnLocation: {
|
|
19
|
-
type: '
|
|
20
|
-
description: '
|
|
74
|
+
type: 'vector',
|
|
75
|
+
description: 'Optional XYZ spawn position for the player pawn.',
|
|
21
76
|
required: false
|
|
22
77
|
}
|
|
78
|
+
},
|
|
79
|
+
build: (args) => {
|
|
80
|
+
const spawnVector = formatVector(args.spawnLocation);
|
|
81
|
+
const spawnLine = spawnVector ? `Spawn the pawn at world coordinates (${spawnVector}).` : 'Spawn the pawn at a safe default player start or the origin.';
|
|
82
|
+
const text = `Build a First Person Character blueprint with:
|
|
83
|
+
- Camera + arms mesh, basic WASD input, jump, crouch, and sprint bindings using Enhanced Input.
|
|
84
|
+
- Proper collision capsule sizing for a 180cm tall human.
|
|
85
|
+
- Momentum-preserving air control with configurable acceleration and friction.
|
|
86
|
+
- A configurable base turn rate with mouse sensitivity scaling.
|
|
87
|
+
- Serialized defaults for walking speed (600 uu/s) and sprint speed (900 uu/s).
|
|
88
|
+
- Expose key movement settings as editable defaults.
|
|
89
|
+
- ${spawnLine}
|
|
90
|
+
|
|
91
|
+
Finish by compiling, saving, and summarizing the created blueprint path plus the mapped input actions.`;
|
|
92
|
+
return [{
|
|
93
|
+
role: 'user',
|
|
94
|
+
content: { type: 'text', text }
|
|
95
|
+
}];
|
|
23
96
|
}
|
|
24
97
|
},
|
|
25
98
|
{
|
|
26
99
|
name: 'setup_post_processing',
|
|
27
|
-
description: '
|
|
100
|
+
description: 'Author a post-process volume tuned to a named cinematic grade.',
|
|
28
101
|
arguments: {
|
|
29
102
|
style: {
|
|
30
103
|
type: 'string',
|
|
31
104
|
enum: ['cinematic', 'realistic', 'stylized', 'noir'],
|
|
32
105
|
default: 'cinematic',
|
|
33
|
-
description: '
|
|
106
|
+
description: 'Look preset to emphasize color grading and tone-mapping style.'
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
build: (args) => {
|
|
110
|
+
const style = clampChoice(args.style, ['cinematic', 'realistic', 'stylized', 'noir'], 'cinematic');
|
|
111
|
+
const styleNotes = {
|
|
112
|
+
cinematic: 'filmic tonemapper, gentle bloom, warm highlights, cool shadows, slight vignette',
|
|
113
|
+
realistic: 'minimal grading, accurate white balance, restrained bloom, detail-preserving sharpening',
|
|
114
|
+
stylized: 'bold saturation shifts, custom color LUT, exaggerated contrast, selective bloom',
|
|
115
|
+
noir: 'monochrome conversion, strong contrast curve, subtle film grain, heavy vignette'
|
|
116
|
+
};
|
|
117
|
+
const text = `Create a global post-process volume with priority over level defaults.
|
|
118
|
+
- Apply the "${style}" look: ${styleNotes[style]}.
|
|
119
|
+
- Configure tone mapping, exposure, bloom, chromatic aberration, and LUTs as required.
|
|
120
|
+
- Ensure the volume is unbound unless level-specific constraints apply.
|
|
121
|
+
- Provide sanity checks for HDR output and keep auto-exposure transitions smooth.
|
|
122
|
+
- Summarize all modified settings with their final numeric values or asset references.`;
|
|
123
|
+
return [{
|
|
124
|
+
role: 'user',
|
|
125
|
+
content: { type: 'text', text }
|
|
126
|
+
}];
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'setup_dynamic_day_night_cycle',
|
|
131
|
+
description: 'Create or update a Blueprint to drive a dynamic day/night cycle with optional weather hooks.',
|
|
132
|
+
arguments: {
|
|
133
|
+
startTime: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
enum: ['dawn', 'noon', 'dusk', 'midnight'],
|
|
136
|
+
default: 'dawn',
|
|
137
|
+
description: 'Initial lighting state for the cycle.'
|
|
138
|
+
},
|
|
139
|
+
transitionMinutes: {
|
|
140
|
+
type: 'number',
|
|
141
|
+
default: 5,
|
|
142
|
+
description: 'Game-time minutes to blend between major lighting states.'
|
|
143
|
+
},
|
|
144
|
+
enableWeather: {
|
|
145
|
+
type: 'boolean',
|
|
146
|
+
default: false,
|
|
147
|
+
description: 'Whether to expose hooks for weather-driven sky adjustments.'
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
build: (args) => {
|
|
151
|
+
const startTime = clampChoice(args.startTime, ['dawn', 'noon', 'dusk', 'midnight'], 'dawn');
|
|
152
|
+
const transitionMinutes = coerceNumber(args.transitionMinutes, 5, 1, 60);
|
|
153
|
+
const enableWeather = Boolean(args.enableWeather);
|
|
154
|
+
const weatherLine = enableWeather
|
|
155
|
+
? '- Expose interfaces for cloud opacity, precipitation-driven skylight updates, and lightning flashes.'
|
|
156
|
+
: '- Weather hooks are disabled; keep the blueprint lean';
|
|
157
|
+
const text = `Implement a Blueprint-based day/night cycle manager.
|
|
158
|
+
- Start the sequence at ${startTime} lighting.
|
|
159
|
+
- Advance sun rotation, skylight captures, fog, and sky atmosphere continuously with ${transitionMinutes} minute blends between key states.
|
|
160
|
+
- Sync directional light intensity/color with real-world sun elevation and inject moonlight at night.
|
|
161
|
+
- ${weatherLine}.
|
|
162
|
+
- Provide editor controls for time-of-day multiplier and manual overrides.
|
|
163
|
+
- Document the generated blueprint path and exposed parameters.`;
|
|
164
|
+
return [{
|
|
165
|
+
role: 'user',
|
|
166
|
+
content: { type: 'text', text }
|
|
167
|
+
}];
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'design_cinematic_camera_move',
|
|
172
|
+
description: 'Author a sequencer shot with a polished camera move and easing markers.',
|
|
173
|
+
arguments: {
|
|
174
|
+
durationSeconds: {
|
|
175
|
+
type: 'number',
|
|
176
|
+
default: 6,
|
|
177
|
+
description: 'Shot duration in seconds.'
|
|
178
|
+
},
|
|
179
|
+
moveStyle: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
enum: ['push_in', 'orbit', 'tracking', 'crane'],
|
|
182
|
+
default: 'push_in',
|
|
183
|
+
description: 'Camera move archetype to emphasize.'
|
|
184
|
+
},
|
|
185
|
+
focusTarget: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'Optional actor or component name to keep in focus.',
|
|
188
|
+
required: false
|
|
34
189
|
}
|
|
190
|
+
},
|
|
191
|
+
build: (args) => {
|
|
192
|
+
const duration = coerceNumber(args.durationSeconds, 6, 2, 30);
|
|
193
|
+
const moveStyle = clampChoice(args.moveStyle, ['push_in', 'orbit', 'tracking', 'crane'], 'push_in');
|
|
194
|
+
const focusLine = typeof args.focusTarget === 'string' && args.focusTarget.trim().length > 0
|
|
195
|
+
? `Lock focus distance on "${args.focusTarget}" and animate depth of field pulls if necessary.`
|
|
196
|
+
: 'Pick the most prominent subject in frame and maintain crisp focus throughout the move.';
|
|
197
|
+
const moveHints = {
|
|
198
|
+
push_in: 'Ease-in push toward the subject with gentle camera roll stabilization.',
|
|
199
|
+
orbit: '360° orbit with consistent parallax and a tracked look-at target.',
|
|
200
|
+
tracking: 'Match the subject velocity along a spline with smoothed acceleration.',
|
|
201
|
+
crane: 'Combine vertical rise with lateral drift for a reveal shot.'
|
|
202
|
+
};
|
|
203
|
+
const text = `In Sequencer, author a ${duration.toFixed(1)} second cinematic shot.
|
|
204
|
+
- Movement style: ${moveStyle} (${moveHints[moveStyle]}).
|
|
205
|
+
- Key auto-exposure, camera focal length, and focal distance for a premium look.
|
|
206
|
+
- Add ease-in/ease-out tangents at shot boundaries to avoid abrupt starts/stops.
|
|
207
|
+
- ${focusLine}
|
|
208
|
+
- Annotate the timeline with intent markers (intro beat, climax, resolve).
|
|
209
|
+
- Render a preview range and summarize the created assets.`;
|
|
210
|
+
return [{
|
|
211
|
+
role: 'user',
|
|
212
|
+
content: { type: 'text', text }
|
|
213
|
+
}];
|
|
35
214
|
}
|
|
36
215
|
}
|
|
37
216
|
];
|
|
@@ -7,7 +7,25 @@ export declare class ActorResources {
|
|
|
7
7
|
private getFromCache;
|
|
8
8
|
private setCache;
|
|
9
9
|
listActors(): Promise<any>;
|
|
10
|
-
getActorByName(actorName: string): Promise<
|
|
10
|
+
getActorByName(actorName: string): Promise<{
|
|
11
|
+
success: true;
|
|
12
|
+
name: string;
|
|
13
|
+
path: string | undefined;
|
|
14
|
+
class: string | undefined;
|
|
15
|
+
error?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
success: false;
|
|
18
|
+
error: string;
|
|
19
|
+
name?: undefined;
|
|
20
|
+
path?: undefined;
|
|
21
|
+
class?: undefined;
|
|
22
|
+
} | {
|
|
23
|
+
error: string;
|
|
24
|
+
success?: undefined;
|
|
25
|
+
name?: undefined;
|
|
26
|
+
path?: undefined;
|
|
27
|
+
class?: undefined;
|
|
28
|
+
}>;
|
|
11
29
|
getActorTransform(actorPath: string): Promise<any>;
|
|
12
30
|
}
|
|
13
31
|
//# sourceMappingURL=actors.d.ts.map
|