mcpforunityserver 9.2.0__py3-none-any.whl → 9.3.0b20260128055651__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.
cli/commands/editor.py CHANGED
@@ -6,7 +6,8 @@ from typing import Optional, Any
6
6
 
7
7
  from cli.utils.config import get_config
8
8
  from cli.utils.output import format_output, print_error, print_success, print_info
9
- from cli.utils.connection import run_command, UnityConnectionError
9
+ from cli.utils.connection import run_command, run_list_custom_tools, UnityConnectionError
10
+ from cli.utils.suggestions import suggest_matches, format_suggestions
10
11
 
11
12
 
12
13
  @click.group()
@@ -472,6 +473,9 @@ def custom_tool(tool_name: str, params: str):
472
473
  params_dict = json.loads(params)
473
474
  except json.JSONDecodeError as e:
474
475
  print_error(f"Invalid JSON for params: {e}")
476
+ print_info("Example: --params '{\"key\":\"value\"}'")
477
+ print_info(
478
+ "Tip: wrap JSON in single quotes to avoid shell escaping issues.")
475
479
  sys.exit(1)
476
480
 
477
481
  try:
@@ -482,6 +486,28 @@ def custom_tool(tool_name: str, params: str):
482
486
  click.echo(format_output(result, config.format))
483
487
  if result.get("success"):
484
488
  print_success(f"Executed custom tool: {tool_name}")
489
+ else:
490
+ message = (result.get("message")
491
+ or result.get("error") or "").lower()
492
+ if "not found" in message and "tool" in message:
493
+ try:
494
+ tools_result = run_list_custom_tools(config)
495
+ tools = tools_result.get("tools")
496
+ if tools is None:
497
+ data = tools_result.get("data", {})
498
+ tools = data.get("tools") if isinstance(
499
+ data, dict) else None
500
+ names = [
501
+ t.get("name") for t in tools if isinstance(t, dict) and t.get("name")
502
+ ] if isinstance(tools, list) else []
503
+ matches = suggest_matches(tool_name, names)
504
+ suggestion = format_suggestions(matches)
505
+ if suggestion:
506
+ print_info(suggestion)
507
+ print_info(
508
+ f'Example: unity-mcp editor custom-tool "{matches[0]}"')
509
+ except UnityConnectionError:
510
+ pass
485
511
  except UnityConnectionError as e:
486
512
  print_error(str(e))
487
513
  sys.exit(1)
cli/commands/prefab.py CHANGED
@@ -11,18 +11,13 @@ from cli.utils.connection import run_command, UnityConnectionError
11
11
 
12
12
  @click.group()
13
13
  def prefab():
14
- """Prefab operations - open, save, create prefabs."""
14
+ """Prefab operations - info, hierarchy, open, save, close, create prefabs."""
15
15
  pass
16
16
 
17
17
 
18
18
  @prefab.command("open")
19
19
  @click.argument("path")
20
- @click.option(
21
- "--mode", "-m",
22
- default="InIsolation",
23
- help="Prefab stage mode (InIsolation)."
24
- )
25
- def open_stage(path: str, mode: str):
20
+ def open_stage(path: str):
26
21
  """Open a prefab in the prefab stage for editing.
27
22
 
28
23
  \b
@@ -34,7 +29,6 @@ def open_stage(path: str, mode: str):
34
29
  params: dict[str, Any] = {
35
30
  "action": "open_stage",
36
31
  "prefabPath": path,
37
- "mode": mode,
38
32
  }
39
33
 
40
34
  try:
@@ -80,18 +74,29 @@ def close_stage(save: bool):
80
74
 
81
75
 
82
76
  @prefab.command("save")
83
- def save_stage():
77
+ @click.option(
78
+ "--force", "-f",
79
+ is_flag=True,
80
+ help="Force save even if no changes detected. Useful for automated workflows."
81
+ )
82
+ def save_stage(force: bool):
84
83
  """Save the currently open prefab stage.
85
84
 
86
85
  \b
87
86
  Examples:
88
87
  unity-mcp prefab save
