mcpforunityserver 8.5.0__py3-none-any.whl → 9.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +3 -0
  3. cli/commands/animation.py +87 -0
  4. cli/commands/asset.py +310 -0
  5. cli/commands/audio.py +133 -0
  6. cli/commands/batch.py +184 -0
  7. cli/commands/code.py +189 -0
  8. cli/commands/component.py +212 -0
  9. cli/commands/editor.py +487 -0
  10. cli/commands/gameobject.py +510 -0
  11. cli/commands/instance.py +101 -0
  12. cli/commands/lighting.py +128 -0
  13. cli/commands/material.py +268 -0
  14. cli/commands/prefab.py +144 -0
  15. cli/commands/scene.py +255 -0
  16. cli/commands/script.py +240 -0
  17. cli/commands/shader.py +238 -0
  18. cli/commands/ui.py +263 -0
  19. cli/commands/vfx.py +439 -0
  20. cli/main.py +248 -0
  21. cli/utils/__init__.py +31 -0
  22. cli/utils/config.py +58 -0
  23. cli/utils/connection.py +191 -0
  24. cli/utils/output.py +195 -0
  25. main.py +207 -62
  26. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/METADATA +4 -2
  27. mcpforunityserver-9.1.0.dist-info/RECORD +96 -0
  28. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/WHEEL +1 -1
  29. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/entry_points.txt +1 -0
  30. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/top_level.txt +1 -2
  31. services/custom_tool_service.py +179 -19
  32. services/resources/__init__.py +6 -1
  33. services/resources/active_tool.py +1 -1
  34. services/resources/custom_tools.py +2 -2
  35. services/resources/editor_state.py +283 -21
  36. services/resources/gameobject.py +243 -0
  37. services/resources/layers.py +1 -1
  38. services/resources/prefab_stage.py +1 -1
  39. services/resources/project_info.py +1 -1
  40. services/resources/selection.py +1 -1
  41. services/resources/tags.py +1 -1
  42. services/resources/unity_instances.py +1 -1
  43. services/resources/windows.py +1 -1
  44. services/state/external_changes_scanner.py +245 -0
  45. services/tools/__init__.py +6 -1
  46. services/tools/batch_execute.py +24 -9
  47. services/tools/debug_request_context.py +8 -2
  48. services/tools/execute_custom_tool.py +6 -1
  49. services/tools/execute_menu_item.py +6 -3
  50. services/tools/find_gameobjects.py +89 -0
  51. services/tools/find_in_file.py +26 -19
  52. services/tools/manage_asset.py +19 -43
  53. services/tools/manage_components.py +131 -0
  54. services/tools/manage_editor.py +9 -8
  55. services/tools/manage_gameobject.py +120 -79
  56. services/tools/manage_material.py +80 -31
  57. services/tools/manage_prefabs.py +7 -1
  58. services/tools/manage_scene.py +34 -13
  59. services/tools/manage_script.py +62 -19
  60. services/tools/manage_scriptable_object.py +22 -10
  61. services/tools/manage_shader.py +8 -1
  62. services/tools/manage_vfx.py +738 -0
  63. services/tools/preflight.py +110 -0
  64. services/tools/read_console.py +81 -18
  65. services/tools/refresh_unity.py +153 -0
  66. services/tools/run_tests.py +202 -41
  67. services/tools/script_apply_edits.py +15 -7
  68. services/tools/set_active_instance.py +12 -7
  69. services/tools/utils.py +60 -6
  70. transport/legacy/port_discovery.py +2 -2
  71. transport/legacy/unity_connection.py +129 -26
  72. transport/plugin_hub.py +191 -19
  73. transport/unity_instance_middleware.py +93 -2
  74. transport/unity_transport.py +17 -6
  75. utils/focus_nudge.py +321 -0
  76. __init__.py +0 -0
  77. mcpforunityserver-8.5.0.dist-info/RECORD +0 -66
  78. routes/__init__.py +0 -0
  79. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/licenses/LICENSE +0 -0
