@akiojin/unity-mcp-server 2.32.0 → 2.37.0

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 (26) hide show
  1. package/README.md +30 -5
  2. package/package.json +9 -4
  3. package/src/core/config.js +241 -242
  4. package/src/core/projectInfo.js +15 -0
  5. package/src/core/transports/HybridStdioServerTransport.js +78 -75
  6. package/src/handlers/addressables/AddressablesAnalyzeToolHandler.js +45 -47
  7. package/src/handlers/addressables/AddressablesBuildToolHandler.js +32 -33
  8. package/src/handlers/addressables/AddressablesManageToolHandler.js +74 -75
  9. package/src/handlers/component/ComponentFieldSetToolHandler.js +419 -419
  10. package/src/handlers/index.js +437 -437
  11. package/src/handlers/input/InputGamepadToolHandler.js +162 -0
  12. package/src/handlers/input/InputKeyboardToolHandler.js +127 -0
  13. package/src/handlers/input/InputMouseToolHandler.js +188 -0
  14. package/src/handlers/input/InputSystemControlToolHandler.js +63 -64
  15. package/src/handlers/input/InputTouchToolHandler.js +178 -0
  16. package/src/handlers/playmode/PlaymodePlayToolHandler.js +36 -23
  17. package/src/handlers/playmode/PlaymodeStopToolHandler.js +32 -21
  18. package/src/handlers/test/TestGetStatusToolHandler.js +37 -10
  19. package/src/handlers/test/TestRunToolHandler.js +36 -35
  20. package/src/lsp/LspProcessManager.js +18 -12
  21. package/src/utils/editorState.js +42 -0
  22. package/src/utils/testResultsCache.js +70 -0
  23. package/src/handlers/input/InputGamepadSimulateToolHandler.js +0 -116
  24. package/src/handlers/input/InputKeyboardSimulateToolHandler.js +0 -79
  25. package/src/handlers/input/InputMouseSimulateToolHandler.js +0 -107
  26. package/src/handlers/input/InputTouchSimulateToolHandler.js +0 -142