88
+ unity-mcp prefab save --force
89
89
  """
90
90
  config = get_config()
91
91
 
92
+ params: dict[str, Any] = {
93
+ "action": "save_open_stage",
94
+ }
95
+ if force:
96
+ params["force"] = True
97
+
92
98
  try:
93
- result = run_command("manage_prefabs", {
94
- "action": "save_open_stage"}, config)
99
+ result = run_command("manage_prefabs", params, config)
95
100
  click.echo(format_output(result, config.format))
96
101
  if result.get("success"):
97
102
  print_success("Saved prefab")
@@ -100,6 +105,115 @@ def save_stage():
100
105
  sys.exit(1)
101
106
 
102
107
 
108
+ @prefab.command("info")
109
+ @click.argument("path")
110
+ @click.option(
111
+ "--compact", "-c",
112
+ is_flag=True,
113
+ help="Show compact output (key values only)."
114
+ )
115
+ def info(path: str, compact: bool):
116
+ """Get information about a prefab asset.
117
+
118
+ \b
119
+ Examples:
120
+ unity-mcp prefab info "Assets/Prefabs/Player.prefab"
121
+ unity-mcp prefab info "Assets/Prefabs/UI.prefab" --compact
122
+ """
123
+ config = get_config()
124
+
125
+ params: dict[str, Any] = {
126
+ "action": "get_info",
127
+ "prefabPath": path,
128
+ }
129
+
130
+ try:
131
+ result = run_command("manage_prefabs", params, config)
132
+ # Get the actual response data from the wrapped result structure
133
+ response_data = result.get("result", result)
134
+ if compact and response_data.get("success") and response_data.get("data"):
135
+ data = response_data["data"]
136
+ click.echo(f"Prefab: {data.get('assetPath', path)}")
137
+ click.echo(f" Type: {data.get('prefabType', 'Unknown')}")
138
+ click.echo(f" Root: {data.get('rootObjectName', 'N/A')}")
139
+ click.echo(f" GUID: {data.get('guid', 'N/A')}")
140
+ click.echo(
141
+ f" Components: {len(data.get('rootComponentTypes', []))}")
142
+ click.echo(f" Children: {data.get('childCount', 0)}")
143
+ if data.get('isVariant'):
144
+ click.echo(f" Variant of: {data.get('parentPrefab', 'N/A')}")
145
+ else:
146
+ click.echo(format_output(result, config.format))
147
+ except UnityConnectionError as e:
148
+ print_error(str(e))
149
+ sys.exit(1)
150
+
151
+
152
+ @prefab.command("hierarchy")
153
+ @click.argument("path")
154
+ @click.option(
155
+ "--compact", "-c",
156
+ is_flag=True,
157
+ help="Show compact output (names and paths only)."
158
+ )
159
+ @click.option(
160
+ "--show-prefab-info", "-p",
161
+ is_flag=True,
162
+ help="Show prefab nesting information."
163
+ )
164
+ def hierarchy(path: str, compact: bool, show_prefab_info: bool):
165
+ """Get the hierarchical structure of a prefab.
166
+
167
+ \b
168
+ Examples:
169
+ unity-mcp prefab hierarchy "Assets/Prefabs/Player.prefab"
170
+ unity-mcp prefab hierarchy "Assets/Prefabs/UI.prefab" --compact
171
+ unity-mcp prefab hierarchy "Assets/Prefabs/Complex.prefab" --show-prefab-info
172
+ """
173
+ config = get_config()
174
+
175
+ params: dict[str, Any] = {
176
+ "action": "get_hierarchy",
177
+ "prefabPath": path,
178
+ }
179
+
180
+ try:
181
+ result = run_command("manage_prefabs", params, config)
182
+ # Get the actual response data from the wrapped result structure
183
+ response_data = result.get("result", result)
184
+ if compact and response_data.get("success") and response_data.get("data"):
185
+ data = response_data["data"]
186
+ items = data.get("items", [])
187
+ for item in items:
188
+ indent = " " * item.get("path", "").count("/")
189
+ prefab_info = ""
190
+ if show_prefab_info and item.get("prefab", {}).get("isNestedRoot"):
191
+ prefab_info = f" [nested: {item['prefab']['assetPath']}]"
192
+ click.echo(f"{indent}{item.get('name')}{prefab_info}")
193
+ click.echo(f"\nTotal: {data.get('total', 0)} objects")
194
+ elif show_prefab_info:
195
+ # Show prefab info in readable format
196
+ if response_data.get("success") and response_data.get("data"):
197
+ data = response_data["data"]
198
+ items = data.get("items", [])
199
+ for item in items:
200
+ prefab = item.get("prefab", {})
201
+ prefab_info = ""
202
+ if prefab.get("isRoot"):
203
+ prefab_info = " [root]"
204
+ elif prefab.get("isNestedRoot"):
205
+ prefab_info = f" [nested: {prefab.get('nestingDepth', 0)}]"
206
+ click.echo(f"{item.get('path')}{prefab_info}")
207
+ click.echo(f"\nTotal: {data.get('total', 0)} objects")
208
+ else:
209
+ click.echo(format_output(result, config.format))
210
+ else:
211
+ click.echo(format_output(result, config.format))
212
+ except UnityConnectionError as e:
213
+ print_error(str(e))
214
+ sys.exit(1)
215
+
216
+
103
217
  @prefab.command("create")
104
218
  @click.argument("target")
105
219
  @click.argument("path")
@@ -113,13 +227,19 @@ def save_stage():
113
227
  is_flag=True,
114
228
  help="Include inactive objects when finding target."
115
229
  )
116
- def create(target: str, path: str, overwrite: bool, include_inactive: bool):
230
+ @click.option(
231
+ "--unlink-if-instance",
232
+ is_flag=True,
233
+ help="Unlink from existing prefab before creating new one."
234
+ )
235
+ def create(target: str, path: str, overwrite: bool, include_inactive: bool, unlink_if_instance: bool):
117
236
  """Create a prefab from a scene GameObject.
118
237
 
119
238
  \b
120
239
  Examples:
121
240
  unity-mcp prefab create "Player" "Assets/Prefabs/Player.prefab"
122
241
  unity-mcp prefab create "Enemy" "Assets/Prefabs/Enemy.prefab" --overwrite
242
+ unity-mcp prefab create "EnemyInstance" "Assets/Prefabs/BossEnemy.prefab" --unlink-if-instance
123
243
  """
124
244
  config = get_config()
125
245
 
@@ -133,6 +253,8 @@ def create(target: str, path: str, overwrite: bool, include_inactive: bool):
133
253
  params["allowOverwrite"] = True
134
254
  if include_inactive:
135
255
  params["searchInactive"] = True
256
+ if unlink_if_instance:
257
+ params["unlinkIfInstance"] = True
136
258
 
137
259
  try:
138
260
  result = run_command("manage_prefabs", params, config)