@akiojin/unity-mcp-server 4.2.1 → 5.1.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.
- package/README.md +7 -0
- package/package.json +4 -2
- package/scripts/ensure-better-sqlite3.mjs +204 -0
- package/scripts/run-non-unity-tests.mjs +80 -0
- package/scripts/simulate-code-index-status.mjs +104 -0
- package/scripts/sync-unity-package-version.js +50 -0
- package/src/core/config.js +1 -1
- package/src/core/unityConnection.js +2 -3
- package/src/handlers/script/ScriptEditSnippetToolHandler.js +40 -9
package/README.md
CHANGED
|
@@ -72,6 +72,13 @@ More details:
|
|
|
72
72
|
- Node.js 18.x / 20.x / 22.x LTS (23+ not supported)
|
|
73
73
|
- MCP client (Claude Desktop, Cursor, etc.)
|
|
74
74
|
|
|
75
|
+
## Native SQLite preload (optional)
|
|
76
|
+
|
|
77
|
+
The server uses `fast-sql`, which can preload a native `better-sqlite3` binding when a prebuilt binary is packaged.
|
|
78
|
+
|
|
79
|
+
- `UNITY_MCP_SKIP_NATIVE_BUILD=1` to skip native preload (forces sql.js fallback)
|
|
80
|
+
- `UNITY_MCP_FORCE_NATIVE=1` to require the prebuilt binary (install fails if missing)
|
|
81
|
+
|
|
75
82
|
## Troubleshooting
|
|
76
83
|
|
|
77
84
|
- Troubleshooting index: [`docs/troubleshooting/README.md`](https://github.com/akiojin/unity-mcp-server/blob/main/docs/troubleshooting/README.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akiojin/unity-mcp-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "MCP server and Unity Editor bridge — enables AI assistants to control Unity for AI-assisted workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/core/server.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test:verbose": "VERBOSE_TEST=true node --test tests/**/*.test.js",
|
|
28
28
|
"prepare": "cd .. && husky || true",
|
|
29
29
|
"prepublishOnly": "npm run test:ci",
|
|
30
|
-
"postinstall": "
|
|
30
|
+
"postinstall": "node scripts/ensure-better-sqlite3.mjs",
|
|
31
31
|
"test:ci:unity": "timeout 60 node --test tests/unit/core/codeIndex.test.js tests/unit/core/config.test.js tests/unit/core/indexWatcher.test.js tests/unit/core/projectInfo.test.js tests/unit/core/server.test.js tests/unit/core/startupPerformance.test.js || exit 0",
|
|
32
32
|
"test:unity": "node tests/run-unity-integration.mjs",
|
|
33
33
|
"test:nounity": "npm run test:integration",
|
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"files": [
|
|
69
69
|
"src/",
|
|
70
70
|
"bin/",
|
|
71
|
+
"scripts/",
|
|
72
|
+
"prebuilt/",
|
|
71
73
|
"README.md",
|
|
72
74
|
"LICENSE"
|
|
73
75
|
],
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
|
|
7
|
+
const ABI_BY_NODE_MAJOR = new Map([
|
|
8
|
+
[18, 115],
|
|
9
|
+
[20, 120],
|
|
10
|
+
[22, 131]
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function parseEnvFlag(value) {
|
|
14
|
+
if (!value) return false;
|
|
15
|
+
const normalized = String(value).trim().toLowerCase();
|
|
16
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolveNodeMajor(nodeVersion) {
|
|
20
|
+
if (!nodeVersion) return null;
|
|
21
|
+
const major = Number.parseInt(String(nodeVersion).split('.')[0], 10);
|
|
22
|
+
return Number.isFinite(major) ? major : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveNodeAbi({ nodeVersion, nodeAbi } = {}) {
|
|
26
|
+
const abi = Number.parseInt(nodeAbi, 10);
|
|
27
|
+
if (Number.isFinite(abi)) return abi;
|
|
28
|
+
const major = resolveNodeMajor(nodeVersion);
|
|
29
|
+
if (!major) return null;
|
|
30
|
+
return ABI_BY_NODE_MAJOR.get(major) ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveRuntimeInfo({
|
|
34
|
+
env = process.env,
|
|
35
|
+
platform = process.platform,
|
|
36
|
+
arch = process.arch,
|
|
37
|
+
nodeVersion = process.versions?.node,
|
|
38
|
+
nodeAbi = process.versions?.modules
|
|
39
|
+
} = {}) {
|
|
40
|
+
const resolvedPlatform = env.UNITY_MCP_PLATFORM || platform;
|
|
41
|
+
const resolvedArch = env.UNITY_MCP_ARCH || arch;
|
|
42
|
+
const envNodeMajor = env.UNITY_MCP_NODE_MAJOR
|
|
43
|
+
? Number.parseInt(env.UNITY_MCP_NODE_MAJOR, 10)
|
|
44
|
+
: null;
|
|
45
|
+
const resolvedNodeMajor = Number.isFinite(envNodeMajor)
|
|
46
|
+
? envNodeMajor
|
|
47
|
+
: resolveNodeMajor(nodeVersion);
|
|
48
|
+
const envNodeAbi = env.UNITY_MCP_NODE_ABI ? Number.parseInt(env.UNITY_MCP_NODE_ABI, 10) : null;
|
|
49
|
+
const resolvedNodeAbi = Number.isFinite(envNodeAbi)
|
|
50
|
+
? envNodeAbi
|
|
51
|
+
: resolveNodeAbi({ nodeVersion, nodeAbi });
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
platform: resolvedPlatform,
|
|
55
|
+
arch: resolvedArch,
|
|
56
|
+
nodeMajor: resolvedNodeMajor,
|
|
57
|
+
nodeAbi: resolvedNodeAbi
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildPlatformKey({ platform, arch, nodeMajor }) {
|
|
62
|
+
if (!platform || !arch || !nodeMajor) return null;
|
|
63
|
+
return `${platform}-${arch}-node${nodeMajor}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function resolvePackageRoot(env = process.env) {
|
|
67
|
+
if (env.UNITY_MCP_PACKAGE_ROOT) {
|
|
68
|
+
return path.resolve(env.UNITY_MCP_PACKAGE_ROOT);
|
|
69
|
+
}
|
|
70
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
71
|
+
return path.resolve(scriptDir, '..');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resolvePrebuiltRoot(packageRoot, env = process.env) {
|
|
75
|
+
if (env.UNITY_MCP_PREBUILT_DIR) {
|
|
76
|
+
return path.resolve(env.UNITY_MCP_PREBUILT_DIR);
|
|
77
|
+
}
|
|
78
|
+
return path.join(packageRoot, 'prebuilt', 'better-sqlite3');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function resolveBetterSqlite3Root(packageRoot, env = process.env) {
|
|
82
|
+
if (env.UNITY_MCP_BETTER_SQLITE3_ROOT) {
|
|
83
|
+
return path.resolve(env.UNITY_MCP_BETTER_SQLITE3_ROOT);
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const req = createRequire(path.join(packageRoot, 'package.json'));
|
|
87
|
+
const pkgPath = req.resolve('better-sqlite3/package.json');
|
|
88
|
+
return path.dirname(pkgPath);
|
|
89
|
+
} catch {
|
|
90
|
+
return path.join(packageRoot, 'node_modules', 'better-sqlite3');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function resolvePrebuiltBindingPath(prebuiltRoot, platformKey) {
|
|
95
|
+
if (!prebuiltRoot || !platformKey) return null;
|
|
96
|
+
return path.join(prebuiltRoot, platformKey, 'better_sqlite3.node');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolveInstalledBindingPath(moduleRoot) {
|
|
100
|
+
return path.join(moduleRoot, 'build', 'Release', 'better_sqlite3.node');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function ensureBinExecutable({ packageRoot, env = process.env, logger = console } = {}) {
|
|
104
|
+
if (parseEnvFlag(env.UNITY_MCP_SKIP_BIN_CHMOD)) return false;
|
|
105
|
+
const binPath = path.join(packageRoot, 'bin', 'unity-mcp-server.js');
|
|
106
|
+
try {
|
|
107
|
+
if (fs.existsSync(binPath)) {
|
|
108
|
+
fs.chmodSync(binPath, 0o755);
|
|
109
|
+
logger.info(`[ensure-better-sqlite3] chmod +x ${binPath}`);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logger.warn(
|
|
114
|
+
`[ensure-better-sqlite3] Failed to chmod ${binPath}: ${err?.message || String(err)}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function ensureBetterSqlite3({
|
|
121
|
+
env = process.env,
|
|
122
|
+
logger = console,
|
|
123
|
+
packageRoot,
|
|
124
|
+
prebuiltRoot,
|
|
125
|
+
moduleRoot,
|
|
126
|
+
platform,
|
|
127
|
+
arch,
|
|
128
|
+
nodeVersion,
|
|
129
|
+
nodeAbi
|
|
130
|
+
} = {}) {
|
|
131
|
+
const skipNative = parseEnvFlag(env.UNITY_MCP_SKIP_NATIVE_BUILD);
|
|
132
|
+
const forceNative = parseEnvFlag(env.UNITY_MCP_FORCE_NATIVE);
|
|
133
|
+
|
|
134
|
+
const resolvedPackageRoot = packageRoot || resolvePackageRoot(env);
|
|
135
|
+
const resolvedPrebuiltRoot = prebuiltRoot || resolvePrebuiltRoot(resolvedPackageRoot, env);
|
|
136
|
+
const resolvedModuleRoot = moduleRoot || resolveBetterSqlite3Root(resolvedPackageRoot, env);
|
|
137
|
+
|
|
138
|
+
if (skipNative) {
|
|
139
|
+
logger.info('[ensure-better-sqlite3] Native preload skipped (UNITY_MCP_SKIP_NATIVE_BUILD).');
|
|
140
|
+
ensureBinExecutable({ packageRoot: resolvedPackageRoot, env, logger });
|
|
141
|
+
return { action: 'skip' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!fs.existsSync(resolvedModuleRoot)) {
|
|
145
|
+
const message = `[ensure-better-sqlite3] better-sqlite3 module not found at ${resolvedModuleRoot}`;
|
|
146
|
+
if (forceNative) {
|
|
147
|
+
throw new Error(message);
|
|
148
|
+
}
|
|
149
|
+
logger.warn(message);
|
|
150
|
+
ensureBinExecutable({ packageRoot: resolvedPackageRoot, env, logger });
|
|
151
|
+
return { action: 'no-module' };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const runtimeInfo = resolveRuntimeInfo({
|
|
155
|
+
env,
|
|
156
|
+
platform,
|
|
157
|
+
arch,
|
|
158
|
+
nodeVersion,
|
|
159
|
+
nodeAbi
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const platformKey = buildPlatformKey(runtimeInfo);
|
|
163
|
+
if (!platformKey) {
|
|
164
|
+
const message = '[ensure-better-sqlite3] Unable to resolve platform key';
|
|
165
|
+
if (forceNative) throw new Error(message);
|
|
166
|
+
logger.warn(message);
|
|
167
|
+
ensureBinExecutable({ packageRoot: resolvedPackageRoot, env, logger });
|
|
168
|
+
return { action: 'fallback' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const prebuiltPath = resolvePrebuiltBindingPath(resolvedPrebuiltRoot, platformKey);
|
|
172
|
+
if (!prebuiltPath || !fs.existsSync(prebuiltPath)) {
|
|
173
|
+
const message = `[ensure-better-sqlite3] Prebuilt binary not found for ${platformKey}`;
|
|
174
|
+
if (forceNative) throw new Error(message);
|
|
175
|
+
logger.info(message);
|
|
176
|
+
ensureBinExecutable({ packageRoot: resolvedPackageRoot, env, logger });
|
|
177
|
+
return { action: 'fallback', platformKey, prebuiltPath };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const destPath = resolveInstalledBindingPath(resolvedModuleRoot);
|
|
181
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
182
|
+
fs.copyFileSync(prebuiltPath, destPath);
|
|
183
|
+
logger.info(`[ensure-better-sqlite3] Installed prebuilt ${platformKey} -> ${destPath}`);
|
|
184
|
+
|
|
185
|
+
ensureBinExecutable({ packageRoot: resolvedPackageRoot, env, logger });
|
|
186
|
+
return { action: 'copied', platformKey, prebuiltPath, destPath };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isCliInvocation() {
|
|
190
|
+
if (!process.argv[1]) return false;
|
|
191
|
+
const invoked = path.resolve(process.argv[1]);
|
|
192
|
+
const current = path.resolve(fileURLToPath(import.meta.url));
|
|
193
|
+
return invoked === current;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (isCliInvocation()) {
|
|
197
|
+
try {
|
|
198
|
+
ensureBetterSqlite3();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
const message = err?.message || String(err);
|
|
201
|
+
console.error(`[ensure-better-sqlite3] ${message}`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
10
|
+
const integrationRoot = path.join(repoRoot, 'tests', 'integration');
|
|
11
|
+
|
|
12
|
+
const curatedUnitTests = [
|
|
13
|
+
'tests/unit/core/codeIndex.test.js',
|
|
14
|
+
'tests/unit/core/config.test.js',
|
|
15
|
+
'tests/unit/core/indexWatcher.test.js',
|
|
16
|
+
'tests/unit/core/projectInfo.test.js',
|
|
17
|
+
'tests/unit/core/server.test.js',
|
|
18
|
+
'tests/unit/handlers/script/CodeIndexStatusToolHandler.test.js'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function collectIntegrationTests(dir) {
|
|
22
|
+
const entries = fs.existsSync(dir) ? fs.readdirSync(dir, { withFileTypes: true }) : [];
|
|
23
|
+
const files = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const full = path.join(dir, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
files.push(...collectIntegrationTests(full));
|
|
28
|
+
} else if (entry.isFile() && entry.name.endsWith('.test.js')) {
|
|
29
|
+
files.push(full);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function detectUnityDependency(filePath) {
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
38
|
+
return /UnityConnection|connect\(\)\s*=>\s*new UnityConnection/i.test(content);
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const allIntegrationTests = collectIntegrationTests(integrationRoot);
|
|
45
|
+
const nonUnityTests = [];
|
|
46
|
+
const unityTests = [];
|
|
47
|
+
|
|
48
|
+
for (const absPath of allIntegrationTests) {
|
|
49
|
+
const relPath = path.relative(repoRoot, absPath);
|
|
50
|
+
if (detectUnityDependency(absPath)) {
|
|
51
|
+
unityTests.push(relPath);
|
|
52
|
+
} else {
|
|
53
|
+
nonUnityTests.push(relPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (nonUnityTests.length === 0) {
|
|
58
|
+
console.warn('[tests] No non-Unity integration tests found.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (unityTests.length > 0) {
|
|
62
|
+
console.log('[tests] Skipping Unity-dependent integration suites in this run:');
|
|
63
|
+
for (const rel of unityTests) {
|
|
64
|
+
console.log(` - ${rel}`);
|
|
65
|
+
}
|
|
66
|
+
console.log(
|
|
67
|
+
'[tests] These suites are executed via `npm run test:unity --workspace=mcp-server` when a Unity Editor is available.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const testTargets = [...curatedUnitTests, ...nonUnityTests];
|
|
72
|
+
|
|
73
|
+
const child = spawn(process.execPath, ['--test', ...testTargets], {
|
|
74
|
+
stdio: 'inherit',
|
|
75
|
+
cwd: repoRoot
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
child.on('exit', code => {
|
|
79
|
+
process.exit(code ?? 1);
|
|
80
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
import { CodeIndexBuildToolHandler } from '../src/handlers/script/CodeIndexBuildToolHandler.js';
|
|
7
|
+
import { CodeIndexStatusToolHandler } from '../src/handlers/script/CodeIndexStatusToolHandler.js';
|
|
8
|
+
import { ProjectInfoProvider } from '../src/core/projectInfo.js';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
const argv = process.argv.slice(2);
|
|
13
|
+
const options = {
|
|
14
|
+
throttleMs: 50,
|
|
15
|
+
pollMs: 500,
|
|
16
|
+
reset: false,
|
|
17
|
+
delayStartMs: 0
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
for (const arg of argv) {
|
|
21
|
+
if (arg.startsWith('--throttle=')) {
|
|
22
|
+
options.throttleMs = Math.max(0, Number(arg.split('=')[1] || 0));
|
|
23
|
+
} else if (arg.startsWith('--poll=')) {
|
|
24
|
+
options.pollMs = Math.max(100, Number(arg.split('=')[1] || 500));
|
|
25
|
+
} else if (arg.startsWith('--delayStart=')) {
|
|
26
|
+
options.delayStartMs = Math.max(0, Number(arg.split('=')[1] || 0));
|
|
27
|
+
} else if (arg === '--reset' || arg === '--clean') {
|
|
28
|
+
options.reset = true;
|
|
29
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
30
|
+
printHelp();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function printHelp() {
|
|
36
|
+
console.log(`Usage: node scripts/simulate-code-index-status.mjs [--throttle=MS] [--poll=MS] [--reset]
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
--throttle=MS Delay (ms) after each file during build to keep job running (default: 50)
|
|
40
|
+
--poll=MS Poll interval for code_index_status (default: 500)
|
|
41
|
+
--delayStart=MS Delay (ms) before processing begins (default: 0)
|
|
42
|
+
--reset Delete existing code index database before running
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mockUnityConnection = {
|
|
47
|
+
isConnected() {
|
|
48
|
+
return false;
|
|
49
|
+
},
|
|
50
|
+
sendCommand() {
|
|
51
|
+
throw new Error('Unity connection not available in simulation');
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const buildHandler = new CodeIndexBuildToolHandler(mockUnityConnection);
|
|
56
|
+
const statusHandler = new CodeIndexStatusToolHandler(mockUnityConnection);
|
|
57
|
+
const projectInfo = new ProjectInfoProvider(mockUnityConnection);
|
|
58
|
+
|
|
59
|
+
(async () => {
|
|
60
|
+
const info = await projectInfo.get();
|
|
61
|
+
const dbDir = path.resolve(info.codeIndexRoot);
|
|
62
|
+
if (options.reset) {
|
|
63
|
+
try {
|
|
64
|
+
for (const file of ['code-index.db', 'code-index.db-shm', 'code-index.db-wal']) {
|
|
65
|
+
const target = path.join(dbDir, file);
|
|
66
|
+
if (fs.existsSync(target)) fs.unlinkSync(target);
|
|
67
|
+
}
|
|
68
|
+
console.log('[simulate] Existing code index removed.');
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error('[simulate] Failed to clean code index:', err.message);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log('[simulate] Starting code_index_build with throttleMs =', options.throttleMs);
|
|
75
|
+
const buildResult = await buildHandler.execute({
|
|
76
|
+
throttleMs: options.throttleMs,
|
|
77
|
+
delayStartMs: options.delayStartMs
|
|
78
|
+
});
|
|
79
|
+
console.log('[simulate] Job ID:', buildResult.jobId);
|
|
80
|
+
|
|
81
|
+
const interval = setInterval(async () => {
|
|
82
|
+
try {
|
|
83
|
+
const status = await statusHandler.execute({});
|
|
84
|
+
const job = status.index?.buildJob;
|
|
85
|
+
const line = {
|
|
86
|
+
time: new Date().toISOString(),
|
|
87
|
+
ready: status.index?.ready,
|
|
88
|
+
rows: status.index?.rows,
|
|
89
|
+
jobStatus: job?.status ?? null,
|
|
90
|
+
processed: job?.progress?.processed ?? null,
|
|
91
|
+
total: job?.progress?.total ?? null
|
|
92
|
+
};
|
|
93
|
+
console.log('[status]', JSON.stringify(line));
|
|
94
|
+
|
|
95
|
+
if (!job || job.status !== 'running') {
|
|
96
|
+
clearInterval(interval);
|
|
97
|
+
console.log('[simulate] Build finished.');
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error('[status]', err.message);
|
|
102
|
+
}
|
|
103
|
+
}, options.pollMs);
|
|
104
|
+
})();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sync Unity Package version with mcp-server version
|
|
5
|
+
* Called by release-please via extra-files or manual invocation
|
|
6
|
+
*
|
|
7
|
+
* Usage: node sync-unity-package-version.js <version>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
|
|
17
|
+
// Get version from command line argument
|
|
18
|
+
const version = process.argv[2];
|
|
19
|
+
|
|
20
|
+
if (!version) {
|
|
21
|
+
console.error('Error: Version argument is required');
|
|
22
|
+
console.error('Usage: node sync-unity-package-version.js <version>');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Resolve Unity Package path (mcp-server and UnityMCPServer are siblings)
|
|
27
|
+
const unityPackageJsonPath = path.join(
|
|
28
|
+
__dirname,
|
|
29
|
+
'../../UnityMCPServer/Packages/unity-mcp-server/package.json'
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Read Unity package.json
|
|
34
|
+
const packageJsonContent = fs.readFileSync(unityPackageJsonPath, 'utf8');
|
|
35
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
36
|
+
|
|
37
|
+
// Update version
|
|
38
|
+
packageJson.version = version;
|
|
39
|
+
|
|
40
|
+
// Write back with 2-space indentation and trailing newline
|
|
41
|
+
const updatedContent = JSON.stringify(packageJson, null, 2) + '\n';
|
|
42
|
+
fs.writeFileSync(unityPackageJsonPath, updatedContent, 'utf8');
|
|
43
|
+
|
|
44
|
+
console.log(`Unity Package version synced to ${version}`);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`Error: Failed to sync Unity Package version`);
|
|
48
|
+
console.error(error.message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
package/src/core/config.js
CHANGED
|
@@ -649,10 +649,9 @@ function wrapUnityConnectError(error, host, port) {
|
|
|
649
649
|
}
|
|
650
650
|
|
|
651
651
|
function buildUnityConnectionHint(_host, _port) {
|
|
652
|
-
const configPath = config.__configPath || '.unity/config.json';
|
|
653
652
|
return (
|
|
654
653
|
`Start Unity Editor and ensure the Unity MCP package is running (TCP listener). ` +
|
|
655
|
-
`Check
|
|
656
|
-
`If using WSL2/Docker → Windows Unity, set
|
|
654
|
+
`Check UNITY_MCP_MCP_HOST / UNITY_MCP_PORT and Unity Project Settings (Host/Port). ` +
|
|
655
|
+
`If using WSL2/Docker → Windows Unity, set UNITY_MCP_MCP_HOST=host.docker.internal.`
|
|
657
656
|
);
|
|
658
657
|
}
|
|
@@ -30,6 +30,11 @@ export class ScriptEditSnippetToolHandler extends BaseToolHandler {
|
|
|
30
30
|
description:
|
|
31
31
|
'If true, run validation and return preview text without writing to disk. Default=false.'
|
|
32
32
|
},
|
|
33
|
+
skipValidation: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
description:
|
|
36
|
+
'If true, skip LSP validation for faster execution. Lightweight syntax checks (brace balance) are still performed. Use for simple edits on large files. Default=false.'
|
|
37
|
+
},
|
|
33
38
|
instructions: {
|
|
34
39
|
type: 'array',
|
|
35
40
|
minItems: 1,
|
|
@@ -114,6 +119,7 @@ export class ScriptEditSnippetToolHandler extends BaseToolHandler {
|
|
|
114
119
|
const info = await this.projectInfo.get();
|
|
115
120
|
const { relative, absolute } = this.#resolvePaths(info, params.path);
|
|
116
121
|
const preview = params.preview === true;
|
|
122
|
+
const skipValidation = params.skipValidation === true;
|
|
117
123
|
const instructions = params.instructions;
|
|
118
124
|
|
|
119
125
|
let original;
|
|
@@ -142,7 +148,13 @@ export class ScriptEditSnippetToolHandler extends BaseToolHandler {
|
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
if (working === original) {
|
|
145
|
-
return this.#buildResponse({
|
|
151
|
+
return this.#buildResponse({
|
|
152
|
+
preview,
|
|
153
|
+
results,
|
|
154
|
+
original,
|
|
155
|
+
updated: working,
|
|
156
|
+
validationSkipped: skipValidation
|
|
157
|
+
});
|
|
146
158
|
}
|
|
147
159
|
|
|
148
160
|
// Pre-syntax check on edited content before LSP validation
|
|
@@ -154,19 +166,30 @@ export class ScriptEditSnippetToolHandler extends BaseToolHandler {
|
|
|
154
166
|
);
|
|
155
167
|
}
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
169
|
+
// LSP validation (skip if skipValidation=true for large files)
|
|
170
|
+
let diagnostics = [];
|
|
171
|
+
if (!skipValidation) {
|
|
172
|
+
diagnostics = await this.#validateWithLsp(info, relative, working);
|
|
173
|
+
const hasErrors = diagnostics.some(d => this.#severityIsError(d.severity));
|
|
174
|
+
if (hasErrors) {
|
|
175
|
+
const first = diagnostics.find(d => this.#severityIsError(d.severity));
|
|
176
|
+
const msg = first?.message || 'syntax error';
|
|
177
|
+
throw new Error(`syntax_error: ${msg}`);
|
|
178
|
+
}
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
if (!preview) {
|
|
166
182
|
await fs.writeFile(absolute, working, 'utf8');
|
|
167
183
|
}
|
|
168
184
|
|
|
169
|
-
return this.#buildResponse({
|
|
185
|
+
return this.#buildResponse({
|
|
186
|
+
preview,
|
|
187
|
+
results,
|
|
188
|
+
original,
|
|
189
|
+
updated: working,
|
|
190
|
+
diagnostics,
|
|
191
|
+
validationSkipped: skipValidation
|
|
192
|
+
});
|
|
170
193
|
}
|
|
171
194
|
|
|
172
195
|
#resolvePaths(info, rawPath) {
|
|
@@ -279,12 +302,20 @@ export class ScriptEditSnippetToolHandler extends BaseToolHandler {
|
|
|
279
302
|
return await this.lsp.validateText(relative, updatedText);
|
|
280
303
|
}
|
|
281
304
|
|
|
282
|
-
#buildResponse({
|
|
305
|
+
#buildResponse({
|
|
306
|
+
preview,
|
|
307
|
+
results,
|
|
308
|
+
original,
|
|
309
|
+
updated,
|
|
310
|
+
diagnostics = [],
|
|
311
|
+
validationSkipped = false
|
|
312
|
+
}) {
|
|
283
313
|
const out = {
|
|
284
314
|
success: true,
|
|
285
315
|
applied: !preview,
|
|
286
316
|
results,
|
|
287
317
|
diagnostics,
|
|
318
|
+
validationSkipped,
|
|
288
319
|
beforeHash: this.#hash(original),
|
|
289
320
|
afterHash: this.#hash(updated)
|
|
290
321
|
};
|