uloop-cli 0.44.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/.prettierrc.json +28 -0
- package/CLAUDE.md +61 -0
- package/dist/cli.bundle.cjs +4761 -0
- package/dist/cli.bundle.cjs.map +7 -0
- package/eslint.config.mjs +72 -0
- package/jest.config.cjs +19 -0
- package/package.json +61 -0
- package/src/__tests__/cli-e2e.test.ts +349 -0
- package/src/__tests__/setup.ts +24 -0
- package/src/arg-parser.ts +128 -0
- package/src/cli.ts +489 -0
- package/src/default-tools.json +355 -0
- package/src/direct-unity-client.ts +125 -0
- package/src/execute-tool.ts +155 -0
- package/src/port-resolver.ts +60 -0
- package/src/project-root.ts +31 -0
- package/src/simple-framer.ts +97 -0
- package/src/skills/bundled-skills.ts +64 -0
- package/src/skills/markdown.d.ts +4 -0
- package/src/skills/skill-definitions/uloop-capture-gameview/SKILL.md +39 -0
- package/src/skills/skill-definitions/uloop-clear-console/SKILL.md +34 -0
- package/src/skills/skill-definitions/uloop-compile/SKILL.md +37 -0
- package/src/skills/skill-definitions/uloop-execute-dynamic-code/SKILL.md +79 -0
- package/src/skills/skill-definitions/uloop-execute-menu-item/SKILL.md +43 -0
- package/src/skills/skill-definitions/uloop-find-game-objects/SKILL.md +46 -0
- package/src/skills/skill-definitions/uloop-focus-window/SKILL.md +34 -0
- package/src/skills/skill-definitions/uloop-get-hierarchy/SKILL.md +44 -0
- package/src/skills/skill-definitions/uloop-get-logs/SKILL.md +45 -0
- package/src/skills/skill-definitions/uloop-get-menu-items/SKILL.md +44 -0
- package/src/skills/skill-definitions/uloop-get-project-info/SKILL.md +34 -0
- package/src/skills/skill-definitions/uloop-get-provider-details/SKILL.md +45 -0
- package/src/skills/skill-definitions/uloop-get-version/SKILL.md +31 -0
- package/src/skills/skill-definitions/uloop-run-tests/SKILL.md +43 -0
- package/src/skills/skill-definitions/uloop-unity-search/SKILL.md +44 -0
- package/src/skills/skills-command.ts +118 -0
- package/src/skills/skills-manager.ts +135 -0
- package/src/tool-cache.ts +104 -0
- package/src/version.ts +7 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.44.1",
|
|
3
|
+
"tools": [
|
|
4
|
+
{
|
|
5
|
+
"name": "compile",
|
|
6
|
+
"description": "Execute Unity project compilation",
|
|
7
|
+
"inputSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"ForceRecompile": {
|
|
11
|
+
"type": "boolean",
|
|
12
|
+
"description": "Force full recompilation"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "get-logs",
|
|
19
|
+
"description": "Retrieve logs from Unity Console",
|
|
20
|
+
"inputSchema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"LogType": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Log type filter",
|
|
26
|
+
"enum": [
|
|
27
|
+
"Error",
|
|
28
|
+
"Warning",
|
|
29
|
+
"Log",
|
|
30
|
+
"All"
|
|
31
|
+
],
|
|
32
|
+
"default": "All"
|
|
33
|
+
},
|
|
34
|
+
"MaxCount": {
|
|
35
|
+
"type": "integer",
|
|
36
|
+
"description": "Maximum number of logs to retrieve",
|
|
37
|
+
"default": 100
|
|
38
|
+
},
|
|
39
|
+
"SearchText": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Text to search within logs"
|
|
42
|
+
},
|
|
43
|
+
"IncludeStackTrace": {
|
|
44
|
+
"type": "boolean",
|
|
45
|
+
"description": "Include stack trace in output",
|
|
46
|
+
"default": true
|
|
47
|
+
},
|
|
48
|
+
"UseRegex": {
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"description": "Use regex for search"
|
|
51
|
+
},
|
|
52
|
+
"SearchInStackTrace": {
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"description": "Search within stack trace"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "run-tests",
|
|
61
|
+
"description": "Execute Unity Test Runner",
|
|
62
|
+
"inputSchema": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"properties": {
|
|
65
|
+
"TestMode": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"description": "Test mode",
|
|
68
|
+
"enum": [
|
|
69
|
+
"EditMode",
|
|
70
|
+
"PlayMode"
|
|
71
|
+
],
|
|
72
|
+
"default": "EditMode"
|
|
73
|
+
},
|
|
74
|
+
"FilterType": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "Filter type",
|
|
77
|
+
"enum": [
|
|
78
|
+
"all",
|
|
79
|
+
"exact",
|
|
80
|
+
"regex",
|
|
81
|
+
"assembly"
|
|
82
|
+
],
|
|
83
|
+
"default": "all"
|
|
84
|
+
},
|
|
85
|
+
"FilterValue": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "Filter value"
|
|
88
|
+
},
|
|
89
|
+
"SaveXml": {
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"description": "Save test results as XML"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "clear-console",
|
|
98
|
+
"description": "Clear Unity console logs",
|
|
99
|
+
"inputSchema": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"properties": {
|
|
102
|
+
"AddConfirmationMessage": {
|
|
103
|
+
"type": "boolean",
|
|
104
|
+
"description": "Add confirmation message after clearing"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "focus-window",
|
|
111
|
+
"description": "Bring Unity Editor window to front",
|
|
112
|
+
"inputSchema": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "get-hierarchy",
|
|
119
|
+
"description": "Get Unity Hierarchy structure",
|
|
120
|
+
"inputSchema": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"RootPath": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "Root GameObject path"
|
|
126
|
+
},
|
|
127
|
+
"MaxDepth": {
|
|
128
|
+
"type": "integer",
|
|
129
|
+
"description": "Maximum depth (-1 for unlimited)",
|
|
130
|
+
"default": -1
|
|
131
|
+
},
|
|
132
|
+
"IncludeComponents": {
|
|
133
|
+
"type": "boolean",
|
|
134
|
+
"description": "Include component information",
|
|
135
|
+
"default": true
|
|
136
|
+
},
|
|
137
|
+
"IncludeInactive": {
|
|
138
|
+
"type": "boolean",
|
|
139
|
+
"description": "Include inactive GameObjects",
|
|
140
|
+
"default": true
|
|
141
|
+
},
|
|
142
|
+
"IncludePaths": {
|
|
143
|
+
"type": "boolean",
|
|
144
|
+
"description": "Include path information"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "unity-search",
|
|
151
|
+
"description": "Search Unity project",
|
|
152
|
+
"inputSchema": {
|
|
153
|
+
"type": "object",
|
|
154
|
+
"properties": {
|
|
155
|
+
"SearchQuery": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"description": "Search query"
|
|
158
|
+
},
|
|
159
|
+
"Providers": {
|
|
160
|
+
"type": "array",
|
|
161
|
+
"description": "Search providers",
|
|
162
|
+
"items": {
|
|
163
|
+
"type": "string"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"MaxResults": {
|
|
167
|
+
"type": "integer",
|
|
168
|
+
"description": "Maximum number of results",
|
|
169
|
+
"default": 50
|
|
170
|
+
},
|
|
171
|
+
"SaveToFile": {
|
|
172
|
+
"type": "boolean",
|
|
173
|
+
"description": "Save results to file"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "get-menu-items",
|
|
180
|
+
"description": "Retrieve Unity MenuItems",
|
|
181
|
+
"inputSchema": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"properties": {
|
|
184
|
+
"FilterText": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"description": "Filter text"
|
|
187
|
+
},
|
|
188
|
+
"FilterType": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"description": "Filter type",
|
|
191
|
+
"enum": [
|
|
192
|
+
"contains",
|
|
193
|
+
"exact",
|
|
194
|
+
"startswith"
|
|
195
|
+
],
|
|
196
|
+
"default": "contains"
|
|
197
|
+
},
|
|
198
|
+
"MaxCount": {
|
|
199
|
+
"type": "integer",
|
|
200
|
+
"description": "Maximum number of items",
|
|
201
|
+
"default": 200
|
|
202
|
+
},
|
|
203
|
+
"IncludeValidation": {
|
|
204
|
+
"type": "boolean",
|
|
205
|
+
"description": "Include validation functions"
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"name": "execute-menu-item",
|
|
212
|
+
"description": "Execute Unity MenuItem",
|
|
213
|
+
"inputSchema": {
|
|
214
|
+
"type": "object",
|
|
215
|
+
"properties": {
|
|
216
|
+
"MenuItemPath": {
|
|
217
|
+
"type": "string",
|
|
218
|
+
"description": "Menu item path (e.g., \"GameObject/Create Empty\")"
|
|
219
|
+
},
|
|
220
|
+
"UseReflectionFallback": {
|
|
221
|
+
"type": "boolean",
|
|
222
|
+
"description": "Use reflection fallback",
|
|
223
|
+
"default": true
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "find-game-objects",
|
|
230
|
+
"description": "Find GameObjects with search criteria",
|
|
231
|
+
"inputSchema": {
|
|
232
|
+
"type": "object",
|
|
233
|
+
"properties": {
|
|
234
|
+
"NamePattern": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"description": "Name pattern to search"
|
|
237
|
+
},
|
|
238
|
+
"SearchMode": {
|
|
239
|
+
"type": "string",
|
|
240
|
+
"description": "Search mode",
|
|
241
|
+
"enum": [
|
|
242
|
+
"Exact",
|
|
243
|
+
"Path",
|
|
244
|
+
"Regex",
|
|
245
|
+
"Contains"
|
|
246
|
+
],
|
|
247
|
+
"default": "Contains"
|
|
248
|
+
},
|
|
249
|
+
"RequiredComponents": {
|
|
250
|
+
"type": "array",
|
|
251
|
+
"description": "Required components",
|
|
252
|
+
"items": {
|
|
253
|
+
"type": "string"
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
"Tag": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"description": "Tag filter"
|
|
259
|
+
},
|
|
260
|
+
"Layer": {
|
|
261
|
+
"type": "string",
|
|
262
|
+
"description": "Layer filter"
|
|
263
|
+
},
|
|
264
|
+
"MaxResults": {
|
|
265
|
+
"type": "integer",
|
|
266
|
+
"description": "Maximum number of results",
|
|
267
|
+
"default": 20
|
|
268
|
+
},
|
|
269
|
+
"IncludeInactive": {
|
|
270
|
+
"type": "boolean",
|
|
271
|
+
"description": "Include inactive GameObjects"
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"name": "capture-gameview",
|
|
278
|
+
"description": "Capture Unity Game View as PNG",
|
|
279
|
+
"inputSchema": {
|
|
280
|
+
"type": "object",
|
|
281
|
+
"properties": {
|
|
282
|
+
"ResolutionScale": {
|
|
283
|
+
"type": "number",
|
|
284
|
+
"description": "Resolution scale (0.1 to 1.0)",
|
|
285
|
+
"default": 1
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"name": "execute-dynamic-code",
|
|
292
|
+
"description": "Execute C# code in Unity Editor",
|
|
293
|
+
"inputSchema": {
|
|
294
|
+
"type": "object",
|
|
295
|
+
"properties": {
|
|
296
|
+
"Code": {
|
|
297
|
+
"type": "string",
|
|
298
|
+
"description": "C# code to execute"
|
|
299
|
+
},
|
|
300
|
+
"CompileOnly": {
|
|
301
|
+
"type": "boolean",
|
|
302
|
+
"description": "Compile only without execution"
|
|
303
|
+
},
|
|
304
|
+
"AutoQualifyUnityTypesOnce": {
|
|
305
|
+
"type": "boolean",
|
|
306
|
+
"description": "Auto-qualify Unity types"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"name": "get-provider-details",
|
|
313
|
+
"description": "Get Unity Search provider details",
|
|
314
|
+
"inputSchema": {
|
|
315
|
+
"type": "object",
|
|
316
|
+
"properties": {
|
|
317
|
+
"ProviderId": {
|
|
318
|
+
"type": "string",
|
|
319
|
+
"description": "Specific provider ID"
|
|
320
|
+
},
|
|
321
|
+
"ActiveOnly": {
|
|
322
|
+
"type": "boolean",
|
|
323
|
+
"description": "Only active providers"
|
|
324
|
+
},
|
|
325
|
+
"IncludeDescriptions": {
|
|
326
|
+
"type": "boolean",
|
|
327
|
+
"description": "Include descriptions",
|
|
328
|
+
"default": true
|
|
329
|
+
},
|
|
330
|
+
"SortByPriority": {
|
|
331
|
+
"type": "boolean",
|
|
332
|
+
"description": "Sort by priority",
|
|
333
|
+
"default": true
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"name": "get-project-info",
|
|
340
|
+
"description": "Get Unity project information",
|
|
341
|
+
"inputSchema": {
|
|
342
|
+
"type": "object",
|
|
343
|
+
"properties": {}
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"name": "get-version",
|
|
348
|
+
"description": "Get uLoopMCP version information",
|
|
349
|
+
"inputSchema": {
|
|
350
|
+
"type": "object",
|
|
351
|
+
"properties": {}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct Unity TCP client for CLI usage.
|
|
3
|
+
* Establishes one-shot TCP connections to Unity without going through MCP server.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as net from 'net';
|
|
7
|
+
import { createFrame, parseFrameFromBuffer, extractFrameFromBuffer } from './simple-framer.js';
|
|
8
|
+
|
|
9
|
+
const JSONRPC_VERSION = '2.0';
|
|
10
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
11
|
+
const NETWORK_TIMEOUT_MS = 180000;
|
|
12
|
+
|
|
13
|
+
export interface JsonRpcRequest {
|
|
14
|
+
jsonrpc: string;
|
|
15
|
+
method: string;
|
|
16
|
+
params?: Record<string, unknown>;
|
|
17
|
+
id: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface JsonRpcResponse {
|
|
21
|
+
jsonrpc: string;
|
|
22
|
+
result?: unknown;
|
|
23
|
+
error?: {
|
|
24
|
+
code: number;
|
|
25
|
+
message: string;
|
|
26
|
+
data?: unknown;
|
|
27
|
+
};
|
|
28
|
+
id: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class DirectUnityClient {
|
|
32
|
+
private socket: net.Socket | null = null;
|
|
33
|
+
private requestId: number = 0;
|
|
34
|
+
private receiveBuffer: Buffer = Buffer.alloc(0);
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly port: number,
|
|
38
|
+
private readonly host: string = DEFAULT_HOST,
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
async connect(): Promise<void> {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
this.socket = new net.Socket();
|
|
44
|
+
|
|
45
|
+
this.socket.on('error', (error: Error) => {
|
|
46
|
+
reject(new Error(`Connection error: ${error.message}`));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.socket.connect(this.port, this.host, () => {
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async sendRequest<T>(method: string, params?: Record<string, unknown>): Promise<T> {
|
|
56
|
+
if (!this.socket) {
|
|
57
|
+
throw new Error('Not connected to Unity');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const request: JsonRpcRequest = {
|
|
61
|
+
jsonrpc: JSONRPC_VERSION,
|
|
62
|
+
method,
|
|
63
|
+
params: params ?? {},
|
|
64
|
+
id: ++this.requestId,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const requestJson = JSON.stringify(request);
|
|
68
|
+
const framedMessage = createFrame(requestJson);
|
|
69
|
+
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const socket = this.socket!;
|
|
72
|
+
const timeoutId = setTimeout(() => {
|
|
73
|
+
reject(new Error(`Request timed out after ${NETWORK_TIMEOUT_MS}ms`));
|
|
74
|
+
}, NETWORK_TIMEOUT_MS);
|
|
75
|
+
|
|
76
|
+
const onData = (chunk: Buffer): void => {
|
|
77
|
+
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
|
|
78
|
+
|
|
79
|
+
const parseResult = parseFrameFromBuffer(this.receiveBuffer);
|
|
80
|
+
if (!parseResult.isComplete) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const extractResult = extractFrameFromBuffer(
|
|
85
|
+
this.receiveBuffer,
|
|
86
|
+
parseResult.contentLength,
|
|
87
|
+
parseResult.headerLength,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (extractResult.jsonContent === null) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
clearTimeout(timeoutId);
|
|
95
|
+
socket.off('data', onData);
|
|
96
|
+
|
|
97
|
+
this.receiveBuffer = extractResult.remainingData;
|
|
98
|
+
|
|
99
|
+
const response = JSON.parse(extractResult.jsonContent) as JsonRpcResponse;
|
|
100
|
+
|
|
101
|
+
if (response.error) {
|
|
102
|
+
reject(new Error(`Unity error: ${response.error.message}`));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resolve(response.result as T);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
socket.on('data', onData);
|
|
110
|
+
socket.write(framedMessage);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
disconnect(): void {
|
|
115
|
+
if (this.socket) {
|
|
116
|
+
this.socket.destroy();
|
|
117
|
+
this.socket = null;
|
|
118
|
+
}
|
|
119
|
+
this.receiveBuffer = Buffer.alloc(0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
isConnected(): boolean {
|
|
123
|
+
return this.socket !== null && !this.socket.destroyed;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool execution logic for CLI.
|
|
3
|
+
* Handles dynamic tool execution by connecting to Unity and sending requests.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DirectUnityClient } from './direct-unity-client.js';
|
|
7
|
+
import { resolveUnityPort } from './port-resolver.js';
|
|
8
|
+
import { saveToolsCache, getCacheFilePath, ToolsCache, ToolDefinition } from './tool-cache.js';
|
|
9
|
+
import { VERSION } from './version.js';
|
|
10
|
+
|
|
11
|
+
export interface GlobalOptions {
|
|
12
|
+
port?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function executeToolCommand(
|
|
16
|
+
toolName: string,
|
|
17
|
+
params: Record<string, unknown>,
|
|
18
|
+
globalOptions: GlobalOptions,
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
let portNumber: number | undefined;
|
|
21
|
+
if (globalOptions.port) {
|
|
22
|
+
const parsed = parseInt(globalOptions.port, 10);
|
|
23
|
+
if (isNaN(parsed)) {
|
|
24
|
+
throw new Error(`Invalid port number: ${globalOptions.port}`);
|
|
25
|
+
}
|
|
26
|
+
portNumber = parsed;
|
|
27
|
+
}
|
|
28
|
+
const port = await resolveUnityPort(portNumber);
|
|
29
|
+
|
|
30
|
+
const client = new DirectUnityClient(port);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await client.connect();
|
|
34
|
+
|
|
35
|
+
const result = await client.sendRequest(toolName, params);
|
|
36
|
+
|
|
37
|
+
// Always output JSON to match MCP response format
|
|
38
|
+
console.log(JSON.stringify(result, null, 2));
|
|
39
|
+
} finally {
|
|
40
|
+
client.disconnect();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function listAvailableTools(globalOptions: GlobalOptions): Promise<void> {
|
|
45
|
+
let portNumber: number | undefined;
|
|
46
|
+
if (globalOptions.port) {
|
|
47
|
+
const parsed = parseInt(globalOptions.port, 10);
|
|
48
|
+
if (isNaN(parsed)) {
|
|
49
|
+
throw new Error(`Invalid port number: ${globalOptions.port}`);
|
|
50
|
+
}
|
|
51
|
+
portNumber = parsed;
|
|
52
|
+
}
|
|
53
|
+
const port = await resolveUnityPort(portNumber);
|
|
54
|
+
|
|
55
|
+
const client = new DirectUnityClient(port);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await client.connect();
|
|
59
|
+
|
|
60
|
+
const result = await client.sendRequest<{
|
|
61
|
+
Tools: Array<{ name: string; description: string }>;
|
|
62
|
+
}>('get-tool-details', { IncludeDevelopmentOnly: false });
|
|
63
|
+
|
|
64
|
+
if (!result.Tools || !Array.isArray(result.Tools)) {
|
|
65
|
+
throw new Error('Unexpected response from Unity: missing Tools array');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const tool of result.Tools) {
|
|
69
|
+
console.log(` - ${tool.name}`);
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
client.disconnect();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface UnityToolInfo {
|
|
77
|
+
name: string;
|
|
78
|
+
description: string;
|
|
79
|
+
parameterSchema: {
|
|
80
|
+
Properties: Record<string, UnityPropertyInfo>;
|
|
81
|
+
Required?: string[];
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface UnityPropertyInfo {
|
|
86
|
+
Type: string;
|
|
87
|
+
Description?: string;
|
|
88
|
+
DefaultValue?: unknown;
|
|
89
|
+
Enum?: string[] | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function convertProperties(
|
|
93
|
+
unityProps: Record<string, UnityPropertyInfo>,
|
|
94
|
+
): Record<string, ToolDefinition['inputSchema']['properties'][string]> {
|
|
95
|
+
const result: Record<string, ToolDefinition['inputSchema']['properties'][string]> = {};
|
|
96
|
+
for (const [key, prop] of Object.entries(unityProps)) {
|
|
97
|
+
result[key] = {
|
|
98
|
+
type: prop.Type?.toLowerCase() ?? 'string',
|
|
99
|
+
description: prop.Description,
|
|
100
|
+
default: prop.DefaultValue,
|
|
101
|
+
enum: prop.Enum ?? undefined,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function syncTools(globalOptions: GlobalOptions): Promise<void> {
|
|
108
|
+
let portNumber: number | undefined;
|
|
109
|
+
if (globalOptions.port) {
|
|
110
|
+
const parsed = parseInt(globalOptions.port, 10);
|
|
111
|
+
if (isNaN(parsed)) {
|
|
112
|
+
throw new Error(`Invalid port number: ${globalOptions.port}`);
|
|
113
|
+
}
|
|
114
|
+
portNumber = parsed;
|
|
115
|
+
}
|
|
116
|
+
const port = await resolveUnityPort(portNumber);
|
|
117
|
+
|
|
118
|
+
const client = new DirectUnityClient(port);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
await client.connect();
|
|
122
|
+
|
|
123
|
+
const result = await client.sendRequest<{
|
|
124
|
+
Tools: UnityToolInfo[];
|
|
125
|
+
}>('get-tool-details', { IncludeDevelopmentOnly: false });
|
|
126
|
+
|
|
127
|
+
if (!result.Tools || !Array.isArray(result.Tools)) {
|
|
128
|
+
throw new Error('Unexpected response from Unity: missing Tools array');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const cache: ToolsCache = {
|
|
132
|
+
version: VERSION,
|
|
133
|
+
updatedAt: new Date().toISOString(),
|
|
134
|
+
tools: result.Tools.map((tool) => ({
|
|
135
|
+
name: tool.name,
|
|
136
|
+
description: tool.description,
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: convertProperties(tool.parameterSchema.Properties),
|
|
140
|
+
required: tool.parameterSchema.Required,
|
|
141
|
+
},
|
|
142
|
+
})),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
saveToolsCache(cache);
|
|
146
|
+
|
|
147
|
+
console.log(`Synced ${cache.tools.length} tools to ${getCacheFilePath()}`);
|
|
148
|
+
console.log('\nTools:');
|
|
149
|
+
for (const tool of cache.tools) {
|
|
150
|
+
console.log(` - ${tool.name}`);
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
client.disconnect();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port resolution utility for CLI.
|
|
3
|
+
* Resolves Unity server port from various sources.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFile } from 'fs/promises';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { findUnityProjectRoot } from './project-root.js';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PORT = 8700;
|
|
11
|
+
|
|
12
|
+
interface UnityMcpSettings {
|
|
13
|
+
serverPort?: number;
|
|
14
|
+
customPort?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function resolveUnityPort(explicitPort?: number): Promise<number> {
|
|
18
|
+
if (explicitPort !== undefined) {
|
|
19
|
+
return explicitPort;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const settingsPort = await readPortFromSettings();
|
|
23
|
+
if (settingsPort !== null) {
|
|
24
|
+
return settingsPort;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return DEFAULT_PORT;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function readPortFromSettings(): Promise<number | null> {
|
|
31
|
+
const projectRoot = findUnityProjectRoot();
|
|
32
|
+
if (projectRoot === null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const settingsPath = join(projectRoot, 'UserSettings/UnityMcpSettings.json');
|
|
36
|
+
|
|
37
|
+
let content: string;
|
|
38
|
+
try {
|
|
39
|
+
content = await readFile(settingsPath, 'utf-8');
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let settings: UnityMcpSettings;
|
|
45
|
+
try {
|
|
46
|
+
settings = JSON.parse(content) as UnityMcpSettings;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof settings.serverPort === 'number') {
|
|
52
|
+
return settings.serverPort;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof settings.customPort === 'number') {
|
|
56
|
+
return settings.customPort;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|