@@ -0,0 +1,42 @@
1
+ export function extractEditorState(payload) {
2
+ if (!isObjectLike(payload)) {
3
+ return null;
4
+ }
5
+
6
+ const queue = [payload];
7
+ const seen = new Set();
8
+
9
+ while (queue.length > 0) {
10
+ const current = queue.shift();
11
+ if (!isObjectLike(current) || seen.has(current)) {
12
+ continue;
13
+ }
14
+
15
+ seen.add(current);
16
+
17
+ if (typeof current.isPlaying === 'boolean') {
18
+ return current;
19
+ }
20
+
21
+ if (Array.isArray(current)) {
22
+ for (const value of current) {
23
+ if (isObjectLike(value)) {
24
+ queue.push(value);
25
+ }
26
+ }
27
+ continue;
28
+ }
29
+
30
+ for (const value of Object.values(current)) {
31
+ if (isObjectLike(value)) {
32
+ queue.push(value);
33
+ }
34
+ }
35
+ }
36
+
37
+ return null;
38
+ }
39
+
40
+ function isObjectLike(value) {
41
+ return typeof value === 'object' && value !== null;
42
+ }
@@ -0,0 +1,70 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { WORKSPACE_ROOT, logger } from '../core/config.js';
5
+
6
+ const UNITY_DIRNAME = '.unity';
7
+ const TEST_RESULTS_FILENAME = 'TestResults.json';
8
+
9
+ function resolveWorkspaceRoot() {
10
+ return WORKSPACE_ROOT || process.cwd();
11
+ }
12
+
13
+ export function getResultsFilePath() {
14
+ const workspaceRoot = resolveWorkspaceRoot();
15
+ if (!workspaceRoot) {
16
+ return null;
17
+ }
18
+
19
+ return path.join(workspaceRoot, UNITY_DIRNAME, TEST_RESULTS_FILENAME);
20
+ }
21
+
22
+ export async function persistTestResults(result) {
23
+ const filePath = getResultsFilePath();
24
+ if (!filePath) {
25
+ return null;
26
+ }
27
+
28
+ try {
29
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
30
+ await fs.writeFile(filePath, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
31
+ return filePath;
32
+ } catch (error) {
33
+ logger.warn(`[TestResultsCache] Failed to write test results to ${filePath}: ${error.message}`);
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export async function loadCachedTestResults(targetPath) {
39
+ const filePath = targetPath || getResultsFilePath();
40
+ if (!filePath) {
41
+ return null;
42
+ }
43
+
44
+ try {
45
+ const data = await fs.readFile(filePath, 'utf8');
46
+ return JSON.parse(data);
47
+ } catch (error) {
48
+ if (error.code !== 'ENOENT') {
49
+ logger.warn(
50
+ `[TestResultsCache] Failed to read test results from ${filePath}: ${error.message}`
51
+ );
52
+ }
53
+ return null;
54
+ }
55
+ }
56
+
57
+ export async function resetTestResultsCache() {
58
+ const filePath = getResultsFilePath();
59
+ if (!filePath) {
60
+ return;
61
+ }
62
+
63
+ try {
64
+ await fs.rm(filePath, { force: true });
65
+ } catch (error) {
66
+ logger.warn(
67
+ `[TestResultsCache] Failed to reset test results cache ${filePath}: ${error.message}`
68
+ );
69
+ }
70
+ }
@@ -1,116 +0,0 @@
1
- import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
-
3
- /**
4
- * Handler for the input_gamepad_simulate tool
5
- */
6
- export class InputGamepadSimulateToolHandler extends BaseToolHandler {
7
- constructor(unityConnection) {
8
- super(
9
- 'input_gamepad_simulate',
10
- 'Simulate gamepad input (buttons/sticks/triggers/dpad) with value ranges.',
11
- {
12
- type: 'object',
13
- properties: {
14
- action: {
15
- type: 'string',
16
- enum: ['button', 'stick', 'trigger', 'dpad'],
17
- description: 'The gamepad action to perform'
18
- },
19
- button: {
20
- type: 'string',
21
- description: 'Button name (a/cross, b/circle, x/square, y/triangle, start, select, etc.)'
22
- },
23
- buttonAction: {
24
- type: 'string',
25
- enum: ['press', 'release'],
26
-
27
- description: 'Button action (press or release)'
28
- },
29
- stick: {
30
- type: 'string',
31
- enum: ['left', 'right'],
32
-
33
- description: 'Which analog stick to control'
34
- },
35
- x: {
36
- type: 'number',
37
- minimum: -1,
38
- maximum: 1,
39
- description: 'Horizontal axis value for stick (-1 to 1)'
40
- },
41
- y: {
42
- type: 'number',
43
- minimum: -1,
44
- maximum: 1,
45
- description: 'Vertical axis value for stick (-1 to 1)'
46
- },
47
- trigger: {
48
- type: 'string',
49
- enum: ['left', 'right'],
50
-
51
- description: 'Which trigger to control'
52
- },
53
- value: {
54
- type: 'number',
55
- minimum: 0,
56
- maximum: 1,
57
- description: 'Trigger pressure value (0 to 1)'
58
- },
59
- direction: {
60
- type: 'string',
61
- enum: ['up', 'down', 'left', 'right', 'none'],
62
- description: 'D-pad direction'
63
- }
64
- },
65
- required: ['action']
66
- }
67
- );
68
- this.unityConnection = unityConnection;
69
- }
70
-
71
- validate(params) {
72
- const { action, button, x, y, value, direction } = params;
73
-
74
- if (!action) {
75
- throw new Error('action is required');
76
- }
77
-
78
- switch (action) {
79
- case 'button':
80
- if (!button) {
81
- throw new Error('button is required for button action');
82
- }
83
- break;
84
- case 'stick':
85
- if (x === undefined || y === undefined) {
86
- throw new Error('x and y values are required for stick action');
87
- }
88
- if (x < -1 || x > 1 || y < -1 || y > 1) {
89
- throw new Error('Stick values must be between -1 and 1');
90
- }
91
- break;
92
- case 'trigger':
93
- if (value === undefined) {
94
- throw new Error('value is required for trigger action');
95
- }
96
- if (value < 0 || value > 1) {
97
- throw new Error('Trigger value must be between 0 and 1');
98
- }
99
- break;
100
- case 'dpad':
101
- if (!direction) {
102
- throw new Error('direction is required for dpad action');
103
- }
104
- break; }
105
- }
106
-
107
- async execute(params) {
108
- // Ensure connected
109
- if (!this.unityConnection.isConnected()) {
110
- await this.unityConnection.connect();
111
- }
112
-
113
- const result = await this.unityConnection.sendCommand('simulate_gamepad_input', params);
114
- return result;
115
- }
116
- }
@@ -1,79 +0,0 @@
1
- import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
-
3
- /**
4
- * Handler for the input_keyboard_simulate tool
5
- */
6
- export class InputKeyboardSimulateToolHandler extends BaseToolHandler {
7
- constructor(unityConnection) {
8
- super(
9
- 'input_keyboard_simulate',
10
- 'Simulate keyboard input (press/release/type/combo) with typing speed.',
11
- {
12
- type: 'object',
13
- properties: {
14
- action: {
15
- type: 'string',
16
- enum: ['press', 'release', 'type', 'combo'],
17
- description: 'The keyboard action to perform'
18
- },
19
- key: {
20
- type: 'string',
21
- description: 'The key to press/release (for press/release actions)'
22
- },
23
- keys: {
24
- type: 'array',
25
- items: { type: 'string' },
26
- description: 'Array of keys for combo action'
27
- },
28
- text: {
29
- type: 'string',
30
- description: 'Text to type (for type action)'
31
- },
32
- typingSpeed: {
33
- type: 'number',
34
-
35
- description: 'Milliseconds per character when typing'
36
- }
37
- },
38
- required: ['action']
39
- }
40
- );
41
- this.unityConnection = unityConnection;
42
- }
43
-
44
- validate(params) {
45
- const { action, key, keys, text } = params;
46
-
47
- if (!action) {
48
- throw new Error('action is required');
49
- }
50
-
51
- switch (action) {
52
- case 'press':
53
- case 'release':
54
- if (!key) {
55
- throw new Error(`key is required for ${action} action`);
56
- }
57
- break;
58
- case 'type':
59
- if (!text) {
60
- throw new Error('text is required for type action');
61
- }
62
- break;
63
- case 'combo':
64
- if (!keys || !Array.isArray(keys) || keys.length === 0) {
65
- throw new Error('keys array is required for combo action');
66
- }
67
- break; }
68
- }
69
-
70
- async execute(params) {
71
- // Ensure connected
72
- if (!this.unityConnection.isConnected()) {
73
- await this.unityConnection.connect();
74
- }
75
-
76
- const result = await this.unityConnection.sendCommand('simulate_keyboard_input', params);
77
- return result;
78
- }
79
- }
@@ -1,107 +0,0 @@
1
- import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
-
3
- /**
4
- * Handler for the input_mouse_simulate tool
5
- */
6
- export class InputMouseSimulateToolHandler extends BaseToolHandler {
7
- constructor(unityConnection) {
8
- super(
9
- 'input_mouse_simulate',
10
- 'Simulate mouse input (move/click/drag/scroll) with absolute/relative coords.',
11
- {
12
- type: 'object',
13
- properties: {
14
- action: {
15
- type: 'string',
16
- enum: ['move', 'click', 'drag', 'scroll'],
17
- description: 'The mouse action to perform'
18
- },
19
- x: {
20
- type: 'number',
21
- description: 'X coordinate for move action'
22
- },
23
- y: {
24
- type: 'number',
25
- description: 'Y coordinate for move action'
26
- },
27
- absolute: {
28
- type: 'boolean',
29
- description: 'Whether coordinates are absolute or relative (default: true)'
30
- },
31
- button: {
32
- type: 'string',
33
- enum: ['left', 'right', 'middle'],
34
- description: 'Mouse button for click/drag actions'
35
- },
36
- clickCount: {
37
- type: 'number',
38
- description: 'Number of clicks (for double/triple click)'
39
- },
40
- startX: {
41
- type: 'number',
42
- description: 'Start X for drag action'
43
- },
44
- startY: {
45
- type: 'number',
46
- description: 'Start Y for drag action'
47
- },
48
- endX: {
49
- type: 'number',
50
- description: 'End X for drag action'
51
- },
52
- endY: {
53
- type: 'number',
54
- description: 'End Y for drag action'
55
- },
56
- deltaX: {
57
- type: 'number',
58
-
59
- description: 'Horizontal scroll delta'
60
- },
61
- deltaY: {
62
- type: 'number',
63
-
64
- description: 'Vertical scroll delta'
65
- }
66
- },
67
- required: ['action']
68
- }
69
- );
70
- this.unityConnection = unityConnection;
71
- }
72
-
73
- validate(params) {
74
- const { action, x, y, startX, startY, endX, endY } = params;
75
-
76
- if (!action) {
77
- throw new Error('action is required');
78
- }
79
-
80
- switch (action) {
81
- case 'move':
82
- if (x === undefined || y === undefined) {
83
- throw new Error('x and y coordinates are required for move action');
84
- }
85
- break;
86
- case 'drag':
87
- if (startX === undefined || startY === undefined ||
88
- endX === undefined || endY === undefined) {
89
- throw new Error('startX, startY, endX, and endY are required for drag action');
90
- }
91
- break;
92
- case 'click':
93
- case 'scroll':
94
- // These actions have optional parameters
95
- break; }
96
- }
97
-
98
- async execute(params) {
99
- // Ensure connected
100
- if (!this.unityConnection.isConnected()) {
101
- await this.unityConnection.connect();
102
- }
103
-
104
- const result = await this.unityConnection.sendCommand('simulate_mouse_input', params);
105
- return result;
106
- }
107
- }
@@ -1,142 +0,0 @@
1
- import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
-
3
- /**
4
- * Handler for the input_touch_simulate tool
5
- */
6
- export class InputTouchSimulateToolHandler extends BaseToolHandler {
7
- constructor(unityConnection) {
8
- super(
9
- 'input_touch_simulate',
10
- 'Simulate touch input (tap/swipe/pinch/multi) with coordinates and timing.',
11
- {
12
- type: 'object',
13
- properties: {
14
- action: {
15
- type: 'string',
16
- enum: ['tap', 'swipe', 'pinch', 'multi'],
17
- description: 'The touch action to perform'
18
- },
19
- x: {
20
- type: 'number',
21
- description: 'X coordinate for tap action'
22
- },
23
- y: {
24
- type: 'number',
25
- description: 'Y coordinate for tap action'
26
- },
27
- touchId: {
28
- type: 'number',
29
-
30
- minimum: 0,
31
- maximum: 9,
32
- description: 'Touch ID (0-9)'
33
- },
34
- startX: {
35
- type: 'number',
36
- description: 'Start X for swipe action'
37
- },
38
- startY: {
39
- type: 'number',
40
- description: 'Start Y for swipe action'
41
- },
42
- endX: {
43
- type: 'number',
44
- description: 'End X for swipe action'
45
- },
46
- endY: {
47
- type: 'number',
48
- description: 'End Y for swipe action'
49
- },
50
- duration: {
51
- type: 'number',
52
-
53
- description: 'Swipe duration in milliseconds'
54
- },
55
- centerX: {
56
- type: 'number',
57
- description: 'Center X for pinch gesture'
58
- },
59
- centerY: {
60
- type: 'number',
61
- description: 'Center Y for pinch gesture'
62
- },
63
- startDistance: {
64
- type: 'number',
65
-
66
- description: 'Start distance between fingers for pinch'
67
- },
68
- endDistance: {
69
- type: 'number',
70
-
71
- description: 'End distance between fingers for pinch'
72
- },
73
- touches: {
74
- type: 'array',
75
- items: {
76
- type: 'object',
77
- properties: {
78
- x: { type: 'number' },
79
- y: { type: 'number' },
80
- phase: {
81
- type: 'string',
82
- enum: ['began', 'moved', 'stationary', 'ended']
83
- }
84
- },
85
- required: ['x', 'y']
86
- },
87
- description: 'Array of touch points for multi-touch'
88
- }
89
- },
90
- required: ['action']
91
- }
92
- );
93
- this.unityConnection = unityConnection;
94
- }
95
-
96
- validate(params) {
97
- const { action, x, y, startX, startY, endX, endY, touches } = params;
98
-
99
- if (!action) {
100
- throw new Error('action is required');
101
- }
102
-
103
- switch (action) {
104
- case 'tap':
105
- if (x === undefined || y === undefined) {
106
- throw new Error('x and y coordinates are required for tap action');
107
- }
108
- break;
109
- case 'swipe':
110
- if (startX === undefined || startY === undefined ||
111
- endX === undefined || endY === undefined) {
112
- throw new Error('startX, startY, endX, and endY are required for swipe action');
113
- }
114
- break;
115
- case 'pinch':
116
- // Optional parameters with defaults
117
- break;
118
- case 'multi':
119
- if (!touches || !Array.isArray(touches) || touches.length === 0) {
120
- throw new Error('touches array is required for multi-touch action');
121
- }
122
- if (touches.length > 10) {
123
- throw new Error('Maximum 10 simultaneous touches supported');
124
- }
125
- for (const touch of touches) {
126
- if (touch.x === undefined || touch.y === undefined) {
127
- throw new Error('Each touch must have x and y coordinates');
128
- }
129
- }
130
- break; }
131
- }
132
-
133
- async execute(params) {
134
- // Ensure connected
135
- if (!this.unityConnection.isConnected()) {
136
- await this.unityConnection.connect();
137
- }
138
-
139
- const result = await this.unityConnection.sendCommand('simulate_touch_input', params);
140
- return result;
141
- }
142
- }