mcpforunityserver 9.3.0b20260128055651__py3-none-any.whl → 9.3.0b20260129121506__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 (61) hide show
  1. cli/commands/animation.py +6 -9
  2. cli/commands/asset.py +50 -80
  3. cli/commands/audio.py +14 -22
  4. cli/commands/batch.py +20 -33
  5. cli/commands/code.py +63 -70
  6. cli/commands/component.py +33 -55
  7. cli/commands/editor.py +122 -188
  8. cli/commands/gameobject.py +60 -83
  9. cli/commands/instance.py +28 -36
  10. cli/commands/lighting.py +54 -59
  11. cli/commands/material.py +39 -68
  12. cli/commands/prefab.py +63 -81
  13. cli/commands/scene.py +30 -54
  14. cli/commands/script.py +32 -50
  15. cli/commands/shader.py +43 -55
  16. cli/commands/texture.py +53 -51
  17. cli/commands/tool.py +24 -27
  18. cli/commands/ui.py +125 -130
  19. cli/commands/vfx.py +84 -138
  20. cli/utils/confirmation.py +37 -0
  21. cli/utils/connection.py +32 -2
  22. cli/utils/constants.py +23 -0
  23. cli/utils/parsers.py +112 -0
  24. core/config.py +0 -4
  25. core/telemetry.py +20 -2
  26. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/METADATA +21 -1
  27. mcpforunityserver-9.3.0b20260129121506.dist-info/RECORD +103 -0
  28. services/resources/active_tool.py +1 -1
  29. services/resources/custom_tools.py +1 -1
  30. services/resources/editor_state.py +1 -1
  31. services/resources/gameobject.py +4 -4
  32. services/resources/layers.py +1 -1
  33. services/resources/menu_items.py +1 -1
  34. services/resources/prefab.py +3 -3
  35. services/resources/prefab_stage.py +1 -1
  36. services/resources/project_info.py +1 -1
  37. services/resources/selection.py +1 -1
  38. services/resources/tags.py +1 -1
  39. services/resources/tests.py +40 -8
  40. services/resources/unity_instances.py +1 -1
  41. services/resources/windows.py +1 -1
  42. services/tools/__init__.py +3 -1
  43. services/tools/find_gameobjects.py +32 -11
  44. services/tools/manage_gameobject.py +11 -66
  45. services/tools/manage_material.py +4 -37
  46. services/tools/manage_prefabs.py +51 -7
  47. services/tools/manage_script.py +1 -1
  48. services/tools/manage_texture.py +10 -96
  49. services/tools/run_tests.py +67 -4
  50. services/tools/utils.py +217 -0
  51. transport/models.py +1 -0
  52. transport/plugin_hub.py +2 -1
  53. transport/plugin_registry.py +3 -0
  54. transport/unity_transport.py +0 -51
  55. utils/focus_nudge.py +291 -23
  56. mcpforunityserver-9.3.0b20260128055651.dist-info/RECORD +0 -101
  57. utils/reload_sentinel.py +0 -9
  58. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/WHEEL +0 -0
  59. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/entry_points.txt +0 -0
  60. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/licenses/LICENSE +0 -0
  61. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,9 @@ from typing import Optional, Tuple, Any
7
7
 
8
8
  from cli.utils.config import get_config
9
9
  from cli.utils.output import format_output, print_error, print_success, print_warning
10
- from cli.utils.connection import run_command, UnityConnectionError
10
+ from cli.utils.connection import run_command, handle_unity_errors, UnityConnectionError
11
+ from cli.utils.constants import SEARCH_METHOD_CHOICE_FULL, SEARCH_METHOD_CHOICE_TAGGED
12
+ from cli.utils.confirmation import confirm_destructive_action
11
13
 
12
14
 
13
15
  @click.group()
@@ -20,8 +22,7 @@ def gameobject():
20
22
  @click.argument("search_term")
21
23
  @click.option(
22
24
  "--method", "-m",
23
- type=click.Choice(["by_name", "by_tag", "by_layer",
24
- "by_component", "by_path", "by_id"]),
25
+ type=SEARCH_METHOD_CHOICE_FULL,
25
26
  default="by_name",
26
27
  help="Search method."
27
28
  )
@@ -42,6 +43,7 @@ def gameobject():
42
43
  type=int,
43
44
  help="Pagination cursor (offset)."
44
45
  )
46
+ @handle_unity_errors
45
47
  def find(search_term: str, method: str, include_inactive: bool, limit: int, cursor: int):
46
48
  """Find GameObjects by search criteria.
47
49
 
@@ -54,19 +56,14 @@ def find(search_term: str, method: str, include_inactive: bool, limit: int, curs
54
56
  unity-mcp gameobject find "/Canvas/Panel" --method by_path
55
57
  """
