ue-mcp 0.4.0-alpha
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/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/bridge.d.ts +25 -0
- package/dist/bridge.js +124 -0
- package/dist/bridge.js.map +1 -0
- package/dist/deployer.d.ts +18 -0
- package/dist/deployer.js +175 -0
- package/dist/deployer.js.map +1 -0
- package/dist/editor-control.d.ts +15 -0
- package/dist/editor-control.js +181 -0
- package/dist/editor-control.js.map +1 -0
- package/dist/github-app.d.ts +4 -0
- package/dist/github-app.js +94 -0
- package/dist/github-app.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +290 -0
- package/dist/init.js.map +1 -0
- package/dist/instructions.d.ts +1 -0
- package/dist/instructions.js +150 -0
- package/dist/instructions.js.map +1 -0
- package/dist/project.d.ts +32 -0
- package/dist/project.js +175 -0
- package/dist/project.js.map +1 -0
- package/dist/tools/animation.d.ts +2 -0
- package/dist/tools/animation.js +62 -0
- package/dist/tools/animation.js.map +1 -0
- package/dist/tools/asset.d.ts +2 -0
- package/dist/tools/asset.js +128 -0
- package/dist/tools/asset.js.map +1 -0
- package/dist/tools/audio.d.ts +2 -0
- package/dist/tools/audio.js +24 -0
- package/dist/tools/audio.js.map +1 -0
- package/dist/tools/blueprint.d.ts +2 -0
- package/dist/tools/blueprint.js +64 -0
- package/dist/tools/blueprint.js.map +1 -0
- package/dist/tools/demo.d.ts +2 -0
- package/dist/tools/demo.js +10 -0
- package/dist/tools/demo.js.map +1 -0
- package/dist/tools/editor.d.ts +2 -0
- package/dist/tools/editor.js +133 -0
- package/dist/tools/editor.js.map +1 -0
- package/dist/tools/feedback.d.ts +2 -0
- package/dist/tools/feedback.js +44 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/foliage.d.ts +2 -0
- package/dist/tools/foliage.js +29 -0
- package/dist/tools/foliage.js.map +1 -0
- package/dist/tools/gameplay.d.ts +2 -0
- package/dist/tools/gameplay.js +101 -0
- package/dist/tools/gameplay.js.map +1 -0
- package/dist/tools/gas.d.ts +2 -0
- package/dist/tools/gas.js +43 -0
- package/dist/tools/gas.js.map +1 -0
- package/dist/tools/landscape.d.ts +2 -0
- package/dist/tools/landscape.js +32 -0
- package/dist/tools/landscape.js.map +1 -0
- package/dist/tools/level.d.ts +2 -0
- package/dist/tools/level.js +66 -0
- package/dist/tools/level.js.map +1 -0
- package/dist/tools/material.d.ts +2 -0
- package/dist/tools/material.js +59 -0
- package/dist/tools/material.js.map +1 -0
- package/dist/tools/networking.d.ts +2 -0
- package/dist/tools/networking.js +42 -0
- package/dist/tools/networking.js.map +1 -0
- package/dist/tools/niagara.d.ts +2 -0
- package/dist/tools/niagara.js +42 -0
- package/dist/tools/niagara.js.map +1 -0
- package/dist/tools/pcg.d.ts +2 -0
- package/dist/tools/pcg.js +39 -0
- package/dist/tools/pcg.js.map +1 -0
- package/dist/tools/project.d.ts +2 -0
- package/dist/tools/project.js +282 -0
- package/dist/tools/project.js.map +1 -0
- package/dist/tools/reflection.d.ts +2 -0
- package/dist/tools/reflection.js +26 -0
- package/dist/tools/reflection.js.map +1 -0
- package/dist/tools/widget.d.ts +2 -0
- package/dist/tools/widget.js +34 -0
- package/dist/tools/widget.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +38 -0
- package/dist/types.js.map +1 -0
- package/package.json +76 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Lyon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# UE-MCP
|
|
2
|
+
|
|
3
|
+
**Unreal Engine Model Context Protocol Server** — gives AI assistants deep read/write access to the Unreal Editor through 19 category tools covering 300+ actions.
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
flowchart LR
|
|
7
|
+
AI[AI Assistant] -->|stdio| MCP[MCP Server<br/>TypeScript / Node.js]
|
|
8
|
+
MCP -->|WebSocket<br/>JSON-RPC| Plugin[C++ Bridge Plugin<br/>inside Unreal Editor]
|
|
9
|
+
Plugin -->|UE API| Editor[Editor Subsystems]
|
|
10
|
+
MCP -->|direct fs| FS[Config INI<br/>C++ Headers<br/>Asset Listing]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Blueprints, materials, levels, actors, animation, VFX, landscape, PCG, foliage, audio, UI, physics, navigation, AI, GAS, networking, sequencer, build pipeline — all programmable through natural language.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/db-lyon/ue-mcp.git
|
|
19
|
+
cd ue-mcp
|
|
20
|
+
npm install && npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Add to your MCP client config (Claude Code, Claude Desktop, Cursor, etc.):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"ue-mcp": {
|
|
29
|
+
"command": "node",
|
|
30
|
+
"args": [
|
|
31
|
+
"C:/path/to/ue-mcp/dist/index.js",
|
|
32
|
+
"C:/path/to/MyGame.uproject"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Restart the editor once after first run to load the C++ bridge plugin. Then:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
project(action="get_status") — verify connection
|
|
43
|
+
level(action="get_outliner") — see what's in the level
|
|
44
|
+
asset(action="list") — browse project assets
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
**[db-lyon.github.io/ue-mcp](https://db-lyon.github.io/ue-mcp/)**
|
|
50
|
+
|
|
51
|
+
- [Getting Started](https://db-lyon.github.io/ue-mcp/getting-started/) — Installation, configuration, first run
|
|
52
|
+
- [Architecture](https://db-lyon.github.io/ue-mcp/architecture/) — How the pieces fit together
|
|
53
|
+
- [Tool Reference](https://db-lyon.github.io/ue-mcp/tool-reference/) — All 19 tools with every action
|
|
54
|
+
- [Configuration](https://db-lyon.github.io/ue-mcp/configuration/) — `.ue-mcp.json` and MCP client config
|
|
55
|
+
- [Neon Shrine Demo](https://db-lyon.github.io/ue-mcp/neon-shrine-demo/) — Interactive guided demo
|
|
56
|
+
- [Feedback](https://db-lyon.github.io/ue-mcp/feedback/) — Agent feedback system
|
|
57
|
+
- [Troubleshooting](https://db-lyon.github.io/ue-mcp/troubleshooting/) — Common issues and fixes
|
|
58
|
+
- [Development](https://db-lyon.github.io/ue-mcp/development/) — Building, testing, contributing
|
|
59
|
+
|
|
60
|
+
## What Can It Do?
|
|
61
|
+
|
|
62
|
+
| Category | Examples |
|
|
63
|
+
|----------|----------|
|
|
64
|
+
| **Levels** | Place/move/delete actors, spawn lights and volumes, manage splines |
|
|
65
|
+
| **Blueprints** | Read/write graphs, add nodes, connect pins, compile |
|
|
66
|
+
| **Materials** | Create materials and instances, author expression graphs |
|
|
67
|
+
| **Assets** | CRUD, import meshes/textures/animations, datatables |
|
|
68
|
+
| **Animation** | Anim blueprints, montages, blendspaces, skeletons |
|
|
69
|
+
| **VFX** | Niagara systems, emitters, parameters |
|
|
70
|
+
| **Landscape** | Sculpt terrain, paint layers, import heightmaps |
|
|
71
|
+
| **PCG** | Author and execute Procedural Content Generation graphs |
|
|
72
|
+
| **Gameplay** | Physics, collision, navigation, behavior trees, EQS, perception |
|
|
73
|
+
| **GAS** | Gameplay Ability System — attributes, abilities, effects, cues |
|
|
74
|
+
| **Networking** | Replication, dormancy, relevancy, net priority |
|
|
75
|
+
| **UI** | UMG widgets, editor utility widgets and blueprints |
|
|
76
|
+
| **Editor** | Console, Python, PIE, viewport, sequencer, build pipeline, logs |
|
|
77
|
+
| **Reflection** | Class/struct/enum introspection, gameplay tags |
|
|
78
|
+
|
|
79
|
+
## Supported Versions
|
|
80
|
+
|
|
81
|
+
Tested with UE 5.4–5.7. Requires `PythonScriptPlugin` (ships with UE 4.26+).
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
Issues and pull requests welcome. If an AI agent had to fall back to `execute_python` during your session, it will offer to submit structured feedback automatically — this helps us prioritize which native handlers to add next.
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT — see [LICENSE](LICENSE).
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface BridgeResponse {
|
|
2
|
+
id: string;
|
|
3
|
+
result?: unknown;
|
|
4
|
+
error?: {
|
|
5
|
+
code: number;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare class EditorBridge {
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
private ws;
|
|
13
|
+
private pending;
|
|
14
|
+
private reconnectTimer;
|
|
15
|
+
private idCounter;
|
|
16
|
+
constructor(host?: string, port?: number);
|
|
17
|
+
get isConnected(): boolean;
|
|
18
|
+
connect(timeoutMs?: number): Promise<void>;
|
|
19
|
+
startReconnecting(intervalMs?: number): void;
|
|
20
|
+
stopReconnecting(): void;
|
|
21
|
+
call(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
22
|
+
callOrThrow(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
23
|
+
disconnect(): void;
|
|
24
|
+
private setupListeners;
|
|
25
|
+
}
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
export class EditorBridge {
|
|
3
|
+
host;
|
|
4
|
+
port;
|
|
5
|
+
ws = null;
|
|
6
|
+
pending = new Map();
|
|
7
|
+
reconnectTimer = null;
|
|
8
|
+
idCounter = 0;
|
|
9
|
+
constructor(host = "localhost", port = 9877) {
|
|
10
|
+
this.host = host;
|
|
11
|
+
this.port = port;
|
|
12
|
+
}
|
|
13
|
+
get isConnected() {
|
|
14
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
15
|
+
}
|
|
16
|
+
async connect(timeoutMs = 3000) {
|
|
17
|
+
if (this.isConnected)
|
|
18
|
+
return;
|
|
19
|
+
this.ws?.terminate();
|
|
20
|
+
const url = `ws://${this.host}:${this.port}`;
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const timer = setTimeout(() => {
|
|
23
|
+
ws.terminate();
|
|
24
|
+
reject(new Error(`Connection to editor bridge timed out (${url})`));
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
const ws = new WebSocket(url);
|
|
27
|
+
ws.on("open", () => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
this.ws = ws;
|
|
30
|
+
this.setupListeners(ws);
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
ws.on("error", (err) => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
reject(new Error(`Failed to connect to editor bridge at ${url}: ${err.message}`));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
startReconnecting(intervalMs = 15000) {
|
|
40
|
+
if (this.reconnectTimer)
|
|
41
|
+
return;
|
|
42
|
+
this.reconnectTimer = setInterval(async () => {
|
|
43
|
+
if (this.isConnected)
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
await this.connect();
|
|
47
|
+
console.error("[ue-mcp] Editor bridge reconnected");
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// silent retry
|
|
51
|
+
}
|
|
52
|
+
}, intervalMs);
|
|
53
|
+
}
|
|
54
|
+
stopReconnecting() {
|
|
55
|
+
if (this.reconnectTimer) {
|
|
56
|
+
clearInterval(this.reconnectTimer);
|
|
57
|
+
this.reconnectTimer = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async call(method, params) {
|
|
61
|
+
if (!this.isConnected) {
|
|
62
|
+
throw new Error("Not connected to editor bridge. Is Unreal Editor running with the MCP bridge plugin?");
|
|
63
|
+
}
|
|
64
|
+
const id = String(++this.idCounter);
|
|
65
|
+
const request = { id, method, params: params ?? {} };
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const timer = setTimeout(() => {
|
|
68
|
+
this.pending.delete(id);
|
|
69
|
+
reject(new Error(`Bridge call '${method}' timed out after 30s`));
|
|
70
|
+
}, 30_000);
|
|
71
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
72
|
+
this.ws.send(JSON.stringify(request));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async callOrThrow(method, params) {
|
|
76
|
+
const result = await this.call(method, params);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
disconnect() {
|
|
80
|
+
this.stopReconnecting();
|
|
81
|
+
for (const [, pending] of this.pending) {
|
|
82
|
+
clearTimeout(pending.timer);
|
|
83
|
+
pending.reject(new Error("Bridge disconnected"));
|
|
84
|
+
}
|
|
85
|
+
this.pending.clear();
|
|
86
|
+
if (this.ws) {
|
|
87
|
+
this.ws.close();
|
|
88
|
+
this.ws = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
setupListeners(ws) {
|
|
92
|
+
ws.on("message", (data) => {
|
|
93
|
+
try {
|
|
94
|
+
const msg = JSON.parse(data.toString());
|
|
95
|
+
const pending = this.pending.get(msg.id);
|
|
96
|
+
if (!pending)
|
|
97
|
+
return;
|
|
98
|
+
this.pending.delete(msg.id);
|
|
99
|
+
clearTimeout(pending.timer);
|
|
100
|
+
if (msg.error) {
|
|
101
|
+
pending.reject(new Error(`Bridge error: ${msg.error.message}`));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
pending.resolve(msg.result);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// malformed message, ignore
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
ws.on("close", () => {
|
|
112
|
+
for (const [, pending] of this.pending) {
|
|
113
|
+
clearTimeout(pending.timer);
|
|
114
|
+
pending.reject(new Error("Bridge connection lost"));
|
|
115
|
+
}
|
|
116
|
+
this.pending.clear();
|
|
117
|
+
this.ws = null;
|
|
118
|
+
});
|
|
119
|
+
ws.on("error", () => {
|
|
120
|
+
// handled by close
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProjectContext } from "./project.js";
|
|
2
|
+
export interface DeployResult {
|
|
3
|
+
pythonPluginEnabled: boolean;
|
|
4
|
+
cppPluginDeployed: boolean;
|
|
5
|
+
cppPluginEnabled: boolean;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Deploy the C++ bridge plugin to the target UE project.
|
|
10
|
+
*
|
|
11
|
+
* Copies plugin source from plugin/ue_mcp_bridge/ into the target
|
|
12
|
+
* project's Plugins/UE_MCP_Bridge/ directory (skipping build artifacts).
|
|
13
|
+
* Also enables PythonScriptPlugin in the .uproject because the C++
|
|
14
|
+
* bridge's `execute_python` handler calls into it at runtime.
|
|
15
|
+
*/
|
|
16
|
+
export declare function deploy(context: ProjectContext): DeployResult;
|
|
17
|
+
export declare function deploySummary(r: DeployResult): string;
|
|
18
|
+
export declare function findEngineInstall(engineAssociation: string | null): string | null;
|
package/dist/deployer.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
/**
|
|
5
|
+
* Deploy the C++ bridge plugin to the target UE project.
|
|
6
|
+
*
|
|
7
|
+
* Copies plugin source from plugin/ue_mcp_bridge/ into the target
|
|
8
|
+
* project's Plugins/UE_MCP_Bridge/ directory (skipping build artifacts).
|
|
9
|
+
* Also enables PythonScriptPlugin in the .uproject because the C++
|
|
10
|
+
* bridge's `execute_python` handler calls into it at runtime.
|
|
11
|
+
*/
|
|
12
|
+
export function deploy(context) {
|
|
13
|
+
const result = {
|
|
14
|
+
pythonPluginEnabled: false,
|
|
15
|
+
cppPluginDeployed: false,
|
|
16
|
+
cppPluginEnabled: false,
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
result.pythonPluginEnabled = ensurePythonPlugin(context.projectPath);
|
|
20
|
+
result.cppPluginDeployed = deployCppPlugin(context.projectPath);
|
|
21
|
+
result.cppPluginEnabled = ensureCppPluginEnabled(context.projectPath);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
result.error = e instanceof Error ? e.message : String(e);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
export function deploySummary(r) {
|
|
29
|
+
if (r.error)
|
|
30
|
+
return `Bridge deployment failed: ${r.error}`;
|
|
31
|
+
const changes = [];
|
|
32
|
+
if (r.pythonPluginEnabled)
|
|
33
|
+
changes.push("enabled PythonScriptPlugin");
|
|
34
|
+
if (r.cppPluginDeployed)
|
|
35
|
+
changes.push("deployed C++ bridge plugin");
|
|
36
|
+
if (r.cppPluginEnabled)
|
|
37
|
+
changes.push("enabled UE_MCP_Bridge in .uproject");
|
|
38
|
+
if (changes.length === 0)
|
|
39
|
+
return "Bridge already configured";
|
|
40
|
+
return "Bridge setup: " + changes.join(", ");
|
|
41
|
+
}
|
|
42
|
+
/* ------------------------------------------------------------------ */
|
|
43
|
+
/* PythonScriptPlugin — still needed for execute_python escape hatch */
|
|
44
|
+
/* ------------------------------------------------------------------ */
|
|
45
|
+
function ensurePythonPlugin(uprojectPath) {
|
|
46
|
+
const raw = fs.readFileSync(uprojectPath, "utf-8");
|
|
47
|
+
const root = JSON.parse(raw);
|
|
48
|
+
if (!root.Plugins)
|
|
49
|
+
root.Plugins = [];
|
|
50
|
+
const already = root.Plugins.some((p) => p.Name?.toLowerCase() === "pythonscriptplugin");
|
|
51
|
+
if (already)
|
|
52
|
+
return false;
|
|
53
|
+
root.Plugins.unshift({ Name: "PythonScriptPlugin", Enabled: true });
|
|
54
|
+
fs.writeFileSync(uprojectPath, JSON.stringify(root, null, "\t"));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/* ------------------------------------------------------------------ */
|
|
58
|
+
/* C++ Plugin deployment */
|
|
59
|
+
/* ------------------------------------------------------------------ */
|
|
60
|
+
function deployCppPlugin(uprojectPath) {
|
|
61
|
+
const projectDir = path.dirname(uprojectPath);
|
|
62
|
+
const pluginsDir = path.join(projectDir, "Plugins");
|
|
63
|
+
const sourcePluginDir = path.resolve(import.meta.dirname ?? path.dirname(new URL(import.meta.url).pathname), "..", "plugin", "ue_mcp_bridge");
|
|
64
|
+
if (!fs.existsSync(sourcePluginDir)) {
|
|
65
|
+
console.error(`[ue-mcp] C++ plugin source not found at ${sourcePluginDir}`);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const targetPluginDir = path.join(pluginsDir, "UE_MCP_Bridge");
|
|
69
|
+
let anyDeployed = false;
|
|
70
|
+
function copyRecursive(src, dest) {
|
|
71
|
+
if (!fs.existsSync(dest)) {
|
|
72
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
75
|
+
const srcPath = path.join(src, entry.name);
|
|
76
|
+
const destPath = path.join(dest, entry.name);
|
|
77
|
+
// Skip build artifacts
|
|
78
|
+
if (entry.name === "Binaries" ||
|
|
79
|
+
entry.name === "Intermediate" ||
|
|
80
|
+
entry.name === "Saved") {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
copyRecursive(srcPath, destPath);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const srcBytes = fs.readFileSync(srcPath);
|
|
88
|
+
let shouldWrite = true;
|
|
89
|
+
if (fs.existsSync(destPath)) {
|
|
90
|
+
const destBytes = fs.readFileSync(destPath);
|
|
91
|
+
shouldWrite = !srcBytes.equals(destBytes);
|
|
92
|
+
}
|
|
93
|
+
if (shouldWrite) {
|
|
94
|
+
fs.writeFileSync(destPath, srcBytes);
|
|
95
|
+
anyDeployed = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
copyRecursive(sourcePluginDir, targetPluginDir);
|
|
101
|
+
return anyDeployed;
|
|
102
|
+
}
|
|
103
|
+
function ensureCppPluginEnabled(uprojectPath) {
|
|
104
|
+
const raw = fs.readFileSync(uprojectPath, "utf-8");
|
|
105
|
+
const root = JSON.parse(raw);
|
|
106
|
+
if (!root.Plugins)
|
|
107
|
+
root.Plugins = [];
|
|
108
|
+
const already = root.Plugins.some((p) => p.Name === "UE_MCP_Bridge");
|
|
109
|
+
if (already)
|
|
110
|
+
return false;
|
|
111
|
+
root.Plugins.push({
|
|
112
|
+
Name: "UE_MCP_Bridge",
|
|
113
|
+
Enabled: true,
|
|
114
|
+
});
|
|
115
|
+
fs.writeFileSync(uprojectPath, JSON.stringify(root, null, "\t"));
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
/* ------------------------------------------------------------------ */
|
|
119
|
+
/* Engine discovery (used by editor-control) */
|
|
120
|
+
/* ------------------------------------------------------------------ */
|
|
121
|
+
export function findEngineInstall(engineAssociation) {
|
|
122
|
+
if (!engineAssociation)
|
|
123
|
+
return null;
|
|
124
|
+
const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
125
|
+
if (guidRegex.test(engineAssociation)) {
|
|
126
|
+
return findEngineByGuid(engineAssociation);
|
|
127
|
+
}
|
|
128
|
+
return findLauncherEngine(engineAssociation);
|
|
129
|
+
}
|
|
130
|
+
function findEngineByGuid(guid) {
|
|
131
|
+
try {
|
|
132
|
+
const output = execSync(`reg query "HKCU\\SOFTWARE\\Epic Games\\Unreal Engine\\Builds" /v "${guid}"`, { stdio: "pipe", encoding: "utf-8" });
|
|
133
|
+
const match = output.match(/REG_SZ\s+(.+)/);
|
|
134
|
+
if (match) {
|
|
135
|
+
const p = match[1].trim();
|
|
136
|
+
if (fs.existsSync(p))
|
|
137
|
+
return p;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// registry key not found
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function findLauncherEngine(association) {
|
|
146
|
+
const launcherDat = path.join(process.env.PROGRAMDATA || "C:\\ProgramData", "Epic", "UnrealEngineLauncher", "LauncherInstalled.dat");
|
|
147
|
+
if (fs.existsSync(launcherDat)) {
|
|
148
|
+
try {
|
|
149
|
+
const data = JSON.parse(fs.readFileSync(launcherDat, "utf-8"));
|
|
150
|
+
for (const entry of data.InstallationList ?? []) {
|
|
151
|
+
if (entry.AppName?.toLowerCase() ===
|
|
152
|
+
`ue_${association}`.toLowerCase()) {
|
|
153
|
+
if (fs.existsSync(entry.InstallLocation)) {
|
|
154
|
+
return entry.InstallLocation;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// malformed manifest
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const root of [
|
|
164
|
+
"C:\\Program Files\\Epic Games",
|
|
165
|
+
"D:\\Program Files\\Epic Games",
|
|
166
|
+
"C:\\Epic Games",
|
|
167
|
+
"D:\\Epic Games",
|
|
168
|
+
]) {
|
|
169
|
+
const candidate = path.join(root, `UE_${association}`);
|
|
170
|
+
if (fs.existsSync(candidate))
|
|
171
|
+
return candidate;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=deployer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployer.js","sourceRoot":"","sources":["../src/deployer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProjectContext } from "./project.js";
|
|
2
|
+
export declare function startEditor(project: ProjectContext): Promise<{
|
|
3
|
+
success: boolean;
|
|
4
|
+
message: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function stopEditor(force?: boolean): Promise<{
|
|
7
|
+
success: boolean;
|
|
8
|
+
message: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function restartEditor(project: ProjectContext, bridge?: {
|
|
11
|
+
connect: (timeoutMs?: number) => Promise<void>;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { spawn, execSync } from "child_process";
|
|
4
|
+
import * as net from "net";
|
|
5
|
+
let editorProcess = null;
|
|
6
|
+
function findUEBuildTool() {
|
|
7
|
+
const envPath = process.env.UE_BUILD_TOOL_PATH;
|
|
8
|
+
if (envPath)
|
|
9
|
+
return envPath;
|
|
10
|
+
const versions = ["5.7", "5.6", "5.5", "5.4", "5.3"];
|
|
11
|
+
const basePath = "C:/Program Files/Epic Games";
|
|
12
|
+
for (const version of versions) {
|
|
13
|
+
const buildToolPath = path.join(basePath, `UE_${version}`, "Engine", "Build", "BatchFiles", "Build.bat");
|
|
14
|
+
if (fs.existsSync(buildToolPath)) {
|
|
15
|
+
return buildToolPath;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function findEditorExecutable() {
|
|
21
|
+
const envPath = process.env.UE_EDITOR_PATH;
|
|
22
|
+
if (envPath)
|
|
23
|
+
return envPath;
|
|
24
|
+
const buildTool = findUEBuildTool();
|
|
25
|
+
if (!buildTool)
|
|
26
|
+
return null;
|
|
27
|
+
const engineRoot = path.resolve(buildTool, "..", "..", "..", "..");
|
|
28
|
+
const editorExe = path.join(engineRoot, "Engine", "Binaries", "Win64", "UnrealEditor.exe");
|
|
29
|
+
if (fs.existsSync(editorExe)) {
|
|
30
|
+
return editorExe;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function isEditorRunning() {
|
|
35
|
+
try {
|
|
36
|
+
execSync('tasklist /FI "IMAGENAME eq UnrealEditor.exe" | find /I "UnrealEditor.exe"', { stdio: "pipe" });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function isBridgeAvailable(host = "localhost", port = 9877, timeoutMs = 1000) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const socket = new net.Socket();
|
|
46
|
+
let resolved = false;
|
|
47
|
+
const timer = setTimeout(() => {
|
|
48
|
+
if (!resolved) {
|
|
49
|
+
resolved = true;
|
|
50
|
+
socket.destroy();
|
|
51
|
+
resolve(false);
|
|
52
|
+
}
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
socket.once("connect", () => {
|
|
55
|
+
if (!resolved) {
|
|
56
|
+
resolved = true;
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
socket.destroy();
|
|
59
|
+
resolve(true);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
socket.once("error", () => {
|
|
63
|
+
if (!resolved) {
|
|
64
|
+
resolved = true;
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
resolve(false);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
socket.connect(port, host);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function waitForBridge(maxWaitSeconds = 120, checkIntervalMs = 2000) {
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const maxWaitMs = maxWaitSeconds * 1000;
|
|
75
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
76
|
+
if (await isBridgeAvailable()) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, checkIntervalMs));
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
export async function startEditor(project) {
|
|
84
|
+
if (isEditorRunning()) {
|
|
85
|
+
return { success: false, message: "Editor is already running" };
|
|
86
|
+
}
|
|
87
|
+
const editorExe = findEditorExecutable();
|
|
88
|
+
if (!editorExe) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
message: "Unreal Editor executable not found. Set UE_EDITOR_PATH environment variable or install UE5.3+ to default location.",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (!project.projectPath) {
|
|
95
|
+
return { success: false, message: "No project loaded. Use project(action='set_project') first." };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
editorProcess = spawn(editorExe, [project.projectPath], {
|
|
99
|
+
stdio: "ignore",
|
|
100
|
+
detached: true,
|
|
101
|
+
});
|
|
102
|
+
editorProcess.unref();
|
|
103
|
+
// Wait for bridge to become available (editor fully started)
|
|
104
|
+
const bridgeAvailable = await waitForBridge(120, 2000);
|
|
105
|
+
if (!bridgeAvailable) {
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
message: "Editor launched but bridge did not become available within 120 seconds. Editor may still be starting up.",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return { success: true, message: `Editor launched and bridge available: ${editorExe}` };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
message: `Failed to launch editor: ${error instanceof Error ? error.message : String(error)}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export async function stopEditor(force = false) {
|
|
121
|
+
if (!isEditorRunning()) {
|
|
122
|
+
return { success: false, message: "Editor is not running" };
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
if (force) {
|
|
126
|
+
execSync('taskkill /F /IM UnrealEditor.exe', { stdio: "pipe" });
|
|
127
|
+
editorProcess = null;
|
|
128
|
+
return { success: true, message: "Editor force-killed" };
|
|
129
|
+
}
|
|
130
|
+
// Graceful close - sends WM_CLOSE, allows save dialogs
|
|
131
|
+
execSync('taskkill /IM UnrealEditor.exe', { stdio: "pipe" });
|
|
132
|
+
// Wait up to 10 seconds for editor to close gracefully
|
|
133
|
+
for (let i = 0; i < 10; i++) {
|
|
134
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
135
|
+
if (!isEditorRunning()) {
|
|
136
|
+
editorProcess = null;
|
|
137
|
+
return { success: true, message: "Editor closed successfully" };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Graceful close failed — force kill
|
|
141
|
+
execSync('taskkill /F /IM UnrealEditor.exe', { stdio: "pipe" });
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
143
|
+
editorProcess = null;
|
|
144
|
+
if (!isEditorRunning()) {
|
|
145
|
+
return { success: true, message: "Editor force-killed after graceful close timed out" };
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
message: "Editor still running after force kill attempt. Close manually.",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
message: `Failed to stop editor: ${error instanceof Error ? error.message : String(error)}`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export async function restartEditor(project, bridge) {
|
|
160
|
+
const stopResult = await stopEditor();
|
|
161
|
+
if (!stopResult.success && isEditorRunning()) {
|
|
162
|
+
return { success: false, message: `Failed to stop editor: ${stopResult.message}` };
|
|
163
|
+
}
|
|
164
|
+
// Wait for process to fully terminate and release locks
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
166
|
+
const startResult = await startEditor(project);
|
|
167
|
+
if (!startResult.success) {
|
|
168
|
+
return startResult;
|
|
169
|
+
}
|
|
170
|
+
// Reconnect the bridge if provided
|
|
171
|
+
if (bridge) {
|
|
172
|
+
try {
|
|
173
|
+
await bridge.connect(5000);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Bridge reconnect timer will handle it
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return startResult;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=editor-control.js.map
|