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