56
58
  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)
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))
70
67
 
71
68
 
72
69
  @gameobject.command("create")
@@ -128,6 +125,7 @@ def find(search_term: str, method: str, include_inactive: bool, limit: int, curs
128
125
  default=None,
129
126
  help="Path for prefab (e.g., Assets/Prefabs/MyPrefab.prefab)."
130
127
  )
128
+ @handle_unity_errors
131
129
  def create(
132
130
  name: str,
133
131
  primitive: Optional[str],
@@ -177,32 +175,27 @@ def create(
177
175
  if prefab_path:
178
176
  params["prefabPath"] = prefab_path
179
177
 
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)
178
+ result = run_command("manage_gameobject", params, config)
179
+
180
+ # Add components separately since componentsToAdd doesn't work
181
+ if components and (result.get("success") or result.get("data") or result.get("result")):
182
+ component_list = [c.strip() for c in components.split(",")]
183
+ failed_components = []
184
+ for component in component_list:
185
+ try:
186
+ run_command("manage_components", {
187
+ "action": "add",
188
+ "target": name,
189
+ "componentType": component,
190
+ }, config)
191
+ except UnityConnectionError:
192
+ failed_components.append(component)
193
+ if failed_components:
194
+ print_warning(f"Failed to add components: {', '.join(failed_components)}")
195
+
196
+ click.echo(format_output(result, config.format))
197
+ if result.get("success") or result.get("result"):
198
+ print_success(f"Created GameObject '{name}'")
206
199
 
207
200
 
208
201
  @gameobject.command("modify")
@@ -265,10 +258,11 @@ def create(
265
258
  )
266
259
  @click.option(
267
260
  "--search-method",
268
- type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
261
+ type=SEARCH_METHOD_CHOICE_TAGGED,
269
262
  default=None,
270
263
  help="How to find the target GameObject."
271
264
  )
265
+ @handle_unity_errors
272
266
  def modify(
273
267
  target: str,
274
268
  name: Optional[str],
@@ -319,27 +313,21 @@ def modify(
319
313
  if active is not None:
320
314
  params["setActive"] = active
321
315
  if add_components:
322
- params["componentsToAdd"] = [c.strip()
323
- for c in add_components.split(",")]
316
+ params["componentsToAdd"] = [c.strip() for c in add_components.split(",")]
324
317
  if remove_components:
325
- params["componentsToRemove"] = [c.strip()
326
- for c in remove_components.split(",")]
318
+ params["componentsToRemove"] = [c.strip() for c in remove_components.split(",")]
327
319
  if search_method:
328
320
  params["searchMethod"] = search_method
329
321
 
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)
322
+ result = run_command("manage_gameobject", params, config)
323
+ click.echo(format_output(result, config.format))
336
324
 
337
325
 
338
326
  @gameobject.command("delete")
339
327
  @click.argument("target")
340
328
  @click.option(
341
329
  "--search-method",
342
- type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
330
+ type=SEARCH_METHOD_CHOICE_TAGGED,
343
331
  default=None,
344
332
  help="How to find the target GameObject."
345
333
  )
@@ -348,6 +336,7 @@ def modify(
348
336
  is_flag=True,
349
337
  help="Skip confirmation prompt."
350
338
  )
339
+ @handle_unity_errors
351
340
  def delete(target: str, search_method: Optional[str], force: bool):
352
341
  """Delete a GameObject.
353
342
 
@@ -359,8 +348,7 @@ def delete(target: str, search_method: Optional[str], force: bool):
359
348
  """
360
349
  config = get_config()
361
350
 
362
- if not force:
363
- click.confirm(f"Delete GameObject '{target}'?", abort=True)
351
+ confirm_destructive_action("Delete", "GameObject", target, force)
364
352
 
365
353
  params = {
366
354
  "action": "delete",
@@ -370,14 +358,10 @@ def delete(target: str, search_method: Optional[str], force: bool):
370
358
  if search_method:
371
359
  params["searchMethod"] = search_method
372
360
 
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)
361
+ result = run_command("manage_gameobject", params, config)
362
+ click.echo(format_output(result, config.format))
363
+ if result.get("success"):
364
+ print_success(f"Deleted GameObject '{target}'")
381
365
 
382
366
 
383
367
  @gameobject.command("duplicate")
@@ -396,10 +380,11 @@ def delete(target: str, search_method: Optional[str], force: bool):
396
380
  )
397
381
  @click.option(
398
382
  "--search-method",
399
- type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
383
+ type=SEARCH_METHOD_CHOICE_TAGGED,
400
384
  default=None,
401
385
  help="How to find the target GameObject."
402
386
  )
387
+ @handle_unity_errors
403
388
  def duplicate(
404
389
  target: str,
405
390
  name: Optional[str],
@@ -428,14 +413,10 @@ def duplicate(
428
413
  if search_method:
429
414
  params["searchMethod"] = search_method
430
415
 
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)
416
+ result = run_command("manage_gameobject", params, config)
417
+ click.echo(format_output(result, config.format))
418
+ if result.get("success"):
419
+ print_success(f"Duplicated GameObject '{target}'")
439
420
 
440
421
 
441
422
  @gameobject.command("move")
@@ -465,10 +446,11 @@ def duplicate(
465
446
  )
466
447
  @click.option(
467
448
  "--search-method",
468
- type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
449
+ type=SEARCH_METHOD_CHOICE_TAGGED,
469
450
  default=None,
470
451
  help="How to find the target GameObject."
471
452
  )
453
+ @handle_unity_errors
472
454
  def move(
473
455
  target: str,
474
456
  reference: str,
@@ -499,12 +481,7 @@ def move(
499
481
  if search_method:
500
482
  params["searchMethod"] = search_method
501
483
 
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)
484
+ result = run_command("manage_gameobject", params, config)
485
+ click.echo(format_output(result, config.format))
486
+ if result.get("success"):
487
+ print_success(f"Moved '{target}' {direction} of '{reference}' by {distance} units")
cli/commands/instance.py CHANGED
@@ -1,12 +1,11 @@
1
1
  """Instance CLI commands for managing Unity instances."""
2
2
 
3
- import sys
4
3
  import click
5
4
  from typing import Optional
6
5
 
7
6
  from cli.utils.config import get_config
8
7
  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
8
+ from cli.utils.connection import run_command, run_list_instances, handle_unity_errors
10
9
 
11
10
 
12
11
  @click.group()
@@ -16,6 +15,7 @@ def instance():
16
15
 
17
16
 
18
17
  @instance.command("list")
18
+ @handle_unity_errors
19
19
  def list_instances():
20
20
  """List available Unity instances.
21
21
 
@@ -25,35 +25,31 @@ def list_instances():
25
25
  """
26
26
  config = get_config()
27
27
 
28
- try:
29
- result = run_list_instances(config)
30
- instances = result.get("instances", []) if isinstance(
31
- result, dict) else []
28
+ result = run_list_instances(config)
29
+ instances = result.get("instances", []) if isinstance(
30
+ result, dict) else []
32
31
 
33
- if not instances:
34
- print_info("No Unity instances currently connected")
35
- return
32
+ if not instances:
33
+ print_info("No Unity instances currently connected")
34
+ return
36
35
 
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", "")
36
+ click.echo("Available Unity instances:")
37
+ for inst in instances:
38
+ project = inst.get("project", "Unknown")
39
+ version = inst.get("unity_version", "Unknown")
40
+ hash_id = inst.get("hash", "")
41
+ session_id = inst.get("session_id", "")
43
42
 
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)
43
+ # Format: ProjectName@hash (Unity version)
44
+ display_id = f"{project}@{hash_id}" if hash_id else project
45
+ click.echo(f" • {display_id} (Unity {version})")
46
+ if session_id:
47
+ click.echo(f" Session: {session_id[:8]}...")
53
48
 
54
49
 
55
50
  @instance.command("set")
56
51
  @click.argument("instance_id")
52
+ @handle_unity_errors
57
53
  def set_instance(instance_id: str):
58
54
  """Set the active Unity instance.
59
55
 
@@ -66,18 +62,14 @@ def set_instance(instance_id: str):
66
62
  """
67
63
  config = get_config()
68
64
 
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)
65
+ result = run_command("set_active_instance", {
66
+ "instance": instance_id,
67
+ }, config)
68
+ click.echo(format_output(result, config.format))
69
+ if result.get("success"):
70
+ data = result.get("data", {})
71
+ active = data.get("instance", instance_id)
72
+ print_success(f"Active instance set to: {active}")
81
73
 
82
74
 
83
75
  @instance.command("current")
cli/commands/lighting.py CHANGED
@@ -1,12 +1,11 @@
1
1
  """Lighting CLI commands."""
2
2
 
3
- import sys
4
3
  import click
5
4
  from typing import Optional, Tuple
6
5
 
7
6
  from cli.utils.config import get_config
8
7
  from cli.utils.output import format_output, print_error, print_success
9
- from cli.utils.connection import run_command, UnityConnectionError
8
+ from cli.utils.connection import run_command, handle_unity_errors
10
9
 
11
10
 
12
11
  @click.group()
@@ -44,6 +43,7 @@ def lighting():
44
43
  type=float,
45
44
  help="Light intensity."
46
45
  )
