@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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/bin/unity-mcp-server +2 -0
  4. package/package.json +73 -0
  5. package/src/core/codeIndex.js +163 -0
  6. package/src/core/codeIndexDb.js +96 -0
  7. package/src/core/config.js +165 -0
  8. package/src/core/indexWatcher.js +52 -0
  9. package/src/core/projectInfo.js +111 -0
  10. package/src/core/server.js +294 -0
  11. package/src/core/unityConnection.js +426 -0
  12. package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +35 -0
  13. package/src/handlers/analysis/FindByComponentToolHandler.js +20 -0
  14. package/src/handlers/analysis/GetAnimatorStateToolHandler.js +37 -0
  15. package/src/handlers/analysis/GetComponentValuesToolHandler.js +20 -0
  16. package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +35 -0
  17. package/src/handlers/analysis/GetInputActionsStateToolHandler.js +37 -0
  18. package/src/handlers/analysis/GetObjectReferencesToolHandler.js +20 -0
  19. package/src/handlers/asset/AssetDatabaseToolHandler.js +221 -0
  20. package/src/handlers/asset/AssetDependencyToolHandler.js +201 -0
  21. package/src/handlers/asset/AssetImportSettingsToolHandler.js +170 -0
  22. package/src/handlers/asset/CreateMaterialToolHandler.js +96 -0
  23. package/src/handlers/asset/CreatePrefabToolHandler.js +78 -0
  24. package/src/handlers/asset/ExitPrefabModeToolHandler.js +83 -0
  25. package/src/handlers/asset/InstantiatePrefabToolHandler.js +133 -0
  26. package/src/handlers/asset/ModifyMaterialToolHandler.js +76 -0
  27. package/src/handlers/asset/ModifyPrefabToolHandler.js +72 -0
  28. package/src/handlers/asset/OpenPrefabToolHandler.js +121 -0
  29. package/src/handlers/asset/SavePrefabToolHandler.js +106 -0
  30. package/src/handlers/base/BaseToolHandler.js +133 -0
  31. package/src/handlers/compilation/GetCompilationStateToolHandler.js +90 -0
  32. package/src/handlers/component/AddComponentToolHandler.js +126 -0
  33. package/src/handlers/component/GetComponentTypesToolHandler.js +100 -0
  34. package/src/handlers/component/ListComponentsToolHandler.js +85 -0
  35. package/src/handlers/component/ModifyComponentToolHandler.js +143 -0
  36. package/src/handlers/component/RemoveComponentToolHandler.js +108 -0
  37. package/src/handlers/console/ClearConsoleToolHandler.js +160 -0
  38. package/src/handlers/console/ReadConsoleToolHandler.js +276 -0
  39. package/src/handlers/editor/LayerManagementToolHandler.js +160 -0
  40. package/src/handlers/editor/SelectionToolHandler.js +141 -0
  41. package/src/handlers/editor/TagManagementToolHandler.js +129 -0
  42. package/src/handlers/editor/ToolManagementToolHandler.js +135 -0
  43. package/src/handlers/editor/WindowManagementToolHandler.js +125 -0
  44. package/src/handlers/gameobject/CreateGameObjectToolHandler.js +131 -0
  45. package/src/handlers/gameobject/DeleteGameObjectToolHandler.js +101 -0
  46. package/src/handlers/gameobject/FindGameObjectToolHandler.js +119 -0
  47. package/src/handlers/gameobject/GetHierarchyToolHandler.js +132 -0
  48. package/src/handlers/gameobject/ModifyGameObjectToolHandler.js +128 -0
  49. package/src/handlers/index.js +389 -0
  50. package/src/handlers/input/AddInputActionToolHandler.js +20 -0
  51. package/src/handlers/input/AddInputBindingToolHandler.js +20 -0
  52. package/src/handlers/input/CreateActionMapToolHandler.js +20 -0
  53. package/src/handlers/input/CreateCompositeBindingToolHandler.js +20 -0
  54. package/src/handlers/input/GamepadSimulationHandler.js +116 -0
  55. package/src/handlers/input/InputSystemHandler.js +80 -0
  56. package/src/handlers/input/KeyboardSimulationHandler.js +79 -0
  57. package/src/handlers/input/ManageControlSchemesToolHandler.js +20 -0
  58. package/src/handlers/input/MouseSimulationHandler.js +107 -0
  59. package/src/handlers/input/RemoveActionMapToolHandler.js +20 -0
  60. package/src/handlers/input/RemoveAllBindingsToolHandler.js +20 -0
  61. package/src/handlers/input/RemoveInputActionToolHandler.js +20 -0
  62. package/src/handlers/input/RemoveInputBindingToolHandler.js +20 -0
  63. package/src/handlers/input/TouchSimulationHandler.js +142 -0
  64. package/src/handlers/menu/ExecuteMenuItemToolHandler.js +304 -0
  65. package/src/handlers/package/PackageManagerToolHandler.js +248 -0
  66. package/src/handlers/package/RegistryConfigToolHandler.js +198 -0
  67. package/src/handlers/playmode/GetEditorStateToolHandler.js +81 -0
  68. package/src/handlers/playmode/PauseToolHandler.js +44 -0
  69. package/src/handlers/playmode/PlayToolHandler.js +91 -0
  70. package/src/handlers/playmode/StopToolHandler.js +77 -0
  71. package/src/handlers/playmode/WaitForEditorStateToolHandler.js +45 -0
  72. package/src/handlers/scene/CreateSceneToolHandler.js +91 -0
  73. package/src/handlers/scene/GetSceneInfoToolHandler.js +20 -0
  74. package/src/handlers/scene/ListScenesToolHandler.js +58 -0
  75. package/src/handlers/scene/LoadSceneToolHandler.js +92 -0
  76. package/src/handlers/scene/SaveSceneToolHandler.js +76 -0
  77. package/src/handlers/screenshot/AnalyzeScreenshotToolHandler.js +238 -0
  78. package/src/handlers/screenshot/CaptureScreenshotToolHandler.js +692 -0
  79. package/src/handlers/script/BuildCodeIndexToolHandler.js +163 -0
  80. package/src/handlers/script/ScriptCreateClassFileToolHandler.js +60 -0
  81. package/src/handlers/script/ScriptEditStructuredToolHandler.js +173 -0
  82. package/src/handlers/script/ScriptIndexStatusToolHandler.js +61 -0
  83. package/src/handlers/script/ScriptPackagesListToolHandler.js +103 -0
  84. package/src/handlers/script/ScriptReadToolHandler.js +106 -0
  85. package/src/handlers/script/ScriptRefactorRenameToolHandler.js +83 -0
  86. package/src/handlers/script/ScriptRefsFindToolHandler.js +144 -0
  87. package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +79 -0
  88. package/src/handlers/script/ScriptSearchToolHandler.js +320 -0
  89. package/src/handlers/script/ScriptSymbolFindToolHandler.js +117 -0
  90. package/src/handlers/script/ScriptSymbolsGetToolHandler.js +96 -0
  91. package/src/handlers/settings/GetProjectSettingsToolHandler.js +161 -0
  92. package/src/handlers/settings/UpdateProjectSettingsToolHandler.js +272 -0
  93. package/src/handlers/system/GetCommandStatsToolHandler.js +25 -0
  94. package/src/handlers/system/PingToolHandler.js +53 -0
  95. package/src/handlers/system/RefreshAssetsToolHandler.js +45 -0
  96. package/src/handlers/ui/ClickUIElementToolHandler.js +110 -0
  97. package/src/handlers/ui/FindUIElementsToolHandler.js +63 -0
  98. package/src/handlers/ui/GetUIElementStateToolHandler.js +50 -0
  99. package/src/handlers/ui/SetUIElementValueToolHandler.js +49 -0
  100. package/src/handlers/ui/SimulateUIInputToolHandler.js +156 -0
  101. package/src/handlers/video/CaptureVideoForToolHandler.js +96 -0
  102. package/src/handlers/video/CaptureVideoStartToolHandler.js +38 -0
  103. package/src/handlers/video/CaptureVideoStatusToolHandler.js +30 -0
  104. package/src/handlers/video/CaptureVideoStopToolHandler.js +32 -0
  105. package/src/lsp/CSharpLspUtils.js +134 -0
  106. package/src/lsp/LspProcessManager.js +60 -0
  107. package/src/lsp/LspRpcClient.js +133 -0
  108. package/src/tools/analysis/analyzeSceneContents.js +100 -0
  109. package/src/tools/analysis/findByComponent.js +87 -0
  110. package/src/tools/analysis/getAnimatorState.js +326 -0
  111. package/src/tools/analysis/getComponentValues.js +182 -0
  112. package/src/tools/analysis/getGameObjectDetails.js +159 -0
  113. package/src/tools/analysis/getInputActionsState.js +329 -0
  114. package/src/tools/analysis/getObjectReferences.js +86 -0
  115. package/src/tools/input/inputActionsEditor.js +556 -0
  116. package/src/tools/scene/createScene.js +112 -0
  117. package/src/tools/scene/getSceneInfo.js +95 -0
  118. package/src/tools/scene/listScenes.js +82 -0
  119. package/src/tools/scene/loadScene.js +122 -0
  120. package/src/tools/scene/saveScene.js +91 -0
  121. package/src/tools/system/ping.js +72 -0
  122. package/src/tools/video/recordFor.js +31 -0
  123. package/src/tools/video/recordPlayMode.js +61 -0
  124. package/src/utils/csharpParse.js +88 -0
  125. 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
+ }