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
@@ -0,0 +1,510 @@
1
+ """GameObject CLI commands."""
2
+
3
+ import sys
4
+ import json
5
+ import click
6
+ from typing import Optional, Tuple, Any
7
+
8
+ from cli.utils.config import get_config
9
+ from cli.utils.output import format_output, print_error, print_success, print_warning
10
+ from cli.utils.connection import run_command, UnityConnectionError
11
+
12
+
13
+ @click.group()
14
+ def gameobject():
15
+ """GameObject operations - create, find, modify, delete GameObjects."""
16
+ pass
17
+
18
+
19
+ @gameobject.command("find")
20
+ @click.argument("search_term")
21
+ @click.option(
22
+ "--method", "-m",
23
+ type=click.Choice(["by_name", "by_tag", "by_layer",
24
+ "by_component", "by_path", "by_id"]),
25
+ default="by_name",
26
+ help="Search method."
27
+ )
28
+ @click.option(
29
+ "--include-inactive", "-i",
30
+ is_flag=True,
31
+ help="Include inactive GameObjects."
32
+ )
33
+ @click.option(
34
+ "--limit", "-l",
35
+ default=50,
36
+ type=int,
37
+ help="Maximum results to return."
38
+ )
39
+ @click.option(
40
+ "--cursor", "-c",
41
+ default=0,
42
+ type=int,
43
+ help="Pagination cursor (offset)."
44
+ )
45
+ def find(search_term: str, method: str, include_inactive: bool, limit: int, cursor: int):
46
+ """Find GameObjects by search criteria.
47
+
48
+ \b
49
+ Examples:
50
+ unity-mcp gameobject find "Player"
51
+ unity-mcp gameobject find "Enemy" --method by_tag
52
+ unity-mcp gameobject find "-81840" --method by_id
53
+ unity-mcp gameobject find "Rigidbody" --method by_component
54
+ unity-mcp gameobject find "/Canvas/Panel" --method by_path
55
+ """
56
+ config = get_config()
57
+
58
+ try:
59
+ result = run_command("find_gameobjects", {
60
+ "searchMethod": method,
61
+ "searchTerm": search_term,
62
+ "includeInactive": include_inactive,
63
+ "pageSize": limit,
64
+ "cursor": cursor,
65
+ }, config)
66
+ click.echo(format_output(result, config.format))
67
+ except UnityConnectionError as e:
68
+ print_error(str(e))
69
+ sys.exit(1)
70
+
71
+
72
+ @gameobject.command("create")
73
+ @click.argument("name")
74
+ @click.option(
75
+ "--primitive", "-p",
76
+ type=click.Choice(["Cube", "Sphere", "Cylinder",
77
+ "Plane", "Capsule", "Quad"]),
78
+ help="Create a primitive type."
79
+ )
80
+ @click.option(
81
+ "--position", "-pos",
82
+ nargs=3,
83
+ type=float,
84
+ default=None,
85
+ help="Position as X Y Z."
86
+ )
87
+ @click.option(
88
+ "--rotation", "-rot",
89
+ nargs=3,
90
+ type=float,
91
+ default=None,
92
+ help="Rotation as X Y Z (euler angles)."
93
+ )
94
+ @click.option(
95
+ "--scale", "-s",
96
+ nargs=3,
97
+ type=float,
98
+ default=None,
99
+ help="Scale as X Y Z."
100
+ )
101
+ @click.option(
102
+ "--parent",
103
+ default=None,
104
+ help="Parent GameObject name or path."
105
+ )
106
+ @click.option(
107
+ "--tag", "-t",
108
+ default=None,
109
+ help="Tag to assign."
110
+ )
111
+ @click.option(
112
+ "--layer",
113
+ default=None,
114
+ help="Layer to assign."
115
+ )
116
+ @click.option(
117
+ "--components",
118
+ default=None,
119
+ help="Comma-separated list of components to add."
120
+ )
121
+ @click.option(
122
+ "--save-prefab",
123
+ is_flag=True,
124
+ help="Save as prefab after creation."
125
+ )
126
+ @click.option(
127
+ "--prefab-path",
128
+ default=None,
129
+ help="Path for prefab (e.g., Assets/Prefabs/MyPrefab.prefab)."
130
+ )
131
+ def create(
132
+ name: str,
133
+ primitive: Optional[str],
134
+ position: Optional[Tuple[float, float, float]],
135
+ rotation: Optional[Tuple[float, float, float]],
136
+ scale: Optional[Tuple[float, float, float]],
137
+ parent: Optional[str],
138
+ tag: Optional[str],
139
+ layer: Optional[str],
140
+ components: Optional[str],
141
+ save_prefab: bool,
142
+ prefab_path: Optional[str],
143
+ ):
144
+ """Create a new GameObject.
145
+
146
+ \b
147
+ Examples:
148
+ unity-mcp gameobject create "MyCube" --primitive Cube
149
+ unity-mcp gameobject create "Player" --position 0 1 0
150
+ unity-mcp gameobject create "Enemy" --primitive Sphere --tag Enemy
151
+ unity-mcp gameobject create "Child" --parent "ParentObject"
152
+ unity-mcp gameobject create "Item" --components "Rigidbody,BoxCollider"
153
+ """
154
+ config = get_config()
155
+
156
+ params: dict[str, Any] = {
157
+ "action": "create",
158
+ "name": name,
159
+ }
160
+
161
+ if primitive:
162
+ params["primitiveType"] = primitive
163
+ if position:
164
+ params["position"] = list(position)
165
+ if rotation:
166
+ params["rotation"] = list(rotation)
167
+ if scale:
168
+ params["scale"] = list(scale)
169
+ if parent:
170
+ params["parent"] = parent
171
+ if tag:
172
+ params["tag"] = tag
173
+ if layer:
174
+ params["layer"] = layer
175
+ if save_prefab:
176
+ params["saveAsPrefab"] = True
177
+ if prefab_path:
178
+ params["prefabPath"] = prefab_path
179
+
180
+ try:
181
+ result = run_command("manage_gameobject", params, config)
182
+
183
+ # Add components separately since componentsToAdd doesn't work
184
+ if components and (result.get("success") or result.get("data") or result.get("result")):
185
+ component_list = [c.strip() for c in components.split(",")]
186
+ failed_components = []
187
+ for component in component_list:
188
+ try:
189
+ run_command("manage_components", {
190
+ "action": "add",
191
+ "target": name,
192
+ "componentType": component,
193
+ }, config)
194
+ except UnityConnectionError:
195
+ failed_components.append(component)
196
+ if failed_components:
197
+ print_warning(
198
+ f"Failed to add components: {', '.join(failed_components)}")
199
+
200
+ click.echo(format_output(result, config.format))
201
+ if result.get("success") or result.get("result"):
202
+ print_success(f"Created GameObject '{name}'")
203
+ except UnityConnectionError as e:
204
+ print_error(str(e))
205
+ sys.exit(1)
206
+
207
+
208
+ @gameobject.command("modify")
209
+ @click.argument("target")
210
+ @click.option(
211
+ "--name", "-n",
212
+ default=None,
213
+ help="New name for the GameObject."
214
+ )
215
+ @click.option(
216
+ "--position", "-pos",
217
+ nargs=3,
218
+ type=float,
219
+ default=None,
220
+ help="New position as X Y Z."
221
+ )
222
+ @click.option(
223
+ "--rotation", "-rot",
224
+ nargs=3,
225
+ type=float,
226
+ default=None,
227
+ help="New rotation as X Y Z (euler angles)."
228
+ )
229
+ @click.option(
230
+ "--scale", "-s",
231
+ nargs=3,
232
+ type=float,
233
+ default=None,
234
+ help="New scale as X Y Z."
235
+ )
236
+ @click.option(
237
+ "--parent",
238
+ default=None,
239
+ help="New parent GameObject."
240
+ )
241
+ @click.option(
242
+ "--tag", "-t",
243
+ default=None,
244
+ help="New tag."
245
+ )
246
+ @click.option(
247
+ "--layer",
248
+ default=None,
249
+ help="New layer."
250
+ )
251
+ @click.option(
252
+ "--active/--inactive",
253
+ default=None,
254
+ help="Set active state."
255
+ )
256
+ @click.option(
257
+ "--add-components",
258
+ default=None,
259
+ help="Comma-separated list of components to add."
260
+ )
261
+ @click.option(
262
+ "--remove-components",
263
+ default=None,
264
+ help="Comma-separated list of components to remove."
265
+ )
266
+ @click.option(
267
+ "--search-method",
268
+ type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
269
+ default=None,
270
+ help="How to find the target GameObject."
271
+ )
272
+ def modify(
273
+ target: str,
274
+ name: Optional[str],
275
+ position: Optional[Tuple[float, float, float]],
276
+ rotation: Optional[Tuple[float, float, float]],
277
+ scale: Optional[Tuple[float, float, float]],
278
+ parent: Optional[str],
279
+ tag: Optional[str],
280
+ layer: Optional[str],
281
+ active: Optional[bool],
282
+ add_components: Optional[str],
283
+ remove_components: Optional[str],
284
+ search_method: Optional[str],
285
+ ):
286
+ """Modify an existing GameObject.
287
+
288
+ TARGET can be a name, path, instance ID, or tag depending on --search-method.
289
+
290
+ \b
291
+ Examples:
292
+ unity-mcp gameobject modify "Player" --position 0 5 0
293
+ unity-mcp gameobject modify "Enemy" --name "Boss" --tag "Boss"
294
+ unity-mcp gameobject modify "-81840" --search-method by_id --active
295
+ unity-mcp gameobject modify "/Canvas/Panel" --search-method by_path --inactive
296
+ unity-mcp gameobject modify "Cube" --add-components "Rigidbody,BoxCollider"
297
+ """
298
+ config = get_config()
299
+
300
+ params: dict[str, Any] = {
301
+ "action": "modify",
302
+ "target": target,
303
+ }
304
+
305
+ if name:
306
+ params["name"] = name
307
+ if position:
308
+ params["position"] = list(position)
309
+ if rotation:
310
+ params["rotation"] = list(rotation)
311
+ if scale:
312
+ params["scale"] = list(scale)
313
+ if parent:
314
+ params["parent"] = parent
315
+ if tag:
316
+ params["tag"] = tag
317
+ if layer:
318
+ params["layer"] = layer
319
+ if active is not None:
320
+ params["setActive"] = active
321
+ if add_components:
322
+ params["componentsToAdd"] = [c.strip()
323
+ for c in add_components.split(",")]
324
+ if remove_components:
325
+ params["componentsToRemove"] = [c.strip()
326
+ for c in remove_components.split(",")]
327
+ if search_method:
328
+ params["searchMethod"] = search_method
329
+
330
+ try:
331
+ result = run_command("manage_gameobject", params, config)
332
+ click.echo(format_output(result, config.format))
333
+ except UnityConnectionError as e:
334
+ print_error(str(e))
335
+ sys.exit(1)
336
+
337
+
338
+ @gameobject.command("delete")
339
+ @click.argument("target")
340
+ @click.option(
341
+ "--search-method",
342
+ type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
343
+ default=None,
344
+ help="How to find the target GameObject."
345
+ )
346
+ @click.option(
347
+ "--force", "-f",
348
+ is_flag=True,
349
+ help="Skip confirmation prompt."
350
+ )
351
+ def delete(target: str, search_method: Optional[str], force: bool):
352
+ """Delete a GameObject.
353
+
354
+ \b
355
+ Examples:
356
+ unity-mcp gameobject delete "OldObject"
357
+ unity-mcp gameobject delete "-81840" --search-method by_id
358
+ unity-mcp gameobject delete "TempObjects" --search-method by_tag --force
359
+ """
360
+ config = get_config()
361
+
362
+ if not force:
363
+ click.confirm(f"Delete GameObject '{target}'?", abort=True)
364
+
365
+ params = {
366
+ "action": "delete",
367
+ "target": target,
368
+ }
369
+
370
+ if search_method:
371
+ params["searchMethod"] = search_method
372
+
373
+ try:
374
+ result = run_command("manage_gameobject", params, config)
375
+ click.echo(format_output(result, config.format))
376
+ if result.get("success"):
377
+ print_success(f"Deleted GameObject '{target}'")
378
+ except UnityConnectionError as e:
379
+ print_error(str(e))
380
+ sys.exit(1)
381
+
382
+
383
+ @gameobject.command("duplicate")
384
+ @click.argument("target")
385
+ @click.option(
386
+ "--name", "-n",
387
+ default=None,
388
+ help="Name for the duplicate (default: OriginalName_Copy)."
389
+ )
390
+ @click.option(
391
+ "--offset",
392
+ nargs=3,
393
+ type=float,
394
+ default=None,
395
+ help="Position offset from original as X Y Z."
396
+ )
397
+ @click.option(
398
+ "--search-method",
399
+ type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
400
+ default=None,
401
+ help="How to find the target GameObject."
402
+ )
403
+ def duplicate(
404
+ target: str,
405
+ name: Optional[str],
406
+ offset: Optional[Tuple[float, float, float]],
407
+ search_method: Optional[str],
408
+ ):
409
+ """Duplicate a GameObject.
410
+
411
+ \b
412
+ Examples:
413
+ unity-mcp gameobject duplicate "Player"
414
+ unity-mcp gameobject duplicate "Enemy" --name "Enemy2" --offset 5 0 0
415
+ unity-mcp gameobject duplicate "-81840" --search-method by_id
416
+ """
417
+ config = get_config()
418
+
419
+ params: dict[str, Any] = {
420
+ "action": "duplicate",
421
+ "target": target,
422
+ }
423
+
424
+ if name:
425
+ params["new_name"] = name
426
+ if offset:
427
+ params["offset"] = list(offset)
428
+ if search_method:
429
+ params["searchMethod"] = search_method
430
+
431
+ try:
432
+ result = run_command("manage_gameobject", params, config)
433
+ click.echo(format_output(result, config.format))
434
+ if result.get("success"):
435
+ print_success(f"Duplicated GameObject '{target}'")
436
+ except UnityConnectionError as e:
437
+ print_error(str(e))
438
+ sys.exit(1)
439
+
440
+
441
+ @gameobject.command("move")
442
+ @click.argument("target")
443
+ @click.option(
444
+ "--reference", "-r",
445
+ required=True,
446
+ help="Reference object for relative movement."
447
+ )
448
+ @click.option(
449
+ "--direction", "-d",
450
+ type=click.Choice(["left", "right", "up", "down", "forward",
451
+ "back", "front", "backward", "behind"]),
452
+ required=True,
453
+ help="Direction to move."
454
+ )
455
+ @click.option(
456
+ "--distance",
457
+ type=float,
458
+ default=1.0,
459
+ help="Distance to move (default: 1.0)."
460
+ )
461
+ @click.option(
462
+ "--local",
463
+ is_flag=True,
464
+ help="Use reference object's local space instead of world space."
465
+ )
466
+ @click.option(
467
+ "--search-method",
468
+ type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
469
+ default=None,
470
+ help="How to find the target GameObject."
471
+ )
472
+ def move(
473
+ target: str,
474
+ reference: str,
475
+ direction: str,
476
+ distance: float,
477
+ local: bool,
478
+ search_method: Optional[str],
479
+ ):
480
+ """Move a GameObject relative to another object.
481
+
482
+ \b
483
+ Examples:
484
+ unity-mcp gameobject move "Chair" --reference "Table" --direction right --distance 2
485
+ unity-mcp gameobject move "Light" --reference "Player" --direction up --distance 3
486
+ unity-mcp gameobject move "NPC" --reference "Player" --direction forward --local
487
+ """
488
+ config = get_config()
489
+
490
+ params: dict[str, Any] = {
491
+ "action": "move_relative",
492
+ "target": target,
493
+ "reference_object": reference,
494
+ "direction": direction,
495
+ "distance": distance,
496
+ "world_space": not local,
497
+ }
498
+
499
+ if search_method:
500
+ params["searchMethod"] = search_method
501
+
502
+ try:
503
+ result = run_command("manage_gameobject", params, config)
504
+ click.echo(format_output(result, config.format))
505
+ if result.get("success"):
506
+ print_success(
507
+ f"Moved '{target}' {direction} of '{reference}' by {distance} units")
508
+ except UnityConnectionError as e:
509
+ print_error(str(e))
510
+ sys.exit(1)
@@ -0,0 +1,101 @@
1
+ """Instance CLI commands for managing Unity instances."""
2
+
3
+ import sys
4
+ import click
5
+ from typing import Optional
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, run_list_instances, UnityConnectionError
10
+
11
+
12
+ @click.group()
13
+ def instance():
14
+ """Unity instance management - list, select, and view instances."""
15
+ pass
16
+
17
+
18
+ @instance.command("list")
19
+ def list_instances():
20
+ """List available Unity instances.
21
+
22
+ \\b
23
+ Examples:
24
+ unity-mcp instance list
25
+ """
26
+ config = get_config()
27
+
28
+ try:
29
+ result = run_list_instances(config)
30
+ instances = result.get("instances", []) if isinstance(
31
+ result, dict) else []
32
+
33
+ if not instances:
34
+ print_info("No Unity instances currently connected")
35
+ return
36
+
37
+ click.echo("Available Unity instances:")
38
+ for inst in instances:
39
+ project = inst.get("project", "Unknown")
40
+ version = inst.get("unity_version", "Unknown")
41
+ hash_id = inst.get("hash", "")
42
+ session_id = inst.get("session_id", "")
43
+
44
+ # Format: ProjectName@hash (Unity version)
45
+ display_id = f"{project}@{hash_id}" if hash_id else project
46
+ click.echo(f" • {display_id} (Unity {version})")
47
+ if session_id:
48
+ click.echo(f" Session: {session_id[:8]}...")
49
+
50
+ except UnityConnectionError as e:
51
+ print_error(str(e))
52
+ sys.exit(1)
53
+
54
+
55
+ @instance.command("set")
56
+ @click.argument("instance_id")
57
+ def set_instance(instance_id: str):
58
+ """Set the active Unity instance.
59
+
60
+ INSTANCE_ID can be Name@hash or just a hash prefix.
61
+
62
+ \\b
63
+ Examples:
64
+ unity-mcp instance set "MyProject@abc123"
65
+ unity-mcp instance set abc123
66
+ """
67
+ config = get_config()
68
+
69
+ try:
70
+ result = run_command("set_active_instance", {
71
+ "instance": instance_id,
72
+ }, config)
73
+ click.echo(format_output(result, config.format))
74
+ if result.get("success"):
75
+ data = result.get("data", {})
76
+ active = data.get("instance", instance_id)
77
+ print_success(f"Active instance set to: {active}")
78
+ except UnityConnectionError as e:
79
+ print_error(str(e))
80
+ sys.exit(1)
81
+
82
+
83
+ @instance.command("current")
84
+ def current_instance():
85
+ """Show the currently selected Unity instance.
86
+
87
+ \\b
88
+ Examples:
89
+ unity-mcp instance current
90
+ """
91
+ config = get_config()
92
+
93
+ # The current instance is typically shown in telemetry or needs to be tracked
94
+ # For now, we can show the configured instance from CLI options
95
+ if config.unity_instance:
96
+ click.echo(f"Configured instance: {config.unity_instance}")
97
+ else:
98
+ print_info(
99
+ "No instance explicitly set. Using default (auto-select single instance).")
100
+ print_info("Use 'unity-mcp instance list' to see available instances.")
101
+ print_info("Use 'unity-mcp instance set <id>' to select one.")