cli/commands/editor.py ADDED
@@ -0,0 +1,487 @@
1
+ """Editor CLI commands."""
2
+
3
+ import sys
4
+ import click
5
+ from typing import Optional, Any
6
+
7
+ from cli.utils.config import get_config
8
+ from cli.utils.output import format_output, print_error, print_success, print_info
9
+ from cli.utils.connection import run_command, UnityConnectionError
10
+
11
+
12
+ @click.group()
13
+ def editor():
14
+ """Editor operations - play mode, console, tags, layers."""
15
+ pass
16
+
17
+
18
+ @editor.command("play")
19
+ def play():
20
+ """Enter play mode."""
21
+ config = get_config()
22
+
23
+ try:
24
+ result = run_command("manage_editor", {"action": "play"}, config)
25
+ click.echo(format_output(result, config.format))
26
+ if result.get("success"):
27
+ print_success("Entered play mode")
28
+ except UnityConnectionError as e:
29
+ print_error(str(e))
30
+ sys.exit(1)
31
+
32
+
33
+ @editor.command("pause")
34
+ def pause():
35
+ """Pause play mode."""
36
+ config = get_config()
37
+
38
+ try:
39
+ result = run_command("manage_editor", {"action": "pause"}, config)
40
+ click.echo(format_output(result, config.format))
41
+ if result.get("success"):
42
+ print_success("Paused play mode")
43
+ except UnityConnectionError as e:
44
+ print_error(str(e))
45
+ sys.exit(1)
46
+
47
+
48
+ @editor.command("stop")
49
+ def stop():
50
+ """Stop play mode."""
51
+ config = get_config()
52
+
53
+ try:
54
+ result = run_command("manage_editor", {"action": "stop"}, config)
55
+ click.echo(format_output(result, config.format))
56
+ if result.get("success"):
57
+ print_success("Stopped play mode")
58
+ except UnityConnectionError as e:
59
+ print_error(str(e))
60
+ sys.exit(1)
61
+
62
+
63
+ @editor.command("console")
64
+ @click.option(
65
+ "--type", "-t",
66
+ "log_types",
67
+ multiple=True,
68
+ type=click.Choice(["error", "warning", "log", "all"]),
69
+ default=["error", "warning", "log"],
70
+ help="Message types to retrieve."
71
+ )
72
+ @click.option(
73
+ "--count", "-n",
74
+ default=10,
75
+ type=int,
76
+ help="Number of messages to retrieve."
77
+ )
78
+ @click.option(
79
+ "--filter", "-f",
80
+ "filter_text",
81
+ default=None,
82
+ help="Filter messages containing this text."
83
+ )
84
+ @click.option(
85
+ "--stacktrace", "-s",
86
+ is_flag=True,
87
+ help="Include stack traces."
88
+ )
89
+ @click.option(
90
+ "--clear",
91
+ is_flag=True,
92
+ help="Clear the console instead of reading."
93
+ )
94
+ def console(log_types: tuple, count: int, filter_text: Optional[str], stacktrace: bool, clear: bool):
95
+ """Read or clear the Unity console.
96
+
97
+ \b
98
+ Examples:
99
+ unity-mcp editor console
100
+ unity-mcp editor console --type error --count 20
101
+ unity-mcp editor console --filter "NullReference" --stacktrace
102
+ unity-mcp editor console --clear
103
+ """
104
+ config = get_config()
105
+
106
+ if clear:
107
+ try:
108
+ result = run_command("read_console", {"action": "clear"}, config)
109
+ click.echo(format_output(result, config.format))
110
+ if result.get("success"):
111
+ print_success("Console cleared")
112
+ except UnityConnectionError as e:
113
+ print_error(str(e))
114
+ sys.exit(1)
115
+ return
116
+
117
+ params: dict[str, Any] = {
118
+ "action": "get",
119
+ "types": list(log_types),
120
+ "count": count,
121
+ "include_stacktrace": stacktrace,
122
+ }
123
+
124
+ if filter_text:
125
+ params["filter_text"] = filter_text
126
+
127
+ try:
128
+ result = run_command("read_console", params, config)
129
+ click.echo(format_output(result, config.format))
130
+ except UnityConnectionError as e:
131
+ print_error(str(e))
132
+ sys.exit(1)
133
+
134
+
135
+ @editor.command("add-tag")
136
+ @click.argument("tag_name")
137
+ def add_tag(tag_name: str):
138
+ """Add a new tag.
139
+
140
+ \b
141
+ Examples:
142
+ unity-mcp editor add-tag "Enemy"
143
+ unity-mcp editor add-tag "Collectible"
144
+ """
145
+ config = get_config()
146
+
147
+ try:
148
+ result = run_command(
149
+ "manage_editor", {"action": "add_tag", "tagName": tag_name}, config)
150
+ click.echo(format_output(result, config.format))
151
+ if result.get("success"):
152
+ print_success(f"Added tag: {tag_name}")
153
+ except UnityConnectionError as e:
154
+ print_error(str(e))
155
+ sys.exit(1)
156
+
157
+
158
+ @editor.command("remove-tag")
159
+ @click.argument("tag_name")
160
+ def remove_tag(tag_name: str):
161
+ """Remove a tag.
162
+
163
+ \b
164
+ Examples:
165
+ unity-mcp editor remove-tag "OldTag"
166
+ """
167
+ config = get_config()
168
+
169
+ try:
170
+ result = run_command(
171
+ "manage_editor", {"action": "remove_tag", "tagName": tag_name}, config)
172
+ click.echo(format_output(result, config.format))
173
+ if result.get("success"):
174
+ print_success(f"Removed tag: {tag_name}")
175
+ except UnityConnectionError as e:
176
+ print_error(str(e))
177
+ sys.exit(1)
178
+
179
+
180
+ @editor.command("add-layer")
181
+ @click.argument("layer_name")
182
+ def add_layer(layer_name: str):
183
+ """Add a new layer.
184
+
185
+ \b
186
+ Examples:
187
+ unity-mcp editor add-layer "Interactable"
188
+ """
189
+ config = get_config()
190
+
191
+ try:
192
+ result = run_command(
193
+ "manage_editor", {"action": "add_layer", "layerName": layer_name}, config)
194
+ click.echo(format_output(result, config.format))
195
+ if result.get("success"):
196
+ print_success(f"Added layer: {layer_name}")
197
+ except UnityConnectionError as e:
198
+ print_error(str(e))
199
+ sys.exit(1)
200
+
201
+
202
+ @editor.command("remove-layer")
203
+ @click.argument("layer_name")
204
+ def remove_layer(layer_name: str):
205
+ """Remove a layer.
206
+
207
+ \b
208
+ Examples:
209
+ unity-mcp editor remove-layer "OldLayer"
210
+ """
211
+ config = get_config()
212
+
213
+ try:
214
+ result = run_command(
215
+ "manage_editor", {"action": "remove_layer", "layerName": layer_name}, config)
216
+ click.echo(format_output(result, config.format))
217
+ if result.get("success"):
218
+ print_success(f"Removed layer: {layer_name}")
219
+ except UnityConnectionError as e:
220
+ print_error(str(e))
221
+ sys.exit(1)
222
+
223
+
224
+ @editor.command("tool")
225
+ @click.argument("tool_name")
226
+ def set_tool(tool_name: str):
227
+ """Set the active editor tool.
228
+
229
+ \b
230
+ Examples:
231
+ unity-mcp editor tool "Move"
232
+ unity-mcp editor tool "Rotate"
233
+ unity-mcp editor tool "Scale"
234
+ """
235
+ config = get_config()
236
+
237
+ try:
238
+ result = run_command(
239
+ "manage_editor", {"action": "set_active_tool", "toolName": tool_name}, config)
240
+ click.echo(format_output(result, config.format))
241
+ if result.get("success"):
242
+ print_success(f"Set active tool: {tool_name}")
243
+ except UnityConnectionError as e:
244
+ print_error(str(e))
245
+ sys.exit(1)
246
+
247
+
248
+ @editor.command("menu")
249
+ @click.argument("menu_path")
250
+ def execute_menu(menu_path: str):
251
+ """Execute a menu item.
252
+
253
+ \b
254
+ Examples:
255
+ unity-mcp editor menu "File/Save"
256
+ unity-mcp editor menu "Edit/Undo"
257
+ unity-mcp editor menu "GameObject/Create Empty"
258
+ """
259
+ config = get_config()
260
+
261
+ try:
262
+ result = run_command("execute_menu_item", {
263
+ "menu_path": menu_path}, config)
264
+ click.echo(format_output(result, config.format))
265
+ if result.get("success"):
266
+ print_success(f"Executed: {menu_path}")
267
+ except UnityConnectionError as e:
268
+ print_error(str(e))
269
+ sys.exit(1)
270
+
271
+
272
+ @editor.command("tests")
273
+ @click.option(
274
+ "--mode", "-m",
275
+ type=click.Choice(["EditMode", "PlayMode"]),
276
+ default="EditMode",
277
+ help="Test mode to run."
278
+ )
279
+ @click.option(
280
+ "--async", "async_mode",
281
+ is_flag=True,
282
+ help="Run asynchronously and return job ID for polling."
283
+ )
284
+ @click.option(
285
+ "--wait", "-w",
286
+ type=int,
287
+ default=None,
288
+ help="Wait up to N seconds for completion (default: no wait)."
289
+ )
290
+ @click.option(
291
+ "--details",
292
+ is_flag=True,
293
+ help="Include detailed results for all tests."
294
+ )
295
+ @click.option(
296
+ "--failed-only",
297
+ is_flag=True,
298
+ help="Include details for failed/skipped tests only."
299
+ )
300
+ def run_tests(mode: str, async_mode: bool, wait: Optional[int], details: bool, failed_only: bool):
301
+ """Run Unity tests.
302
+
303
+ \b
304
+ Examples:
305
+ unity-mcp editor tests
306
+ unity-mcp editor tests --mode PlayMode
307
+ unity-mcp editor tests --async
308
+ unity-mcp editor tests --wait 60 --failed-only
309
+ """
310
+ config = get_config()
311
+
312
+ params: dict[str, Any] = {"mode": mode}
313
+ if wait is not None:
314
+ params["wait_timeout"] = wait
315
+ if details:
316
+ params["include_details"] = True
317
+ if failed_only:
318
+ params["include_failed_tests"] = True
319
+
320
+ try:
321
+ result = run_command("run_tests", params, config)
322
+
323
+ # For async mode, just show job ID
324
+ if async_mode and result.get("success"):
325
+ job_id = result.get("data", {}).get("job_id")
326
+ if job_id:
327
+ click.echo(f"Test job started: {job_id}")
328
+ print_info("Poll with: unity-mcp editor poll-test " + job_id)
329
+ return
330
+
331
+ click.echo(format_output(result, config.format))
332
+ except UnityConnectionError as e:
333
+ print_error(str(e))
334
+ sys.exit(1)
335
+
336
+
337
+ @editor.command("poll-test")
338
+ @click.argument("job_id")
339
+ @click.option(
340
+ "--wait", "-w",
341
+ type=int,
342
+ default=30,
343
+ help="Wait up to N seconds for completion (default: 30)."
344
+ )
345
+ @click.option(
346
+ "--details",
347
+ is_flag=True,
348
+ help="Include detailed results for all tests."
349
+ )
350
+ @click.option(
351
+ "--failed-only",
352
+ is_flag=True,
353
+ help="Include details for failed/skipped tests only."
354
+ )
355
+ def poll_test(job_id: str, wait: int, details: bool, failed_only: bool):
356
+ """Poll an async test job for status/results.
357
+
358
+ \b
359
+ Examples:
360
+ unity-mcp editor poll-test abc123
361
+ unity-mcp editor poll-test abc123 --wait 60
362
+ unity-mcp editor poll-test abc123 --failed-only
363
+ """
364
+ config = get_config()
365
+
366
+ params: dict[str, Any] = {"job_id": job_id}
367
+ if wait:
368
+ params["wait_timeout"] = wait
369
+ if details:
370
+ params["include_details"] = True
371
+ if failed_only:
372
+ params["include_failed_tests"] = True
373
+
374
+ try:
375
+ result = run_command("get_test_job", params, config)
376
+ click.echo(format_output(result, config.format))
377
+
378
+ if isinstance(result, dict) and result.get("success"):
379
+ data = result.get("data", {})
380
+ status = data.get("status", "unknown")
381
+ if status == "succeeded":
382
+ print_success("Tests completed successfully")
383
+ elif status == "failed":
384
+ summary = data.get("result", {}).get("summary", {})
385
+ failed = summary.get("failed", 0)
386
+ print_error(f"Tests failed: {failed} failures")
387
+ elif status == "running":
388
+ progress = data.get("progress", {})
389
+ completed = progress.get("completed", 0)
390
+ total = progress.get("total", 0)
391
+ print_info(f"Tests running: {completed}/{total}")
392
+ except UnityConnectionError as e:
393
+ print_error(str(e))
394
+ sys.exit(1)
395
+
396
+
397
+ @editor.command("refresh")
398
+ @click.option(
399
+ "--mode",
400
+ type=click.Choice(["if_dirty", "force"]),
401
+ default="if_dirty",
402
+ help="Refresh mode."
403
+ )
404
+ @click.option(
405
+ "--scope",
406
+ type=click.Choice(["assets", "scripts", "all"]),
407
+ default="all",
408
+ help="What to refresh."
409
+ )
410
+ @click.option(
411
+ "--compile",
412
+ is_flag=True,
413
+ help="Request script compilation."
414
+ )
415
+ @click.option(
416
+ "--no-wait",
417
+ is_flag=True,
418
+ help="Don't wait for refresh to complete."
419
+ )
420
+ def refresh(mode: str, scope: str, compile: bool, no_wait: bool):
421
+ """Force Unity to refresh assets/scripts.
422
+
423
+ \b
424
+ Examples:
425
+ unity-mcp editor refresh
426
+ unity-mcp editor refresh --mode force
427
+ unity-mcp editor refresh --compile
428
+ unity-mcp editor refresh --scope scripts --compile
429
+ """
430
+ config = get_config()
431
+
432
+ params: dict[str, Any] = {
433
+ "mode": mode,
434
+ "scope": scope,
435
+ "wait_for_ready": not no_wait,
436
+ }
437
+ if compile:
438
+ params["compile"] = "request"
439
+
440
+ try:
441
+ click.echo("Refreshing Unity...")
442
+ result = run_command("refresh_unity", params, config)
443
+ click.echo(format_output(result, config.format))
444
+ if result.get("success"):
445
+ print_success("Unity refreshed")
446
+ except UnityConnectionError as e:
447
+ print_error(str(e))
448
+ sys.exit(1)
449
+
450
+
451
+ @editor.command("custom-tool")
452
+ @click.argument("tool_name")
453
+ @click.option(
454
+ "--params", "-p",
455
+ default="{}",
456
+ help="Tool parameters as JSON."
457
+ )
458
+ def custom_tool(tool_name: str, params: str):
459
+ """Execute a custom Unity tool.
460
+
461
+ Custom tools are registered by Unity projects via the MCP plugin.
462
+
463
+ \b
464
+ Examples:
465
+ unity-mcp editor custom-tool "MyCustomTool"
466
+ unity-mcp editor custom-tool "BuildPipeline" --params '{"target": "Android"}'
467
+ """
468
+ import json
469
+ config = get_config()
470
+
471
+ try:
472
+ params_dict = json.loads(params)
473
+ except json.JSONDecodeError as e:
474
+ print_error(f"Invalid JSON for params: {e}")
475
+ sys.exit(1)
476
+
477
+ try:
478
+ result = run_command("execute_custom_tool", {
479
+ "tool_name": tool_name,
480
+ "parameters": params_dict,
481
+ }, config)
482
+ click.echo(format_output(result, config.format))
483
+ if result.get("success"):
484
+ print_success(f"Executed custom tool: {tool_name}")
485
+ except UnityConnectionError as e:
486
+ print_error(str(e))
487
+ sys.exit(1)