@akiojin/unity-mcp-server 2.14.14
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 +206 -0
- package/bin/unity-mcp-server +2 -0
- package/package.json +73 -0
- package/src/core/codeIndex.js +163 -0
- package/src/core/codeIndexDb.js +96 -0
- package/src/core/config.js +165 -0
- package/src/core/indexWatcher.js +52 -0
- package/src/core/projectInfo.js +111 -0
- package/src/core/server.js +294 -0
- package/src/core/unityConnection.js +426 -0
- package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +35 -0
- package/src/handlers/analysis/FindByComponentToolHandler.js +20 -0
- package/src/handlers/analysis/GetAnimatorStateToolHandler.js +37 -0
- package/src/handlers/analysis/GetComponentValuesToolHandler.js +20 -0
- package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +35 -0
- package/src/handlers/analysis/GetInputActionsStateToolHandler.js +37 -0
- package/src/handlers/analysis/GetObjectReferencesToolHandler.js +20 -0
- package/src/handlers/asset/AssetDatabaseToolHandler.js +221 -0
- package/src/handlers/asset/AssetDependencyToolHandler.js +201 -0
- package/src/handlers/asset/AssetImportSettingsToolHandler.js +170 -0
- package/src/handlers/asset/CreateMaterialToolHandler.js +96 -0
- package/src/handlers/asset/CreatePrefabToolHandler.js +78 -0
- package/src/handlers/asset/ExitPrefabModeToolHandler.js +83 -0
- package/src/handlers/asset/InstantiatePrefabToolHandler.js +133 -0
- package/src/handlers/asset/ModifyMaterialToolHandler.js +76 -0
- package/src/handlers/asset/ModifyPrefabToolHandler.js +72 -0
- package/src/handlers/asset/OpenPrefabToolHandler.js +121 -0
- package/src/handlers/asset/SavePrefabToolHandler.js +106 -0
- package/src/handlers/base/BaseToolHandler.js +133 -0
- package/src/handlers/compilation/GetCompilationStateToolHandler.js +90 -0
- package/src/handlers/component/AddComponentToolHandler.js +126 -0
- package/src/handlers/component/GetComponentTypesToolHandler.js +100 -0
- package/src/handlers/component/ListComponentsToolHandler.js +85 -0
- package/src/handlers/component/ModifyComponentToolHandler.js +143 -0
- package/src/handlers/component/RemoveComponentToolHandler.js +108 -0
- package/src/handlers/console/ClearConsoleToolHandler.js +160 -0
- package/src/handlers/console/ReadConsoleToolHandler.js +276 -0
- package/src/handlers/editor/LayerManagementToolHandler.js +160 -0
- package/src/handlers/editor/SelectionToolHandler.js +141 -0
- package/src/handlers/editor/TagManagementToolHandler.js +129 -0
- package/src/handlers/editor/ToolManagementToolHandler.js +135 -0
- package/src/handlers/editor/WindowManagementToolHandler.js +125 -0
- package/src/handlers/gameobject/CreateGameObjectToolHandler.js +131 -0
- package/src/handlers/gameobject/DeleteGameObjectToolHandler.js +101 -0
- package/src/handlers/gameobject/FindGameObjectToolHandler.js +119 -0
- package/src/handlers/gameobject/GetHierarchyToolHandler.js +132 -0
- package/src/handlers/gameobject/ModifyGameObjectToolHandler.js +128 -0
- package/src/handlers/index.js +389 -0
- package/src/handlers/input/AddInputActionToolHandler.js +20 -0
- package/src/handlers/input/AddInputBindingToolHandler.js +20 -0
- package/src/handlers/input/CreateActionMapToolHandler.js +20 -0
- package/src/handlers/input/CreateCompositeBindingToolHandler.js +20 -0
- package/src/handlers/input/GamepadSimulationHandler.js +116 -0
- package/src/handlers/input/InputSystemHandler.js +80 -0
- package/src/handlers/input/KeyboardSimulationHandler.js +79 -0
- package/src/handlers/input/ManageControlSchemesToolHandler.js +20 -0
- package/src/handlers/input/MouseSimulationHandler.js +107 -0
- package/src/handlers/input/RemoveActionMapToolHandler.js +20 -0
- package/src/handlers/input/RemoveAllBindingsToolHandler.js +20 -0
- package/src/handlers/input/RemoveInputActionToolHandler.js +20 -0
- package/src/handlers/input/RemoveInputBindingToolHandler.js +20 -0
- package/src/handlers/input/TouchSimulationHandler.js +142 -0
- package/src/handlers/menu/ExecuteMenuItemToolHandler.js +304 -0
- package/src/handlers/package/PackageManagerToolHandler.js +248 -0
- package/src/handlers/package/RegistryConfigToolHandler.js +198 -0
- package/src/handlers/playmode/GetEditorStateToolHandler.js +81 -0
- package/src/handlers/playmode/PauseToolHandler.js +44 -0
- package/src/handlers/playmode/PlayToolHandler.js +91 -0
- package/src/handlers/playmode/StopToolHandler.js +77 -0
- package/src/handlers/playmode/WaitForEditorStateToolHandler.js +45 -0
- package/src/handlers/scene/CreateSceneToolHandler.js +91 -0
- package/src/handlers/scene/GetSceneInfoToolHandler.js +20 -0
- package/src/handlers/scene/ListScenesToolHandler.js +58 -0
- package/src/handlers/scene/LoadSceneToolHandler.js +92 -0
- package/src/handlers/scene/SaveSceneToolHandler.js +76 -0
- package/src/handlers/screenshot/AnalyzeScreenshotToolHandler.js +238 -0
- package/src/handlers/screenshot/CaptureScreenshotToolHandler.js +692 -0
- package/src/handlers/script/BuildCodeIndexToolHandler.js +163 -0
- package/src/handlers/script/ScriptCreateClassFileToolHandler.js +60 -0
- package/src/handlers/script/ScriptEditStructuredToolHandler.js +173 -0
- package/src/handlers/script/ScriptIndexStatusToolHandler.js +61 -0
- package/src/handlers/script/ScriptPackagesListToolHandler.js +103 -0
- package/src/handlers/script/ScriptReadToolHandler.js +106 -0
- package/src/handlers/script/ScriptRefactorRenameToolHandler.js +83 -0
- package/src/handlers/script/ScriptRefsFindToolHandler.js +144 -0
- package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +79 -0
- package/src/handlers/script/ScriptSearchToolHandler.js +320 -0
- package/src/handlers/script/ScriptSymbolFindToolHandler.js +117 -0
- package/src/handlers/script/ScriptSymbolsGetToolHandler.js +96 -0
- package/src/handlers/settings/GetProjectSettingsToolHandler.js +161 -0
- package/src/handlers/settings/UpdateProjectSettingsToolHandler.js +272 -0
- package/src/handlers/system/GetCommandStatsToolHandler.js +25 -0
- package/src/handlers/system/PingToolHandler.js +53 -0
- package/src/handlers/system/RefreshAssetsToolHandler.js +45 -0
- package/src/handlers/ui/ClickUIElementToolHandler.js +110 -0
- package/src/handlers/ui/FindUIElementsToolHandler.js +63 -0
- package/src/handlers/ui/GetUIElementStateToolHandler.js +50 -0
- package/src/handlers/ui/SetUIElementValueToolHandler.js +49 -0
- package/src/handlers/ui/SimulateUIInputToolHandler.js +156 -0
- package/src/handlers/video/CaptureVideoForToolHandler.js +96 -0
- package/src/handlers/video/CaptureVideoStartToolHandler.js +38 -0
- package/src/handlers/video/CaptureVideoStatusToolHandler.js +30 -0
- package/src/handlers/video/CaptureVideoStopToolHandler.js +32 -0
- package/src/lsp/CSharpLspUtils.js +134 -0
- package/src/lsp/LspProcessManager.js +60 -0
- package/src/lsp/LspRpcClient.js +133 -0
- package/src/tools/analysis/analyzeSceneContents.js +100 -0
- package/src/tools/analysis/findByComponent.js +87 -0
- package/src/tools/analysis/getAnimatorState.js +326 -0
- package/src/tools/analysis/getComponentValues.js +182 -0
- package/src/tools/analysis/getGameObjectDetails.js +159 -0
- package/src/tools/analysis/getInputActionsState.js +329 -0
- package/src/tools/analysis/getObjectReferences.js +86 -0
- package/src/tools/input/inputActionsEditor.js +556 -0
- package/src/tools/scene/createScene.js +112 -0
- package/src/tools/scene/getSceneInfo.js +95 -0
- package/src/tools/scene/listScenes.js +82 -0
- package/src/tools/scene/loadScene.js +122 -0
- package/src/tools/scene/saveScene.js +91 -0
- package/src/tools/system/ping.js +72 -0
- package/src/tools/video/recordFor.js +31 -0
- package/src/tools/video/recordPlayMode.js +61 -0
- package/src/utils/csharpParse.js +88 -0
- package/src/utils/validators.js +90 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handler for the ping tool
|
|
5
|
+
* Tests connection to Unity Editor
|
|
6
|
+
*/
|
|
7
|
+
export class PingToolHandler extends BaseToolHandler {
|
|
8
|
+
constructor(unityConnection) {
|
|
9
|
+
super(
|
|
10
|
+
'ping',
|
|
11
|
+
'Test connection to Unity Editor',
|
|
12
|
+
{
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
message: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Optional message to echo back'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
required: []
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
this.unityConnection = unityConnection;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Executes the ping command
|
|
29
|
+
* @param {object} params - Input parameters
|
|
30
|
+
* @returns {Promise<object>} Ping result
|
|
31
|
+
*/
|
|
32
|
+
async execute(params) {
|
|
33
|
+
// Ensure connected
|
|
34
|
+
if (!this.unityConnection.isConnected()) {
|
|
35
|
+
await this.unityConnection.connect();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Send ping command with optional message
|
|
39
|
+
const result = await this.unityConnection.sendCommand('ping', {
|
|
40
|
+
message: params.message || 'ping'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Format the result for the response
|
|
44
|
+
return {
|
|
45
|
+
message: result.message,
|
|
46
|
+
echo: result.echo || params.message || 'ping',
|
|
47
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
48
|
+
unityVersion: result.unityVersion,
|
|
49
|
+
version: result._version || result.version,
|
|
50
|
+
editorState: result._editorState || result.editorState
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handler for the refresh_assets tool
|
|
5
|
+
* Triggers Unity to refresh assets and potentially recompile
|
|
6
|
+
*/
|
|
7
|
+
export class RefreshAssetsToolHandler extends BaseToolHandler {
|
|
8
|
+
constructor(unityConnection) {
|
|
9
|
+
super(
|
|
10
|
+
'refresh_assets',
|
|
11
|
+
'Refresh assets and check compilation status.',
|
|
12
|
+
{
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {},
|
|
15
|
+
required: []
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
this.unityConnection = unityConnection;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Executes the refresh_assets command
|
|
24
|
+
* @param {object} params - Input parameters (none required)
|
|
25
|
+
* @returns {Promise<object>} Refresh result
|
|
26
|
+
*/
|
|
27
|
+
async execute(params) {
|
|
28
|
+
// Ensure connected
|
|
29
|
+
if (!this.unityConnection.isConnected()) {
|
|
30
|
+
await this.unityConnection.connect();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Send refresh_assets command
|
|
34
|
+
const result = await this.unityConnection.sendCommand('refresh_assets', {});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
message: result.message,
|
|
38
|
+
isCompiling: result.isCompiling,
|
|
39
|
+
timestamp: result.timestamp,
|
|
40
|
+
note: result.isCompiling
|
|
41
|
+
? 'Unity is compiling. New commands will be available after compilation completes.'
|
|
42
|
+
: 'Asset refresh complete. Unity is not currently compiling.'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
+
|
|
3
|
+
export class ClickUIElementToolHandler extends BaseToolHandler {
|
|
4
|
+
constructor(unityConnection) {
|
|
5
|
+
super(
|
|
6
|
+
'click_ui_element',
|
|
7
|
+
'Simulate clicking on UI elements',
|
|
8
|
+
{
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
elementPath: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Full hierarchy path to the UI element'
|
|
14
|
+
},
|
|
15
|
+
clickType: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
enum: ['left', 'right', 'middle'],
|
|
18
|
+
|
|
19
|
+
description: 'Type of click (left, right, middle)'
|
|
20
|
+
},
|
|
21
|
+
holdDuration: {
|
|
22
|
+
type: 'number',
|
|
23
|
+
|
|
24
|
+
minimum: 0,
|
|
25
|
+
maximum: 10000,
|
|
26
|
+
description: 'Duration to hold click in milliseconds'
|
|
27
|
+
},
|
|
28
|
+
position: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
x: {
|
|
32
|
+
type: 'number',
|
|
33
|
+
minimum: 0,
|
|
34
|
+
maximum: 1,
|
|
35
|
+
description: 'X position within element (0-1)'
|
|
36
|
+
},
|
|
37
|
+
y: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
minimum: 0,
|
|
40
|
+
maximum: 1,
|
|
41
|
+
description: 'Y position within element (0-1)'
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
description: 'Specific position within element to click'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
required: ['elementPath']
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
this.unityConnection = unityConnection;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
validate(params) {
|
|
54
|
+
// Call parent validation for required fields
|
|
55
|
+
super.validate(params);
|
|
56
|
+
|
|
57
|
+
// Validate clickType enum
|
|
58
|
+
if (params.clickType && !['left', 'right', 'middle'].includes(params.clickType)) {
|
|
59
|
+
throw new Error('clickType must be one of: left, right, middle');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate holdDuration range
|
|
63
|
+
if (params.holdDuration !== undefined) {
|
|
64
|
+
const duration = params.holdDuration;
|
|
65
|
+
if (typeof duration !== 'number' || duration < 0 || duration > 10000) {
|
|
66
|
+
throw new Error('holdDuration must be between 0 and 10000 milliseconds');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate position object
|
|
71
|
+
if (params.position) {
|
|
72
|
+
const pos = params.position;
|
|
73
|
+
if (pos.x !== undefined && typeof pos.x !== 'number') {
|
|
74
|
+
throw new Error('position.x must be a number');
|
|
75
|
+
}
|
|
76
|
+
if (pos.y !== undefined && typeof pos.y !== 'number') {
|
|
77
|
+
throw new Error('position.y must be a number');
|
|
78
|
+
}
|
|
79
|
+
if (pos.x !== undefined && (pos.x < 0 || pos.x > 1)) {
|
|
80
|
+
throw new Error('position.x must be between 0 and 1');
|
|
81
|
+
}
|
|
82
|
+
if (pos.y !== undefined && (pos.y < 0 || pos.y > 1)) {
|
|
83
|
+
throw new Error('position.y must be between 0 and 1');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async execute(params) {
|
|
89
|
+
const {
|
|
90
|
+
elementPath,
|
|
91
|
+
clickType = 'left',
|
|
92
|
+
holdDuration = 0,
|
|
93
|
+
position
|
|
94
|
+
} = params;
|
|
95
|
+
|
|
96
|
+
// Ensure connected
|
|
97
|
+
if (!this.unityConnection.isConnected()) {
|
|
98
|
+
await this.unityConnection.connect();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const result = await this.unityConnection.sendCommand('click_ui_element', {
|
|
102
|
+
elementPath,
|
|
103
|
+
clickType,
|
|
104
|
+
holdDuration,
|
|
105
|
+
position
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
+
|
|
3
|
+
export class FindUIElementsToolHandler extends BaseToolHandler {
|
|
4
|
+
constructor(unityConnection) {
|
|
5
|
+
super(
|
|
6
|
+
'find_ui_elements',
|
|
7
|
+
'Find UI elements by component type, tag, or name pattern.',
|
|
8
|
+
{
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
elementType: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'UI component type (e.g., Button, Toggle, Slider).'
|
|
14
|
+
},
|
|
15
|
+
tagFilter: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Filter by GameObject tag.'
|
|
18
|
+
},
|
|
19
|
+
namePattern: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Search by name substring or regex.'
|
|
22
|
+
},
|
|
23
|
+
includeInactive: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
description: 'If true, include inactive UI elements.'
|
|
26
|
+
},
|
|
27
|
+
canvasFilter: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Filter by parent Canvas name.'
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
required: []
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
this.unityConnection = unityConnection;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async execute(params = {}) {
|
|
39
|
+
const {
|
|
40
|
+
elementType,
|
|
41
|
+
tagFilter,
|
|
42
|
+
namePattern,
|
|
43
|
+
includeInactive = false,
|
|
44
|
+
canvasFilter
|
|
45
|
+
} = params;
|
|
46
|
+
|
|
47
|
+
// Ensure connected
|
|
48
|
+
if (!this.unityConnection.isConnected()) {
|
|
49
|
+
await this.unityConnection.connect();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = await this.unityConnection.sendCommand('find_ui_elements', {
|
|
53
|
+
elementType,
|
|
54
|
+
tagFilter,
|
|
55
|
+
namePattern,
|
|
56
|
+
includeInactive,
|
|
57
|
+
canvasFilter
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Return the result for the BaseToolHandler to format
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
+
|
|
3
|
+
export class GetUIElementStateToolHandler extends BaseToolHandler {
|
|
4
|
+
constructor(unityConnection) {
|
|
5
|
+
super(
|
|
6
|
+
'get_ui_element_state',
|
|
7
|
+
'Get detailed state information about UI elements',
|
|
8
|
+
{
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
elementPath: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Full hierarchy path to the UI element'
|
|
14
|
+
},
|
|
15
|
+
includeChildren: {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
description: 'Include child element states (default: false)'
|
|
18
|
+
},
|
|
19
|
+
includeInteractableInfo: {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
description: 'Include interaction capabilities (default: true)'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: ['elementPath']
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
this.unityConnection = unityConnection;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async execute(params) {
|
|
31
|
+
const {
|
|
32
|
+
elementPath,
|
|
33
|
+
includeChildren = false,
|
|
34
|
+
includeInteractableInfo = true
|
|
35
|
+
} = params;
|
|
36
|
+
|
|
37
|
+
// Ensure connected
|
|
38
|
+
if (!this.unityConnection.isConnected()) {
|
|
39
|
+
await this.unityConnection.connect();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await this.unityConnection.sendCommand('get_ui_element_state', {
|
|
43
|
+
elementPath,
|
|
44
|
+
includeChildren,
|
|
45
|
+
includeInteractableInfo
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
+
|
|
3
|
+
export class SetUIElementValueToolHandler extends BaseToolHandler {
|
|
4
|
+
constructor(unityConnection) {
|
|
5
|
+
super(
|
|
6
|
+
'set_ui_element_value',
|
|
7
|
+
'Set values for UI input elements',
|
|
8
|
+
{
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
elementPath: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Full hierarchy path to the UI element'
|
|
14
|
+
},
|
|
15
|
+
value: {
|
|
16
|
+
description: 'New value to set (type depends on element type)'
|
|
17
|
+
},
|
|
18
|
+
triggerEvents: {
|
|
19
|
+
type: 'boolean',
|
|
20
|
+
description: 'Whether to trigger associated events (default: true)'
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
required: ['elementPath', 'value']
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
this.unityConnection = unityConnection;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async execute(params) {
|
|
30
|
+
const {
|
|
31
|
+
elementPath,
|
|
32
|
+
value,
|
|
33
|
+
triggerEvents = true
|
|
34
|
+
} = params;
|
|
35
|
+
|
|
36
|
+
// Ensure connected
|
|
37
|
+
if (!this.unityConnection.isConnected()) {
|
|
38
|
+
await this.unityConnection.connect();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = await this.unityConnection.sendCommand('set_ui_element_value', {
|
|
42
|
+
elementPath,
|
|
43
|
+
value,
|
|
44
|
+
triggerEvents
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
+
|
|
3
|
+
export class SimulateUIInputToolHandler extends BaseToolHandler {
|
|
4
|
+
constructor(unityConnection) {
|
|
5
|
+
super(
|
|
6
|
+
'simulate_ui_input',
|
|
7
|
+
'Simulate complex UI interactions and input sequences',
|
|
8
|
+
{
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
// Option 1: Simple single input
|
|
12
|
+
elementPath: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
description: 'Target UI element path (for simple input)'
|
|
15
|
+
},
|
|
16
|
+
inputType: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
enum: ['click', 'doubleclick', 'rightclick', 'hover', 'focus', 'type'],
|
|
19
|
+
description: 'Type of input to simulate (for simple input)'
|
|
20
|
+
},
|
|
21
|
+
inputData: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Data for input (e.g., text to type)'
|
|
24
|
+
},
|
|
25
|
+
// Option 2: Complex sequence
|
|
26
|
+
inputSequence: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
items: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
type: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Action type (click, setvalue)'
|
|
34
|
+
},
|
|
35
|
+
params: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
description: 'Parameters for the action'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
required: ['type', 'params']
|
|
41
|
+
},
|
|
42
|
+
description: 'Array of input actions to perform (for complex input)'
|
|
43
|
+
},
|
|
44
|
+
waitBetween: {
|
|
45
|
+
type: 'number',
|
|
46
|
+
|
|
47
|
+
minimum: 0,
|
|
48
|
+
maximum: 10000,
|
|
49
|
+
description: 'Delay between actions in milliseconds'
|
|
50
|
+
},
|
|
51
|
+
validateState: {
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
description: 'Validate UI state between actions (default: true)'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
this.unityConnection = unityConnection;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
validate(params) {
|
|
62
|
+
// Check if using simple mode or complex mode
|
|
63
|
+
const hasSimpleParams = params.elementPath && params.inputType;
|
|
64
|
+
const hasComplexParams = params.inputSequence;
|
|
65
|
+
|
|
66
|
+
if (!hasSimpleParams && !hasComplexParams) {
|
|
67
|
+
throw new Error('Either (elementPath + inputType) or inputSequence is required');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (hasSimpleParams && hasComplexParams) {
|
|
71
|
+
throw new Error('Cannot use both simple and complex input modes simultaneously');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate simple mode
|
|
75
|
+
if (hasSimpleParams) {
|
|
76
|
+
if (!params.elementPath || typeof params.elementPath !== 'string') {
|
|
77
|
+
throw new Error('elementPath must be a non-empty string');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const validInputTypes = ['click', 'doubleclick', 'rightclick', 'hover', 'focus', 'type'];
|
|
81
|
+
if (!validInputTypes.includes(params.inputType)) {
|
|
82
|
+
throw new Error(`inputType must be one of: ${validInputTypes.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (params.inputType === 'type' && !params.inputData) {
|
|
86
|
+
throw new Error('inputData is required when inputType is "type"');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validate complex mode
|
|
91
|
+
if (hasComplexParams) {
|
|
92
|
+
if (!Array.isArray(params.inputSequence)) {
|
|
93
|
+
throw new Error('inputSequence must be an array');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (params.inputSequence.length === 0) {
|
|
97
|
+
throw new Error('inputSequence must contain at least one action');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Validate each action
|
|
101
|
+
for (const action of params.inputSequence) {
|
|
102
|
+
if (!action.type || !action.params) {
|
|
103
|
+
throw new Error('Each action must have type and params');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate waitBetween range
|
|
109
|
+
if (params.waitBetween !== undefined) {
|
|
110
|
+
const wait = params.waitBetween;
|
|
111
|
+
if (typeof wait !== 'number' || wait < 0 || wait > 10000) {
|
|
112
|
+
throw new Error('waitBetween must be between 0 and 10000 milliseconds');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async execute(params) {
|
|
118
|
+
const {
|
|
119
|
+
elementPath,
|
|
120
|
+
inputType,
|
|
121
|
+
inputData,
|
|
122
|
+
inputSequence,
|
|
123
|
+
waitBetween = 100,
|
|
124
|
+
validateState = true
|
|
125
|
+
} = params;
|
|
126
|
+
|
|
127
|
+
// Ensure connected
|
|
128
|
+
if (!this.unityConnection.isConnected()) {
|
|
129
|
+
await this.unityConnection.connect();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let actualInputSequence;
|
|
133
|
+
|
|
134
|
+
// Handle simple mode - convert to sequence format
|
|
135
|
+
if (elementPath && inputType) {
|
|
136
|
+
actualInputSequence = [{
|
|
137
|
+
type: inputType,
|
|
138
|
+
params: {
|
|
139
|
+
elementPath,
|
|
140
|
+
inputData: inputData || null
|
|
141
|
+
}
|
|
142
|
+
}];
|
|
143
|
+
} else {
|
|
144
|
+
// Use provided sequence
|
|
145
|
+
actualInputSequence = inputSequence;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = await this.unityConnection.sendCommand('simulate_ui_input', {
|
|
149
|
+
inputSequence: actualInputSequence,
|
|
150
|
+
waitBetween,
|
|
151
|
+
validateState
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler for recording video for a fixed duration (auto-stop).
|
|
3
|
+
* Orchestrates start→wait→stop via Unity MCP commands.
|
|
4
|
+
*/
|
|
5
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
6
|
+
|
|
7
|
+
export class CaptureVideoForToolHandler extends BaseToolHandler {
|
|
8
|
+
constructor(unityConnection) {
|
|
9
|
+
super(
|
|
10
|
+
'capture_video_for',
|
|
11
|
+
'Record video for a fixed duration (auto-stop). Optionally enters Play Mode first.',
|
|
12
|
+
{
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
captureMode: { type: 'string', enum: ['game'], description: 'Capture source. Currently only "game" supported.' },
|
|
16
|
+
width: { type: 'number', description: 'Output width (0 = default 1280)' },
|
|
17
|
+
height: { type: 'number', description: 'Output height (0 = default 720)' },
|
|
18
|
+
fps: { type: 'number', description: 'Frames per second (default 30)' },
|
|
19
|
+
durationSec: { type: 'number', description: 'Duration to record in seconds' },
|
|
20
|
+
play: { type: 'boolean', description: 'Enter Play Mode before recording (default true if not already playing)' }
|
|
21
|
+
},
|
|
22
|
+
required: ['durationSec']
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
this.unityConnection = unityConnection;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @override */
|
|
29
|
+
async execute(params) {
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
let enteredPlay = false;
|
|
32
|
+
try {
|
|
33
|
+
// Optionally enter Play Mode (default true if not already playing)
|
|
34
|
+
let needPlay = !!params.play;
|
|
35
|
+
if (params.play === undefined) {
|
|
36
|
+
try {
|
|
37
|
+
const s0 = await this.unityConnection.sendCommand('get_editor_state', {});
|
|
38
|
+
needPlay = !(s0 && s0.isPlaying);
|
|
39
|
+
} catch { needPlay = true; }
|
|
40
|
+
}
|
|
41
|
+
if (needPlay) {
|
|
42
|
+
await this.unityConnection.sendCommand('play_game', {});
|
|
43
|
+
for (let i = 0; i < 60; i++) {
|
|
44
|
+
const s = await this.unityConnection.sendCommand('get_editor_state', {});
|
|
45
|
+
if (s && s.isPlaying) { enteredPlay = true; break; }
|
|
46
|
+
await sleep(250);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Start with auto-stop
|
|
51
|
+
const { WORKSPACE_ROOT } = await import('../../core/config.js');
|
|
52
|
+
const startResp = await this.unityConnection.sendCommand('capture_video_start', {
|
|
53
|
+
captureMode: params.captureMode || 'game',
|
|
54
|
+
width: params.width ?? 1280,
|
|
55
|
+
height: params.height ?? 720,
|
|
56
|
+
fps: params.fps ?? 30,
|
|
57
|
+
maxDurationSec: params.durationSec,
|
|
58
|
+
workspaceRoot: WORKSPACE_ROOT
|
|
59
|
+
});
|
|
60
|
+
if (startResp && startResp.error) {
|
|
61
|
+
return { error: startResp.error, code: startResp.code || 'UNITY_ERROR' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Wait until stopped (status reports isRecording=false)
|
|
65
|
+
const deadline = Date.now() + Math.max(0, Math.floor((params.durationSec || 0) * 1000)) + 1500; // small buffer
|
|
66
|
+
let lastStatus = null;
|
|
67
|
+
while (Date.now() < deadline) {
|
|
68
|
+
lastStatus = await this.unityConnection.sendCommand('capture_video_status', {});
|
|
69
|
+
if (lastStatus && lastStatus.isRecording === false) break;
|
|
70
|
+
await sleep(250);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Safety stop if still recording after deadline
|
|
74
|
+
if (lastStatus && lastStatus.isRecording) {
|
|
75
|
+
await this.unityConnection.sendCommand('capture_video_stop', {});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Final stop result
|
|
79
|
+
const stopResp = await this.unityConnection.sendCommand('capture_video_stop', {});
|
|
80
|
+
const elapsedMs = Date.now() - startTime;
|
|
81
|
+
return {
|
|
82
|
+
...stopResp,
|
|
83
|
+
requestedDurationSec: params.durationSec,
|
|
84
|
+
elapsedMs,
|
|
85
|
+
message: 'Video recorded for requested duration and stopped'
|
|
86
|
+
};
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return { error: e.message, code: 'CLIENT_ERROR' };
|
|
89
|
+
} finally {
|
|
90
|
+
// If we entered play, attempt to leave play (best-effort)
|
|
91
|
+
try { if (enteredPlay) await this.unityConnection.sendCommand('stop_game', {}); } catch {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler for starting video capture in Unity Editor (via MCP)
|
|
3
|
+
*/
|
|
4
|
+
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
5
|
+
|
|
6
|
+
export class CaptureVideoStartToolHandler extends BaseToolHandler {
|
|
7
|
+
constructor(unityConnection) {
|
|
8
|
+
super(
|
|
9
|
+
'capture_video_start',
|
|
10
|
+
'Start video recording (Game view). Requires com.unity.recorder.',
|
|
11
|
+
{
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
captureMode: { type: 'string', enum: ['game'], description: 'Capture source. Currently only "game" supported.' },
|
|
15
|
+
width: { type: 'number', description: 'Output width (0 = auto/default)' },
|
|
16
|
+
height: { type: 'number', description: 'Output height (0 = auto/default)' },
|
|
17
|
+
fps: { type: 'number', description: 'Frames per second (e.g., 30)' },
|
|
18
|
+
maxDurationSec: { type: 'number', description: 'Auto stop after N seconds (0 = unlimited)' }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
this.unityConnection = unityConnection;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @override */
|
|
26
|
+
async execute(params, context) {
|
|
27
|
+
const { WORKSPACE_ROOT } = await import('../../core/config.js');
|
|
28
|
+
const { outputPath, ...rest } = params || {};
|
|
29
|
+
const response = await this.unityConnection.sendCommand('capture_video_start', { ...rest, workspaceRoot: WORKSPACE_ROOT });
|
|
30
|
+
if (response.error) {
|
|
31
|
+
return { error: response.error, code: response.code || 'UNITY_ERROR' };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
...response,
|
|
35
|
+
message: response.message || 'Video recording started'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|