mcpforunityserver 8.7.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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +3 -0
- cli/commands/animation.py +87 -0
- cli/commands/asset.py +310 -0
- cli/commands/audio.py +133 -0
- cli/commands/batch.py +184 -0
- cli/commands/code.py +189 -0
- cli/commands/component.py +212 -0
- cli/commands/editor.py +487 -0
- cli/commands/gameobject.py +510 -0
- cli/commands/instance.py +101 -0
- cli/commands/lighting.py +128 -0
- cli/commands/material.py +268 -0
- cli/commands/prefab.py +144 -0
- cli/commands/scene.py +255 -0
- cli/commands/script.py +240 -0
- cli/commands/shader.py +238 -0
- cli/commands/ui.py +263 -0
- cli/commands/vfx.py +439 -0
- cli/main.py +248 -0
- cli/utils/__init__.py +31 -0
- cli/utils/config.py +58 -0
- cli/utils/connection.py +191 -0
- cli/utils/output.py +195 -0
- main.py +177 -62
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/METADATA +4 -2
- mcpforunityserver-9.1.0.dist-info/RECORD +96 -0
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/WHEEL +1 -1
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/entry_points.txt +1 -0
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/top_level.txt +1 -2
- services/custom_tool_service.py +179 -19
- services/resources/__init__.py +6 -1
- services/resources/active_tool.py +1 -1
- services/resources/custom_tools.py +2 -2
- services/resources/editor_state.py +283 -30
- services/resources/gameobject.py +243 -0
- services/resources/layers.py +1 -1
- services/resources/prefab_stage.py +1 -1
- services/resources/project_info.py +1 -1
- services/resources/selection.py +1 -1
- services/resources/tags.py +1 -1
- services/resources/unity_instances.py +1 -1
- services/resources/windows.py +1 -1
- services/state/external_changes_scanner.py +3 -4
- services/tools/__init__.py +6 -1
- services/tools/batch_execute.py +24 -9
- services/tools/debug_request_context.py +8 -2
- services/tools/execute_custom_tool.py +6 -1
- services/tools/execute_menu_item.py +6 -3
- services/tools/find_gameobjects.py +89 -0
- services/tools/find_in_file.py +26 -19
- services/tools/manage_asset.py +13 -44
- services/tools/manage_components.py +131 -0
- services/tools/manage_editor.py +9 -8
- services/tools/manage_gameobject.py +115 -79
- services/tools/manage_material.py +80 -31
- services/tools/manage_prefabs.py +7 -1
- services/tools/manage_scene.py +30 -13
- services/tools/manage_script.py +62 -19
- services/tools/manage_scriptable_object.py +22 -10
- services/tools/manage_shader.py +8 -1
- services/tools/manage_vfx.py +738 -0
- services/tools/preflight.py +15 -12
- services/tools/read_console.py +70 -17
- services/tools/refresh_unity.py +92 -29
- services/tools/run_tests.py +187 -53
- services/tools/script_apply_edits.py +15 -7
- services/tools/set_active_instance.py +12 -7
- services/tools/utils.py +60 -6
- transport/legacy/port_discovery.py +2 -2
- transport/legacy/unity_connection.py +129 -26
- transport/plugin_hub.py +85 -24
- transport/unity_instance_middleware.py +4 -3
- transport/unity_transport.py +2 -1
- utils/focus_nudge.py +321 -0
- __init__.py +0 -0
- mcpforunityserver-8.7.0.dist-info/RECORD +0 -71
- routes/__init__.py +0 -0
- services/resources/editor_state_v2.py +0 -270
- services/tools/test_jobs.py +0 -94
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/licenses/LICENSE +0 -0
cli/commands/vfx.py
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""VFX CLI commands for managing Unity visual effects."""
|
|
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
|
|
10
|
+
from cli.utils.connection import run_command, UnityConnectionError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def vfx():
|
|
15
|
+
"""VFX operations - particle systems, line renderers, trails."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# Particle System Commands
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
@vfx.group()
|
|
24
|
+
def particle():
|
|
25
|
+
"""Particle system operations."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@particle.command("info")
|
|
30
|
+
@click.argument("target")
|
|
31
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
32
|
+
def particle_info(target: str, search_method: Optional[str]):
|
|
33
|
+
"""Get particle system info.
|
|
34
|
+
|
|
35
|
+
\\b
|
|
36
|
+
Examples:
|
|
37
|
+
unity-mcp vfx particle info "Fire"
|
|
38
|
+
unity-mcp vfx particle info "-12345" --search-method by_id
|
|
39
|
+
"""
|
|
40
|
+
config = get_config()
|
|
41
|
+
params: dict[str, Any] = {"action": "particle_get_info", "target": target}
|
|
42
|
+
if search_method:
|
|
43
|
+
params["searchMethod"] = search_method
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
result = run_command("manage_vfx", params, config)
|
|
47
|
+
click.echo(format_output(result, config.format))
|
|
48
|
+
except UnityConnectionError as e:
|
|
49
|
+
print_error(str(e))
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@particle.command("play")
|
|
54
|
+
@click.argument("target")
|
|
55
|
+
@click.option("--with-children", is_flag=True, help="Also play child particle systems.")
|
|
56
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
57
|
+
def particle_play(target: str, with_children: bool, search_method: Optional[str]):
|
|
58
|
+
"""Play a particle system.
|
|
59
|
+
|
|
60
|
+
\\b
|
|
61
|
+
Examples:
|
|
62
|
+
unity-mcp vfx particle play "Fire"
|
|
63
|
+
unity-mcp vfx particle play "Effects" --with-children
|
|
64
|
+
"""
|
|
65
|
+
config = get_config()
|
|
66
|
+
params: dict[str, Any] = {"action": "particle_play", "target": target}
|
|
67
|
+
if with_children:
|
|
68
|
+
params["withChildren"] = True
|
|
69
|
+
if search_method:
|
|
70
|
+
params["searchMethod"] = search_method
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
result = run_command("manage_vfx", params, config)
|
|
74
|
+
click.echo(format_output(result, config.format))
|
|
75
|
+
if result.get("success"):
|
|
76
|
+
print_success(f"Playing particle system: {target}")
|
|
77
|
+
except UnityConnectionError as e:
|
|
78
|
+
print_error(str(e))
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@particle.command("stop")
|
|
83
|
+
@click.argument("target")
|
|
84
|
+
@click.option("--with-children", is_flag=True, help="Also stop child particle systems.")
|
|
85
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
86
|
+
def particle_stop(target: str, with_children: bool, search_method: Optional[str]):
|
|
87
|
+
"""Stop a particle system."""
|
|
88
|
+
config = get_config()
|
|
89
|
+
params: dict[str, Any] = {"action": "particle_stop", "target": target}
|
|
90
|
+
if with_children:
|
|
91
|
+
params["withChildren"] = True
|
|
92
|
+
if search_method:
|
|
93
|
+
params["searchMethod"] = search_method
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
result = run_command("manage_vfx", params, config)
|
|
97
|
+
click.echo(format_output(result, config.format))
|
|
98
|
+
if result.get("success"):
|
|
99
|
+
print_success(f"Stopped particle system: {target}")
|
|
100
|
+
except UnityConnectionError as e:
|
|
101
|
+
print_error(str(e))
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@particle.command("pause")
|
|
106
|
+
@click.argument("target")
|
|
107
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
108
|
+
def particle_pause(target: str, search_method: Optional[str]):
|
|
109
|
+
"""Pause a particle system."""
|
|
110
|
+
config = get_config()
|
|
111
|
+
params: dict[str, Any] = {"action": "particle_pause", "target": target}
|
|
112
|
+
if search_method:
|
|
113
|
+
params["searchMethod"] = search_method
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
result = run_command("manage_vfx", params, config)
|
|
117
|
+
click.echo(format_output(result, config.format))
|
|
118
|
+
except UnityConnectionError as e:
|
|
119
|
+
print_error(str(e))
|
|
120
|
+
sys.exit(1)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@particle.command("restart")
|
|
124
|
+
@click.argument("target")
|
|
125
|
+
@click.option("--with-children", is_flag=True)
|
|
126
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
127
|
+
def particle_restart(target: str, with_children: bool, search_method: Optional[str]):
|
|
128
|
+
"""Restart a particle system."""
|
|
129
|
+
config = get_config()
|
|
130
|
+
params: dict[str, Any] = {"action": "particle_restart", "target": target}
|
|
131
|
+
if with_children:
|
|
132
|
+
params["withChildren"] = True
|
|
133
|
+
if search_method:
|
|
134
|
+
params["searchMethod"] = search_method
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
result = run_command("manage_vfx", params, config)
|
|
138
|
+
click.echo(format_output(result, config.format))
|
|
139
|
+
except UnityConnectionError as e:
|
|
140
|
+
print_error(str(e))
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@particle.command("clear")
|
|
145
|
+
@click.argument("target")
|
|
146
|
+
@click.option("--with-children", is_flag=True)
|
|
147
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
148
|
+
def particle_clear(target: str, with_children: bool, search_method: Optional[str]):
|
|
149
|
+
"""Clear all particles from a particle system."""
|
|
150
|
+
config = get_config()
|
|
151
|
+
params: dict[str, Any] = {"action": "particle_clear", "target": target}
|
|
152
|
+
if with_children:
|
|
153
|
+
params["withChildren"] = True
|
|
154
|
+
if search_method:
|
|
155
|
+
params["searchMethod"] = search_method
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
result = run_command("manage_vfx", params, config)
|
|
159
|
+
click.echo(format_output(result, config.format))
|
|
160
|
+
except UnityConnectionError as e:
|
|
161
|
+
print_error(str(e))
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# =============================================================================
|
|
166
|
+
# Line Renderer Commands
|
|
167
|
+
# =============================================================================
|
|
168
|
+
|
|
169
|
+
@vfx.group()
|
|
170
|
+
def line():
|
|
171
|
+
"""Line renderer operations."""
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@line.command("info")
|
|
176
|
+
@click.argument("target")
|
|
177
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
178
|
+
def line_info(target: str, search_method: Optional[str]):
|
|
179
|
+
"""Get line renderer info.
|
|
180
|
+
|
|
181
|
+
\\b
|
|
182
|
+
Examples:
|
|
183
|
+
unity-mcp vfx line info "LaserBeam"
|
|
184
|
+
"""
|
|
185
|
+
config = get_config()
|
|
186
|
+
params: dict[str, Any] = {"action": "line_get_info", "target": target}
|
|
187
|
+
if search_method:
|
|
188
|
+
params["searchMethod"] = search_method
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
result = run_command("manage_vfx", params, config)
|
|
192
|
+
click.echo(format_output(result, config.format))
|
|
193
|
+
except UnityConnectionError as e:
|
|
194
|
+
print_error(str(e))
|
|
195
|
+
sys.exit(1)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@line.command("set-positions")
|
|
199
|
+
@click.argument("target")
|
|
200
|
+
@click.option("--positions", "-p", required=True, help='Positions as JSON array: [[0,0,0], [1,1,1], [2,0,0]]')
|
|
201
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
202
|
+
def line_set_positions(target: str, positions: str, search_method: Optional[str]):
|
|
203
|
+
"""Set all positions on a line renderer.
|
|
204
|
+
|
|
205
|
+
\\b
|
|
206
|
+
Examples:
|
|
207
|
+
unity-mcp vfx line set-positions "Line" --positions "[[0,0,0], [5,2,0], [10,0,0]]"
|
|
208
|
+
"""
|
|
209
|
+
config = get_config()
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
positions_list = json.loads(positions)
|
|
213
|
+
except json.JSONDecodeError as e:
|
|
214
|
+
print_error(f"Invalid JSON for positions: {e}")
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
params: dict[str, Any] = {
|
|
218
|
+
"action": "line_set_positions",
|
|
219
|
+
"target": target,
|
|
220
|
+
"positions": positions_list,
|
|
221
|
+
}
|
|
222
|
+
if search_method:
|
|
223
|
+
params["searchMethod"] = search_method
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
result = run_command("manage_vfx", params, config)
|
|
227
|
+
click.echo(format_output(result, config.format))
|
|
228
|
+
except UnityConnectionError as e:
|
|
229
|
+
print_error(str(e))
|
|
230
|
+
sys.exit(1)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@line.command("create-line")
|
|
234
|
+
@click.argument("target")
|
|
235
|
+
@click.option("--start", nargs=3, type=float, required=True, help="Start point X Y Z")
|
|
236
|
+
@click.option("--end", nargs=3, type=float, required=True, help="End point X Y Z")
|
|
237
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
238
|
+
def line_create_line(target: str, start: Tuple[float, float, float], end: Tuple[float, float, float], search_method: Optional[str]):
|
|
239
|
+
"""Create a simple line between two points.
|
|
240
|
+
|
|
241
|
+
\\b
|
|
242
|
+
Examples:
|
|
243
|
+
unity-mcp vfx line create-line "MyLine" --start 0 0 0 --end 10 5 0
|
|
244
|
+
"""
|
|
245
|
+
config = get_config()
|
|
246
|
+
params: dict[str, Any] = {
|
|
247
|
+
"action": "line_create_line",
|
|
248
|
+
"target": target,
|
|
249
|
+
"start": list(start),
|
|
250
|
+
"end": list(end),
|
|
251
|
+
}
|
|
252
|
+
if search_method:
|
|
253
|
+
params["searchMethod"] = search_method
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
result = run_command("manage_vfx", params, config)
|
|
257
|
+
click.echo(format_output(result, config.format))
|
|
258
|
+
except UnityConnectionError as e:
|
|
259
|
+
print_error(str(e))
|
|
260
|
+
sys.exit(1)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@line.command("create-circle")
|
|
264
|
+
@click.argument("target")
|
|
265
|
+
@click.option("--center", nargs=3, type=float, default=(0, 0, 0), help="Center point X Y Z")
|
|
266
|
+
@click.option("--radius", type=float, required=True, help="Circle radius")
|
|
267
|
+
@click.option("--segments", type=int, default=32, help="Number of segments")
|
|
268
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
269
|
+
def line_create_circle(target: str, center: Tuple[float, float, float], radius: float, segments: int, search_method: Optional[str]):
|
|
270
|
+
"""Create a circle shape.
|
|
271
|
+
|
|
272
|
+
\\b
|
|
273
|
+
Examples:
|
|
274
|
+
unity-mcp vfx line create-circle "Circle" --radius 5 --segments 64
|
|
275
|
+
unity-mcp vfx line create-circle "Ring" --center 0 2 0 --radius 3
|
|
276
|
+
"""
|
|
277
|
+
config = get_config()
|
|
278
|
+
params: dict[str, Any] = {
|
|
279
|
+
"action": "line_create_circle",
|
|
280
|
+
"target": target,
|
|
281
|
+
"center": list(center),
|
|
282
|
+
"radius": radius,
|
|
283
|
+
"segments": segments,
|
|
284
|
+
}
|
|
285
|
+
if search_method:
|
|
286
|
+
params["searchMethod"] = search_method
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
result = run_command("manage_vfx", params, config)
|
|
290
|
+
click.echo(format_output(result, config.format))
|
|
291
|
+
except UnityConnectionError as e:
|
|
292
|
+
print_error(str(e))
|
|
293
|
+
sys.exit(1)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@line.command("clear")
|
|
297
|
+
@click.argument("target")
|
|
298
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
299
|
+
def line_clear(target: str, search_method: Optional[str]):
|
|
300
|
+
"""Clear all positions from a line renderer."""
|
|
301
|
+
config = get_config()
|
|
302
|
+
params: dict[str, Any] = {"action": "line_clear", "target": target}
|
|
303
|
+
if search_method:
|
|
304
|
+
params["searchMethod"] = search_method
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
result = run_command("manage_vfx", params, config)
|
|
308
|
+
click.echo(format_output(result, config.format))
|
|
309
|
+
except UnityConnectionError as e:
|
|
310
|
+
print_error(str(e))
|
|
311
|
+
sys.exit(1)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# =============================================================================
|
|
315
|
+
# Trail Renderer Commands
|
|
316
|
+
# =============================================================================
|
|
317
|
+
|
|
318
|
+
@vfx.group()
|
|
319
|
+
def trail():
|
|
320
|
+
"""Trail renderer operations."""
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@trail.command("info")
|
|
325
|
+
@click.argument("target")
|
|
326
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
327
|
+
def trail_info(target: str, search_method: Optional[str]):
|
|
328
|
+
"""Get trail renderer info."""
|
|
329
|
+
config = get_config()
|
|
330
|
+
params: dict[str, Any] = {"action": "trail_get_info", "target": target}
|
|
331
|
+
if search_method:
|
|
332
|
+
params["searchMethod"] = search_method
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
result = run_command("manage_vfx", params, config)
|
|
336
|
+
click.echo(format_output(result, config.format))
|
|
337
|
+
except UnityConnectionError as e:
|
|
338
|
+
print_error(str(e))
|
|
339
|
+
sys.exit(1)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@trail.command("set-time")
|
|
343
|
+
@click.argument("target")
|
|
344
|
+
@click.argument("duration", type=float)
|
|
345
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
346
|
+
def trail_set_time(target: str, duration: float, search_method: Optional[str]):
|
|
347
|
+
"""Set trail duration.
|
|
348
|
+
|
|
349
|
+
\\b
|
|
350
|
+
Examples:
|
|
351
|
+
unity-mcp vfx trail set-time "PlayerTrail" 2.0
|
|
352
|
+
"""
|
|
353
|
+
config = get_config()
|
|
354
|
+
params: dict[str, Any] = {
|
|
355
|
+
"action": "trail_set_time",
|
|
356
|
+
"target": target,
|
|
357
|
+
"time": duration,
|
|
358
|
+
}
|
|
359
|
+
if search_method:
|
|
360
|
+
params["searchMethod"] = search_method
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
result = run_command("manage_vfx", params, config)
|
|
364
|
+
click.echo(format_output(result, config.format))
|
|
365
|
+
except UnityConnectionError as e:
|
|
366
|
+
print_error(str(e))
|
|
367
|
+
sys.exit(1)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@trail.command("clear")
|
|
371
|
+
@click.argument("target")
|
|
372
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
373
|
+
def trail_clear(target: str, search_method: Optional[str]):
|
|
374
|
+
"""Clear a trail renderer."""
|
|
375
|
+
config = get_config()
|
|
376
|
+
params: dict[str, Any] = {"action": "trail_clear", "target": target}
|
|
377
|
+
if search_method:
|
|
378
|
+
params["searchMethod"] = search_method
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
result = run_command("manage_vfx", params, config)
|
|
382
|
+
click.echo(format_output(result, config.format))
|
|
383
|
+
except UnityConnectionError as e:
|
|
384
|
+
print_error(str(e))
|
|
385
|
+
sys.exit(1)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# =============================================================================
|
|
389
|
+
# Raw Command (escape hatch for all VFX actions)
|
|
390
|
+
# =============================================================================
|
|
391
|
+
|
|
392
|
+
@vfx.command("raw")
|
|
393
|
+
@click.argument("action")
|
|
394
|
+
@click.argument("target", required=False)
|
|
395
|
+
@click.option("--params", "-p", default="{}", help="Additional parameters as JSON.")
|
|
396
|
+
@click.option("--search-method", type=click.Choice(["by_name", "by_path", "by_id", "by_tag"]), default=None)
|
|
397
|
+
def vfx_raw(action: str, target: Optional[str], params: str, search_method: Optional[str]):
|
|
398
|
+
"""Execute any VFX action directly.
|
|
399
|
+
|
|
400
|
+
For advanced users who need access to all 60+ VFX actions.
|
|
401
|
+
|
|
402
|
+
\\b
|
|
403
|
+
Actions include:
|
|
404
|
+
particle_*: particle_set_main, particle_set_emission, particle_set_shape, ...
|
|
405
|
+
vfx_*: vfx_set_float, vfx_send_event, vfx_play, ...
|
|
406
|
+
line_*: line_create_arc, line_create_bezier, ...
|
|
407
|
+
trail_*: trail_set_width, trail_set_color, ...
|
|
408
|
+
|
|
409
|
+
\\b
|
|
410
|
+
Examples:
|
|
411
|
+
unity-mcp vfx raw particle_set_main "Fire" --params '{"duration": 5, "looping": true}'
|
|
412
|
+
unity-mcp vfx raw line_create_arc "Arc" --params '{"radius": 3, "startAngle": 0, "endAngle": 180}'
|
|
413
|
+
unity-mcp vfx raw vfx_send_event "Explosion" --params '{"eventName": "OnSpawn"}'
|
|
414
|
+
"""
|
|
415
|
+
config = get_config()
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
extra_params = json.loads(params)
|
|
419
|
+
except json.JSONDecodeError as e:
|
|
420
|
+
print_error(f"Invalid JSON for params: {e}")
|
|
421
|
+
sys.exit(1)
|
|
422
|
+
if not isinstance(extra_params, dict):
|
|
423
|
+
print_error("Invalid JSON for params: expected an object")
|
|
424
|
+
sys.exit(1)
|
|
425
|
+
|
|
426
|
+
request_params: dict[str, Any] = {"action": action}
|
|
427
|
+
if target:
|
|
428
|
+
request_params["target"] = target
|
|
429
|
+
if search_method:
|
|
430
|
+
request_params["searchMethod"] = search_method
|
|
431
|
+
|
|
432
|
+
# Merge extra params
|
|
433
|
+
request_params.update(extra_params)
|
|
434
|
+
try:
|
|
435
|
+
result = run_command("manage_vfx", request_params, config)
|
|
436
|
+
click.echo(format_output(result, config.format))
|
|
437
|
+
except UnityConnectionError as e:
|
|
438
|
+
print_error(str(e))
|
|
439
|
+
sys.exit(1)
|
cli/main.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Unity MCP Command Line Interface - Main Entry Point."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from importlib import import_module
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from cli import __version__
|
|
10
|
+
from cli.utils.config import CLIConfig, set_config, get_config
|
|
11
|
+
from cli.utils.output import format_output, print_error, print_success, print_info
|
|
12
|
+
from cli.utils.connection import (
|
|
13
|
+
run_command,
|
|
14
|
+
run_check_connection,
|
|
15
|
+
run_list_instances,
|
|
16
|
+
UnityConnectionError,
|
|
17
|
+
warn_if_remote_host,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Context object to pass configuration between commands
|
|
22
|
+
class Context:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.config: Optional[CLIConfig] = None
|
|
25
|
+
self.verbose: bool = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
pass_context = click.make_pass_decorator(Context, ensure=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.group()
|
|
32
|
+
@click.version_option(version=__version__, prog_name="unity-mcp")
|
|
33
|
+
@click.option(
|
|
34
|
+
"--host", "-h",
|
|
35
|
+
default="127.0.0.1",
|
|
36
|
+
envvar="UNITY_MCP_HOST",
|
|
37
|
+
help="MCP server host address."
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--port", "-p",
|
|
41
|
+
default=8080,
|
|
42
|
+
type=int,
|
|
43
|
+
envvar="UNITY_MCP_HTTP_PORT",
|
|
44
|
+
help="MCP server port."
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--timeout", "-t",
|
|
48
|
+
default=30,
|
|
49
|
+
type=int,
|
|
50
|
+
envvar="UNITY_MCP_TIMEOUT",
|
|
51
|
+
help="Command timeout in seconds."
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--format", "-f",
|
|
55
|
+
type=click.Choice(["text", "json", "table"]),
|
|
56
|
+
default="text",
|
|
57
|
+
envvar="UNITY_MCP_FORMAT",
|
|
58
|
+
help="Output format."
|
|
59
|
+
)
|
|
60
|
+
@click.option(
|
|
61
|
+
"--instance", "-i",
|
|
62
|
+
default=None,
|
|
63
|
+
envvar="UNITY_MCP_INSTANCE",
|
|
64
|
+
help="Target Unity instance (hash or Name@hash)."
|
|
65
|
+
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"--verbose", "-v",
|
|
68
|
+
is_flag=True,
|
|
69
|
+
help="Enable verbose output."
|
|
70
|
+
)
|
|
71
|
+
@pass_context
|
|
72
|
+
def cli(ctx: Context, host: str, port: int, timeout: int, format: str, instance: Optional[str], verbose: bool):
|
|
73
|
+
"""Unity MCP Command Line Interface.
|
|
74
|
+
|
|
75
|
+
Control Unity Editor directly from the command line using the Model Context Protocol.
|
|
76
|
+
|
|
77
|
+
\b
|
|
78
|
+
Examples:
|
|
79
|
+
unity-mcp status
|
|
80
|
+
unity-mcp gameobject find "Player"
|
|
81
|
+
unity-mcp scene hierarchy --format json
|
|
82
|
+
unity-mcp editor play
|
|
83
|
+
|
|
84
|
+
\b
|
|
85
|
+
Environment Variables:
|
|
86
|
+
UNITY_MCP_HOST Server host (default: 127.0.0.1)
|
|
87
|
+
UNITY_MCP_HTTP_PORT Server port (default: 8080)
|
|
88
|
+
UNITY_MCP_TIMEOUT Timeout in seconds (default: 30)
|
|
89
|
+
UNITY_MCP_FORMAT Output format (default: text)
|
|
90
|
+
UNITY_MCP_INSTANCE Target Unity instance
|
|
91
|
+
"""
|
|
92
|
+
config = CLIConfig(
|
|
93
|
+
host=host,
|
|
94
|
+
port=port,
|
|
95
|
+
timeout=timeout,
|
|
96
|
+
format=format,
|
|
97
|
+
unity_instance=instance,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Security warning for non-localhost connections
|
|
101
|
+
warn_if_remote_host(config)
|
|
102
|
+
|
|
103
|
+
set_config(config)
|
|
104
|
+
ctx.config = config
|
|
105
|
+
ctx.verbose = verbose
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@cli.command("status")
|
|
109
|
+
@pass_context
|
|
110
|
+
def status(ctx: Context):
|
|
111
|
+
"""Check connection status to Unity MCP server."""
|
|
112
|
+
config = ctx.config or get_config()
|
|
113
|
+
|
|
114
|
+
click.echo(f"Checking connection to {config.host}:{config.port}...")
|
|
115
|
+
|
|
116
|
+
if run_check_connection(config):
|
|
117
|
+
print_success(
|
|
118
|
+
f"Connected to Unity MCP server at {config.host}:{config.port}")
|
|
119
|
+
|
|
120
|
+
# Try to get Unity instances
|
|
121
|
+
try:
|
|
122
|
+
result = run_list_instances(config)
|
|
123
|
+
instances = result.get("instances", []) if isinstance(
|
|
124
|
+
result, dict) else []
|
|
125
|
+
if instances:
|
|
126
|
+
click.echo("\nConnected Unity instances:")
|
|
127
|
+
for inst in instances:
|
|
128
|
+
project = inst.get("project", "Unknown")
|
|
129
|
+
version = inst.get("unity_version", "Unknown")
|
|
130
|
+
hash_id = inst.get("hash", "")[:8]
|
|
131
|
+
click.echo(f" • {project} (Unity {version}) [{hash_id}]")
|
|
132
|
+
else:
|
|
133
|
+
print_info("No Unity instances currently connected")
|
|
134
|
+
except UnityConnectionError as e:
|
|
135
|
+
print_info(f"Could not retrieve Unity instances: {e}")
|
|
136
|
+
else:
|
|
137
|
+
print_error(
|
|
138
|
+
f"Cannot connect to Unity MCP server at {config.host}:{config.port}")
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@cli.command("instances")
|
|
143
|
+
@pass_context
|
|
144
|
+
def list_instances(ctx: Context):
|
|
145
|
+
"""List available Unity instances."""
|
|
146
|
+
config = ctx.config or get_config()
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
instances = run_list_instances(config)
|
|
150
|
+
click.echo(format_output(instances, config.format))
|
|
151
|
+
except UnityConnectionError as e:
|
|
152
|
+
print_error(str(e))
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@cli.command("raw")
|
|
157
|
+
@click.argument("command_type")
|
|
158
|
+
@click.argument("params", required=False, default="{}")
|
|
159
|
+
@pass_context
|
|
160
|
+
def raw_command(ctx: Context, command_type: str, params: str):
|
|
161
|
+
"""Send a raw command to Unity.
|
|
162
|
+
|
|
163
|
+
\b
|
|
164
|
+
Examples:
|
|
165
|
+
unity-mcp raw manage_scene '{"action": "get_hierarchy"}'
|
|
166
|
+
unity-mcp raw read_console '{"count": 10}'
|
|
167
|
+
"""
|
|
168
|
+
import json
|
|
169
|
+
config = ctx.config or get_config()
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
params_dict = json.loads(params)
|
|
173
|
+
except json.JSONDecodeError as e:
|
|
174
|
+
print_error(f"Invalid JSON params: {e}")
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
result = run_command(command_type, params_dict, config)
|
|
179
|
+
click.echo(format_output(result, config.format))
|
|
180
|
+
except UnityConnectionError as e:
|
|
181
|
+
print_error(str(e))
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Import and register command groups
|
|
186
|
+
# These will be implemented in subsequent TODOs
|
|
187
|
+
def register_commands():
|
|
188
|
+
"""Register all command groups."""
|
|
189
|
+
def register_optional_command(module_name: str, command_name: str) -> None:
|
|
190
|
+
try:
|
|
191
|
+
module = import_module(module_name)
|
|
192
|
+
except ModuleNotFoundError as e:
|
|
193
|
+
if e.name == module_name:
|
|
194
|
+
return
|
|
195
|
+
print_error(
|
|
196
|
+
f"Failed to load command module '{module_name}': {e}"
|
|
197
|
+
)
|
|
198
|
+
return
|
|
199
|
+
except Exception as e:
|
|
200
|
+
print_error(
|
|
201
|
+
f"Failed to load command module '{module_name}': {e}"
|
|
202
|
+
)
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
command = getattr(module, command_name, None)
|
|
206
|
+
if command is None:
|
|
207
|
+
print_error(
|
|
208
|
+
f"Command '{command_name}' not found in '{module_name}'"
|
|
209
|
+
)
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
cli.add_command(command)
|
|
213
|
+
|
|
214
|
+
optional_commands = [
|
|
215
|
+
("cli.commands.gameobject", "gameobject"),
|
|
216
|
+
("cli.commands.component", "component"),
|
|
217
|
+
("cli.commands.scene", "scene"),
|
|
218
|
+
("cli.commands.asset", "asset"),
|
|
219
|
+
("cli.commands.script", "script"),
|
|
220
|
+
("cli.commands.code", "code"),
|
|
221
|
+
("cli.commands.editor", "editor"),
|
|
222
|
+
("cli.commands.prefab", "prefab"),
|
|
223
|
+
("cli.commands.material", "material"),
|
|
224
|
+
("cli.commands.lighting", "lighting"),
|
|
225
|
+
("cli.commands.animation", "animation"),
|
|
226
|
+
("cli.commands.audio", "audio"),
|
|
227
|
+
("cli.commands.ui", "ui"),
|
|
228
|
+
("cli.commands.instance", "instance"),
|
|
229
|
+
("cli.commands.shader", "shader"),
|
|
230
|
+
("cli.commands.vfx", "vfx"),
|
|
231
|
+
("cli.commands.batch", "batch"),
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
for module_name, command_name in optional_commands:
|
|
235
|
+
register_optional_command(module_name, command_name)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Register commands on import
|
|
239
|
+
register_commands()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def main():
|
|
243
|
+
"""Main entry point for the CLI."""
|
|
244
|
+
cli()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__ == "__main__":
|
|
248
|
+
main()
|