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.
- cli/commands/animation.py +6 -9
- cli/commands/asset.py +50 -80
- cli/commands/audio.py +14 -22
- cli/commands/batch.py +20 -33
- cli/commands/code.py +63 -70
- cli/commands/component.py +33 -55
- cli/commands/editor.py +122 -188
- cli/commands/gameobject.py +60 -83
- cli/commands/instance.py +28 -36
- cli/commands/lighting.py +54 -59
- cli/commands/material.py +39 -68
- cli/commands/prefab.py +63 -81
- cli/commands/scene.py +30 -54
- cli/commands/script.py +32 -50
- cli/commands/shader.py +43 -55
- cli/commands/texture.py +53 -51
- cli/commands/tool.py +24 -27
- cli/commands/ui.py +125 -130
- cli/commands/vfx.py +84 -138
- cli/utils/confirmation.py +37 -0
- cli/utils/connection.py +32 -2
- cli/utils/constants.py +23 -0
- cli/utils/parsers.py +112 -0
- core/config.py +0 -4
- core/telemetry.py +20 -2
- {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/METADATA +21 -1
- mcpforunityserver-9.3.0b20260129121506.dist-info/RECORD +103 -0
- services/resources/active_tool.py +1 -1
- services/resources/custom_tools.py +1 -1
- services/resources/editor_state.py +1 -1
- services/resources/gameobject.py +4 -4
- services/resources/layers.py +1 -1
- services/resources/menu_items.py +1 -1
- services/resources/prefab.py +3 -3
- 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/tests.py +40 -8
- services/resources/unity_instances.py +1 -1
- services/resources/windows.py +1 -1
- services/tools/__init__.py +3 -1
- services/tools/find_gameobjects.py +32 -11
- services/tools/manage_gameobject.py +11 -66
- services/tools/manage_material.py +4 -37
- services/tools/manage_prefabs.py +51 -7
- services/tools/manage_script.py +1 -1
- services/tools/manage_texture.py +10 -96
- services/tools/run_tests.py +67 -4
- services/tools/utils.py +217 -0
- transport/models.py +1 -0
- transport/plugin_hub.py +2 -1
- transport/plugin_registry.py +3 -0
- transport/unity_transport.py +0 -51
- utils/focus_nudge.py +291 -23
- mcpforunityserver-9.3.0b20260128055651.dist-info/RECORD +0 -101
- utils/reload_sentinel.py +0 -9
- {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/WHEEL +0 -0
- {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/entry_points.txt +0 -0
- {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/licenses/LICENSE +0 -0
- {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/top_level.txt +0 -0
core/telemetry.py
CHANGED
|
@@ -249,6 +249,7 @@ class TelemetryCollector:
|
|
|
249
249
|
self._lock: threading.Lock = threading.Lock()
|
|
250
250
|
# Bounded queue with single background worker (records only; no context propagation)
|
|
251
251
|
self._queue: "queue.Queue[TelemetryRecord]" = queue.Queue(maxsize=1000)
|
|
252
|
+
self._shutdown: bool = False
|
|
252
253
|
# Load persistent data before starting worker so first events have UUID
|
|
253
254
|
self._load_persistent_data()
|
|
254
255
|
self._worker: threading.Thread = threading.Thread(
|
|
@@ -349,8 +350,11 @@ class TelemetryCollector:
|
|
|
349
350
|
|
|
350
351
|
def _worker_loop(self):
|
|
351
352
|
"""Background worker that serializes telemetry sends."""
|
|
352
|
-
while
|
|
353
|
-
|
|
353
|
+
while not self._shutdown:
|
|
354
|
+
try:
|
|
355
|
+
rec = self._queue.get(timeout=0.5)
|
|
356
|
+
except queue.Empty:
|
|
357
|
+
continue
|
|
354
358
|
try:
|
|
355
359
|
# Run sender directly; do not reuse caller context/thread-locals
|
|
356
360
|
self._send_telemetry(rec)
|
|
@@ -360,6 +364,12 @@ class TelemetryCollector:
|
|
|
360
364
|
with contextlib.suppress(Exception):
|
|
361
365
|
self._queue.task_done()
|
|
362
366
|
|
|
367
|
+
def shutdown(self):
|
|
368
|
+
"""Shutdown the telemetry collector and worker thread."""
|
|
369
|
+
self._shutdown = True
|
|
370
|
+
if self._worker and self._worker.is_alive():
|
|
371
|
+
self._worker.join(timeout=2.0)
|
|
372
|
+
|
|
363
373
|
def _send_telemetry(self, record: TelemetryRecord):
|
|
364
374
|
"""Send telemetry data to endpoint"""
|
|
365
375
|
try:
|
|
@@ -440,6 +450,14 @@ def get_telemetry() -> TelemetryCollector:
|
|
|
440
450
|
return _telemetry_collector
|
|
441
451
|
|
|
442
452
|
|
|
453
|
+
def reset_telemetry():
|
|
454
|
+
"""Reset the global telemetry collector. For testing only."""
|
|
455
|
+
global _telemetry_collector
|
|
456
|
+
if _telemetry_collector is not None:
|
|
457
|
+
_telemetry_collector.shutdown()
|
|
458
|
+
_telemetry_collector = None
|
|
459
|
+
|
|
460
|
+
|
|
443
461
|
def record_telemetry(record_type: RecordType,
|
|
444
462
|
data: dict[str, Any],
|
|
445
463
|
milestone: MilestoneType | None = None):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpforunityserver
|
|
3
|
-
Version: 9.3.
|
|
3
|
+
Version: 9.3.0b20260129121506
|
|
4
4
|
Summary: MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP).
|
|
5
5
|
Author-email: Marcus Sanatan <msanatan@gmail.com>, David Sarno <david.sarno@gmail.com>, Wu Shutong <martinwfire@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -163,6 +163,26 @@ The server connects to Unity Editor automatically when both are running. No addi
|
|
|
163
163
|
|
|
164
164
|
---
|
|
165
165
|
|
|
166
|
+
## MCP Resources
|
|
167
|
+
|
|
168
|
+
The server provides read-only MCP resources for querying Unity Editor state. Resources provide up-to-date information about your Unity project without modifying it.
|
|
169
|
+
|
|
170
|
+
**Accessing Resources:**
|
|
171
|
+
|
|
172
|
+
Resources are accessed by their URI (not their name). Always use `ListMcpResources` to get the correct URI format.
|
|
173
|
+
|
|
174
|
+
**Example URIs:**
|
|
175
|
+
- `mcpforunity://editor/state` - Editor readiness snapshot
|
|
176
|
+
- `mcpforunity://project/tags` - All project tags
|
|
177
|
+
- `mcpforunity://scene/gameobject/{instance_id}` - GameObject details by ID
|
|
178
|
+
- `mcpforunity://prefab/{encoded_path}` - Prefab info by asset path
|
|
179
|
+
|
|
180
|
+
**Important:** Resource names use underscores (e.g., `editor_state`) but URIs use slashes/hyphens (e.g., `mcpforunity://editor/state`). Always use the URI from `ListMcpResources()` when reading resources.
|
|
181
|
+
|
|
182
|
+
**All resource descriptions now include their URI** for easy reference. List available resources to see the complete catalog with URIs.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
166
186
|
## Example Prompts
|
|
167
187
|
|
|
168
188
|
Once connected, try these commands in your AI assistant:
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
main.py,sha256=EoHA0upWjtQzuoOgN5BfNmGL6bIVnFQjRW5PHO5EmjY,29265
|
|
2
|
+
cli/__init__.py,sha256=f2HjXqR9d8Uhibru211t9HPpdrb_1vdDC2v_NwF_eqA,63
|
|
3
|
+
cli/main.py,sha256=V_VFa8tA-CDHNv9J5NzNSLxRuEGjRVZWDe4xn6rYdog,8457
|
|
4
|
+
cli/commands/__init__.py,sha256=xQHf6o0afDV2HsU9gwSxjcrzS41cMCSGZyWYWxblPIk,69
|
|
5
|
+
cli/commands/animation.py,sha256=a7ZqTkP7CRou9wL7MB8b3TSB7v0PFG_1LcjdxW5_zQ0,2388
|
|
6
|
+
cli/commands/asset.py,sha256=Cm18PFLGLisTLRhycTbxaGT8XU3WnJWcPOwsWiB5E2s,7164
|
|
7
|
+
cli/commands/audio.py,sha256=gTW4F6uutgQezAwDqL6_-i8vM588V-AVwxAaJ4LzGN8,3088
|
|
8
|
+
cli/commands/batch.py,sha256=eo2DD62FzRPDk0mQ5WhFVAXS0qXf8_znT_5kdN0BoG4,5388
|
|
9
|
+
cli/commands/code.py,sha256=N6MaBjH7QkYjjCP4wEhAoAQSwvboh2ub3rrI6qYv8AM,5226
|
|
10
|
+
cli/commands/component.py,sha256=OqjuNOyA2n5UOy-0-vMeiIRCQHOq_OnhVqW4ZzMgqrE,5736
|
|
11
|
+
cli/commands/editor.py,sha256=F_KOfQiE-uuwGHXZQ9o9BF5nURG1nKcWuiL-9hjAFHc,12704
|
|
12
|
+
cli/commands/gameobject.py,sha256=D4h8xuMHjOoc_mdyZToNSzYCGrJKjeOpwrMWCXR0OCU,12881
|
|
13
|
+
cli/commands/instance.py,sha256=5qYJtptOFGCzfhXEMIPoEYcJtpkKlfM76dq36i_ZK5s,2780
|
|
14
|
+
cli/commands/lighting.py,sha256=4OpbzPvR8pesTTUVYmKV554SwbTyCVBRwDwTGlnmh2w,3558
|
|
15
|
+
cli/commands/material.py,sha256=_9atAdD0Wo_mEpZ2FP9Opl54ZBi2ak96euydHD_xGa4,6999
|
|
16
|
+
cli/commands/prefab.py,sha256=ef8mFF5pcBDH8X3zWANuD4Hq23sT3gK1mAXsHSbP1iM,7480
|
|
17
|
+
cli/commands/scene.py,sha256=HRDOcOUMpS6RhqL3FNF4JphjseCZFhTuDrJ0l-MOSRc,5652
|
|
18
|
+
cli/commands/script.py,sha256=70i3I6IuKWq5f49QvHIpTzC9fafYmJrWfynJl4ZAAMU,5966
|
|
19
|
+
cli/commands/shader.py,sha256=w_n8g6GJcFmqqZMulPdJGbvWWOirbfzHr6_vUx_2nVE,5985
|
|
20
|
+
cli/commands/texture.py,sha256=lLiPNMfcWNNcfHbyeNA1ZN16tKv1ljHkzhmr7SM5e50,18595
|
|
21
|
+
cli/commands/tool.py,sha256=A-jC9qFAs-LkWY0rav-vrye0yytgfKxQ7y2FraIAzZ4,1605
|
|
22
|
+
cli/commands/ui.py,sha256=u9rpeF_eT7qxdlYmaA2G4ARSe3a16Y4yt-3if7CKmxY,7225
|
|
23
|
+
cli/commands/vfx.py,sha256=gdx5a_N7Ulu960xjaJIV0E_1Ii422C211KaipoRO6nQ,14453
|
|
24
|
+
cli/utils/__init__.py,sha256=Gbm9hYC7UqwloFwdirXgo6z1iBktR9Y96o3bQcrYudc,613
|
|
25
|
+
cli/utils/config.py,sha256=_k3XAFmXG22sv8tYIb5JmO46kNl3T1sGqFptySAayfc,1550
|
|
26
|
+
cli/utils/confirmation.py,sha256=7NGu0I5ogowpdWRTUndn3g5nmWmJM9mV3e0wWMLJwA8,1234
|
|
27
|
+
cli/utils/connection.py,sha256=RBSOK7WotKgokj-wewsqnKT5rwS0_DhsqXqZrFZWUXo,8534
|
|
28
|
+
cli/utils/constants.py,sha256=xCyRMY1L3cc-sbCyl-TGyqkY5hMCOl5tU6L4ZVbN9w4,1046
|
|
29
|
+
cli/utils/output.py,sha256=96daU55ta_hl7UeOhNh5Iy7OJ4psbdR9Nfx1-q2k3xA,6370
|
|
30
|
+
cli/utils/parsers.py,sha256=mnpH2bhZn3L3Lyl8e7Sh7Zr2UW9Xzu3aUuCaLxpV2pk,3430
|
|
31
|
+
cli/utils/suggestions.py,sha256=n6KG3Mrvub28X9rPFYFLRTtZ6HePp3PhhAeojG2WOJw,929
|
|
32
|
+
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
core/config.py,sha256=kE7yg_89QQck3zScWNjvrq7CnfYt6uLS0VdxK7AJ70o,1453
|
|
34
|
+
core/logging_decorator.py,sha256=D9CD7rFvQz-MBG-G4inizQj0Ivr6dfc9RBmTrw7q8mI,1383
|
|
35
|
+
core/telemetry.py,sha256=zIjmQKUNW0S822SSlkXyjjCIuX0ZpSTaZP4pAU0rCjw,20426
|
|
36
|
+
core/telemetry_decorator.py,sha256=ycSTrzVNCDQHSd-xmIWOpVfKFURPxpiZe_XkOQAGDAo,6705
|
|
37
|
+
mcpforunityserver-9.3.0b20260129121506.dist-info/licenses/LICENSE,sha256=bv5lDJZQEqxBgjjc1rkRbkEwpSIHF-8N-1Od0VnEJFw,1066
|
|
38
|
+
models/__init__.py,sha256=JlscZkGWE9TRmSoBi99v_LSl8OAFNGmr8463PYkXin4,179
|
|
39
|
+
models/models.py,sha256=heXuvdBtdats1SGwW8wKFFHM0qR4hA6A7qETn5s9BZ0,1827
|
|
40
|
+
models/unity_response.py,sha256=oJ1PTsnNc5VBC-9OgM59C0C-R9N-GdmEdmz_yph4GSU,1454
|
|
41
|
+
services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
services/custom_tool_service.py,sha256=WJxljL-hdJE5GMlAhVimHVhQwwnWHCd0StgWhWEFgaI,18592
|
|
43
|
+
services/registry/__init__.py,sha256=QCwcYThvGF0kBt3WR6DBskdyxkegJC7NymEChgJA-YM,470
|
|
44
|
+
services/registry/resource_registry.py,sha256=T_Kznqgvt5kKgV7mU85nb0LlFuB4rg-Tm4Cjhxt-IcI,1467
|
|
45
|
+
services/registry/tool_registry.py,sha256=9tMwOP07JE92QFYUS4KvoysO0qC9pkBD5B79kjRsSPw,1304
|
|
46
|
+
services/resources/__init__.py,sha256=G8uSEYJtiyX3yg0QsfoeGdDXOdbU89l5m0B5Anay1Fc,3054
|
|
47
|
+
services/resources/active_tool.py,sha256=6m65iPCD1Iqmp4xqpDKvPErfOJg7Gl3XU5vMyAVcdj8,1521
|
|
48
|
+
services/resources/custom_tools.py,sha256=uujlJEuqv5svCvSZLILgiY6hiiWZqWHVzTBJpTTL3as,1751
|
|
49
|
+
services/resources/editor_state.py,sha256=LZLmljdHQC7nQcA-YYtoRnpy1xDiiAzo4_iU69TgsH8,10858
|
|
50
|
+
services/resources/gameobject.py,sha256=cviektt_GHmwgria5pkvNrzvD5-6hzBi64Ogm0YGIv8,9356
|
|
51
|
+
services/resources/layers.py,sha256=e3bDhfJ0ZjtUagHhraI35GrzrDnzXrRHnDeobmnbniw,1123
|
|
52
|
+
services/resources/menu_items.py,sha256=01LIVHA96bswYGXyLZzGFw3suDqftsBanhy_lkUB2H0,1054
|
|
53
|
+
services/resources/prefab.py,sha256=4TLEBsrlnQdi5FOKWZZ9eesiUCG5Ryxh7FZvd02FcTc,7434
|
|
54
|
+
services/resources/prefab_stage.py,sha256=YALZrfZh4zpV_tEfQI7sUR6ETLnz8qeTN--gwYxk_y8,1430
|
|
55
|
+
services/resources/project_info.py,sha256=NaHb9v5n-2vi3woMxioMXbgpy1tBcmh2bh0RZOTxtC4,1364
|
|
56
|
+
services/resources/selection.py,sha256=HxmpN0RAb6Op0r4kqqRsizahP5L3Hj8V1baRa3uaKpU,1884
|
|
57
|
+
services/resources/tags.py,sha256=f50DzILLNQsUJ44ooomwIGasrcntbKJgT7boqrU4b5s,1090
|
|
58
|
+
services/resources/tests.py,sha256=KOm1vFcDPAXi21rDRQ1hQmifs1T9Xnd0zUp-BcBrBxo,3458
|
|
59
|
+
services/resources/unity_instances.py,sha256=8vLA5oR2VpeLRjuxHk-_J3eopLhuNpGEoJyCvyUAOXg,4386
|
|
60
|
+
services/resources/windows.py,sha256=inD7zMB3R4Tip_92pCDtKGD7clzsGKOGkdmv4rKmygA,1470
|
|
61
|
+
services/state/external_changes_scanner.py,sha256=ZiXu8ZcK5B-hv7CaJLmnEIa9JxzgOBpdmrsRDY2eK5I,9052
|
|
62
|
+
services/tools/__init__.py,sha256=J-woLzm3aLF0uPC1-VroqG7QV9xLXHDmVYee2ZXuCgk,2746
|
|
63
|
+
services/tools/batch_execute.py,sha256=hjh67kgWvQDHyGd2N-Tfezv9WAj5x_pWTt_Vybmmq7s,3501
|
|
64
|
+
services/tools/debug_request_context.py,sha256=Duq5xiuSmRO5GdvWAlZhCfOfmrwvK7gGkRC4wYnXmXk,2907
|
|
65
|
+
services/tools/execute_custom_tool.py,sha256=hiZbm2A9t84f92jitzvkE2G4CMOIUiDVm7u5B8K-RbU,1527
|
|
66
|
+
services/tools/execute_menu_item.py,sha256=k4J89LlXmEGyo9z3NK8Q0vREIzr11ucF_9tN_JeQq9M,1248
|
|
67
|
+
services/tools/find_gameobjects.py,sha256=Qyls8HxBgXjA5OtdJfXuIX0qNGMleQSt3NOvQwjP25E,3963
|
|
68
|
+
services/tools/find_in_file.py,sha256=SxhMeo8lRrt0OiGApGZSFUnq671bxVfK8qgAsHxLua8,6493
|
|
69
|
+
services/tools/manage_asset.py,sha256=St_iWQWg9icztnRthU78t6JNhJN0AlC6ELiZhn-SNZU,5990
|
|
70
|
+
services/tools/manage_components.py,sha256=2_nKPk9iPAf5VyYiXuRxSkN8U76VNQbMtE68UTPngrw,5061
|
|
71
|
+
services/tools/manage_editor.py,sha256=ShvlSBQRfoNQ0DvqBWak_Hi3MB7tv2WkMKEhrKQipk0,3279
|
|
72
|
+
services/tools/manage_gameobject.py,sha256=SP-y3_7Ckw12JO6bLJy01Jrx303JXF-7RMROaQuA1kU,14154
|
|
73
|
+
services/tools/manage_material.py,sha256=LSn9Kp-cSMZ5caU6Ux0M_OSMghCtZgOKkmvwf0xLTFE,4311
|
|
74
|
+
services/tools/manage_prefabs.py,sha256=7K-6kTavqu2-oxbA49Ug-vTh1Jt39ua02lWcijwM1DI,10163
|
|
75
|
+
services/tools/manage_scene.py,sha256=-ARtRuj7ZNk_14lmMSORnQs0qTAYKBTPtUfk0sNDo6A,5370
|
|
76
|
+
services/tools/manage_script.py,sha256=tT8JmhTtAYgW8CQla71cfn6IjiUw-tiPjBWVd4ipuCE,28551
|
|
77
|
+
services/tools/manage_scriptable_object.py,sha256=tezG_mbGzPLNpL3F7l5JJLyyjJN3rJi1thGMU8cpOC4,3659
|
|
78
|
+
services/tools/manage_shader.py,sha256=bucRKzQww7opy6DK5nf6isVaEECWWqJ-DVkFulp8CV8,3185
|
|
79
|
+
services/tools/manage_texture.py,sha256=uUicuxKl_uSnP2jBrMbYAhAEWnh7RETusgJfnBI1P8c,22538
|
|
80
|
+
services/tools/manage_vfx.py,sha256=7KFbRohF8EzaD0m7vVIEwjUz-QwC7NEXS5cVcU6Die0,4710
|
|
81
|
+
services/tools/preflight.py,sha256=0nvo0BmZMdIGop1Ha_vypkjn2VLiRvskF0uxh_SlZgE,4162
|
|
82
|
+
services/tools/read_console.py,sha256=ps23debJcQkj3Ap-MqTYVhopYnKGspJs9QHLJHZAAkE,6826
|
|
83
|
+
services/tools/refresh_unity.py,sha256=KrRA8bmLkDLFO1XBv2NmagQAp1dmyaXdUAap567Hcv4,7100
|
|
84
|
+
services/tools/run_tests.py,sha256=Wd7hNZqy-OOZ9ZedonxUJ5bVlhta_aOEmD-2uxmrmyM,11743
|
|
85
|
+
services/tools/script_apply_edits.py,sha256=0f-SaP5NUYGuivl4CWHjR8F-CXUpt3-5qkHpf_edn1U,47677
|
|
86
|
+
services/tools/set_active_instance.py,sha256=pdmC1SxFijyzzjeEyC2N1bXk-GNMu_iXsbCieIpa-R4,4242
|
|
87
|
+
services/tools/utils.py,sha256=ETCiNnWdMZEtnJcDD-CtPsCJ7TBp5x5sPsYuhufkxac,13962
|
|
88
|
+
transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
|
+
transport/models.py,sha256=2tnK0Wc-IzvlOS0a04VB3WtuSlAAHeLVFfOnNFHGnhw,1311
|
|
90
|
+
transport/plugin_hub.py,sha256=Kykqnydk_KkCSICaJGE8XQulr7sZeNcIgPOHjtdbjHM,23783
|
|
91
|
+
transport/plugin_registry.py,sha256=L6xl7Ok9rtSW9xwSSKTFjWS7Duu1g4c-qfgfnn6y-sQ,4528
|
|
92
|
+
transport/unity_instance_middleware.py,sha256=DD8gs-peMRmRJz9CYwaHEh4m75LTYPDjVuKuw9sArBw,10438
|
|
93
|
+
transport/unity_transport.py,sha256=RKMH0NYPqTU3rpjXUPUkzZsdWhJqyVaHmkLswab4bGg,2135
|
|
94
|
+
transport/legacy/port_discovery.py,sha256=JDSCqXLodfTT7fOsE0DFC1jJ3QsU6hVaYQb7x7FgdxY,12728
|
|
95
|
+
transport/legacy/stdio_port_registry.py,sha256=j4iARuP6wetppNDG8qKeuvo1bJKcSlgEhZvSyl_uf0A,2313
|
|
96
|
+
transport/legacy/unity_connection.py,sha256=FE9ZQfYMhHvIxBycr_DjI3BKvuEdORXuABnCE5Q2tjQ,36733
|
|
97
|
+
utils/focus_nudge.py,sha256=0MCOms-SxUW7sN2hT3syy1epMdli2zc-6UHBICAfBSM,21330
|
|
98
|
+
utils/module_discovery.py,sha256=My48ofB1BUqxiBoAZAGbEaLQYdsrDhMm8MayBP_bUSQ,2005
|
|
99
|
+
mcpforunityserver-9.3.0b20260129121506.dist-info/METADATA,sha256=NhUC2AYe0dyaYPjuAkHQN1wksQiDGTHHM_8k8IvcEw4,6750
|
|
100
|
+
mcpforunityserver-9.3.0b20260129121506.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
101
|
+
mcpforunityserver-9.3.0b20260129121506.dist-info/entry_points.txt,sha256=pPm70RXQvkt3uBhPOtViDa47ZTA03RaQ6rwXvyi8oiI,70
|
|
102
|
+
mcpforunityserver-9.3.0b20260129121506.dist-info/top_level.txt,sha256=3-A65WsmBO6UZYH8O5mINdyhhZ63SDssr8LncRd1PSQ,46
|
|
103
|
+
mcpforunityserver-9.3.0b20260129121506.dist-info/RECORD,,
|
|
@@ -33,7 +33,7 @@ class ActiveToolResponse(MCPResponse):
|
|
|
33
33
|
@mcp_for_unity_resource(
|
|
34
34
|
uri="mcpforunity://editor/active-tool",
|
|
35
35
|
name="editor_active_tool",
|
|
36
|
-
description="Currently active editor tool (Move, Rotate, Scale, etc.) and transform handle settings
|
|
36
|
+
description="Currently active editor tool (Move, Rotate, Scale, etc.) and transform handle settings.\n\nURI: mcpforunity://editor/active-tool"
|
|
37
37
|
)
|
|
38
38
|
async def get_active_tool(ctx: Context) -> ActiveToolResponse | MCPResponse:
|
|
39
39
|
"""Get active editor tool information."""
|
|
@@ -24,7 +24,7 @@ class CustomToolsResourceResponse(MCPResponse):
|
|
|
24
24
|
@mcp_for_unity_resource(
|
|
25
25
|
uri="mcpforunity://custom-tools",
|
|
26
26
|
name="custom_tools",
|
|
27
|
-
description="Lists custom tools available for the active Unity project
|
|
27
|
+
description="Lists custom tools available for the active Unity project.\n\nURI: mcpforunity://custom-tools",
|
|
28
28
|
)
|
|
29
29
|
async def get_custom_tools(ctx: Context) -> CustomToolsResourceResponse | MCPResponse:
|
|
30
30
|
unity_instance = get_unity_instance_from_context(ctx)
|
|
@@ -214,7 +214,7 @@ def _enrich_advice_and_staleness(state_v2: dict[str, Any]) -> dict[str, Any]:
|
|
|
214
214
|
@mcp_for_unity_resource(
|
|
215
215
|
uri="mcpforunity://editor/state",
|
|
216
216
|
name="editor_state",
|
|
217
|
-
description="Canonical editor readiness snapshot. Includes advice and server-computed staleness
|
|
217
|
+
description="Canonical editor readiness snapshot. Includes advice and server-computed staleness.\n\nURI: mcpforunity://editor/state",
|
|
218
218
|
)
|
|
219
219
|
async def get_editor_state(ctx: Context) -> MCPResponse:
|
|
220
220
|
unity_instance = get_unity_instance_from_context(ctx)
|
services/resources/gameobject.py
CHANGED
|
@@ -42,7 +42,7 @@ def _validate_instance_id(instance_id: str) -> tuple[int | None, MCPResponse | N
|
|
|
42
42
|
@mcp_for_unity_resource(
|
|
43
43
|
uri="mcpforunity://scene/gameobject-api",
|
|
44
44
|
name="gameobject_api",
|
|
45
|
-
description="Documentation for GameObject resources. Use find_gameobjects tool to get instance IDs, then access resources below
|
|
45
|
+
description="Documentation for GameObject resources. Use find_gameobjects tool to get instance IDs, then access resources below.\n\nURI: mcpforunity://scene/gameobject-api"
|
|
46
46
|
)
|
|
47
47
|
async def get_gameobject_api_docs(_ctx: Context) -> MCPResponse:
|
|
48
48
|
"""
|
|
@@ -129,7 +129,7 @@ class GameObjectResponse(MCPResponse):
|
|
|
129
129
|
@mcp_for_unity_resource(
|
|
130
130
|
uri="mcpforunity://scene/gameobject/{instance_id}",
|
|
131
131
|
name="gameobject",
|
|
132
|
-
description="Get detailed information about a single GameObject by instance ID. Returns name, tag, layer, active state, transform data, parent/children IDs, and component type list (no full component properties)
|
|
132
|
+
description="Get detailed information about a single GameObject by instance ID. Returns name, tag, layer, active state, transform data, parent/children IDs, and component type list (no full component properties).\n\nURI: mcpforunity://scene/gameobject/{instance_id}"
|
|
133
133
|
)
|
|
134
134
|
async def get_gameobject(ctx: Context, instance_id: str) -> MCPResponse:
|
|
135
135
|
"""Get GameObject data by instance ID."""
|
|
@@ -170,7 +170,7 @@ class ComponentsResponse(MCPResponse):
|
|
|
170
170
|
@mcp_for_unity_resource(
|
|
171
171
|
uri="mcpforunity://scene/gameobject/{instance_id}/components",
|
|
172
172
|
name="gameobject_components",
|
|
173
|
-
description="Get all components on a GameObject with full property serialization. Supports pagination with pageSize and cursor parameters
|
|
173
|
+
description="Get all components on a GameObject with full property serialization. Supports pagination with pageSize and cursor parameters.\n\nURI: mcpforunity://scene/gameobject/{instance_id}/components"
|
|
174
174
|
)
|
|
175
175
|
async def get_gameobject_components(
|
|
176
176
|
ctx: Context,
|
|
@@ -216,7 +216,7 @@ class SingleComponentResponse(MCPResponse):
|
|
|
216
216
|
@mcp_for_unity_resource(
|
|
217
217
|
uri="mcpforunity://scene/gameobject/{instance_id}/component/{component_name}",
|
|
218
218
|
name="gameobject_component",
|
|
219
|
-
description="Get a specific component on a GameObject by type name. Returns the fully serialized component with all properties
|
|
219
|
+
description="Get a specific component on a GameObject by type name. Returns the fully serialized component with all properties.\n\nURI: mcpforunity://scene/gameobject/{instance_id}/component/{component_name}"
|
|
220
220
|
)
|
|
221
221
|
async def get_gameobject_component(
|
|
222
222
|
ctx: Context,
|
services/resources/layers.py
CHANGED
|
@@ -15,7 +15,7 @@ class LayersResponse(MCPResponse):
|
|
|
15
15
|
@mcp_for_unity_resource(
|
|
16
16
|
uri="mcpforunity://project/layers",
|
|
17
17
|
name="project_layers",
|
|
18
|
-
description="All layers defined in the project's TagManager with their indices (0-31). Read this before using add_layer or remove_layer tools
|
|
18
|
+
description="All layers defined in the project's TagManager with their indices (0-31). Read this before using add_layer or remove_layer tools.\n\nURI: mcpforunity://project/layers"
|
|
19
19
|
)
|
|
20
20
|
async def get_layers(ctx: Context) -> LayersResponse | MCPResponse:
|
|
21
21
|
"""Get all project layers with their indices."""
|
services/resources/menu_items.py
CHANGED
|
@@ -14,7 +14,7 @@ class GetMenuItemsResponse(MCPResponse):
|
|
|
14
14
|
@mcp_for_unity_resource(
|
|
15
15
|
uri="mcpforunity://menu-items",
|
|
16
16
|
name="menu_items",
|
|
17
|
-
description="Provides a list of all menu items
|
|
17
|
+
description="Provides a list of all menu items.\n\nURI: mcpforunity://menu-items"
|
|
18
18
|
)
|
|
19
19
|
async def get_menu_items(ctx: Context) -> GetMenuItemsResponse | MCPResponse:
|
|
20
20
|
"""Provides a list of all menu items.
|
services/resources/prefab.py
CHANGED
|
@@ -43,7 +43,7 @@ def _decode_prefab_path(encoded_path: str) -> str:
|
|
|
43
43
|
@mcp_for_unity_resource(
|
|
44
44
|
uri="mcpforunity://prefab-api",
|
|
45
45
|
name="prefab_api",
|
|
46
|
-
description="Documentation for Prefab resources. Use manage_asset action=search filterType=Prefab to find prefabs, then access resources below
|
|
46
|
+
description="Documentation for Prefab resources. Use manage_asset action=search filterType=Prefab to find prefabs, then access resources below.\n\nURI: mcpforunity://prefab-api"
|
|
47
47
|
)
|
|
48
48
|
async def get_prefab_api_docs(_ctx: Context) -> MCPResponse:
|
|
49
49
|
"""
|
|
@@ -117,7 +117,7 @@ class PrefabInfoResponse(MCPResponse):
|
|
|
117
117
|
@mcp_for_unity_resource(
|
|
118
118
|
uri="mcpforunity://prefab/{encoded_path}",
|
|
119
119
|
name="prefab_info",
|
|
120
|
-
description="Get detailed information about a prefab asset by URL-encoded path. Returns prefab type, root object name, component types, child count, and variant info
|
|
120
|
+
description="Get detailed information about a prefab asset by URL-encoded path. Returns prefab type, root object name, component types, child count, and variant info.\n\nURI: mcpforunity://prefab/{encoded_path}"
|
|
121
121
|
)
|
|
122
122
|
async def get_prefab_info(ctx: Context, encoded_path: str) -> MCPResponse:
|
|
123
123
|
"""Get prefab asset info by path."""
|
|
@@ -169,7 +169,7 @@ class PrefabHierarchyResponse(MCPResponse):
|
|
|
169
169
|
@mcp_for_unity_resource(
|
|
170
170
|
uri="mcpforunity://prefab/{encoded_path}/hierarchy",
|
|
171
171
|
name="prefab_hierarchy",
|
|
172
|
-
description="Get the full hierarchy of a prefab with nested prefab information. Returns all GameObjects with their components and nesting depth
|
|
172
|
+
description="Get the full hierarchy of a prefab with nested prefab information. Returns all GameObjects with their components and nesting depth.\n\nURI: mcpforunity://prefab/{encoded_path}/hierarchy"
|
|
173
173
|
)
|
|
174
174
|
async def get_prefab_hierarchy(ctx: Context, encoded_path: str) -> MCPResponse:
|
|
175
175
|
"""Get prefab hierarchy by path."""
|
|
@@ -25,7 +25,7 @@ class PrefabStageResponse(MCPResponse):
|
|
|
25
25
|
@mcp_for_unity_resource(
|
|
26
26
|
uri="mcpforunity://editor/prefab-stage",
|
|
27
27
|
name="editor_prefab_stage",
|
|
28
|
-
description="Current prefab editing context if a prefab is open in isolation mode. Returns isOpen=false if no prefab is being edited
|
|
28
|
+
description="Current prefab editing context if a prefab is open in isolation mode. Returns isOpen=false if no prefab is being edited.\n\nURI: mcpforunity://editor/prefab-stage"
|
|
29
29
|
)
|
|
30
30
|
async def get_prefab_stage(ctx: Context) -> PrefabStageResponse | MCPResponse:
|
|
31
31
|
"""Get current prefab stage information."""
|
|
@@ -25,7 +25,7 @@ class ProjectInfoResponse(MCPResponse):
|
|
|
25
25
|
@mcp_for_unity_resource(
|
|
26
26
|
uri="mcpforunity://project/info",
|
|
27
27
|
name="project_info",
|
|
28
|
-
description="Static project information including root path, Unity version, and platform. This data rarely changes
|
|
28
|
+
description="Static project information including root path, Unity version, and platform. This data rarely changes.\n\nURI: mcpforunity://project/info"
|
|
29
29
|
)
|
|
30
30
|
async def get_project_info(ctx: Context) -> ProjectInfoResponse | MCPResponse:
|
|
31
31
|
"""Get static project configuration information."""
|
services/resources/selection.py
CHANGED
|
@@ -41,7 +41,7 @@ class SelectionResponse(MCPResponse):
|
|
|
41
41
|
@mcp_for_unity_resource(
|
|
42
42
|
uri="mcpforunity://editor/selection",
|
|
43
43
|
name="editor_selection",
|
|
44
|
-
description="Detailed information about currently selected objects in the editor, including GameObjects, assets, and their properties
|
|
44
|
+
description="Detailed information about currently selected objects in the editor, including GameObjects, assets, and their properties.\n\nURI: mcpforunity://editor/selection"
|
|
45
45
|
)
|
|
46
46
|
async def get_selection(ctx: Context) -> SelectionResponse | MCPResponse:
|
|
47
47
|
"""Get detailed editor selection information."""
|
services/resources/tags.py
CHANGED
|
@@ -16,7 +16,7 @@ class TagsResponse(MCPResponse):
|
|
|
16
16
|
@mcp_for_unity_resource(
|
|
17
17
|
uri="mcpforunity://project/tags",
|
|
18
18
|
name="project_tags",
|
|
19
|
-
description="All tags defined in the project's TagManager. Read this before using add_tag or remove_tag tools
|
|
19
|
+
description="All tags defined in the project's TagManager. Read this before using add_tag or remove_tag tools.\n\nURI: mcpforunity://project/tags"
|
|
20
20
|
)
|
|
21
21
|
async def get_tags(ctx: Context) -> TagsResponse | MCPResponse:
|
|
22
22
|
"""Get all project tags."""
|
services/resources/tests.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Annotated, Literal
|
|
1
|
+
from typing import Annotated, Literal, Optional
|
|
2
2
|
from pydantic import BaseModel, Field
|
|
3
3
|
|
|
4
4
|
from fastmcp import Context
|
|
@@ -17,15 +17,36 @@ class TestItem(BaseModel):
|
|
|
17
17
|
Field(description="The mode the test is for.")]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class PaginatedTestsData(BaseModel):
|
|
21
|
+
"""Paginated test results."""
|
|
22
|
+
items: list[TestItem] = Field(description="Tests on current page")
|
|
23
|
+
cursor: int = Field(description="Current page cursor (0-based)")
|
|
24
|
+
nextCursor: Optional[int] = Field(None, description="Next page cursor, null if last page")
|
|
25
|
+
totalCount: int = Field(description="Total number of tests across all pages")
|
|
26
|
+
pageSize: int = Field(description="Number of items per page")
|
|
27
|
+
hasMore: bool = Field(description="Whether there are more items after this page")
|
|
28
|
+
|
|
29
|
+
|
|
20
30
|
class GetTestsResponse(MCPResponse):
|
|
21
|
-
|
|
31
|
+
"""Response containing paginated test data."""
|
|
32
|
+
data: PaginatedTestsData = Field(description="Paginated test data")
|
|
22
33
|
|
|
23
34
|
|
|
24
|
-
@mcp_for_unity_resource(
|
|
35
|
+
@mcp_for_unity_resource(
|
|
36
|
+
uri="mcpforunity://tests",
|
|
37
|
+
name="get_tests",
|
|
38
|
+
description="Provides the first page of Unity tests (default 50 items). "
|
|
39
|
+
"For filtering or pagination, use the run_tests tool instead.\n\nURI: mcpforunity://tests"
|
|
40
|
+
)
|
|
25
41
|
async def get_tests(ctx: Context) -> GetTestsResponse | MCPResponse:
|
|
26
|
-
"""Provides a list of all tests.
|
|
42
|
+
"""Provides a paginated list of all Unity tests.
|
|
43
|
+
|
|
44
|
+
Returns the first page of tests using Unity's default pagination (50 items).
|
|
45
|
+
For advanced filtering or pagination control, use the run_tests tool which
|
|
46
|
+
accepts mode, filter, page_size, and cursor parameters.
|
|
27
47
|
"""
|
|
28
48
|
unity_instance = get_unity_instance_from_context(ctx)
|
|
49
|
+
|
|
29
50
|
response = await send_with_unity_instance(
|
|
30
51
|
async_send_command_with_retry,
|
|
31
52
|
unity_instance,
|
|
@@ -35,17 +56,28 @@ async def get_tests(ctx: Context) -> GetTestsResponse | MCPResponse:
|
|
|
35
56
|
return GetTestsResponse(**response) if isinstance(response, dict) else response
|
|
36
57
|
|
|
37
58
|
|
|
38
|
-
@mcp_for_unity_resource(
|
|
59
|
+
@mcp_for_unity_resource(
|
|
60
|
+
uri="mcpforunity://tests/{mode}",
|
|
61
|
+
name="get_tests_for_mode",
|
|
62
|
+
description="Provides the first page of tests for a specific mode (EditMode or PlayMode). "
|
|
63
|
+
"For filtering or pagination, use the run_tests tool instead.\n\nURI: mcpforunity://tests/{mode}"
|
|
64
|
+
)
|
|
39
65
|
async def get_tests_for_mode(
|
|
40
66
|
ctx: Context,
|
|
41
|
-
mode: Annotated[Literal["EditMode", "PlayMode"], Field(
|
|
67
|
+
mode: Annotated[Literal["EditMode", "PlayMode"], Field(
|
|
68
|
+
description="The mode to filter tests by (EditMode or PlayMode)."
|
|
69
|
+
)],
|
|
42
70
|
) -> GetTestsResponse | MCPResponse:
|
|
43
|
-
"""Provides
|
|
71
|
+
"""Provides the first page of tests for a specific mode.
|
|
44
72
|
|
|
45
73
|
Args:
|
|
46
|
-
mode: The test mode to filter by (EditMode or PlayMode)
|
|
74
|
+
mode: The test mode to filter by (EditMode or PlayMode)
|
|
75
|
+
|
|
76
|
+
Returns the first page of tests using Unity's default pagination (50 items).
|
|
77
|
+
For advanced filtering or pagination control, use the run_tests tool.
|
|
47
78
|
"""
|
|
48
79
|
unity_instance = get_unity_instance_from_context(ctx)
|
|
80
|
+
|
|
49
81
|
response = await send_with_unity_instance(
|
|
50
82
|
async_send_command_with_retry,
|
|
51
83
|
unity_instance,
|
|
@@ -13,7 +13,7 @@ from transport.unity_transport import _current_transport
|
|
|
13
13
|
@mcp_for_unity_resource(
|
|
14
14
|
uri="mcpforunity://instances",
|
|
15
15
|
name="unity_instances",
|
|
16
|
-
description="Lists all running Unity Editor instances with their details
|
|
16
|
+
description="Lists all running Unity Editor instances with their details.\n\nURI: mcpforunity://instances"
|
|
17
17
|
)
|
|
18
18
|
async def unity_instances(ctx: Context) -> dict[str, Any]:
|
|
19
19
|
"""
|
services/resources/windows.py
CHANGED
|
@@ -33,7 +33,7 @@ class WindowsResponse(MCPResponse):
|
|
|
33
33
|
@mcp_for_unity_resource(
|
|
34
34
|
uri="mcpforunity://editor/windows",
|
|
35
35
|
name="editor_windows",
|
|
36
|
-
description="All currently open editor windows with their titles, types, positions, and focus state
|
|
36
|
+
description="All currently open editor windows with their titles, types, positions, and focus state.\n\nURI: mcpforunity://editor/windows"
|
|
37
37
|
)
|
|
38
38
|
async def get_windows(ctx: Context) -> WindowsResponse | MCPResponse:
|
|
39
39
|
"""Get all open editor windows."""
|
services/tools/__init__.py
CHANGED
|
@@ -51,7 +51,9 @@ def register_all_tools(mcp: FastMCP, *, project_scoped_tools: bool = True):
|
|
|
51
51
|
"Skipping execute_custom_tool registration (project-scoped tools disabled)")
|
|
52
52
|
continue
|
|
53
53
|
|
|
54
|
-
# Apply
|
|
54
|
+
# Apply decorators: logging -> telemetry -> mcp.tool
|
|
55
|
+
# Note: Parameter normalization (camelCase -> snake_case) is handled by
|
|
56
|
+
# ParamNormalizerMiddleware before FastMCP validation
|
|
55
57
|
wrapped = log_execution(tool_name, "Tool")(func)
|
|
56
58
|
wrapped = telemetry_tool(tool_name)(wrapped)
|
|
57
59
|
wrapped = mcp.tool(
|
|
@@ -5,6 +5,7 @@ Returns only instance IDs with pagination support for efficient searches.
|
|
|
5
5
|
from typing import Annotated, Any, Literal
|
|
6
6
|
|
|
7
7
|
from fastmcp import Context
|
|
8
|
+
from pydantic import Field
|
|
8
9
|
from services.registry import mcp_for_unity_tool
|
|
9
10
|
from services.tools import get_unity_instance_from_context
|
|
10
11
|
from transport.unity_transport import send_with_unity_instance
|
|
@@ -14,22 +15,42 @@ from services.tools.preflight import preflight
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@mcp_for_unity_tool(
|
|
17
|
-
description="Search for GameObjects in the scene. Returns instance IDs only (paginated)
|
|
18
|
+
description="Search for GameObjects in the scene. Requires search_term (name, tag, layer name, component type, or path). Returns instance IDs only (paginated). Use mcpforunity://scene/gameobject/{id} resource to get full GameObject data."
|
|
18
19
|
)
|
|
19
20
|
async def find_gameobjects(
|
|
20
21
|
ctx: Context,
|
|
21
|
-
search_term: Annotated[
|
|
22
|
+
search_term: Annotated[
|
|
23
|
+
str,
|
|
24
|
+
Field(description="The value to search for (name, tag, layer name, component type, or path)")
|
|
25
|
+
],
|
|
22
26
|
search_method: Annotated[
|
|
23
|
-
Literal["by_name", "by_tag", "by_layer",
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
Literal["by_name", "by_tag", "by_layer", "by_component", "by_path", "by_id"],
|
|
28
|
+
Field(
|
|
29
|
+
default="by_name",
|
|
30
|
+
description="How to search for GameObjects"
|
|
31
|
+
)
|
|
26
32
|
] = "by_name",
|
|
27
|
-
include_inactive: Annotated[
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
include_inactive: Annotated[
|
|
34
|
+
bool | str | None,
|
|
35
|
+
Field(
|
|
36
|
+
default=None,
|
|
37
|
+
description="Include inactive GameObjects in search"
|
|
38
|
+
)
|
|
39
|
+
] = None,
|
|
40
|
+
page_size: Annotated[
|
|
41
|
+
int | str | None,
|
|
42
|
+
Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="Number of results per page (default: 50, max: 500)"
|
|
45
|
+
)
|
|
46
|
+
] = None,
|
|
47
|
+
cursor: Annotated[
|
|
48
|
+
int | str | None,
|
|
49
|
+
Field(
|
|
50
|
+
default=None,
|
|
51
|
+
description="Pagination cursor (offset for next page)"
|
|
52
|
+
)
|
|
53
|
+
] = None,
|
|
33
54
|
) -> dict[str, Any]:
|
|
34
55
|
"""
|
|
35
56
|
Search for GameObjects and return their instance IDs.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import math
|
|
3
2
|
from typing import Annotated, Any, Literal
|
|
4
3
|
|
|
5
4
|
from fastmcp import Context
|
|
@@ -9,64 +8,10 @@ from services.registry import mcp_for_unity_tool
|
|
|
9
8
|
from services.tools import get_unity_instance_from_context
|
|
10
9
|
from transport.unity_transport import send_with_unity_instance
|
|
11
10
|
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
12
|
-
from services.tools.utils import coerce_bool, parse_json_payload, coerce_int
|
|
11
|
+
from services.tools.utils import coerce_bool, parse_json_payload, coerce_int, normalize_vector3
|
|
13
12
|
from services.tools.preflight import preflight
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
def _normalize_vector(value: Any, param_name: str = "vector") -> tuple[list[float] | None, str | None]:
|
|
17
|
-
"""
|
|
18
|
-
Robustly normalize a vector parameter to [x, y, z] format.
|
|
19
|
-
Handles: list, tuple, JSON string, comma-separated string.
|
|
20
|
-
Returns (parsed_vector, error_message). If error_message is set, parsed_vector is None.
|
|
21
|
-
"""
|
|
22
|
-
if value is None:
|
|
23
|
-
return None, None
|
|
24
|
-
|
|
25
|
-
# If already a list/tuple with 3 elements, convert to floats
|
|
26
|
-
if isinstance(value, (list, tuple)) and len(value) == 3:
|
|
27
|
-
try:
|
|
28
|
-
vec = [float(value[0]), float(value[1]), float(value[2])]
|
|
29
|
-
if all(math.isfinite(n) for n in vec):
|
|
30
|
-
return vec, None
|
|
31
|
-
return None, f"{param_name} values must be finite numbers, got {value}"
|
|
32
|
-
except (ValueError, TypeError):
|
|
33
|
-
return None, f"{param_name} values must be numbers, got {value}"
|
|
34
|
-
|
|
35
|
-
# Try parsing as JSON string
|
|
36
|
-
if isinstance(value, str):
|
|
37
|
-
# Check for obviously invalid values
|
|
38
|
-
if value in ("[object Object]", "undefined", "null", ""):
|
|
39
|
-
return None, f"{param_name} received invalid value: '{value}'. Expected [x, y, z] array (list or JSON string)"
|
|
40
|
-
|
|
41
|
-
parsed = parse_json_payload(value)
|
|
42
|
-
if isinstance(parsed, list) and len(parsed) == 3:
|
|
43
|
-
try:
|
|
44
|
-
vec = [float(parsed[0]), float(parsed[1]), float(parsed[2])]
|
|
45
|
-
if all(math.isfinite(n) for n in vec):
|
|
46
|
-
return vec, None
|
|
47
|
-
return None, f"{param_name} values must be finite numbers, got {parsed}"
|
|
48
|
-
except (ValueError, TypeError):
|
|
49
|
-
return None, f"{param_name} values must be numbers, got {parsed}"
|
|
50
|
-
|
|
51
|
-
# Handle legacy comma-separated strings "1,2,3" or "[1,2,3]"
|
|
52
|
-
s = value.strip()
|
|
53
|
-
if s.startswith("[") and s.endswith("]"):
|
|
54
|
-
s = s[1:-1]
|
|
55
|
-
parts = [p.strip() for p in (s.split(",") if "," in s else s.split())]
|
|
56
|
-
if len(parts) == 3:
|
|
57
|
-
try:
|
|
58
|
-
vec = [float(parts[0]), float(parts[1]), float(parts[2])]
|
|
59
|
-
if all(math.isfinite(n) for n in vec):
|
|
60
|
-
return vec, None
|
|
61
|
-
return None, f"{param_name} values must be finite numbers, got {value}"
|
|
62
|
-
except (ValueError, TypeError):
|
|
63
|
-
return None, f"{param_name} values must be numbers, got {value}"
|
|
64
|
-
|
|
65
|
-
return None, f"{param_name} must be a [x, y, z] array (list or JSON string), got: {value}"
|
|
66
|
-
|
|
67
|
-
return None, f"{param_name} must be a list or JSON string, got {type(value).__name__}"
|
|
68
|
-
|
|
69
|
-
|
|
70
15
|
def _normalize_component_properties(value: Any) -> tuple[dict[str, dict[str, Any]] | None, str | None]:
|
|
71
16
|
"""
|
|
72
17
|
Robustly normalize component_properties to a dict.
|
|
@@ -115,12 +60,12 @@ async def manage_gameobject(
|
|
|
115
60
|
"Tag name - used for both 'create' (initial tag) and 'modify' (change tag)"] | None = None,
|
|
116
61
|
parent: Annotated[str,
|
|
117
62
|
"Parent GameObject reference - used for both 'create' (initial parent) and 'modify' (change parent)"] | None = None,
|
|
118
|
-
position: Annotated[list[float] | str,
|
|
119
|
-
"Position as [x, y, z] array
|
|
120
|
-
rotation: Annotated[list[float] | str,
|
|
121
|
-
"Rotation as [x, y, z] euler angles array
|
|
122
|
-
scale: Annotated[list[float] | str,
|
|
123
|
-
"Scale as [x, y, z] array
|
|
63
|
+
position: Annotated[list[float] | dict[str, float] | str,
|
|
64
|
+
"Position as [x, y, z] array, {x, y, z} object, or JSON string"] | None = None,
|
|
65
|
+
rotation: Annotated[list[float] | dict[str, float] | str,
|
|
66
|
+
"Rotation as [x, y, z] euler angles array, {x, y, z} object, or JSON string"] | None = None,
|
|
67
|
+
scale: Annotated[list[float] | dict[str, float] | str,
|
|
68
|
+
"Scale as [x, y, z] array, {x, y, z} object, or JSON string"] | None = None,
|
|
124
69
|
components_to_add: Annotated[list[str],
|
|
125
70
|
"List of component names to add"] | None = None,
|
|
126
71
|
primitive_type: Annotated[str,
|
|
@@ -196,16 +141,16 @@ async def manage_gameobject(
|
|
|
196
141
|
}
|
|
197
142
|
|
|
198
143
|
# --- Normalize vector parameters with detailed error handling ---
|
|
199
|
-
position, position_error =
|
|
144
|
+
position, position_error = normalize_vector3(position, "position")
|
|
200
145
|
if position_error:
|
|
201
146
|
return {"success": False, "message": position_error}
|
|
202
|
-
rotation, rotation_error =
|
|
147
|
+
rotation, rotation_error = normalize_vector3(rotation, "rotation")
|
|
203
148
|
if rotation_error:
|
|
204
149
|
return {"success": False, "message": rotation_error}
|
|
205
|
-
scale, scale_error =
|
|
150
|
+
scale, scale_error = normalize_vector3(scale, "scale")
|
|
206
151
|
if scale_error:
|
|
207
152
|
return {"success": False, "message": scale_error}
|
|
208
|
-
offset, offset_error =
|
|
153
|
+
offset, offset_error = normalize_vector3(offset, "offset")
|
|
209
154
|
if offset_error:
|
|
210
155
|
return {"success": False, "message": offset_error}
|
|
211
156
|
|