@akiojin/unity-mcp-server 3.1.0 → 3.2.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/bin/unity-mcp-server.js +6 -5
- package/package.json +1 -1
- package/src/core/config.js +135 -76
- package/src/core/projectInfo.js +3 -8
- package/src/core/server.js +6 -0
- package/src/core/unityCommandType.js +10 -0
- package/src/core/unityConnection.js +51 -7
- package/src/handlers/script/ScriptRefsFindToolHandler.js +27 -21
- package/src/handlers/script/ScriptSearchToolHandler.js +3 -10
- package/src/handlers/script/ScriptSymbolFindToolHandler.js +17 -14
- package/src/tools/analysis/analyzeSceneContents.js +1 -1
- package/src/tools/analysis/findByComponent.js +19 -4
- package/src/tools/analysis/getAnimatorState.js +7 -4
- package/src/tools/analysis/getComponentValues.js +1 -7
- package/src/tools/analysis/getGameObjectDetails.js +1 -1
- package/src/tools/analysis/getInputActionsState.js +2 -2
- package/src/tools/analysis/getObjectReferences.js +17 -4
- package/src/tools/input/inputActionsEditor.js +13 -10
package/bin/unity-mcp-server.js
CHANGED
|
@@ -34,7 +34,7 @@ const args = process.argv.slice(2);
|
|
|
34
34
|
const command = args[0] && !args[0].startsWith('--') ? args[0] : null;
|
|
35
35
|
const rest = command ? args.slice(1) : args;
|
|
36
36
|
|
|
37
|
-
let httpEnabled
|
|
37
|
+
let httpEnabled;
|
|
38
38
|
let httpPort;
|
|
39
39
|
let stdioEnabled = true;
|
|
40
40
|
let telemetryEnabled;
|
|
@@ -114,11 +114,12 @@ async function main() {
|
|
|
114
114
|
|
|
115
115
|
// Start MCP server (dynamic import)
|
|
116
116
|
const { startServer } = await import('../src/core/server.js');
|
|
117
|
+
const http = {};
|
|
118
|
+
if (httpEnabled !== undefined) http.enabled = httpEnabled;
|
|
119
|
+
if (httpPort !== undefined) http.port = httpPort;
|
|
120
|
+
|
|
117
121
|
await startServer({
|
|
118
|
-
http:
|
|
119
|
-
enabled: httpEnabled,
|
|
120
|
-
port: httpPort
|
|
121
|
-
},
|
|
122
|
+
http: Object.keys(http).length > 0 ? http : undefined,
|
|
122
123
|
telemetry: telemetryEnabled === undefined ? undefined : { enabled: telemetryEnabled },
|
|
123
124
|
stdioEnabled
|
|
124
125
|
});
|
package/package.json
CHANGED
package/src/core/config.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import * as findUpPkg from 'find-up';
|
|
5
4
|
import { MCPLogger } from './mcpLogger.js';
|
|
6
5
|
|
|
7
|
-
// Diagnostic log: confirm module loading reached this point
|
|
8
|
-
process.stderr.write('[unity-mcp-server] Config module loading...\n');
|
|
9
|
-
|
|
10
6
|
function findUpSyncCompat(matcher, options = {}) {
|
|
11
7
|
if (typeof matcher === 'function') {
|
|
12
8
|
let dir = options.cwd || process.cwd();
|
|
@@ -39,6 +35,30 @@ function merge(a, b) {
|
|
|
39
35
|
return out;
|
|
40
36
|
}
|
|
41
37
|
|
|
38
|
+
function parseBoolEnv(value) {
|
|
39
|
+
if (typeof value !== 'string') return undefined;
|
|
40
|
+
const v = value.trim().toLowerCase();
|
|
41
|
+
if (v === '') return undefined;
|
|
42
|
+
if (v === '1' || v === 'true' || v === 'yes' || v === 'y' || v === 'on') return true;
|
|
43
|
+
if (v === '0' || v === 'false' || v === 'no' || v === 'n' || v === 'off') return false;
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseIntEnv(value) {
|
|
48
|
+
if (typeof value !== 'string') return undefined;
|
|
49
|
+
const v = value.trim();
|
|
50
|
+
if (v === '') return undefined;
|
|
51
|
+
const n = Number.parseInt(v, 10);
|
|
52
|
+
return Number.isFinite(n) ? n : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function envString(key) {
|
|
56
|
+
const raw = process.env[key];
|
|
57
|
+
if (typeof raw !== 'string') return undefined;
|
|
58
|
+
const v = raw.trim();
|
|
59
|
+
return v.length > 0 ? v : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
function resolvePackageVersion() {
|
|
43
63
|
const candidates = [];
|
|
44
64
|
|
|
@@ -71,16 +91,23 @@ function resolvePackageVersion() {
|
|
|
71
91
|
const baseConfig = {
|
|
72
92
|
// Unity connection settings
|
|
73
93
|
unity: {
|
|
74
|
-
unityHost:
|
|
75
|
-
mcpHost:
|
|
76
|
-
bindHost:
|
|
94
|
+
unityHost: 'localhost',
|
|
95
|
+
mcpHost: 'localhost',
|
|
96
|
+
bindHost: 'localhost',
|
|
77
97
|
port: 6400,
|
|
78
98
|
reconnectDelay: 1000,
|
|
79
99
|
maxReconnectDelay: 30000,
|
|
80
100
|
reconnectBackoffMultiplier: 2,
|
|
101
|
+
connectTimeout: 3000,
|
|
81
102
|
commandTimeout: 30000
|
|
82
103
|
},
|
|
83
104
|
|
|
105
|
+
// Project settings (primarily for code index paths)
|
|
106
|
+
project: {
|
|
107
|
+
root: null,
|
|
108
|
+
codeIndexRoot: null
|
|
109
|
+
},
|
|
110
|
+
|
|
84
111
|
// Server settings
|
|
85
112
|
server: {
|
|
86
113
|
name: 'unity-mcp-server',
|
|
@@ -136,83 +163,124 @@ const baseConfig = {
|
|
|
136
163
|
}
|
|
137
164
|
};
|
|
138
165
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
function loadEnvConfig() {
|
|
167
|
+
const unityHost = envString('UNITY_MCP_UNITY_HOST');
|
|
168
|
+
const mcpHost = envString('UNITY_MCP_MCP_HOST');
|
|
169
|
+
const unityPort = parseIntEnv(process.env.UNITY_MCP_PORT);
|
|
170
|
+
|
|
171
|
+
const projectRoot = envString('UNITY_PROJECT_ROOT');
|
|
172
|
+
|
|
173
|
+
const logLevel = envString('UNITY_MCP_LOG_LEVEL');
|
|
174
|
+
|
|
175
|
+
const httpEnabled = parseBoolEnv(process.env.UNITY_MCP_HTTP_ENABLED);
|
|
176
|
+
const httpPort = parseIntEnv(process.env.UNITY_MCP_HTTP_PORT);
|
|
177
|
+
|
|
178
|
+
const telemetryEnabled = parseBoolEnv(process.env.UNITY_MCP_TELEMETRY_ENABLED);
|
|
179
|
+
const lspRequestTimeoutMs = parseIntEnv(process.env.UNITY_MCP_LSP_REQUEST_TIMEOUT_MS);
|
|
180
|
+
|
|
181
|
+
const out = {};
|
|
182
|
+
|
|
183
|
+
if (unityHost || mcpHost || unityPort !== undefined) {
|
|
184
|
+
out.unity = {};
|
|
185
|
+
if (unityHost) out.unity.unityHost = unityHost;
|
|
186
|
+
if (mcpHost) out.unity.mcpHost = mcpHost;
|
|
187
|
+
if (unityPort !== undefined) out.unity.port = unityPort;
|
|
188
|
+
if (out.unity.unityHost) out.unity.bindHost = out.unity.unityHost;
|
|
147
189
|
}
|
|
148
|
-
let userGlobalPath = null;
|
|
149
|
-
try {
|
|
150
|
-
const homeDir = os.homedir();
|
|
151
|
-
userGlobalPath = homeDir ? path.resolve(homeDir, '.unity', 'config.json') : null;
|
|
152
|
-
} catch {}
|
|
153
190
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return fs.existsSync(candidate) ? candidate : undefined;
|
|
159
|
-
},
|
|
160
|
-
{ cwd: process.cwd() }
|
|
161
|
-
);
|
|
191
|
+
if (projectRoot) {
|
|
192
|
+
out.project = {};
|
|
193
|
+
if (projectRoot) out.project.root = projectRoot;
|
|
194
|
+
}
|
|
162
195
|
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
const raw = fs.readFileSync(projectPath, 'utf8');
|
|
166
|
-
const json = JSON.parse(raw);
|
|
167
|
-
const out = json && typeof json === 'object' ? json : {};
|
|
168
|
-
out.__configPath = projectPath;
|
|
169
|
-
return out;
|
|
170
|
-
} catch (e) {
|
|
171
|
-
return { __configLoadError: `${projectPath}: ${e.message}` };
|
|
196
|
+
if (logLevel) {
|
|
197
|
+
out.logging = { level: logLevel };
|
|
172
198
|
}
|
|
173
|
-
}
|
|
174
199
|
|
|
175
|
-
|
|
176
|
-
|
|
200
|
+
if (httpEnabled !== undefined || httpPort !== undefined) {
|
|
201
|
+
out.http = {};
|
|
202
|
+
if (httpEnabled !== undefined) out.http.enabled = httpEnabled;
|
|
203
|
+
if (httpPort !== undefined) out.http.port = httpPort;
|
|
204
|
+
}
|
|
177
205
|
|
|
178
|
-
|
|
179
|
-
|
|
206
|
+
if (telemetryEnabled !== undefined) {
|
|
207
|
+
out.telemetry = { enabled: telemetryEnabled };
|
|
208
|
+
}
|
|
180
209
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const legacyBindHost = unityConfig.bindHost;
|
|
210
|
+
if (lspRequestTimeoutMs !== undefined) {
|
|
211
|
+
out.lsp = { requestTimeoutMs: lspRequestTimeoutMs };
|
|
212
|
+
}
|
|
185
213
|
|
|
186
|
-
|
|
187
|
-
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function validateAndNormalizeConfig(cfg) {
|
|
218
|
+
// Unity port
|
|
219
|
+
if (!Number.isInteger(cfg.unity.port) || cfg.unity.port <= 0 || cfg.unity.port >= 65536) {
|
|
220
|
+
console.error(
|
|
221
|
+
`[unity-mcp-server] WARN: Invalid UNITY_MCP_PORT (${cfg.unity.port}); using default 6400`
|
|
222
|
+
);
|
|
223
|
+
cfg.unity.port = 6400;
|
|
188
224
|
}
|
|
189
225
|
|
|
190
|
-
|
|
191
|
-
|
|
226
|
+
// HTTP port
|
|
227
|
+
if (cfg.http?.port !== undefined) {
|
|
228
|
+
if (!Number.isInteger(cfg.http.port) || cfg.http.port <= 0 || cfg.http.port >= 65536) {
|
|
229
|
+
console.error(
|
|
230
|
+
`[unity-mcp-server] WARN: Invalid UNITY_MCP_HTTP_PORT (${cfg.http.port}); using default 6401`
|
|
231
|
+
);
|
|
232
|
+
cfg.http.port = 6401;
|
|
233
|
+
}
|
|
192
234
|
}
|
|
193
235
|
|
|
194
|
-
//
|
|
195
|
-
if (
|
|
196
|
-
|
|
236
|
+
// logging.level
|
|
237
|
+
if (cfg.logging?.level) {
|
|
238
|
+
const level = String(cfg.logging.level).toLowerCase();
|
|
239
|
+
const allowed = new Set(['debug', 'info', 'warn', 'warning', 'error']);
|
|
240
|
+
if (!allowed.has(level)) {
|
|
241
|
+
console.error(
|
|
242
|
+
`[unity-mcp-server] WARN: Invalid UNITY_MCP_LOG_LEVEL (${cfg.logging.level}); using default info`
|
|
243
|
+
);
|
|
244
|
+
cfg.logging.level = 'info';
|
|
245
|
+
} else {
|
|
246
|
+
cfg.logging.level = level === 'warning' ? 'warn' : level;
|
|
247
|
+
}
|
|
197
248
|
}
|
|
198
249
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
250
|
+
// unity hosts
|
|
251
|
+
if (typeof cfg.unity.unityHost !== 'string' || cfg.unity.unityHost.trim() === '') {
|
|
252
|
+
cfg.unity.unityHost = 'localhost';
|
|
253
|
+
}
|
|
254
|
+
if (typeof cfg.unity.mcpHost !== 'string' || cfg.unity.mcpHost.trim() === '') {
|
|
255
|
+
cfg.unity.mcpHost = cfg.unity.unityHost;
|
|
256
|
+
}
|
|
257
|
+
cfg.unity.bindHost = cfg.unity.unityHost;
|
|
203
258
|
|
|
204
|
-
|
|
259
|
+
// project roots (keep as-is; resolved later)
|
|
260
|
+
if (cfg.project?.root && typeof cfg.project.root !== 'string') {
|
|
261
|
+
cfg.project.root = null;
|
|
262
|
+
}
|
|
263
|
+
if (cfg.project?.codeIndexRoot && typeof cfg.project.codeIndexRoot !== 'string') {
|
|
264
|
+
cfg.project.codeIndexRoot = null;
|
|
265
|
+
}
|
|
205
266
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
267
|
+
// lsp timeout sanity
|
|
268
|
+
if (cfg.lsp?.requestTimeoutMs !== undefined) {
|
|
269
|
+
const t = Number(cfg.lsp.requestTimeoutMs);
|
|
270
|
+
if (!Number.isFinite(t) || t <= 0) {
|
|
271
|
+
console.error(
|
|
272
|
+
`[unity-mcp-server] WARN: Invalid UNITY_MCP_LSP_REQUEST_TIMEOUT_MS (${cfg.lsp.requestTimeoutMs}); using default 60000`
|
|
273
|
+
);
|
|
274
|
+
cfg.lsp.requestTimeoutMs = 60000;
|
|
275
|
+
}
|
|
213
276
|
}
|
|
214
|
-
}
|
|
215
|
-
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export const config = merge(baseConfig, loadEnvConfig());
|
|
280
|
+
validateAndNormalizeConfig(config);
|
|
281
|
+
|
|
282
|
+
// Workspace root: current working directory (used for cache/capture roots)
|
|
283
|
+
export const WORKSPACE_ROOT = process.cwd();
|
|
216
284
|
|
|
217
285
|
/**
|
|
218
286
|
* Logger utility
|
|
@@ -224,18 +292,9 @@ export const WORKSPACE_ROOT = workspaceRoot;
|
|
|
224
292
|
*/
|
|
225
293
|
export const logger = new MCPLogger(config);
|
|
226
294
|
|
|
227
|
-
// Late log if external config failed to load
|
|
228
|
-
if (config.__configLoadError) {
|
|
229
|
-
console.error(
|
|
230
|
-
`${baseConfig.logging.prefix} WARN: Failed to load external config: ${config.__configLoadError}`
|
|
231
|
-
);
|
|
232
|
-
delete config.__configLoadError;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
295
|
// Startup debug log: output config info to stderr for troubleshooting
|
|
236
296
|
// This helps diagnose connection issues (especially in WSL2/Docker environments)
|
|
237
297
|
console.error(`[unity-mcp-server] Startup config:`);
|
|
238
|
-
console.error(`[unity-mcp-server] Config file: ${config.__configPath || '(not found)'}`);
|
|
239
298
|
console.error(
|
|
240
299
|
`[unity-mcp-server] Unity host: ${config.unity.mcpHost || config.unity.unityHost || 'localhost'}`
|
|
241
300
|
);
|
package/src/core/projectInfo.js
CHANGED
|
@@ -46,7 +46,7 @@ export class ProjectInfoProvider {
|
|
|
46
46
|
|
|
47
47
|
async get() {
|
|
48
48
|
if (this.cached) return this.cached;
|
|
49
|
-
// Env-driven project root override (
|
|
49
|
+
// Env-driven project root override (explicit, deterministic)
|
|
50
50
|
const envRootRaw = process.env.UNITY_PROJECT_ROOT;
|
|
51
51
|
if (typeof envRootRaw === 'string' && envRootRaw.trim().length > 0) {
|
|
52
52
|
const envRoot = envRootRaw.trim();
|
|
@@ -62,7 +62,7 @@ export class ProjectInfoProvider {
|
|
|
62
62
|
return this.cached;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Config-driven project root (
|
|
65
|
+
// Config-driven project root (env is mapped into config.project.root)
|
|
66
66
|
const cfgRootRaw = config?.project?.root;
|
|
67
67
|
if (typeof cfgRootRaw === 'string' && cfgRootRaw.trim().length > 0) {
|
|
68
68
|
const cfgRoot = cfgRootRaw.trim();
|
|
@@ -122,13 +122,8 @@ export class ProjectInfoProvider {
|
|
|
122
122
|
return this.cached;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
if (typeof cfgRootRaw === 'string') {
|
|
126
|
-
throw new Error(
|
|
127
|
-
'project.root is configured but empty. Set a valid path in .unity/config.json.'
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
125
|
throw new Error(
|
|
131
|
-
'Unable to resolve Unity project root. Start the server inside a Unity project (directory containing Assets/ and Packages/) or
|
|
126
|
+
'Unable to resolve Unity project root. Start the server inside a Unity project (directory containing Assets/ and Packages/) or set UNITY_PROJECT_ROOT.'
|
|
132
127
|
);
|
|
133
128
|
}
|
|
134
129
|
|
package/src/core/server.js
CHANGED
|
@@ -91,6 +91,12 @@ async function ensureInitialized() {
|
|
|
91
91
|
// Initialize server
|
|
92
92
|
export async function startServer(options = {}) {
|
|
93
93
|
try {
|
|
94
|
+
// MCP stdio transport requires stdout to stay clean (JSON-RPC only).
|
|
95
|
+
// Guard against accidental console.log usage breaking the protocol.
|
|
96
|
+
if (options.stdioEnabled !== false && process.env.UNITY_MCP_ALLOW_STDOUT !== '1') {
|
|
97
|
+
console.log = (...args) => console.error(...args);
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
// Step 1: Load minimal config for server metadata
|
|
95
101
|
// (config import is lightweight; avoid importing the MCP TS SDK on startup)
|
|
96
102
|
const { config: serverConfig, logger: serverLogger } = await import('./config.js');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const TOOL_NAME_TO_UNITY_COMMAND_TYPE = Object.freeze({
|
|
2
|
+
// Animator tools: tool name (MCP) -> Unity command type (TCP)
|
|
3
|
+
analysis_animator_state_get: 'get_animator_state',
|
|
4
|
+
analysis_animator_runtime_info_get: 'get_animator_runtime_info'
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export function normalizeUnityCommandType(type) {
|
|
8
|
+
if (typeof type !== 'string' || type.length === 0) return type;
|
|
9
|
+
return TOOL_NAME_TO_UNITY_COMMAND_TYPE[type] ?? type;
|
|
10
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import { config, logger } from './config.js';
|
|
4
|
+
import { normalizeUnityCommandType } from './unityCommandType.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Manages TCP connection to Unity Editor
|
|
@@ -22,6 +23,7 @@ export class UnityConnection extends EventEmitter {
|
|
|
22
23
|
this.inFlight = 0;
|
|
23
24
|
this.maxInFlight = 1; // process one command at a time by default
|
|
24
25
|
this.connectedAt = null; // Timestamp when connection was established
|
|
26
|
+
this.hasConnectedOnce = false;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -86,6 +88,7 @@ export class UnityConnection extends EventEmitter {
|
|
|
86
88
|
this.connected = true;
|
|
87
89
|
this.reconnectAttempts = 0;
|
|
88
90
|
this.connectPromise = null;
|
|
91
|
+
this.hasConnectedOnce = true;
|
|
89
92
|
this.emit('connected');
|
|
90
93
|
this._pumpQueue(); // flush any queued commands that arrived during reconnect
|
|
91
94
|
settle(resolve);
|
|
@@ -120,7 +123,7 @@ export class UnityConnection extends EventEmitter {
|
|
|
120
123
|
// Destroy the socket to clean up properly
|
|
121
124
|
this.socket.destroy();
|
|
122
125
|
this.isDisconnecting = false;
|
|
123
|
-
reject(error);
|
|
126
|
+
reject(wrapUnityConnectError(error, targetHost, config.unity.port));
|
|
124
127
|
} else if (this.connected) {
|
|
125
128
|
// Force close to trigger reconnect logic
|
|
126
129
|
try {
|
|
@@ -175,9 +178,11 @@ export class UnityConnection extends EventEmitter {
|
|
|
175
178
|
this.socket.removeAllListeners();
|
|
176
179
|
this.socket.destroy();
|
|
177
180
|
this.connectPromise = null;
|
|
178
|
-
|
|
181
|
+
const timeoutError = new Error('Connection timeout');
|
|
182
|
+
timeoutError.code = 'ETIMEDOUT';
|
|
183
|
+
settle(reject, wrapUnityConnectError(timeoutError, targetHost, config.unity.port));
|
|
179
184
|
}
|
|
180
|
-
}, config.unity.commandTimeout);
|
|
185
|
+
}, config.unity.connectTimeout ?? config.unity.commandTimeout);
|
|
181
186
|
});
|
|
182
187
|
return this.connectPromise;
|
|
183
188
|
}
|
|
@@ -356,18 +361,19 @@ export class UnityConnection extends EventEmitter {
|
|
|
356
361
|
* @returns {Promise<any>} - Response from Unity
|
|
357
362
|
*/
|
|
358
363
|
async sendCommand(type, params = {}) {
|
|
359
|
-
|
|
364
|
+
const normalizedType = normalizeUnityCommandType(type);
|
|
365
|
+
logger.info(`[Unity] enqueue sendCommand: ${normalizedType}`, { connected: this.connected });
|
|
360
366
|
|
|
361
367
|
if (!this.connected) {
|
|
362
368
|
logger.warning('[Unity] Not connected; waiting for reconnection before sending command');
|
|
363
369
|
await this.ensureConnected({
|
|
364
|
-
timeoutMs: config.unity.commandTimeout
|
|
370
|
+
timeoutMs: config.unity.connectTimeout ?? config.unity.commandTimeout
|
|
365
371
|
});
|
|
366
372
|
}
|
|
367
373
|
|
|
368
374
|
// Create an external promise that will resolve when Unity responds
|
|
369
375
|
return new Promise((outerResolve, outerReject) => {
|
|
370
|
-
const task = { type, params, outerResolve, outerReject };
|
|
376
|
+
const task = { type: normalizedType, params, outerResolve, outerReject };
|
|
371
377
|
this.sendQueue.push(task);
|
|
372
378
|
this._pumpQueue();
|
|
373
379
|
});
|
|
@@ -518,6 +524,13 @@ export class UnityConnection extends EventEmitter {
|
|
|
518
524
|
if (/test environment/i.test(msg)) {
|
|
519
525
|
throw error;
|
|
520
526
|
}
|
|
527
|
+
if (
|
|
528
|
+
!this.hasConnectedOnce &&
|
|
529
|
+
(error?.code === 'UNITY_CONNECTION_REFUSED' || error?.code === 'ECONNREFUSED')
|
|
530
|
+
) {
|
|
531
|
+
// Fail fast for the initial connection when Unity isn't listening.
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
521
534
|
}
|
|
522
535
|
|
|
523
536
|
if (this.connected) return;
|
|
@@ -525,8 +538,9 @@ export class UnityConnection extends EventEmitter {
|
|
|
525
538
|
}
|
|
526
539
|
|
|
527
540
|
if (!this.connected) {
|
|
541
|
+
const targetHost = config.unity.mcpHost || config.unity.unityHost || 'localhost';
|
|
528
542
|
const error = new Error(
|
|
529
|
-
`Failed to
|
|
543
|
+
`Failed to connect to Unity at ${targetHost}:${config.unity.port} within ${timeoutMs}ms${lastError ? `: ${lastError.message}` : ''}`
|
|
530
544
|
);
|
|
531
545
|
error.code = 'UNITY_RECONNECT_TIMEOUT';
|
|
532
546
|
throw error;
|
|
@@ -545,3 +559,33 @@ export class UnityConnection extends EventEmitter {
|
|
|
545
559
|
function sleep(ms) {
|
|
546
560
|
return new Promise(resolve => setTimeout(resolve, Math.max(0, ms || 0)));
|
|
547
561
|
}
|
|
562
|
+
|
|
563
|
+
function wrapUnityConnectError(error, host, port) {
|
|
564
|
+
const code = error?.code || '';
|
|
565
|
+
if (code === 'ECONNREFUSED') {
|
|
566
|
+
const hint = buildUnityConnectionHint(host, port);
|
|
567
|
+
const wrapped = new Error(
|
|
568
|
+
`Unity TCP connection refused (ECONNREFUSED) at ${host}:${port}. ${hint}`
|
|
569
|
+
);
|
|
570
|
+
wrapped.code = 'UNITY_CONNECTION_REFUSED';
|
|
571
|
+
wrapped.cause = error;
|
|
572
|
+
return wrapped;
|
|
573
|
+
}
|
|
574
|
+
if (code === 'ETIMEDOUT') {
|
|
575
|
+
const hint = buildUnityConnectionHint(host, port);
|
|
576
|
+
const wrapped = new Error(`Unity TCP Connection timeout at ${host}:${port}. ${hint}`);
|
|
577
|
+
wrapped.code = 'UNITY_CONNECTION_TIMEOUT';
|
|
578
|
+
wrapped.cause = error;
|
|
579
|
+
return wrapped;
|
|
580
|
+
}
|
|
581
|
+
return error;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function buildUnityConnectionHint(_host, _port) {
|
|
585
|
+
const configPath = config.__configPath || '.unity/config.json';
|
|
586
|
+
return (
|
|
587
|
+
`Start Unity Editor and ensure the Unity MCP package is running (TCP listener). ` +
|
|
588
|
+
`Check ${configPath} (unity.mcpHost/unity.port). ` +
|
|
589
|
+
`If using WSL2/Docker → Windows Unity, set unity.mcpHost=host.docker.internal.`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
@@ -213,42 +213,48 @@ export class ScriptRefsFindToolHandler extends BaseToolHandler {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
// Phase 3.
|
|
217
|
-
|
|
218
|
-
for (const [p] of perFile) {
|
|
219
|
-
pathSet.add(p);
|
|
220
|
-
}
|
|
221
|
-
const pathTable = [...pathSet];
|
|
222
|
-
const pathIndex = new Map(pathTable.map((p, i) => [p, i]));
|
|
223
|
-
|
|
224
|
-
// Pagination and byte limits with compact format
|
|
216
|
+
// Phase 3.3: Grouped output format (more compact, LLM-friendly)
|
|
217
|
+
// Pagination and byte limits with grouped format
|
|
225
218
|
const results = [];
|
|
226
219
|
let bytes = 0;
|
|
220
|
+
let total = 0;
|
|
221
|
+
let truncated = false;
|
|
222
|
+
|
|
227
223
|
for (const [filePath, arr] of perFile) {
|
|
228
|
-
const
|
|
224
|
+
const references = [];
|
|
229
225
|
for (const it of arr) {
|
|
230
|
-
|
|
231
|
-
const compact = {
|
|
232
|
-
fileId,
|
|
226
|
+
const ref = {
|
|
233
227
|
line: it.line,
|
|
234
228
|
column: it.column,
|
|
235
229
|
snippet: it.snippet
|
|
236
230
|
};
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
231
|
+
const refJson = JSON.stringify(ref);
|
|
232
|
+
const refSize = Buffer.byteLength(refJson, 'utf8');
|
|
233
|
+
|
|
234
|
+
// Check limits before adding
|
|
235
|
+
if (total >= pageSize || bytes + refSize > maxBytes) {
|
|
236
|
+
truncated = true;
|
|
237
|
+
break;
|
|
241
238
|
}
|
|
242
|
-
|
|
243
|
-
bytes +=
|
|
239
|
+
references.push(ref);
|
|
240
|
+
bytes += refSize;
|
|
241
|
+
total++;
|
|
244
242
|
}
|
|
243
|
+
|
|
244
|
+
if (references.length > 0) {
|
|
245
|
+
results.push({ path: filePath, references });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (truncated) break;
|
|
245
249
|
}
|
|
246
250
|
|
|
247
251
|
// Edge case: extreme limits
|
|
248
252
|
const extremeLimits = pageSize <= 1 || maxBytes <= 1;
|
|
249
|
-
|
|
253
|
+
if (extremeLimits && results.length === 0) {
|
|
254
|
+
truncated = true;
|
|
255
|
+
}
|
|
250
256
|
|
|
251
|
-
return { success: true,
|
|
257
|
+
return { success: true, results, total, truncated };
|
|
252
258
|
}
|
|
253
259
|
}
|
|
254
260
|
|
|
@@ -161,9 +161,8 @@ export class ScriptSearchToolHandler extends BaseToolHandler {
|
|
|
161
161
|
}
|
|
162
162
|
const matcher = buildMatcher(patternType, pattern, flags);
|
|
163
163
|
|
|
164
|
+
// Phase 3.3: Grouped output format (more compact, LLM-friendly)
|
|
164
165
|
const results = [];
|
|
165
|
-
const pathTable = [];
|
|
166
|
-
const pathId = new Map();
|
|
167
166
|
let bytes = 0;
|
|
168
167
|
let afterFound = !startAfter;
|
|
169
168
|
|
|
@@ -200,13 +199,8 @@ export class ScriptSearchToolHandler extends BaseToolHandler {
|
|
|
200
199
|
}
|
|
201
200
|
if (matches === 0) continue;
|
|
202
201
|
|
|
203
|
-
const id = pathId.has(rel)
|
|
204
|
-
? pathId.get(rel)
|
|
205
|
-
: (pathTable.push(rel) - 1, pathTable.length - 1);
|
|
206
|
-
pathId.set(rel, id);
|
|
207
|
-
|
|
208
202
|
const lineRanges = toRanges(matchedLines);
|
|
209
|
-
const item = {
|
|
203
|
+
const item = { path: rel, lineRanges };
|
|
210
204
|
|
|
211
205
|
if (normalizedReturnMode === 'snippets') {
|
|
212
206
|
// Build minimal snippets around first few matches
|
|
@@ -229,10 +223,9 @@ export class ScriptSearchToolHandler extends BaseToolHandler {
|
|
|
229
223
|
return {
|
|
230
224
|
success: true,
|
|
231
225
|
total: results.length,
|
|
232
|
-
pathTable,
|
|
233
226
|
results,
|
|
234
227
|
cursor:
|
|
235
|
-
results.length && results.length >= pageSize ?
|
|
228
|
+
results.length && results.length >= pageSize ? results[results.length - 1].path : null
|
|
236
229
|
};
|
|
237
230
|
} catch (e) {
|
|
238
231
|
logger.error(`[script_search] failed: ${e.message}`);
|
|
@@ -22,7 +22,7 @@ export class ScriptSymbolFindToolHandler extends BaseToolHandler {
|
|
|
22
22
|
type: 'string',
|
|
23
23
|
enum: ['assets', 'packages', 'embedded', 'all'],
|
|
24
24
|
description:
|
|
25
|
-
'Search scope: assets (Assets/), packages (Packages/), embedded, or all (default:
|
|
25
|
+
'Search scope: assets (Assets/), packages (Packages/), embedded, or all (default: assets).'
|
|
26
26
|
},
|
|
27
27
|
exact: {
|
|
28
28
|
type: 'boolean',
|
|
@@ -110,18 +110,15 @@ export class ScriptSymbolFindToolHandler extends BaseToolHandler {
|
|
|
110
110
|
filteredRows = filteredRows.filter(r => r.name === target);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// Phase 3.
|
|
114
|
-
//
|
|
115
|
-
const
|
|
113
|
+
// Phase 3.2: Grouped output format (40% more compact than pathTable)
|
|
114
|
+
// Group symbols by file path
|
|
115
|
+
const grouped = new Map();
|
|
116
|
+
let total = 0;
|
|
116
117
|
for (const r of filteredRows) {
|
|
117
|
-
pathSet.add((r.path || '').replace(/\\/g, '/'));
|
|
118
|
-
}
|
|
119
|
-
const pathTable = [...pathSet];
|
|
120
|
-
const pathIndex = new Map(pathTable.map((p, i) => [p, i]));
|
|
121
|
-
|
|
122
|
-
// Build compact results with fileId reference
|
|
123
|
-
const results = filteredRows.map(r => {
|
|
124
118
|
const p = (r.path || '').replace(/\\/g, '/');
|
|
119
|
+
if (!grouped.has(p)) {
|
|
120
|
+
grouped.set(p, []);
|
|
121
|
+
}
|
|
125
122
|
const symbol = {
|
|
126
123
|
name: r.name,
|
|
127
124
|
kind: r.kind,
|
|
@@ -131,10 +128,16 @@ export class ScriptSymbolFindToolHandler extends BaseToolHandler {
|
|
|
131
128
|
// Only include non-null optional fields
|
|
132
129
|
if (r.ns) symbol.namespace = r.ns;
|
|
133
130
|
if (r.container) symbol.container = r.container;
|
|
131
|
+
grouped.get(p).push(symbol);
|
|
132
|
+
total++;
|
|
133
|
+
}
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
// Convert to array format
|
|
136
|
+
const results = [];
|
|
137
|
+
for (const [path, symbols] of grouped) {
|
|
138
|
+
results.push({ path, symbols });
|
|
139
|
+
}
|
|
137
140
|
|
|
138
|
-
return { success: true,
|
|
141
|
+
return { success: true, results, total };
|
|
139
142
|
}
|
|
140
143
|
}
|
|
@@ -47,7 +47,7 @@ export async function analyzeSceneContentsHandler(unityConnection, args) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Send command to Unity with provided parameters
|
|
50
|
-
const result = await unityConnection.sendCommand('
|
|
50
|
+
const result = await unityConnection.sendCommand('analyze_scene_contents', args);
|
|
51
51
|
|
|
52
52
|
// The unityConnection.sendCommand already extracts the result field
|
|
53
53
|
// from the response, so we access properties directly on result
|
|
@@ -48,10 +48,23 @@ export async function findByComponentHandler(unityConnection, args) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Send command to Unity
|
|
51
|
-
const result = await unityConnection.sendCommand('
|
|
51
|
+
const result = await unityConnection.sendCommand('find_by_component', args);
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
53
|
+
// The unityConnection.sendCommand already extracts the result field
|
|
54
|
+
// from the response, so we access properties directly on result
|
|
55
|
+
if (!result || typeof result === 'string') {
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: `Failed to find GameObjects: Invalid response format`
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
isError: true
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (result.error) {
|
|
55
68
|
return {
|
|
56
69
|
content: [
|
|
57
70
|
{
|
|
@@ -63,12 +76,14 @@ export async function findByComponentHandler(unityConnection, args) {
|
|
|
63
76
|
};
|
|
64
77
|
}
|
|
65
78
|
|
|
79
|
+
const summary = result.summary || `Found ${result.totalFound ?? 0} GameObjects`;
|
|
80
|
+
|
|
66
81
|
// Success response
|
|
67
82
|
return {
|
|
68
83
|
content: [
|
|
69
84
|
{
|
|
70
85
|
type: 'text',
|
|
71
|
-
text:
|
|
86
|
+
text: summary
|
|
72
87
|
}
|
|
73
88
|
],
|
|
74
89
|
isError: false
|
|
@@ -99,8 +99,8 @@ export async function getAnimatorStateHandler(unityConnection, args) {
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
// Send command to Unity
|
|
103
|
-
const result = await unityConnection.sendCommand(
|
|
102
|
+
// Send command to Unity (tool name is normalized to Unity command type in UnityConnection)
|
|
103
|
+
const result = await unityConnection.sendCommand(getAnimatorStateToolDefinition.name, args);
|
|
104
104
|
|
|
105
105
|
// Check for errors
|
|
106
106
|
if (!result || typeof result === 'string') {
|
|
@@ -221,8 +221,11 @@ export async function getAnimatorRuntimeInfoHandler(unityConnection, args) {
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// Send command to Unity
|
|
225
|
-
const result = await unityConnection.sendCommand(
|
|
224
|
+
// Send command to Unity (tool name is normalized to Unity command type in UnityConnection)
|
|
225
|
+
const result = await unityConnection.sendCommand(
|
|
226
|
+
getAnimatorRuntimeInfoToolDefinition.name,
|
|
227
|
+
args
|
|
228
|
+
);
|
|
226
229
|
|
|
227
230
|
// Check for errors
|
|
228
231
|
if (!result || typeof result === 'string') {
|
|
@@ -77,7 +77,7 @@ export async function getComponentValuesHandler(unityConnection, args) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Send command to Unity
|
|
80
|
-
const result = await unityConnection.sendCommand('
|
|
80
|
+
const result = await unityConnection.sendCommand('get_component_values', args);
|
|
81
81
|
|
|
82
82
|
// The unityConnection.sendCommand already extracts the result field
|
|
83
83
|
// from the response, so we access properties directly on result
|
|
@@ -107,13 +107,10 @@ export async function getComponentValuesHandler(unityConnection, args) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Success response - result is already the unwrapped data
|
|
110
|
-
console.log('[DEBUG] GetComponentValues - Full result:', JSON.stringify(result, null, 2));
|
|
111
|
-
|
|
112
110
|
let responseText = result.summary || `Component values retrieved`;
|
|
113
111
|
|
|
114
112
|
// Add detailed property information if available
|
|
115
113
|
if (result.properties && Object.keys(result.properties).length > 0) {
|
|
116
|
-
console.log('[DEBUG] Properties found:', Object.keys(result.properties).length);
|
|
117
114
|
responseText += '\n\nProperties:';
|
|
118
115
|
for (const [key, value] of Object.entries(result.properties)) {
|
|
119
116
|
if (value && typeof value === 'object') {
|
|
@@ -144,9 +141,6 @@ export async function getComponentValuesHandler(unityConnection, args) {
|
|
|
144
141
|
}
|
|
145
142
|
}
|
|
146
143
|
}
|
|
147
|
-
} else {
|
|
148
|
-
console.log('[DEBUG] No properties found in result');
|
|
149
|
-
console.log('[DEBUG] Result keys:', Object.keys(result));
|
|
150
144
|
}
|
|
151
145
|
|
|
152
146
|
// Add debug information if available
|
|
@@ -106,7 +106,7 @@ export async function getGameObjectDetailsHandler(unityConnection, args) {
|
|
|
106
106
|
if (args.maxDepth !== undefined) params.maxDepth = args.maxDepth;
|
|
107
107
|
|
|
108
108
|
// Send command to Unity
|
|
109
|
-
const result = await unityConnection.sendCommand('
|
|
109
|
+
const result = await unityConnection.sendCommand('get_gameobject_details', params);
|
|
110
110
|
|
|
111
111
|
// The unityConnection.sendCommand already extracts the result field
|
|
112
112
|
// from the response, so we access properties directly on result
|
|
@@ -75,7 +75,7 @@ export async function getInputActionsStateHandler(unityConnection, args) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// Send command to Unity
|
|
78
|
-
const result = await unityConnection.sendCommand('
|
|
78
|
+
const result = await unityConnection.sendCommand('get_input_actions_state', args);
|
|
79
79
|
|
|
80
80
|
// Check for errors
|
|
81
81
|
if (!result || typeof result === 'string') {
|
|
@@ -216,7 +216,7 @@ export async function analyzeInputActionsAssetHandler(unityConnection, args) {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// Send command to Unity
|
|
219
|
-
const result = await unityConnection.sendCommand('
|
|
219
|
+
const result = await unityConnection.sendCommand('analyze_input_actions_asset', args);
|
|
220
220
|
|
|
221
221
|
// Check for errors
|
|
222
222
|
if (!result || typeof result === 'string') {
|
|
@@ -47,10 +47,23 @@ export async function getObjectReferencesHandler(unityConnection, args) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Send command to Unity
|
|
50
|
-
const result = await unityConnection.sendCommand('
|
|
50
|
+
const result = await unityConnection.sendCommand('get_object_references', args);
|
|
51
51
|
|
|
52
|
-
//
|
|
53
|
-
|
|
52
|
+
// The unityConnection.sendCommand already extracts the result field
|
|
53
|
+
// from the response, so we access properties directly on result
|
|
54
|
+
if (!result || typeof result === 'string') {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text',
|
|
59
|
+
text: `Failed to get object references: Invalid response format`
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
isError: true
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (result.error) {
|
|
54
67
|
return {
|
|
55
68
|
content: [
|
|
56
69
|
{
|
|
@@ -67,7 +80,7 @@ export async function getObjectReferencesHandler(unityConnection, args) {
|
|
|
67
80
|
content: [
|
|
68
81
|
{
|
|
69
82
|
type: 'text',
|
|
70
|
-
text: result.
|
|
83
|
+
text: result.summary || `References analyzed for ${args.gameObjectName}`
|
|
71
84
|
}
|
|
72
85
|
],
|
|
73
86
|
isError: false
|
|
@@ -303,7 +303,10 @@ function formatUnityResponse(result, successMessage) {
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
if (result.success) {
|
|
306
|
-
let text = result.message ||
|
|
306
|
+
let text = successMessage || result.message || 'Operation completed';
|
|
307
|
+
if (successMessage && result.message && result.message !== successMessage) {
|
|
308
|
+
text += `\n${result.message}`;
|
|
309
|
+
}
|
|
307
310
|
// Add any additional info from result
|
|
308
311
|
Object.keys(result).forEach(key => {
|
|
309
312
|
if (key !== 'success' && key !== 'message' && key !== 'error') {
|
|
@@ -348,7 +351,7 @@ export async function createActionMapHandler(unityConnection, args) {
|
|
|
348
351
|
};
|
|
349
352
|
}
|
|
350
353
|
|
|
351
|
-
const result = await unityConnection.sendCommand('
|
|
354
|
+
const result = await unityConnection.sendCommand('create_action_map', args);
|
|
352
355
|
return formatUnityResponse(result, `Created Action Map: ${args.mapName}`);
|
|
353
356
|
} catch (error) {
|
|
354
357
|
return {
|
|
@@ -377,7 +380,7 @@ export async function removeActionMapHandler(unityConnection, args) {
|
|
|
377
380
|
};
|
|
378
381
|
}
|
|
379
382
|
|
|
380
|
-
const result = await unityConnection.sendCommand('
|
|
383
|
+
const result = await unityConnection.sendCommand('remove_action_map', args);
|
|
381
384
|
return formatUnityResponse(result, `Removed Action Map: ${args.mapName}`);
|
|
382
385
|
} catch (error) {
|
|
383
386
|
return {
|
|
@@ -407,7 +410,7 @@ export async function addInputActionHandler(unityConnection, args) {
|
|
|
407
410
|
};
|
|
408
411
|
}
|
|
409
412
|
|
|
410
|
-
const result = await unityConnection.sendCommand('
|
|
413
|
+
const result = await unityConnection.sendCommand('add_input_action', args);
|
|
411
414
|
return formatUnityResponse(result, `Added Action: ${args.actionName}`);
|
|
412
415
|
} catch (error) {
|
|
413
416
|
return {
|
|
@@ -436,7 +439,7 @@ export async function removeInputActionHandler(unityConnection, args) {
|
|
|
436
439
|
};
|
|
437
440
|
}
|
|
438
441
|
|
|
439
|
-
const result = await unityConnection.sendCommand('
|
|
442
|
+
const result = await unityConnection.sendCommand('remove_input_action', args);
|
|
440
443
|
return formatUnityResponse(result, `Removed Action: ${args.actionName}`);
|
|
441
444
|
} catch (error) {
|
|
442
445
|
return {
|
|
@@ -466,7 +469,7 @@ export async function addInputBindingHandler(unityConnection, args) {
|
|
|
466
469
|
};
|
|
467
470
|
}
|
|
468
471
|
|
|
469
|
-
const result = await unityConnection.sendCommand('
|
|
472
|
+
const result = await unityConnection.sendCommand('add_input_binding', args);
|
|
470
473
|
return formatUnityResponse(result, `Added Binding: ${args.path}`);
|
|
471
474
|
} catch (error) {
|
|
472
475
|
return {
|
|
@@ -495,7 +498,7 @@ export async function removeInputBindingHandler(unityConnection, args) {
|
|
|
495
498
|
};
|
|
496
499
|
}
|
|
497
500
|
|
|
498
|
-
const result = await unityConnection.sendCommand('
|
|
501
|
+
const result = await unityConnection.sendCommand('remove_input_binding', args);
|
|
499
502
|
return formatUnityResponse(result, 'Removed Binding');
|
|
500
503
|
} catch (error) {
|
|
501
504
|
return {
|
|
@@ -524,7 +527,7 @@ export async function removeAllBindingsHandler(unityConnection, args) {
|
|
|
524
527
|
};
|
|
525
528
|
}
|
|
526
529
|
|
|
527
|
-
const result = await unityConnection.sendCommand('
|
|
530
|
+
const result = await unityConnection.sendCommand('remove_all_bindings', args);
|
|
528
531
|
return formatUnityResponse(result, `Removed all bindings from ${args.actionName}`);
|
|
529
532
|
} catch (error) {
|
|
530
533
|
return {
|
|
@@ -553,7 +556,7 @@ export async function createCompositeBindingHandler(unityConnection, args) {
|
|
|
553
556
|
};
|
|
554
557
|
}
|
|
555
558
|
|
|
556
|
-
const result = await unityConnection.sendCommand('
|
|
559
|
+
const result = await unityConnection.sendCommand('create_composite_binding', args);
|
|
557
560
|
return formatUnityResponse(
|
|
558
561
|
result,
|
|
559
562
|
`Created composite binding: ${args.name || args.compositeType}`
|
|
@@ -586,7 +589,7 @@ export async function manageControlSchemesHandler(unityConnection, args) {
|
|
|
586
589
|
};
|
|
587
590
|
}
|
|
588
591
|
|
|
589
|
-
const result = await unityConnection.sendCommand('
|
|
592
|
+
const result = await unityConnection.sendCommand('manage_control_schemes', args);
|
|
590
593
|
const operationText =
|
|
591
594
|
args.operation === 'add' ? 'Added' : args.operation === 'remove' ? 'Removed' : 'Modified';
|
|
592
595
|
return formatUnityResponse(result, `${operationText} control scheme: ${args.schemeName}`);
|