46
+ @handle_unity_errors
47
47
  def create(name: str, light_type: str, position: Tuple[float, float, float], color: Optional[Tuple[float, float, float]], intensity: Optional[float]):
48
48
  """Create a new light.
49
49
 
@@ -55,74 +55,69 @@ def create(name: str, light_type: str, position: Tuple[float, float, float], col
55
55
  """
56
56
  config = get_config()
57
57
 
58
- try:
59
- # Step 1: Create empty GameObject with position
60
- create_result = run_command("manage_gameobject", {
61
- "action": "create",
62
- "name": name,
63
- "position": list(position),
64
- }, config)
65
-
66
- if not (create_result.get("success")):
67
- click.echo(format_output(create_result, config.format))
68
- return
58
+ # Step 1: Create empty GameObject with position
59
+ create_result = run_command("manage_gameobject", {
60
+ "action": "create",
61
+ "name": name,
62
+ "position": list(position),
63
+ }, config)
69
64
 
70
- # Step 2: Add Light component using manage_components
71
- add_result = run_command("manage_components", {
72
- "action": "add",
65
+ if not (create_result.get("success")):
66
+ click.echo(format_output(create_result, config.format))
67
+ return
68
+
69
+ # Step 2: Add Light component using manage_components
70
+ add_result = run_command("manage_components", {
71
+ "action": "add",
72
+ "target": name,
73
+ "componentType": "Light",
74
+ }, config)
75
+
76
+ if not add_result.get("success"):
77
+ click.echo(format_output(add_result, config.format))
78
+ return
79
+
80
+ # Step 3: Set light type using manage_components set_property
81
+ type_result = run_command("manage_components", {
82
+ "action": "set_property",
83
+ "target": name,
84
+ "componentType": "Light",
85
+ "property": "type",
86
+ "value": light_type,
87
+ }, config)
88
+
89
+ if not type_result.get("success"):
90
+ click.echo(format_output(type_result, config.format))
91
+ return
92
+
93
+ # Step 4: Set color if provided
94
+ if color:
95
+ color_result = run_command("manage_components", {
96
+ "action": "set_property",
73
97
  "target": name,
74
98
  "componentType": "Light",
99
+ "property": "color",
100
+ "value": {"r": color[0], "g": color[1], "b": color[2], "a": 1},
75
101
  }, config)
76
102
 
77
- if not add_result.get("success"):
78
- click.echo(format_output(add_result, config.format))
103
+ if not color_result.get("success"):
104
+ click.echo(format_output(color_result, config.format))
79
105
  return
80
106
 
81
- # Step 3: Set light type using manage_components set_property
82
- type_result = run_command("manage_components", {
107
+ # Step 5: Set intensity if provided
108
+ if intensity is not None:
109
+ intensity_result = run_command("manage_components", {
83
110
  "action": "set_property",
84
111
  "target": name,
85
112
  "componentType": "Light",
86
- "property": "type",
87
- "value": light_type,
113
+ "property": "intensity",
114
+ "value": intensity,
88
115
  }, config)
89
116
 
90
- if not type_result.get("success"):
91
- click.echo(format_output(type_result, config.format))
117
+ if not intensity_result.get("success"):
118
+ click.echo(format_output(intensity_result, config.format))
92
119
  return
93
120
 
94
- # Step 4: Set color if provided
95
- if color:
96
- color_result = run_command("manage_components", {
97
- "action": "set_property",
98
- "target": name,
99
- "componentType": "Light",
100
- "property": "color",
101
- "value": {"r": color[0], "g": color[1], "b": color[2], "a": 1},
102
- }, config)
103
-
104
- if not color_result.get("success"):
105
- click.echo(format_output(color_result, config.format))
106
- return
107
-
108
- # Step 5: Set intensity if provided
109
- if intensity is not None:
110
- intensity_result = run_command("manage_components", {
111
- "action": "set_property",
112
- "target": name,
113
- "componentType": "Light",
114
- "property": "intensity",
115
- "value": intensity,
116
- }, config)
117
-
118
- if not intensity_result.get("success"):
119
- click.echo(format_output(intensity_result, config.format))
120
- return
121
-
122
- # Output the result
123
- click.echo(format_output(create_result, config.format))
124
- print_success(f"Created {light_type} light: {name}")
125
-
126
- except UnityConnectionError as e:
127
- print_error(str(e))
128
- sys.exit(1)
121
+ # Output the result
122
+ click.echo(format_output(create_result, config.format))
123
+ print_success(f"Created {light_type} light: {name}")