@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,198 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Registry Configuration Tool Handler for Unity MCP
5
+ * Handles Unity Package Registry configuration (OpenUPM, Unity NuGet, etc.)
6
+ */
7
+
8
+ export default class RegistryConfigToolHandler extends BaseToolHandler {
9
+ constructor(unityConnection) {
10
+ super(
11
+ 'registry_config',
12
+ 'Configure package registries (OpenUPM/NuGet): list/add/remove scopes or recommend.',
13
+ {
14
+ type: 'object',
15
+ properties: {
16
+ action: {
17
+ type: 'string',
18
+ enum: ['list', 'add_openupm', 'add_nuget', 'remove', 'add_scope', 'recommend'],
19
+ description: 'The registry operation to perform'
20
+ },
21
+ registryName: {
22
+ type: 'string',
23
+ description: 'Name of the registry (for remove and add_scope actions)'
24
+ },
25
+ scope: {
26
+ type: 'string',
27
+ description: 'Package scope to add (e.g., com.unity, com.cysharp)'
28
+ },
29
+ scopes: {
30
+ type: 'array',
31
+ items: { type: 'string' },
32
+ description: 'Array of package scopes to add'
33
+ },
34
+ autoAddPopular: {
35
+ type: 'boolean',
36
+ description: 'Automatically add popular package scopes (default: true)'
37
+ },
38
+ registry: {
39
+ type: 'string',
40
+ enum: ['openupm', 'nuget'],
41
+ description: 'Registry type for recommendations'
42
+ }
43
+ },
44
+ required: ['action']
45
+ }
46
+ );
47
+ this.unityConnection = unityConnection;
48
+ }
49
+
50
+ validate(params) {
51
+ const { action, registryName, scope } = params || {};
52
+ if (!action) throw new Error('action is required');
53
+
54
+ const valid = ['list', 'add_openupm', 'add_nuget', 'remove', 'add_scope', 'recommend'];
55
+ if (!valid.includes(action)) {
56
+ throw new Error(`Invalid action: ${action}. Must be one of: ${valid.join(', ')}`);
57
+ }
58
+
59
+ switch (action) {
60
+ case 'remove':
61
+ if (!registryName) throw new Error('Registry name is required for remove action');
62
+ break;
63
+ case 'add_scope':
64
+ if (!registryName || !scope) throw new Error('Registry name and scope are required for add_scope action');
65
+ break;
66
+ }
67
+ }
68
+
69
+ async execute(params) {
70
+ const { action, ...parameters } = params;
71
+
72
+ // Ensure connected
73
+ if (!this.unityConnection.isConnected()) {
74
+ await this.unityConnection.connect();
75
+ }
76
+
77
+ const result = await this.unityConnection.sendCommand('registry_config', {
78
+ action,
79
+ ...parameters
80
+ });
81
+
82
+ return this.formatResponse(action, result);
83
+ }
84
+
85
+ formatResponse(action, result) {
86
+ if (result && result.error) {
87
+ return { success: false, error: result.error };
88
+ }
89
+
90
+ switch (action) {
91
+ case 'list':
92
+ return {
93
+ success: result.success,
94
+ action: 'list',
95
+ totalCount: result.totalCount || 0,
96
+ registries: (result.registries || []).map(reg => ({
97
+ name: reg.name,
98
+ url: reg.url,
99
+ scopeCount: reg.scopes ? reg.scopes.length : 0,
100
+ scopes: reg.scopes || []
101
+ })),
102
+ message: result.message || `Found ${result.totalCount || 0} configured registries`
103
+ };
104
+
105
+ case 'add_openupm':
106
+ return {
107
+ success: result.success,
108
+ action: 'add_openupm',
109
+ registryUrl: result.registryUrl,
110
+ scopes: result.scopes || [],
111
+ message: result.message || 'Successfully configured OpenUPM registry',
112
+ instructions: [
113
+ 'OpenUPM registry has been added to your project.',
114
+ 'You can now install packages from OpenUPM using their package IDs.',
115
+ 'Popular packages include:',
116
+ ' - com.cysharp.unitask (UniTask)',
117
+ ' - com.neuecc.unirx (UniRx)',
118
+ ' - jp.keijiro.noiseshader (Noise Shader)'
119
+ ]
120
+ };
121
+
122
+ case 'add_nuget':
123
+ return {
124
+ success: result.success,
125
+ action: 'add_nuget',
126
+ registryUrl: result.registryUrl,
127
+ scopes: result.scopes || [],
128
+ message: result.message || 'Successfully configured Unity NuGet registry',
129
+ instructions: [
130
+ 'Unity NuGet registry has been added to your project.',
131
+ 'You can now install NuGet packages that are compatible with Unity.',
132
+ 'Popular packages include:',
133
+ ' - org.nuget.newtonsoft.json (Newtonsoft.Json)',
134
+ ' - org.nuget.system.threading.tasks.extensions',
135
+ ' - org.nuget.sqlite-net-pcl (SQLite)'
136
+ ]
137
+ };
138
+
139
+ case 'remove':
140
+ return {
141
+ success: result.success,
142
+ action: 'remove',
143
+ message: result.message || `Successfully removed registry`
144
+ };
145
+
146
+ case 'add_scope':
147
+ return {
148
+ success: result.success,
149
+ action: 'add_scope',
150
+ scopes: result.scopes || [],
151
+ message: result.message || 'Successfully added scope to registry'
152
+ };
153
+
154
+ case 'recommend':
155
+ if (result.allRecommendations) {
156
+ const formatted = {
157
+ success: result.success,
158
+ action: 'recommend',
159
+ recommendations: {},
160
+ message: result.message || 'Package recommendations for available registries'
161
+ };
162
+ for (const [registry, packages] of Object.entries(result.allRecommendations)) {
163
+ formatted.recommendations[registry] = packages.map(pkg => ({
164
+ packageId: pkg.packageId,
165
+ name: pkg.name,
166
+ description: pkg.description,
167
+ scope: pkg.scope
168
+ }));
169
+ }
170
+ return formatted;
171
+ }
172
+ return {
173
+ success: result.success,
174
+ action: 'recommend',
175
+ registry: result.registry,
176
+ packages: (result.packages || []).map(pkg => ({
177
+ packageId: pkg.packageId,
178
+ name: pkg.name,
179
+ description: pkg.description,
180
+ scope: pkg.scope
181
+ })),
182
+ message: result.message || `Recommendations for ${result.registry}`
183
+ }; }
184
+ }
185
+
186
+ static getExamples() {
187
+ return [
188
+ { description: 'List configured registries', input: { action: 'list' } },
189
+ { description: 'Add OpenUPM registry with popular scopes', input: { action: 'add_openupm', autoAddPopular: true } },
190
+ { description: 'Add OpenUPM with custom scopes', input: { action: 'add_openupm', scopes: ['com.cysharp', 'com.neuecc', 'jp.keijiro'], autoAddPopular: false } },
191
+ { description: 'Add Unity NuGet registry', input: { action: 'add_nuget', autoAddPopular: true } },
192
+ { description: 'Add scope to existing registry', input: { action: 'add_scope', registryName: 'OpenUPM', scope: 'com.yasirkula' } },
193
+ { description: 'Remove a registry', input: { action: 'remove', registryName: 'OpenUPM' } },
194
+ { description: 'Get recommended packages for OpenUPM', input: { action: 'recommend', registry: 'openupm' } },
195
+ { description: 'Get all registry recommendations', input: { action: 'recommend' } }
196
+ ];
197
+ }
198
+ }
@@ -0,0 +1,81 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Handler for getting Unity editor state
5
+ */
6
+ export class GetEditorStateToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'get_editor_state',
10
+ 'Get editor state (play mode, platform, selected objects, etc.). Optionally wait for a target state.',
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ waitFor: {
15
+ type: 'object',
16
+ properties: { isPlaying: { type: 'boolean' } },
17
+ required: ['isPlaying']
18
+ },
19
+ timeoutMs: { type: 'number' },
20
+ pollMs: { type: 'number' }
21
+ },
22
+ required: []
23
+ }
24
+ );
25
+ this.unityConnection = unityConnection;
26
+ // 軽量なソフトキャッシュ(単発取得時のみ使用)
27
+ this._last = { t: 0, state: null };
28
+ this._minIntervalMs = parseInt(process.env.MIN_EDITOR_STATE_INTERVAL_MS || '', 10);
29
+ if (!(this._minIntervalMs >= 0)) this._minIntervalMs = 250; // 既定250ms
30
+ }
31
+
32
+ /**
33
+ * Executes the get editor state command
34
+ * @param {object} params - Empty object for this command
35
+ * @returns {Promise<object>} Editor state information
36
+ */
37
+ async execute(params) {
38
+ const wait = params?.waitFor;
39
+ const timeoutMs = typeof params?.timeoutMs === 'number' ? params.timeoutMs : null;
40
+ const pollMs = typeof params?.pollMs === 'number' ? params.pollMs : 500;
41
+
42
+ const getOnce = async () => {
43
+ // 非waitモード時はキャッシュを活用
44
+ if (!wait && this._last.state && Date.now() - this._last.t < this._minIntervalMs) {
45
+ return this._last.state;
46
+ }
47
+ if (!this.unityConnection.isConnected()) {
48
+ await this.unityConnection.connect();
49
+ }
50
+ const result = await this.unityConnection.sendCommand('get_editor_state', {});
51
+ if (result.status === 'error') {
52
+ const error = new Error(result.error);
53
+ error.code = 'UNITY_ERROR';
54
+ throw error;
55
+ }
56
+ if (!wait) {
57
+ this._last = { t: Date.now(), state: result };
58
+ }
59
+ return result;
60
+ };
61
+
62
+ if (!wait) {
63
+ return await getOnce();
64
+ }
65
+
66
+ const want = !!wait.isPlaying;
67
+ const start = Date.now();
68
+ for (;;) {
69
+ try {
70
+ const state = await this.unityConnection.sendCommand('get_editor_state', {});
71
+ if (state && !!state.isPlaying === want) {
72
+ return { status: 'success', state, waitedMs: Date.now() - start };
73
+ }
74
+ } catch {}
75
+ await new Promise(r => setTimeout(r, pollMs));
76
+ if (timeoutMs != null && Date.now() - start > timeoutMs) {
77
+ return await getOnce();
78
+ }
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,44 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Handler for pausing/resuming Unity play mode
5
+ */
6
+ export class PauseToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'pause_game',
10
+ 'Toggle Pause/Resume in Play Mode.',
11
+ {
12
+ type: 'object',
13
+ properties: {},
14
+ required: []
15
+ }
16
+ );
17
+ this.unityConnection = unityConnection;
18
+ }
19
+
20
+ /**
21
+ * Executes the pause/resume command
22
+ * @param {object} params - Empty object for this command
23
+ * @returns {Promise<object>} Play mode state
24
+ */
25
+ async execute(params) {
26
+ // Ensure connected
27
+ if (!this.unityConnection.isConnected()) {
28
+ throw new Error('Unity connection not available');
29
+ }
30
+
31
+ // Send pause command to Unity
32
+ const result = await this.unityConnection.sendCommand('pause_game', params);
33
+
34
+ // Check for Unity-side errors
35
+ if (result.status === 'error') {
36
+ const error = new Error(result.error);
37
+ error.code = 'UNITY_ERROR';
38
+ throw error;
39
+ }
40
+
41
+ // Return the result with state information
42
+ return result;
43
+ }
44
+ }
@@ -0,0 +1,91 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Handler for starting Unity play mode
5
+ */
6
+ export class PlayToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'play_game',
10
+ 'Enter Play Mode.',
11
+ {
12
+ type: 'object',
13
+ properties: {},
14
+ required: []
15
+ }
16
+ );
17
+ this.unityConnection = unityConnection;
18
+ }
19
+
20
+ /**
21
+ * Executes the play command
22
+ * @param {object} params - Empty object for this command
23
+ * @returns {Promise<object>} Play mode state
24
+ */
25
+ async execute(params) {
26
+ const initialDelayMs = typeof params?.initialDelayMs === 'number' ? params.initialDelayMs : 3000;
27
+ const reconnectIntervalMs = typeof params?.reconnectIntervalMs === 'number' ? params.reconnectIntervalMs : 500;
28
+ const pollIntervalMs = typeof params?.pollIntervalMs === 'number' ? params.pollIntervalMs : 800;
29
+ const maxWaitMs = typeof params?.maxWaitMs === 'number' ? params.maxWaitMs : null; // null = unlimited
30
+
31
+ try {
32
+ if (!this.unityConnection.isConnected()) {
33
+ await this.unityConnection.connect();
34
+ }
35
+ const result = await this.unityConnection.sendCommand('play_game', params);
36
+ if (result?.status === 'error') {
37
+ const error = new Error(result.error || 'Unity returned error');
38
+ error.code = 'UNITY_ERROR';
39
+ throw error;
40
+ }
41
+ const startOk = Date.now();
42
+ for (;;) {
43
+ try {
44
+ const state = await this.unityConnection.sendCommand('get_editor_state', {});
45
+ if (state && state.isPlaying) {
46
+ return { status: 'success', message: 'Entered play mode', state };
47
+ }
48
+ } catch {}
49
+ await sleep(pollIntervalMs);
50
+ if (maxWaitMs != null && Date.now() - startOk > maxWaitMs) {
51
+ return result;
52
+ }
53
+ }
54
+ } catch (err) {
55
+ const msg = err?.message || '';
56
+ const transient = /(Connection closed|timeout|ECONNRESET|EPIPE|socket|Not connected)/i.test(msg);
57
+ if (!transient) throw err;
58
+
59
+ const start = Date.now();
60
+ await sleep(initialDelayMs);
61
+ // Reconnect until connected (unlimited unless maxWaitMs specified)
62
+ for (;;) {
63
+ if (this.unityConnection.isConnected()) break;
64
+ try { await this.unityConnection.connect(); } catch {}
65
+ await sleep(reconnectIntervalMs);
66
+ if (maxWaitMs != null && Date.now() - start > maxWaitMs) {
67
+ const e = new Error('Timed out waiting to reconnect after Play start');
68
+ e.code = 'RECONNECT_TIMEOUT';
69
+ throw e;
70
+ }
71
+ }
72
+ // Poll for isPlaying
73
+ for (;;) {
74
+ try {
75
+ const state = await this.unityConnection.sendCommand('get_editor_state', {});
76
+ if (state && state.isPlaying) {
77
+ return { status: 'success', message: 'Entered play mode (reconnected after reload)', state };
78
+ }
79
+ } catch {}
80
+ await sleep(pollIntervalMs);
81
+ if (maxWaitMs != null && Date.now() - start > maxWaitMs) {
82
+ const e = new Error('Timed out waiting for Play mode after reconnect');
83
+ e.code = 'PLAY_WAIT_TIMEOUT';
84
+ throw e;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
@@ -0,0 +1,77 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Handler for stopping Unity play mode
5
+ */
6
+ export class StopToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'stop_game',
10
+ 'Exit Play Mode and return to Edit Mode.',
11
+ {
12
+ type: 'object',
13
+ properties: {},
14
+ required: []
15
+ }
16
+ );
17
+ this.unityConnection = unityConnection;
18
+ }
19
+
20
+ /**
21
+ * Executes the stop command
22
+ * @param {object} params - Empty object for this command
23
+ * @returns {Promise<object>} Play mode state
24
+ */
25
+ async execute(params) {
26
+ const reconnectIntervalMs = typeof params?.reconnectIntervalMs === 'number' ? params.reconnectIntervalMs : 500;
27
+ const pollIntervalMs = typeof params?.pollIntervalMs === 'number' ? params.pollIntervalMs : 500;
28
+ const maxWaitMs = typeof params?.maxWaitMs === 'number' ? params.maxWaitMs : null; // null = unlimited
29
+
30
+ try {
31
+ if (!this.unityConnection.isConnected()) {
32
+ await this.unityConnection.connect();
33
+ }
34
+ const result = await this.unityConnection.sendCommand('stop_game', params);
35
+ if (result?.status === 'error') {
36
+ const error = new Error(result.error || 'Unity returned error');
37
+ error.code = 'UNITY_ERROR';
38
+ throw error;
39
+ }
40
+ const startOk = Date.now();
41
+ for (;;) {
42
+ try {
43
+ const state = await this.unityConnection.sendCommand('get_editor_state', {});
44
+ if (state && !state.isPlaying) {
45
+ return { status: 'success', message: 'Exited play mode', state };
46
+ }
47
+ } catch {}
48
+ await sleep(pollIntervalMs);
49
+ if (maxWaitMs != null && Date.now() - startOk > maxWaitMs) return result;
50
+ }
51
+ } catch (err) {
52
+ const transient = /(Connection closed|timeout|ECONNRESET|EPIPE|socket|Not connected)/i.test(err?.message || '');
53
+ if (!transient) throw err;
54
+ const start = Date.now();
55
+ for (;;) {
56
+ if (!this.unityConnection.isConnected()) {
57
+ try { await this.unityConnection.connect(); } catch {}
58
+ await sleep(reconnectIntervalMs);
59
+ }
60
+ try {
61
+ const state = await this.unityConnection.sendCommand('get_editor_state', {});
62
+ if (state && !state.isPlaying) {
63
+ return { status: 'success', message: 'Exited play mode (reconnected after reload)', state };
64
+ }
65
+ } catch {}
66
+ await sleep(pollIntervalMs);
67
+ if (maxWaitMs != null && Date.now() - start > maxWaitMs) {
68
+ const e = new Error('Timed out waiting for stop');
69
+ e.code = 'PLAY_WAIT_TIMEOUT';
70
+ throw e;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
@@ -0,0 +1,45 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ export class WaitForEditorStateToolHandler extends BaseToolHandler {
4
+ constructor(unityConnection) {
5
+ super(
6
+ 'wait_for_editor_state',
7
+ 'Wait until the Unity editor reaches the requested state (e.g., isPlaying).',
8
+ {
9
+ type: 'object',
10
+ properties: {
11
+ isPlaying: { type: 'boolean', description: 'Target play state (true=Play, false=Edit)' },
12
+ timeoutMs: { type: 'number', description: 'Max wait time (null/omitted = unlimited)' },
13
+ pollMs: { type: 'number', description: 'Polling interval in ms (default 500)' }
14
+ },
15
+ required: ['isPlaying']
16
+ }
17
+ );
18
+ this.unityConnection = unityConnection;
19
+ }
20
+
21
+ async execute(params) {
22
+ const want = !!params.isPlaying;
23
+ const timeoutMs = typeof params?.timeoutMs === 'number' ? params.timeoutMs : null;
24
+ const pollMs = typeof params?.pollMs === 'number' ? params.pollMs : 500;
25
+
26
+ const start = Date.now();
27
+ for (;;) {
28
+ if (!this.unityConnection.isConnected()) {
29
+ try { await this.unityConnection.connect(); } catch {}
30
+ }
31
+ try {
32
+ const state = await this.unityConnection.sendCommand('get_editor_state', {});
33
+ if (!!state?.isPlaying === want) {
34
+ return { status: 'success', state, waitedMs: Date.now() - start };
35
+ }
36
+ } catch {}
37
+ await new Promise(r => setTimeout(r, pollMs));
38
+ if (timeoutMs != null && Date.now() - start > timeoutMs) {
39
+ const state = this.unityConnection.isConnected() ? await this.unityConnection.sendCommand('get_editor_state', {}).catch(() => ({})) : {};
40
+ return { status: 'timeout', state, waitedMs: Date.now() - start };
41
+ }
42
+ }
43
+ }
44
+ }
45
+
@@ -0,0 +1,91 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Handler for creating new scenes in Unity
5
+ */
6
+ export class CreateSceneToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'create_scene',
10
+ 'Create a new scene (optionally load it and add to build settings).',
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ sceneName: {
15
+ type: 'string',
16
+ description: 'Name of the scene to create'
17
+ },
18
+ path: {
19
+ type: 'string',
20
+ description: 'Path where the scene should be saved (e.g., "Assets/Scenes/"). If not specified, defaults to "Assets/Scenes/"'
21
+ },
22
+ loadScene: {
23
+ type: 'boolean',
24
+ description: 'Whether to load the scene after creation (default: true)'
25
+ },
26
+ addToBuildSettings: {
27
+ type: 'boolean',
28
+ description: 'Whether to add the scene to build settings (default: false)'
29
+ }
30
+ },
31
+ required: ['sceneName']
32
+ }
33
+ );
34
+ this.unityConnection = unityConnection;
35
+ }
36
+
37
+ /**
38
+ * Validates the input parameters
39
+ * @param {object} params - Input parameters
40
+ * @throws {Error} If validation fails
41
+ */
42
+ validate(params) {
43
+ super.validate(params);
44
+
45
+ // Additional validation for scene name
46
+ if (!params.sceneName || params.sceneName.trim() === '') {
47
+ throw new Error('Scene name cannot be empty');
48
+ }
49
+
50
+ // Check for invalid characters in scene name
51
+ if (params.sceneName.includes('/') || params.sceneName.includes('\\')) {
52
+ throw new Error('Scene name contains invalid characters');
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Executes the create scene command
58
+ * @param {object} params - Validated input parameters
59
+ * @returns {Promise<object>} Creation result
60
+ */
61
+ async execute(params) {
62
+ // Ensure connected
63
+ if (!this.unityConnection.isConnected()) {
64
+ throw new Error('Unity connection not available');
65
+ }
66
+
67
+ // Send command to Unity
68
+ const result = await this.unityConnection.sendCommand('create_scene', params);
69
+
70
+ // Check for Unity-side errors
71
+ if (result.status === 'error') {
72
+ const error = new Error(result.error);
73
+ error.code = 'UNITY_ERROR';
74
+ throw error;
75
+ }
76
+
77
+ // Handle undefined or null results from Unity
78
+ if (result.result === undefined || result.result === null) {
79
+ return {
80
+ status: 'success',
81
+ sceneName: params.sceneName,
82
+ path: params.path || 'Assets/Scenes/',
83
+ loadScene: params.loadScene !== false,
84
+ addToBuildSettings: params.addToBuildSettings === true,
85
+ message: 'Scene creation completed but Unity returned no details'
86
+ };
87
+ }
88
+
89
+ return result.result;
90
+ }
91
+ }
@@ -0,0 +1,20 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import { getSceneInfoToolDefinition, getSceneInfoHandler } from '../../tools/scene/getSceneInfo.js';
3
+
4
+ /**
5
+ * Handler for get_scene_info tool
6
+ */
7
+ export class GetSceneInfoToolHandler extends BaseToolHandler {
8
+ constructor(unityConnection) {
9
+ super(
10
+ getSceneInfoToolDefinition.name,
11
+ getSceneInfoToolDefinition.description,
12
+ getSceneInfoToolDefinition.inputSchema
13
+ );
14
+ this.unityConnection = unityConnection;
15
+ }
16
+
17
+ async execute(args) {
18
+ return getSceneInfoHandler(this.unityConnection, args);
19
+ }
20
+ }