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
transport/models.py
CHANGED
transport/plugin_hub.py
CHANGED
|
@@ -279,6 +279,7 @@ class PluginHub(WebSocketEndpoint):
|
|
|
279
279
|
project_name = payload.project_name
|
|
280
280
|
project_hash = payload.project_hash
|
|
281
281
|
unity_version = payload.unity_version
|
|
282
|
+
project_path = payload.project_path
|
|
282
283
|
|
|
283
284
|
if not project_hash:
|
|
284
285
|
await websocket.close(code=4400)
|
|
@@ -290,7 +291,7 @@ class PluginHub(WebSocketEndpoint):
|
|
|
290
291
|
response = RegisteredMessage(session_id=session_id)
|
|
291
292
|
await websocket.send_json(response.model_dump())
|
|
292
293
|
|
|
293
|
-
session = await registry.register(session_id, project_name, project_hash, unity_version)
|
|
294
|
+
session = await registry.register(session_id, project_name, project_hash, unity_version, project_path)
|
|
294
295
|
async with lock:
|
|
295
296
|
cls._connections[session.session_id] = websocket
|
|
296
297
|
logger.info(f"Plugin registered: {project_name} ({project_hash})")
|
transport/plugin_registry.py
CHANGED
|
@@ -22,6 +22,7 @@ class PluginSession:
|
|
|
22
22
|
connected_at: datetime
|
|
23
23
|
tools: dict[str, ToolDefinitionModel] = field(default_factory=dict)
|
|
24
24
|
project_id: str | None = None
|
|
25
|
+
project_path: str | None = None # Full path to project root (for focus nudging)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class PluginRegistry:
|
|
@@ -43,6 +44,7 @@ class PluginRegistry:
|
|
|
43
44
|
project_name: str,
|
|
44
45
|
project_hash: str,
|
|
45
46
|
unity_version: str,
|
|
47
|
+
project_path: str | None = None,
|
|
46
48
|
) -> PluginSession:
|
|
47
49
|
"""Register (or replace) a plugin session.
|
|
48
50
|
|
|
@@ -60,6 +62,7 @@ class PluginRegistry:
|
|
|
60
62
|
unity_version=unity_version,
|
|
61
63
|
registered_at=now,
|
|
62
64
|
connected_at=now,
|
|
65
|
+
project_path=project_path,
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
# Remove old mapping for this hash if it existed under a different session
|
transport/unity_transport.py
CHANGED
|
@@ -25,57 +25,6 @@ def _current_transport() -> str:
|
|
|
25
25
|
return "http" if _is_http_transport() else "stdio"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def with_unity_instance(
|
|
29
|
-
log: str | Callable[[Context, tuple, dict, str | None], str] | None = None,
|
|
30
|
-
*,
|
|
31
|
-
kwarg_name: str = "unity_instance",
|
|
32
|
-
):
|
|
33
|
-
def _decorate(fn: Callable[..., T]):
|
|
34
|
-
is_coro = asyncio.iscoroutinefunction(fn)
|
|
35
|
-
|
|
36
|
-
def _compose_message(ctx: Context, a: tuple, k: dict, inst: str | None) -> str | None:
|
|
37
|
-
if log is None:
|
|
38
|
-
return None
|
|
39
|
-
if callable(log):
|
|
40
|
-
try:
|
|
41
|
-
return log(ctx, a, k, inst)
|
|
42
|
-
except Exception:
|
|
43
|
-
return None
|
|
44
|
-
try:
|
|
45
|
-
return str(log).format(unity_instance=inst or "default")
|
|
46
|
-
except Exception:
|
|
47
|
-
return str(log)
|
|
48
|
-
|
|
49
|
-
if is_coro:
|
|
50
|
-
async def _wrapper(ctx: Context, *args, **kwargs):
|
|
51
|
-
inst = get_unity_instance_from_context(ctx)
|
|
52
|
-
msg = _compose_message(ctx, args, kwargs, inst)
|
|
53
|
-
if msg:
|
|
54
|
-
try:
|
|
55
|
-
await ctx.info(msg)
|
|
56
|
-
except Exception:
|
|
57
|
-
pass
|
|
58
|
-
kwargs.setdefault(kwarg_name, inst)
|
|
59
|
-
return await fn(ctx, *args, **kwargs)
|
|
60
|
-
else:
|
|
61
|
-
async def _wrapper(ctx: Context, *args, **kwargs):
|
|
62
|
-
inst = get_unity_instance_from_context(ctx)
|
|
63
|
-
msg = _compose_message(ctx, args, kwargs, inst)
|
|
64
|
-
if msg:
|
|
65
|
-
try:
|
|
66
|
-
await ctx.info(msg)
|
|
67
|
-
except Exception:
|
|
68
|
-
pass
|
|
69
|
-
kwargs.setdefault(kwarg_name, inst)
|
|
70
|
-
return fn(ctx, *args, **kwargs)
|
|
71
|
-
|
|
72
|
-
from functools import wraps
|
|
73
|
-
|
|
74
|
-
return wraps(fn)(_wrapper) # type: ignore[arg-type]
|
|
75
|
-
|
|
76
|
-
return _decorate
|
|
77
|
-
|
|
78
|
-
|
|
79
28
|
async def send_with_unity_instance(
|
|
80
29
|
send_fn: Callable[..., Awaitable[T]],
|
|
81
30
|
unity_instance: str | None,
|
utils/focus_nudge.py
CHANGED
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import asyncio
|
|
12
12
|
import logging
|
|
13
|
+
import os
|
|
13
14
|
import platform
|
|
14
15
|
import shutil
|
|
15
16
|
import subprocess
|
|
@@ -17,9 +18,38 @@ import time
|
|
|
17
18
|
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
def _parse_env_float(env_var: str, default: float) -> float:
|
|
23
|
+
"""Safely parse environment variable as float, logging warnings on failure."""
|
|
24
|
+
value = os.environ.get(env_var)
|
|
25
|
+
if value is None:
|
|
26
|
+
return default
|
|
27
|
+
try:
|
|
28
|
+
parsed = float(value)
|
|
29
|
+
if parsed <= 0:
|
|
30
|
+
logger.warning(f"Invalid {env_var}={value!r}, using default {default}: must be > 0")
|
|
31
|
+
return default
|
|
32
|
+
return parsed
|
|
33
|
+
except (ValueError, TypeError) as e:
|
|
34
|
+
logger.warning(f"Invalid {env_var}={value!r}, using default {default}: {e}")
|
|
35
|
+
return default
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Base interval between nudges (exponentially increases with consecutive nudges)
|
|
39
|
+
# Can be overridden via UNITY_MCP_NUDGE_BASE_INTERVAL_S environment variable
|
|
40
|
+
_BASE_NUDGE_INTERVAL_S = _parse_env_float("UNITY_MCP_NUDGE_BASE_INTERVAL_S", 1.0)
|
|
41
|
+
|
|
42
|
+
# Maximum interval between nudges (cap for exponential backoff)
|
|
43
|
+
# Can be overridden via UNITY_MCP_NUDGE_MAX_INTERVAL_S environment variable
|
|
44
|
+
_MAX_NUDGE_INTERVAL_S = _parse_env_float("UNITY_MCP_NUDGE_MAX_INTERVAL_S", 10.0)
|
|
45
|
+
|
|
46
|
+
# Default duration to keep Unity focused during a nudge
|
|
47
|
+
# Can be overridden via UNITY_MCP_NUDGE_DURATION_S environment variable
|
|
48
|
+
_DEFAULT_FOCUS_DURATION_S = _parse_env_float("UNITY_MCP_NUDGE_DURATION_S", 3.0)
|
|
49
|
+
|
|
22
50
|
_last_nudge_time: float = 0.0
|
|
51
|
+
_consecutive_nudges: int = 0
|
|
52
|
+
_last_progress_time: float = 0.0
|
|
23
53
|
|
|
24
54
|
|
|
25
55
|
def _is_available() -> bool:
|
|
@@ -35,6 +65,61 @@ def _is_available() -> bool:
|
|
|
35
65
|
return False
|
|
36
66
|
|
|
37
67
|
|
|
68
|
+
def _get_current_nudge_interval() -> float:
|
|
69
|
+
"""
|
|
70
|
+
Calculate current nudge interval using exponential backoff.
|
|
71
|
+
|
|
72
|
+
Returns interval based on consecutive nudges without progress:
|
|
73
|
+
- 0 nudges: base interval (1.0s)
|
|
74
|
+
- 1 nudge: base * 2 (2.0s)
|
|
75
|
+
- 2 nudges: base * 4 (4.0s)
|
|
76
|
+
- 3+ nudges: base * 8 (8.0s, capped at max)
|
|
77
|
+
"""
|
|
78
|
+
if _consecutive_nudges == 0:
|
|
79
|
+
return _BASE_NUDGE_INTERVAL_S
|
|
80
|
+
|
|
81
|
+
# Exponential backoff: interval = base * (2 ^ consecutive_nudges)
|
|
82
|
+
interval = _BASE_NUDGE_INTERVAL_S * (2 ** _consecutive_nudges)
|
|
83
|
+
return min(interval, _MAX_NUDGE_INTERVAL_S)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _get_current_focus_duration() -> float:
|
|
87
|
+
"""
|
|
88
|
+
Calculate current focus duration using exponential backoff.
|
|
89
|
+
|
|
90
|
+
Base durations (3, 5, 8, 12 seconds) are scaled proportionally by the
|
|
91
|
+
configured UNITY_MCP_NUDGE_DURATION_S relative to _DEFAULT_FOCUS_DURATION_S.
|
|
92
|
+
For example, if UNITY_MCP_NUDGE_DURATION_S=6.0 (2x default), all durations
|
|
93
|
+
are doubled: (6, 10, 16, 24 seconds).
|
|
94
|
+
"""
|
|
95
|
+
# Base durations for each nudge level
|
|
96
|
+
base_durations = [3.0, 5.0, 8.0, 12.0]
|
|
97
|
+
base_duration = base_durations[min(_consecutive_nudges, len(base_durations) - 1)]
|
|
98
|
+
|
|
99
|
+
# Scale by ratio of configured to default duration (if UNITY_MCP_NUDGE_DURATION_S is set)
|
|
100
|
+
scale = 1.0
|
|
101
|
+
if os.environ.get("UNITY_MCP_NUDGE_DURATION_S") is not None:
|
|
102
|
+
configured_duration = _parse_env_float("UNITY_MCP_NUDGE_DURATION_S", _DEFAULT_FOCUS_DURATION_S)
|
|
103
|
+
if _DEFAULT_FOCUS_DURATION_S > 0:
|
|
104
|
+
scale = configured_duration / _DEFAULT_FOCUS_DURATION_S
|
|
105
|
+
duration = base_duration * scale
|
|
106
|
+
if duration <= 0:
|
|
107
|
+
return _DEFAULT_FOCUS_DURATION_S
|
|
108
|
+
return duration
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def reset_nudge_backoff() -> None:
|
|
112
|
+
"""
|
|
113
|
+
Reset exponential backoff when progress is detected.
|
|
114
|
+
|
|
115
|
+
Call this when test job makes progress to reset the nudge interval
|
|
116
|
+
back to the base interval for quick response to future stalls.
|
|
117
|
+
"""
|
|
118
|
+
global _consecutive_nudges, _last_progress_time
|
|
119
|
+
_consecutive_nudges = 0
|
|
120
|
+
_last_progress_time = time.monotonic()
|
|
121
|
+
|
|
122
|
+
|
|
38
123
|
def _get_frontmost_app_macos() -> str | None:
|
|
39
124
|
"""Get the name of the frontmost application on macOS."""
|
|
40
125
|
try:
|
|
@@ -54,21 +139,161 @@ def _get_frontmost_app_macos() -> str | None:
|
|
|
54
139
|
return None
|
|
55
140
|
|
|
56
141
|
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
142
|
+
def _find_unity_pid_by_project_path(project_path: str) -> int | None:
|
|
143
|
+
"""Find Unity Editor PID by matching project path in command line args.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
project_path: Full path to Unity project root, OR just the project name.
|
|
147
|
+
- Full path: "/Users/name/Projects/MyGame"
|
|
148
|
+
- Project name: "MyGame" (will match any path ending with this)
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
PID of matching Unity process, or None if not found
|
|
152
|
+
"""
|
|
59
153
|
try:
|
|
154
|
+
# Use ps to find Unity processes with -projectpath argument
|
|
60
155
|
result = subprocess.run(
|
|
61
|
-
["
|
|
156
|
+
["ps", "aux"],
|
|
62
157
|
capture_output=True,
|
|
63
158
|
text=True,
|
|
64
159
|
timeout=5,
|
|
65
160
|
)
|
|
66
|
-
|
|
161
|
+
if result.returncode != 0:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
# Determine if project_path is a full path or just a name
|
|
165
|
+
is_full_path = "/" in project_path or "\\" in project_path
|
|
166
|
+
|
|
167
|
+
# Look for Unity.app processes with matching -projectpath
|
|
168
|
+
for line in result.stdout.splitlines():
|
|
169
|
+
if "Unity.app/Contents/MacOS/Unity" not in line:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Check for -projectpath argument
|
|
173
|
+
if "-projectpath" not in line:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
if is_full_path:
|
|
177
|
+
# Exact match for full path
|
|
178
|
+
if f"-projectpath {project_path}" not in line:
|
|
179
|
+
continue
|
|
180
|
+
else:
|
|
181
|
+
# Match if path ends with project name (e.g., ".../UnityMCPTests")
|
|
182
|
+
if "-projectpath" in line:
|
|
183
|
+
# Extract the path after -projectpath
|
|
184
|
+
try:
|
|
185
|
+
parts = line.split("-projectpath", 1)[1].split()[0]
|
|
186
|
+
if not parts.endswith(f"/{project_path}") and not parts.endswith(f"\\{project_path}") and parts != project_path:
|
|
187
|
+
continue
|
|
188
|
+
except (IndexError, ValueError):
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
# Extract PID (second column in ps aux output)
|
|
192
|
+
parts = line.split()
|
|
193
|
+
if len(parts) >= 2:
|
|
194
|
+
try:
|
|
195
|
+
pid = int(parts[1])
|
|
196
|
+
logger.debug(f"Found Unity PID {pid} for project path/name {project_path}")
|
|
197
|
+
return pid
|
|
198
|
+
except ValueError:
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
logger.warning(f"No Unity process found with project path/name {project_path}")
|
|
202
|
+
return None
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.debug(f"Failed to find Unity PID: {e}")
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _focus_app_macos(app_name: str, unity_project_path: str | None = None) -> bool:
|
|
209
|
+
"""Focus an application by name on macOS.
|
|
210
|
+
|
|
211
|
+
For Unity, can target a specific instance by project path (multi-instance support).
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
app_name: Application name to focus ("Unity" or specific app name)
|
|
215
|
+
unity_project_path: For Unity apps, the full project root path to match against
|
|
216
|
+
-projectpath command line arg (e.g., "/path/to/project" NOT "/path/to/project/Assets")
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
# For Unity, use PID-based activation for precise targeting
|
|
220
|
+
if app_name == "Unity":
|
|
221
|
+
if unity_project_path:
|
|
222
|
+
# Find specific Unity instance by project path
|
|
223
|
+
pid = _find_unity_pid_by_project_path(unity_project_path)
|
|
224
|
+
if pid is None:
|
|
225
|
+
logger.warning(f"Could not find Unity PID for project {unity_project_path}, falling back to any Unity")
|
|
226
|
+
return _focus_any_unity_macos()
|
|
227
|
+
|
|
228
|
+
# Two-step activation for full Unity wake-up:
|
|
229
|
+
# 1. Bring window to front
|
|
230
|
+
# 2. Activate the application bundle (triggers full app activation like cmd+tab or clicking)
|
|
231
|
+
script = f'''
|
|
232
|
+
tell application "System Events"
|
|
233
|
+
set targetProc to first process whose unix id is {pid}
|
|
234
|
+
set frontmost of targetProc to true
|
|
235
|
+
|
|
236
|
+
-- Get bundle identifier to activate the app properly
|
|
237
|
+
set bundleID to bundle identifier of targetProc
|
|
238
|
+
end tell
|
|
239
|
+
|
|
240
|
+
-- Activate using bundle identifier (ensures Unity wakes up and starts processing)
|
|
241
|
+
tell application id bundleID to activate
|
|
242
|
+
'''
|
|
243
|
+
result = subprocess.run(
|
|
244
|
+
["osascript", "-e", script],
|
|
245
|
+
capture_output=True,
|
|
246
|
+
text=True,
|
|
247
|
+
timeout=5,
|
|
248
|
+
)
|
|
249
|
+
if result.returncode != 0:
|
|
250
|
+
logger.debug(f"Failed to activate Unity PID {pid}: {result.stderr}")
|
|
251
|
+
return False
|
|
252
|
+
logger.info(f"Activated Unity instance with PID {pid} for project {unity_project_path}")
|
|
253
|
+
return True
|
|
254
|
+
else:
|
|
255
|
+
# No project path provided - activate any Unity process
|
|
256
|
+
return _focus_any_unity_macos()
|
|
257
|
+
else:
|
|
258
|
+
# For other apps, use direct activation
|
|
259
|
+
# Escape double quotes in app_name to prevent AppleScript injection
|
|
260
|
+
escaped_app_name = app_name.replace('"', '\\"')
|
|
261
|
+
result = subprocess.run(
|
|
262
|
+
["osascript", "-e", f'tell application "{escaped_app_name}" to activate'],
|
|
263
|
+
capture_output=True,
|
|
264
|
+
text=True,
|
|
265
|
+
timeout=5,
|
|
266
|
+
)
|
|
267
|
+
return result.returncode == 0
|
|
67
268
|
except Exception as e:
|
|
68
269
|
logger.debug(f"Failed to focus app {app_name}: {e}")
|
|
69
270
|
return False
|
|
70
271
|
|
|
71
272
|
|
|
273
|
+
def _focus_any_unity_macos() -> bool:
|
|
274
|
+
"""Focus any Unity process on macOS (fallback when no project path specified)."""
|
|
275
|
+
try:
|
|
276
|
+
script = '''
|
|
277
|
+
tell application "System Events"
|
|
278
|
+
set unityProc to first process whose name contains "Unity"
|
|
279
|
+
set frontmost of unityProc to true
|
|
280
|
+
end tell
|
|
281
|
+
'''
|
|
282
|
+
result = subprocess.run(
|
|
283
|
+
["osascript", "-e", script],
|
|
284
|
+
capture_output=True,
|
|
285
|
+
text=True,
|
|
286
|
+
timeout=5,
|
|
287
|
+
)
|
|
288
|
+
if result.returncode != 0:
|
|
289
|
+
logger.debug(f"Failed to activate Unity via System Events: {result.stderr}")
|
|
290
|
+
return False
|
|
291
|
+
return True
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.debug(f"Failed to focus Unity: {e}")
|
|
294
|
+
return False
|
|
295
|
+
|
|
296
|
+
|
|
72
297
|
def _get_frontmost_app_windows() -> str | None:
|
|
73
298
|
"""Get the title of the frontmost window on Windows."""
|
|
74
299
|
try:
|
|
@@ -212,11 +437,17 @@ def _get_frontmost_app() -> str | None:
|
|
|
212
437
|
return None
|
|
213
438
|
|
|
214
439
|
|
|
215
|
-
def _focus_app(app_or_window: str) -> bool:
|
|
216
|
-
"""Focus an application/window (platform-specific).
|
|
440
|
+
def _focus_app(app_or_window: str, unity_project_path: str | None = None) -> bool:
|
|
441
|
+
"""Focus an application/window (platform-specific).
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
app_or_window: Application name to focus
|
|
445
|
+
unity_project_path: For Unity apps on macOS, the full project root path for
|
|
446
|
+
multi-instance support
|
|
447
|
+
"""
|
|
217
448
|
system = platform.system()
|
|
218
449
|
if system == "Darwin":
|
|
219
|
-
return _focus_app_macos(app_or_window)
|
|
450
|
+
return _focus_app_macos(app_or_window, unity_project_path)
|
|
220
451
|
elif system == "Windows":
|
|
221
452
|
return _focus_app_windows(app_or_window)
|
|
222
453
|
elif system == "Linux":
|
|
@@ -225,29 +456,46 @@ def _focus_app(app_or_window: str) -> bool:
|
|
|
225
456
|
|
|
226
457
|
|
|
227
458
|
async def nudge_unity_focus(
|
|
228
|
-
focus_duration_s: float =
|
|
459
|
+
focus_duration_s: float | None = None,
|
|
229
460
|
force: bool = False,
|
|
461
|
+
unity_project_path: str | None = None,
|
|
230
462
|
) -> bool:
|
|
231
463
|
"""
|
|
232
464
|
Temporarily focus Unity to allow it to process, then return focus.
|
|
233
465
|
|
|
466
|
+
Uses exponential backoff for both interval and duration:
|
|
467
|
+
- Interval: 1s, 2s, 4s, 8s, 10s (time between nudges)
|
|
468
|
+
- Duration: 3s, 5s, 8s, 12s (how long Unity stays focused)
|
|
469
|
+
Resets on progress.
|
|
470
|
+
|
|
234
471
|
Args:
|
|
235
|
-
focus_duration_s: How long to keep Unity focused (seconds)
|
|
472
|
+
focus_duration_s: How long to keep Unity focused (seconds).
|
|
473
|
+
If None, uses exponential backoff (3s/5s/8s/12s based on consecutive nudges).
|
|
474
|
+
Can be overridden with UNITY_MCP_NUDGE_DURATION_S env var.
|
|
236
475
|
force: If True, ignore the minimum interval between nudges
|
|
476
|
+
unity_project_path: Full path to Unity project root for multi-instance support.
|
|
477
|
+
e.g., "/Users/name/project" (NOT "/Users/name/project/Assets")
|
|
478
|
+
If None, targets any Unity process.
|
|
237
479
|
|
|
238
480
|
Returns:
|
|
239
481
|
True if nudge was performed, False if skipped or failed
|
|
240
482
|
"""
|
|
241
|
-
|
|
483
|
+
if focus_duration_s is None:
|
|
484
|
+
# Use exponential backoff for focus duration
|
|
485
|
+
focus_duration_s = _get_current_focus_duration()
|
|
486
|
+
if focus_duration_s <= 0:
|
|
487
|
+
focus_duration_s = _DEFAULT_FOCUS_DURATION_S
|
|
488
|
+
global _last_nudge_time, _consecutive_nudges
|
|
242
489
|
|
|
243
490
|
if not _is_available():
|
|
244
491
|
logger.debug("Focus nudging not available on this platform")
|
|
245
492
|
return False
|
|
246
493
|
|
|
247
|
-
# Rate limit nudges
|
|
494
|
+
# Rate limit nudges using exponential backoff
|
|
248
495
|
now = time.monotonic()
|
|
249
|
-
|
|
250
|
-
|
|
496
|
+
current_interval = _get_current_nudge_interval()
|
|
497
|
+
if not force and (now - _last_nudge_time) < current_interval:
|
|
498
|
+
logger.debug(f"Skipping nudge - too soon since last nudge (interval: {current_interval:.1f}s)")
|
|
251
499
|
return False
|
|
252
500
|
|
|
253
501
|
# Get current frontmost app
|
|
@@ -261,21 +509,35 @@ async def nudge_unity_focus(
|
|
|
261
509
|
logger.debug("Unity already focused, no nudge needed")
|
|
262
510
|
return False
|
|
263
511
|
|
|
264
|
-
|
|
265
|
-
|
|
512
|
+
project_info = f" for {unity_project_path}" if unity_project_path else ""
|
|
513
|
+
logger.info(f"Nudging Unity focus{project_info} (interval: {current_interval:.1f}s, consecutive: {_consecutive_nudges}, duration: {focus_duration_s:.1f}s, will return to {original_app})")
|
|
266
514
|
|
|
267
|
-
# Focus Unity
|
|
268
|
-
if not _focus_app("Unity"):
|
|
269
|
-
logger.warning("Failed to focus Unity")
|
|
515
|
+
# Focus Unity (with optional project path for multi-instance support)
|
|
516
|
+
if not _focus_app("Unity", unity_project_path):
|
|
517
|
+
logger.warning(f"Failed to focus Unity{project_info}")
|
|
270
518
|
return False
|
|
271
519
|
|
|
272
|
-
# Wait for
|
|
520
|
+
# Wait for window switch animation to complete before starting timer
|
|
521
|
+
# macOS activate is asynchronous, so Unity might not be visible yet
|
|
522
|
+
await asyncio.sleep(0.5)
|
|
523
|
+
|
|
524
|
+
# Verify Unity is actually focused now
|
|
525
|
+
current_app = _get_frontmost_app()
|
|
526
|
+
if current_app and "Unity" not in current_app:
|
|
527
|
+
logger.warning(f"Unity activation didn't complete - current app is {current_app}")
|
|
528
|
+
# Continue anyway in case Unity is processing in background
|
|
529
|
+
|
|
530
|
+
# Only update state after successful activation attempt
|
|
531
|
+
_last_nudge_time = now
|
|
532
|
+
_consecutive_nudges += 1
|
|
533
|
+
|
|
534
|
+
# Wait for Unity to process (actual working time)
|
|
273
535
|
await asyncio.sleep(focus_duration_s)
|
|
274
536
|
|
|
275
537
|
# Return focus to original app
|
|
276
538
|
if original_app and original_app != "Unity":
|
|
277
539
|
if _focus_app(original_app):
|
|
278
|
-
logger.info(f"Returned focus to {original_app}")
|
|
540
|
+
logger.info(f"Returned focus to {original_app} after {focus_duration_s:.1f}s Unity focus")
|
|
279
541
|
else:
|
|
280
542
|
logger.warning(f"Failed to return focus to {original_app}")
|
|
281
543
|
|
|
@@ -287,17 +549,23 @@ def should_nudge(
|
|
|
287
549
|
editor_is_focused: bool,
|
|
288
550
|
last_update_unix_ms: int | None,
|
|
289
551
|
current_time_ms: int | None = None,
|
|
290
|
-
stall_threshold_ms: int =
|
|
552
|
+
stall_threshold_ms: int = 3_000,
|
|
291
553
|
) -> bool:
|
|
292
554
|
"""
|
|
293
555
|
Determine if we should nudge Unity based on test job state.
|
|
294
556
|
|
|
557
|
+
Works with exponential backoff in nudge_unity_focus():
|
|
558
|
+
- First nudge happens after 3s of no progress
|
|
559
|
+
- Subsequent nudges use exponential backoff (1s, 2s, 4s, 8s, 10s max)
|
|
560
|
+
- Backoff resets when progress is detected (call reset_nudge_backoff())
|
|
561
|
+
|
|
295
562
|
Args:
|
|
296
563
|
status: Job status ("running", "succeeded", "failed")
|
|
297
564
|
editor_is_focused: Whether Unity reports being focused
|
|
298
565
|
last_update_unix_ms: Last time the job was updated (Unix ms)
|
|
299
566
|
current_time_ms: Current time (Unix ms), or None to use current time
|
|
300
567
|
stall_threshold_ms: How long without updates before considering it stalled
|
|
568
|
+
(default 3s for quick stall detection with exponential backoff)
|
|
301
569
|
|
|
302
570
|
Returns:
|
|
303
571
|
True if conditions suggest a nudge would help
|
|
@@ -1,101 +0,0 @@
|
|
|
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=emBE5oKhFQNU8V2ENm9E5N4Grj0Tah9H0X7fF6grQdk,2442
|
|
6
|
-
cli/commands/asset.py,sha256=V1xzLgBPhdRzXsnj9Wt2HnJYo_8hT3RqoVnR2WrLP5w,7988
|
|
7
|
-
cli/commands/audio.py,sha256=qJ-Whc8aH7oUgT79O_RRRo-lAVktFqtC5pgbyG2bRNo,3333
|
|
8
|
-
cli/commands/batch.py,sha256=rMe8BDsthZ0AwaDrFoj6Kxl4xAVNRIlKSCcJ5eSagyY,5732
|
|
9
|
-
cli/commands/code.py,sha256=FGV8IDx6eFhcEmc6jREQwHwoOdiUyhY8d6Hy7KN4cTw,5624
|
|
10
|
-
cli/commands/component.py,sha256=uIOtno1T2mPF3rnW2OymetggScqtWrs_Th06FI7FISQ,6327
|
|
11
|
-
cli/commands/editor.py,sha256=oM1g8DNoJZ6slOSfNJYbLN02rQVmIX_a2QLhecc_Qog,14586
|
|
12
|
-
cli/commands/gameobject.py,sha256=b7ZxHXyIgUOvjYhHmKavigs-wfxGB6NhDMqqRyEGtNY,13643
|
|
13
|
-
cli/commands/instance.py,sha256=J6uQrNIEWbnJT-Y09ICTA9R11lgtPQflBbmTrBr5bg8,3041
|
|
14
|
-
cli/commands/lighting.py,sha256=eBvSDhQ5jkoUJJ4sito0yFxXwJv0JlpT4iD-D6Q2Pak,3869
|
|
15
|
-
cli/commands/material.py,sha256=51uxeoTgqnnMuUQUbhBTdMdI70kU4pOCH6GUIy2OjQI,7847
|
|
16
|
-
cli/commands/prefab.py,sha256=E6aWXKyosJH0pJPK8krsRYUrZhHjnCm3iUpIAy4dkes,8177
|
|
17
|
-
cli/commands/scene.py,sha256=P08rud-6FZaO8Tw9jnP0xcS043Bf5IAooGbEDZPVBqw,6274
|
|
18
|
-
cli/commands/script.py,sha256=Yf9o00irn4wf0cbsE665mxJehwtiIr0y3IHKLyvYhgY,6434
|
|
19
|
-
cli/commands/shader.py,sha256=CwIIgyrU9OosVmidD6E9Txmn6Yyo4rDJBubrBchAlVw,6380
|
|
20
|
-
cli/commands/texture.py,sha256=qkvxb94W2B4oqyCi0WI0Cvwvvch5sp-UenBH5xMHnNY,18251
|
|
21
|
-
cli/commands/tool.py,sha256=9JQSUNPinLoDfP1T-STjcrn9A_UdIbGBr_c5G7X4r7k,1754
|
|
22
|
-
cli/commands/ui.py,sha256=JDfAXE3ba45r41Svfop-fiy4p8C0gxE4ekJ8aFRG7aI,7627
|
|
23
|
-
cli/commands/vfx.py,sha256=tmHdaGDUABJ339Ia2Y4MTqr72UnoUOf_LxY69qUnAPg,16373
|
|
24
|
-
cli/utils/__init__.py,sha256=Gbm9hYC7UqwloFwdirXgo6z1iBktR9Y96o3bQcrYudc,613
|
|
25
|
-
cli/utils/config.py,sha256=_k3XAFmXG22sv8tYIb5JmO46kNl3T1sGqFptySAayfc,1550
|
|
26
|
-
cli/utils/connection.py,sha256=T9xmjfil0TAYJg5ZAbeqTtnmIhv5angQNG5vw40Ines,7619
|
|
27
|
-
cli/utils/output.py,sha256=96daU55ta_hl7UeOhNh5Iy7OJ4psbdR9Nfx1-q2k3xA,6370
|
|
28
|
-
cli/utils/suggestions.py,sha256=n6KG3Mrvub28X9rPFYFLRTtZ6HePp3PhhAeojG2WOJw,929
|
|
29
|
-
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
-
core/config.py,sha256=czkTtNji1crQcQbUvmdx4OL7f-RBqkVhj_PtHh-w7rs,1623
|
|
31
|
-
core/logging_decorator.py,sha256=D9CD7rFvQz-MBG-G4inizQj0Ivr6dfc9RBmTrw7q8mI,1383
|
|
32
|
-
core/telemetry.py,sha256=eHjYgzd8f7eTwSwF2Kbi8D4TtJIcdaDjKLeo1c-0hVA,19829
|
|
33
|
-
core/telemetry_decorator.py,sha256=ycSTrzVNCDQHSd-xmIWOpVfKFURPxpiZe_XkOQAGDAo,6705
|
|
34
|
-
mcpforunityserver-9.3.0b20260128055651.dist-info/licenses/LICENSE,sha256=bv5lDJZQEqxBgjjc1rkRbkEwpSIHF-8N-1Od0VnEJFw,1066
|
|
35
|
-
models/__init__.py,sha256=JlscZkGWE9TRmSoBi99v_LSl8OAFNGmr8463PYkXin4,179
|
|
36
|
-
models/models.py,sha256=heXuvdBtdats1SGwW8wKFFHM0qR4hA6A7qETn5s9BZ0,1827
|
|
37
|
-
models/unity_response.py,sha256=oJ1PTsnNc5VBC-9OgM59C0C-R9N-GdmEdmz_yph4GSU,1454
|
|
38
|
-
services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
services/custom_tool_service.py,sha256=WJxljL-hdJE5GMlAhVimHVhQwwnWHCd0StgWhWEFgaI,18592
|
|
40
|
-
services/registry/__init__.py,sha256=QCwcYThvGF0kBt3WR6DBskdyxkegJC7NymEChgJA-YM,470
|
|
41
|
-
services/registry/resource_registry.py,sha256=T_Kznqgvt5kKgV7mU85nb0LlFuB4rg-Tm4Cjhxt-IcI,1467
|
|
42
|
-
services/registry/tool_registry.py,sha256=9tMwOP07JE92QFYUS4KvoysO0qC9pkBD5B79kjRsSPw,1304
|
|
43
|
-
services/resources/__init__.py,sha256=G8uSEYJtiyX3yg0QsfoeGdDXOdbU89l5m0B5Anay1Fc,3054
|
|
44
|
-
services/resources/active_tool.py,sha256=zDuWRK1uz853TrMNv0w8vhZVOxemDPoI4QAkXSIezN8,1480
|
|
45
|
-
services/resources/custom_tools.py,sha256=3t0mKAL9PkJbv8S4DpRFU8D-NlRWkCd2geO6QnlQo7I,1716
|
|
46
|
-
services/resources/editor_state.py,sha256=pQdcsWGcKV7-6icpcVXtFD35CHUXodANc0jXkljVdLs,10823
|
|
47
|
-
services/resources/gameobject.py,sha256=RM28kfsV208zdTy-549U2_nwSPiAHYo6SqXy22k4tC8,9116
|
|
48
|
-
services/resources/layers.py,sha256=wE-mSgZsknGrXKu-0Cppv6NeijszD7beFf88dizT0ZI,1086
|
|
49
|
-
services/resources/menu_items.py,sha256=9SNycjwTXoeS1ZHra0Y1fTyCjSEdPCo34JyxtuqauG8,1021
|
|
50
|
-
services/resources/prefab.py,sha256=z5mTWNke5MQauovbgdNa3HRe5-5B6K7FBspp-OtfGHM,7303
|
|
51
|
-
services/resources/prefab_stage.py,sha256=RyVskG-P9lb4szbsTDhPpyDMb0ptLskr0BnoYJylhw0,1388
|
|
52
|
-
services/resources/project_info.py,sha256=ggiUj9rJUvIddxorKu9yqJiHTWOnxyywkjjsKXhIyqA,1329
|
|
53
|
-
services/resources/selection.py,sha256=MALwKkM9xsKing2bALNVTVLWzDTE_b26EVbnVUGZivU,1845
|
|
54
|
-
services/resources/tags.py,sha256=IKZWiZhBO_HkJqFXqBvWeIcMxhGN_QXkonzuAEFsEfg,1055
|
|
55
|
-
services/resources/tests.py,sha256=xDvvgesPSU93nLD_ERQopOpkpq69pbMEqmFsJd0jekI,2063
|
|
56
|
-
services/resources/unity_instances.py,sha256=XRR5YCDe8v_FXG45VlSdEPaqu7Qlbnm4NYIRzK5brjc,4354
|
|
57
|
-
services/resources/windows.py,sha256=FyzPEtEmfKiXYh1lviemZ7-bFyjkAR61_seSTXQA9rk,1433
|
|
58
|
-
services/state/external_changes_scanner.py,sha256=ZiXu8ZcK5B-hv7CaJLmnEIa9JxzgOBpdmrsRDY2eK5I,9052
|
|
59
|
-
services/tools/__init__.py,sha256=mS9EpbPWchYj6gNW1eu0REv-SLPsQkY8xTkk7u-DeMU,2607
|
|
60
|
-
services/tools/batch_execute.py,sha256=hjh67kgWvQDHyGd2N-Tfezv9WAj5x_pWTt_Vybmmq7s,3501
|
|
61
|
-
services/tools/debug_request_context.py,sha256=Duq5xiuSmRO5GdvWAlZhCfOfmrwvK7gGkRC4wYnXmXk,2907
|
|
62
|
-
services/tools/execute_custom_tool.py,sha256=hiZbm2A9t84f92jitzvkE2G4CMOIUiDVm7u5B8K-RbU,1527
|
|
63
|
-
services/tools/execute_menu_item.py,sha256=k4J89LlXmEGyo9z3NK8Q0vREIzr11ucF_9tN_JeQq9M,1248
|
|
64
|
-
services/tools/find_gameobjects.py,sha256=Qpfd_oQG0fluz8S1CfriGh1FmLnZ080-ZEZOrAsij8U,3602
|
|
65
|
-
services/tools/find_in_file.py,sha256=SxhMeo8lRrt0OiGApGZSFUnq671bxVfK8qgAsHxLua8,6493
|
|
66
|
-
services/tools/manage_asset.py,sha256=St_iWQWg9icztnRthU78t6JNhJN0AlC6ELiZhn-SNZU,5990
|
|
67
|
-
services/tools/manage_components.py,sha256=2_nKPk9iPAf5VyYiXuRxSkN8U76VNQbMtE68UTPngrw,5061
|
|
68
|
-
services/tools/manage_editor.py,sha256=ShvlSBQRfoNQ0DvqBWak_Hi3MB7tv2WkMKEhrKQipk0,3279
|
|
69
|
-
services/tools/manage_gameobject.py,sha256=AXHT4fcrxvsaX53bypKoz3egY2uFI5kf7JCn2SizfB4,16604
|
|
70
|
-
services/tools/manage_material.py,sha256=Zt-tqGRCmOKTmttsu5yeudFNWzkDBkeuf44av06g-w0,5548
|
|
71
|
-
services/tools/manage_prefabs.py,sha256=mGGuYYpB2b9OV0fxNOtI8WnTZj9KjF7A3Isdzx8GGuI,6973
|
|
72
|
-
services/tools/manage_scene.py,sha256=-ARtRuj7ZNk_14lmMSORnQs0qTAYKBTPtUfk0sNDo6A,5370
|
|
73
|
-
services/tools/manage_script.py,sha256=MzPw0xXjtbdjEyjvUfLem9fa3GVE-WGvCr4WEVfW9Cs,28461
|
|
74
|
-
services/tools/manage_scriptable_object.py,sha256=tezG_mbGzPLNpL3F7l5JJLyyjJN3rJi1thGMU8cpOC4,3659
|
|
75
|
-
services/tools/manage_shader.py,sha256=bucRKzQww7opy6DK5nf6isVaEECWWqJ-DVkFulp8CV8,3185
|
|
76
|
-
services/tools/manage_texture.py,sha256=ap2WolIJw2iVnLyAHhY6WahiGNLmtejJX7k0kq1zWrc,25932
|
|
77
|
-
services/tools/manage_vfx.py,sha256=7KFbRohF8EzaD0m7vVIEwjUz-QwC7NEXS5cVcU6Die0,4710
|
|
78
|
-
services/tools/preflight.py,sha256=0nvo0BmZMdIGop1Ha_vypkjn2VLiRvskF0uxh_SlZgE,4162
|
|
79
|
-
services/tools/read_console.py,sha256=ps23debJcQkj3Ap-MqTYVhopYnKGspJs9QHLJHZAAkE,6826
|
|
80
|
-
services/tools/refresh_unity.py,sha256=KrRA8bmLkDLFO1XBv2NmagQAp1dmyaXdUAap567Hcv4,7100
|
|
81
|
-
services/tools/run_tests.py,sha256=wg8Ke8vpKHxyz0kqFaJC5feXTL3e6Cxzi0QKNitLDRE,9176
|
|
82
|
-
services/tools/script_apply_edits.py,sha256=0f-SaP5NUYGuivl4CWHjR8F-CXUpt3-5qkHpf_edn1U,47677
|
|
83
|
-
services/tools/set_active_instance.py,sha256=pdmC1SxFijyzzjeEyC2N1bXk-GNMu_iXsbCieIpa-R4,4242
|
|
84
|
-
services/tools/utils.py,sha256=uk--6w_-O0eVAxczackXbgKde2ONmsgci43G3wY7dfA,4258
|
|
85
|
-
transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
|
-
transport/models.py,sha256=6wp7wsmSaeeJEvUGXPF1m6zuJnxJ1NJlCC4YZ9oQIq0,1226
|
|
87
|
-
transport/plugin_hub.py,sha256=X6tAnJU0s1LQtgIgiK_YHBhSWMRD5bRjbkGjOl8eLFQ,23725
|
|
88
|
-
transport/plugin_registry.py,sha256=nW-7O7PN0QUgSWivZTkpAVKKq9ZOe2b2yeIdpaNt_3I,4359
|
|
89
|
-
transport/unity_instance_middleware.py,sha256=DD8gs-peMRmRJz9CYwaHEh4m75LTYPDjVuKuw9sArBw,10438
|
|
90
|
-
transport/unity_transport.py,sha256=G6aMC1qR31YZOBZs4fxQbSQBHuXBP1d5Qn0MJaB3yGs,3908
|
|
91
|
-
transport/legacy/port_discovery.py,sha256=JDSCqXLodfTT7fOsE0DFC1jJ3QsU6hVaYQb7x7FgdxY,12728
|
|
92
|
-
transport/legacy/stdio_port_registry.py,sha256=j4iARuP6wetppNDG8qKeuvo1bJKcSlgEhZvSyl_uf0A,2313
|
|
93
|
-
transport/legacy/unity_connection.py,sha256=FE9ZQfYMhHvIxBycr_DjI3BKvuEdORXuABnCE5Q2tjQ,36733
|
|
94
|
-
utils/focus_nudge.py,sha256=HaTOSI7wzDmdRviodUHx2oQFPIL_jSwubai3YkDJbH0,9910
|
|
95
|
-
utils/module_discovery.py,sha256=My48ofB1BUqxiBoAZAGbEaLQYdsrDhMm8MayBP_bUSQ,2005
|
|
96
|
-
utils/reload_sentinel.py,sha256=s1xMWhl-r2XwN0OUbiUv_VGUy8TvLtV5bkql-5n2DT0,373
|
|
97
|
-
mcpforunityserver-9.3.0b20260128055651.dist-info/METADATA,sha256=oYHDprvEMpsJnYVleQbe99kXV7CRCOoPY32jTDkZ7Jc,5804
|
|
98
|
-
mcpforunityserver-9.3.0b20260128055651.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
99
|
-
mcpforunityserver-9.3.0b20260128055651.dist-info/entry_points.txt,sha256=pPm70RXQvkt3uBhPOtViDa47ZTA03RaQ6rwXvyi8oiI,70
|
|
100
|
-
mcpforunityserver-9.3.0b20260128055651.dist-info/top_level.txt,sha256=3-A65WsmBO6UZYH8O5mINdyhhZ63SDssr8LncRd1PSQ,46
|
|
101
|
-
mcpforunityserver-9.3.0b20260128055651.dist-info/RECORD,,
|
utils/reload_sentinel.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Deprecated: Sentinel flipping is handled inside Unity via the MCP menu
|
|
3
|
-
'MCP/Flip Reload Sentinel'. This module remains only as a compatibility shim.
|
|
4
|
-
All functions are no-ops to prevent accidental external writes.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def flip_reload_sentinel(*args, **kwargs) -> str:
|
|
9
|
-
return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|