@akiojin/unity-mcp-server 2.26.0 → 2.26.1
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/package.json +1 -1
- package/src/core/codeIndexDb.js +26 -10
- package/src/core/projectInfo.js +74 -65
- package/src/core/server.js +88 -65
- package/src/core/transports/HybridStdioServerTransport.js +179 -0
- package/src/core/unityConnection.js +52 -45
- package/src/handlers/base/BaseToolHandler.js +5 -5
- package/src/handlers/console/ConsoleReadToolHandler.js +285 -295
- package/src/handlers/editor/EditorSelectionManageToolHandler.js +10 -9
- package/src/handlers/gameobject/GameObjectModifyToolHandler.js +22 -11
- package/src/handlers/menu/MenuItemExecuteToolHandler.js +75 -37
- package/src/handlers/screenshot/ScreenshotAnalyzeToolHandler.js +12 -10
- package/src/handlers/script/ScriptEditStructuredToolHandler.js +162 -154
- package/src/handlers/script/ScriptReadToolHandler.js +80 -85
- package/src/handlers/script/ScriptRefsFindToolHandler.js +123 -123
- package/src/handlers/script/ScriptSymbolFindToolHandler.js +125 -112
- package/src/handlers/system/SystemGetCommandStatsToolHandler.js +1 -1
- package/src/handlers/system/SystemRefreshAssetsToolHandler.js +10 -14
- package/src/handlers/video/VideoCaptureStartToolHandler.js +15 -5
- package/src/handlers/video/VideoCaptureStatusToolHandler.js +5 -9
- package/src/handlers/video/VideoCaptureStopToolHandler.js +8 -9
- package/src/lsp/LspProcessManager.js +26 -9
- package/src/tools/video/recordFor.js +13 -7
- package/src/tools/video/recordPlayMode.js +7 -6
- package/src/utils/csharpParse.js +14 -8
|
@@ -5,21 +5,20 @@ import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
|
5
5
|
|
|
6
6
|
export class VideoCaptureStopToolHandler extends BaseToolHandler {
|
|
7
7
|
constructor(unityConnection) {
|
|
8
|
-
super(
|
|
9
|
-
'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
recordingId: { type: 'string', description: 'Optional. Stop a specific recording session. Defaults to the latest.' }
|
|
8
|
+
super('video_capture_stop', 'Stop current video recording and finalize the file.', {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
recordingId: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Optional. Stop a specific recording session. Defaults to the latest.'
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
|
-
);
|
|
16
|
+
});
|
|
18
17
|
this.unityConnection = unityConnection;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
/** @override */
|
|
22
|
-
async execute(params,
|
|
21
|
+
async execute(params, _context) {
|
|
23
22
|
const response = await this.unityConnection.sendCommand('capture_video_stop', params || {});
|
|
24
23
|
if (response.error) {
|
|
25
24
|
return { error: response.error, code: response.code || 'UNITY_ERROR' };
|
|
@@ -16,7 +16,7 @@ export class LspProcessManager {
|
|
|
16
16
|
const rid = this.utils.detectRid();
|
|
17
17
|
const bin = await this.utils.ensureLocal(rid);
|
|
18
18
|
const proc = spawn(bin, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
19
|
-
proc.on('error',
|
|
19
|
+
proc.on('error', e => logger.error(`[csharp-lsp] process error: ${e.message}`));
|
|
20
20
|
proc.on('close', (code, sig) => {
|
|
21
21
|
logger.warn(`[csharp-lsp] exited code=${code} signal=${sig || ''}`);
|
|
22
22
|
this.proc = null;
|
|
@@ -29,7 +29,11 @@ export class LspProcessManager {
|
|
|
29
29
|
logger.info(`[csharp-lsp] started (pid=${proc.pid})`);
|
|
30
30
|
return proc;
|
|
31
31
|
})();
|
|
32
|
-
try {
|
|
32
|
+
try {
|
|
33
|
+
return await this.starting;
|
|
34
|
+
} finally {
|
|
35
|
+
this.starting = null;
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
async stop(graceMs = 3000) {
|
|
@@ -38,7 +42,7 @@ export class LspProcessManager {
|
|
|
38
42
|
this.proc = null;
|
|
39
43
|
try {
|
|
40
44
|
// Send LSP shutdown/exit if possible
|
|
41
|
-
const shutdown =
|
|
45
|
+
const shutdown = obj => {
|
|
42
46
|
try {
|
|
43
47
|
const json = JSON.stringify(obj);
|
|
44
48
|
const payload = `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`;
|
|
@@ -49,12 +53,25 @@ export class LspProcessManager {
|
|
|
49
53
|
shutdown({ jsonrpc: '2.0', method: 'exit' });
|
|
50
54
|
p.stdin.end();
|
|
51
55
|
} catch {}
|
|
52
|
-
await new Promise(
|
|
53
|
-
const to = setTimeout(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
await new Promise(resolve => {
|
|
57
|
+
const to = setTimeout(
|
|
58
|
+
() => {
|
|
59
|
+
try {
|
|
60
|
+
p.kill('SIGTERM');
|
|
61
|
+
} catch {}
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
try {
|
|
64
|
+
p.kill('SIGKILL');
|
|
65
|
+
} catch {}
|
|
66
|
+
resolve();
|
|
67
|
+
}, 1000);
|
|
68
|
+
},
|
|
69
|
+
Math.max(0, graceMs)
|
|
70
|
+
);
|
|
71
|
+
p.on('close', () => {
|
|
72
|
+
clearTimeout(to);
|
|
73
|
+
resolve();
|
|
74
|
+
});
|
|
58
75
|
});
|
|
59
76
|
}
|
|
60
77
|
}
|
|
@@ -3,18 +3,24 @@ import { VideoCaptureForToolHandler } from '../../handlers/video/VideoCaptureFor
|
|
|
3
3
|
|
|
4
4
|
async function main() {
|
|
5
5
|
const unity = new UnityConnection();
|
|
6
|
-
try {
|
|
6
|
+
try {
|
|
7
|
+
await unity.connect();
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.error('connect failed:', e.message);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
7
12
|
try {
|
|
8
13
|
const ts = new Date().toISOString().replace(/[:.]/g, '').slice(0, 15);
|
|
9
14
|
const outputPath = `Assets/Screenshots/recordings/mcp_for_${ts}.mp4`;
|
|
10
15
|
const handler = new VideoCaptureForToolHandler(unity);
|
|
11
16
|
const result = await handler.execute({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
captureMode: 'game',
|
|
18
|
+
width: 1280,
|
|
19
|
+
height: 720,
|
|
20
|
+
fps: 30,
|
|
21
|
+
durationSec: 2,
|
|
22
|
+
play: true,
|
|
23
|
+
outputPath
|
|
18
24
|
});
|
|
19
25
|
if (result && result.error) {
|
|
20
26
|
console.error('capture_video_for error:', result.error);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { UnityConnection } from '../../core/unityConnection.js';
|
|
2
|
-
import { config } from '../../core/config.js';
|
|
3
2
|
|
|
4
3
|
async function main() {
|
|
5
4
|
const unity = new UnityConnection();
|
|
@@ -16,7 +15,9 @@ async function main() {
|
|
|
16
15
|
// Domain reload causes disconnect. Reconnect and wait for play.
|
|
17
16
|
for (let i = 0; i < 60; i++) {
|
|
18
17
|
if (!unity.isConnected()) {
|
|
19
|
-
try {
|
|
18
|
+
try {
|
|
19
|
+
await unity.connect();
|
|
20
|
+
} catch {}
|
|
20
21
|
}
|
|
21
22
|
try {
|
|
22
23
|
const s = await unity.sendCommand('get_editor_state', {});
|
|
@@ -39,19 +40,19 @@ async function main() {
|
|
|
39
40
|
|
|
40
41
|
// Poll status a few times
|
|
41
42
|
for (let i = 0; i < 16; i++) {
|
|
42
|
-
|
|
43
|
-
// console.log('[recordPlayMode] status', st);
|
|
43
|
+
await unity.sendCommand('capture_video_status', {});
|
|
44
44
|
await new Promise(r => setTimeout(r, 250));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Ensure stopped
|
|
48
48
|
await unity.sendCommand('capture_video_stop', {});
|
|
49
|
-
|
|
50
49
|
} catch (e) {
|
|
51
50
|
console.error('[recordPlayMode] error:', e.message);
|
|
52
51
|
} finally {
|
|
53
52
|
try {
|
|
54
|
-
if (!unity.isConnected()) {
|
|
53
|
+
if (!unity.isConnected()) {
|
|
54
|
+
await unity.connect();
|
|
55
|
+
}
|
|
55
56
|
await unity.sendCommand('stop_game', {});
|
|
56
57
|
} catch {}
|
|
57
58
|
unity.disconnect();
|
package/src/utils/csharpParse.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
/* eslint-disable no-useless-escape */
|
|
1
2
|
// Lightweight C# symbol extractor for Node-side fallback (not Roslyn-accurate)
|
|
2
3
|
export function parseFileSymbols(relPath, text) {
|
|
3
4
|
const lines = text.split('\n');
|
|
4
5
|
const result = { path: relPath, symbols: [] };
|
|
5
6
|
const nsRx = /^\s*namespace\s+([A-Za-z0-9_.]+)/;
|
|
6
|
-
const typeRx =
|
|
7
|
-
|
|
8
|
-
const
|
|
7
|
+
const typeRx =
|
|
8
|
+
/^\s*(?:public|internal|protected|private|abstract|sealed|static|partial|new|readonly|\s)*\s*(class|struct|interface|enum)\s+([A-Za-z0-9_]+)/;
|
|
9
|
+
const methodRx =
|
|
10
|
+
/^\s*(?:public|internal|protected|private|static|virtual|override|async|sealed|extern|unsafe|new|readonly|\s)+[A-Za-z0-9_<>,\[\]\?\(\)\.:\s]+\s+([A-Za-z0-9_]+)\s*\(([^)]*)\)\s*(?:\{|=>|;)/;
|
|
11
|
+
const propRx =
|
|
12
|
+
/^\s*(?:public|internal|protected|private|static|virtual|override|sealed|new|readonly|\s)+[A-Za-z0-9_<>,\[\]\?\.:\s]+\s+([A-Za-z0-9_]+)\s*\{/;
|
|
9
13
|
|
|
10
14
|
const nsStack = [];
|
|
11
15
|
const typeStack = [];
|
|
@@ -29,7 +33,7 @@ export function parseFileSymbols(relPath, text) {
|
|
|
29
33
|
startLine: i + 1,
|
|
30
34
|
endLine: 0,
|
|
31
35
|
startColumn: 1,
|
|
32
|
-
endColumn: 1
|
|
36
|
+
endColumn: 1
|
|
33
37
|
});
|
|
34
38
|
}
|
|
35
39
|
|
|
@@ -44,7 +48,7 @@ export function parseFileSymbols(relPath, text) {
|
|
|
44
48
|
startLine: i + 1,
|
|
45
49
|
endLine: i + 1,
|
|
46
50
|
startColumn: 1,
|
|
47
|
-
endColumn: 1
|
|
51
|
+
endColumn: 1
|
|
48
52
|
});
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -59,7 +63,7 @@ export function parseFileSymbols(relPath, text) {
|
|
|
59
63
|
startLine: i + 1,
|
|
60
64
|
endLine: i + 1,
|
|
61
65
|
startColumn: 1,
|
|
62
|
-
endColumn: 1
|
|
66
|
+
endColumn: 1
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -74,7 +78,10 @@ export function parseFileSymbols(relPath, text) {
|
|
|
74
78
|
// set endLine for the last matching symbol
|
|
75
79
|
for (let j = result.symbols.length - 1; j >= 0; j--) {
|
|
76
80
|
const s = result.symbols[j];
|
|
77
|
-
if (s.kind === closed.kind && s.name === closed.name && s.endLine === 0) {
|
|
81
|
+
if (s.kind === closed.kind && s.name === closed.name && s.endLine === 0) {
|
|
82
|
+
s.endLine = i + 1;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
}
|
|
@@ -85,4 +92,3 @@ export function parseFileSymbols(relPath, text) {
|
|
|
85
92
|
}
|
|
86
93
|
return result;
|
|
87
94
|
}
|
|
88
|
-
|