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
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import os
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from fastmcp import Context
|
|
6
|
-
|
|
7
|
-
from models import MCPResponse
|
|
8
|
-
from services.registry import mcp_for_unity_resource
|
|
9
|
-
from services.tools import get_unity_instance_from_context
|
|
10
|
-
import transport.unity_transport as unity_transport
|
|
11
|
-
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
12
|
-
from services.state.external_changes_scanner import external_changes_scanner
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _now_unix_ms() -> int:
|
|
16
|
-
return int(time.time() * 1000)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _in_pytest() -> bool:
|
|
20
|
-
# Avoid instance-discovery side effects during the Python integration test suite.
|
|
21
|
-
return bool(os.environ.get("PYTEST_CURRENT_TEST"))
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async def _infer_single_instance_id(ctx: Context) -> str | None:
|
|
25
|
-
"""
|
|
26
|
-
Best-effort: if exactly one Unity instance is connected, return its Name@hash id.
|
|
27
|
-
This makes editor_state outputs self-describing even when no explicit active instance is set.
|
|
28
|
-
"""
|
|
29
|
-
if _in_pytest():
|
|
30
|
-
return None
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
transport = unity_transport._current_transport()
|
|
34
|
-
except Exception:
|
|
35
|
-
transport = None
|
|
36
|
-
|
|
37
|
-
if transport == "http":
|
|
38
|
-
# HTTP/WebSocket transport: derive from PluginHub sessions.
|
|
39
|
-
try:
|
|
40
|
-
from transport.plugin_hub import PluginHub
|
|
41
|
-
|
|
42
|
-
sessions_data = await PluginHub.get_sessions()
|
|
43
|
-
sessions = sessions_data.sessions if hasattr(sessions_data, "sessions") else {}
|
|
44
|
-
if isinstance(sessions, dict) and len(sessions) == 1:
|
|
45
|
-
session = next(iter(sessions.values()))
|
|
46
|
-
project = getattr(session, "project", None)
|
|
47
|
-
project_hash = getattr(session, "hash", None)
|
|
48
|
-
if project and project_hash:
|
|
49
|
-
return f"{project}@{project_hash}"
|
|
50
|
-
except Exception:
|
|
51
|
-
return None
|
|
52
|
-
return None
|
|
53
|
-
|
|
54
|
-
# Stdio/TCP transport: derive from connection pool discovery.
|
|
55
|
-
try:
|
|
56
|
-
from transport.legacy.unity_connection import get_unity_connection_pool
|
|
57
|
-
|
|
58
|
-
pool = get_unity_connection_pool()
|
|
59
|
-
instances = pool.discover_all_instances(force_refresh=False)
|
|
60
|
-
if isinstance(instances, list) and len(instances) == 1:
|
|
61
|
-
inst = instances[0]
|
|
62
|
-
inst_id = getattr(inst, "id", None)
|
|
63
|
-
return str(inst_id) if inst_id else None
|
|
64
|
-
except Exception:
|
|
65
|
-
return None
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _build_v2_from_legacy(legacy: dict[str, Any]) -> dict[str, Any]:
|
|
70
|
-
"""
|
|
71
|
-
Best-effort mapping from legacy get_editor_state payload into the v2 contract.
|
|
72
|
-
Legacy shape (Unity): {isPlaying,isPaused,isCompiling,isUpdating,timeSinceStartup,...}
|
|
73
|
-
"""
|
|
74
|
-
now_ms = _now_unix_ms()
|
|
75
|
-
# legacy may arrive already wrapped as MCPResponse-like {success,data:{...}}
|
|
76
|
-
state = legacy.get("data") if isinstance(legacy.get("data"), dict) else {}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
"schema_version": "unity-mcp/editor_state@2",
|
|
80
|
-
"observed_at_unix_ms": now_ms,
|
|
81
|
-
"sequence": 0,
|
|
82
|
-
"unity": {
|
|
83
|
-
"instance_id": None,
|
|
84
|
-
"unity_version": None,
|
|
85
|
-
"project_id": None,
|
|
86
|
-
"platform": None,
|
|
87
|
-
"is_batch_mode": None,
|
|
88
|
-
},
|
|
89
|
-
"editor": {
|
|
90
|
-
"is_focused": None,
|
|
91
|
-
"play_mode": {
|
|
92
|
-
"is_playing": bool(state.get("isPlaying", False)),
|
|
93
|
-
"is_paused": bool(state.get("isPaused", False)),
|
|
94
|
-
"is_changing": None,
|
|
95
|
-
},
|
|
96
|
-
"active_scene": {
|
|
97
|
-
"path": None,
|
|
98
|
-
"guid": None,
|
|
99
|
-
"name": state.get("activeSceneName", "") or "",
|
|
100
|
-
},
|
|
101
|
-
"selection": {
|
|
102
|
-
"count": int(state.get("selectionCount", 0) or 0),
|
|
103
|
-
"active_object_name": state.get("activeObjectName", None),
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
"activity": {
|
|
107
|
-
"phase": "unknown",
|
|
108
|
-
"since_unix_ms": now_ms,
|
|
109
|
-
"reasons": ["legacy_fallback"],
|
|
110
|
-
},
|
|
111
|
-
"compilation": {
|
|
112
|
-
"is_compiling": bool(state.get("isCompiling", False)),
|
|
113
|
-
"is_domain_reload_pending": None,
|
|
114
|
-
"last_compile_started_unix_ms": None,
|
|
115
|
-
"last_compile_finished_unix_ms": None,
|
|
116
|
-
},
|
|
117
|
-
"assets": {
|
|
118
|
-
"is_updating": bool(state.get("isUpdating", False)),
|
|
119
|
-
"external_changes_dirty": False,
|
|
120
|
-
"external_changes_last_seen_unix_ms": None,
|
|
121
|
-
"refresh": {
|
|
122
|
-
"is_refresh_in_progress": False,
|
|
123
|
-
"last_refresh_requested_unix_ms": None,
|
|
124
|
-
"last_refresh_finished_unix_ms": None,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
"tests": {
|
|
128
|
-
"is_running": False,
|
|
129
|
-
"mode": None,
|
|
130
|
-
"started_unix_ms": None,
|
|
131
|
-
"started_by": "unknown",
|
|
132
|
-
"last_run": None,
|
|
133
|
-
},
|
|
134
|
-
"transport": {
|
|
135
|
-
"unity_bridge_connected": None,
|
|
136
|
-
"last_message_unix_ms": None,
|
|
137
|
-
},
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _enrich_advice_and_staleness(state_v2: dict[str, Any]) -> dict[str, Any]:
|
|
142
|
-
now_ms = _now_unix_ms()
|
|
143
|
-
observed = state_v2.get("observed_at_unix_ms")
|
|
144
|
-
try:
|
|
145
|
-
observed_ms = int(observed)
|
|
146
|
-
except Exception:
|
|
147
|
-
observed_ms = now_ms
|
|
148
|
-
|
|
149
|
-
age_ms = max(0, now_ms - observed_ms)
|
|
150
|
-
# Conservative default: treat >2s as stale (covers common unfocused-editor throttling).
|
|
151
|
-
is_stale = age_ms > 2000
|
|
152
|
-
|
|
153
|
-
compilation = state_v2.get("compilation") or {}
|
|
154
|
-
tests = state_v2.get("tests") or {}
|
|
155
|
-
assets = state_v2.get("assets") or {}
|
|
156
|
-
refresh = (assets.get("refresh") or {}) if isinstance(assets, dict) else {}
|
|
157
|
-
|
|
158
|
-
blocking: list[str] = []
|
|
159
|
-
if compilation.get("is_compiling") is True:
|
|
160
|
-
blocking.append("compiling")
|
|
161
|
-
if compilation.get("is_domain_reload_pending") is True:
|
|
162
|
-
blocking.append("domain_reload")
|
|
163
|
-
if tests.get("is_running") is True:
|
|
164
|
-
blocking.append("running_tests")
|
|
165
|
-
if refresh.get("is_refresh_in_progress") is True:
|
|
166
|
-
blocking.append("asset_refresh")
|
|
167
|
-
if is_stale:
|
|
168
|
-
blocking.append("stale_status")
|
|
169
|
-
|
|
170
|
-
ready_for_tools = len(blocking) == 0
|
|
171
|
-
|
|
172
|
-
state_v2["advice"] = {
|
|
173
|
-
"ready_for_tools": ready_for_tools,
|
|
174
|
-
"blocking_reasons": blocking,
|
|
175
|
-
"recommended_retry_after_ms": 0 if ready_for_tools else 500,
|
|
176
|
-
"recommended_next_action": "none" if ready_for_tools else "retry_later",
|
|
177
|
-
}
|
|
178
|
-
state_v2["staleness"] = {"age_ms": age_ms, "is_stale": is_stale}
|
|
179
|
-
return state_v2
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
@mcp_for_unity_resource(
|
|
183
|
-
uri="unity://editor_state",
|
|
184
|
-
name="editor_state_v2",
|
|
185
|
-
description="Canonical editor readiness snapshot (v2). Includes advice and server-computed staleness.",
|
|
186
|
-
)
|
|
187
|
-
async def get_editor_state_v2(ctx: Context) -> MCPResponse:
|
|
188
|
-
unity_instance = get_unity_instance_from_context(ctx)
|
|
189
|
-
|
|
190
|
-
# Try v2 snapshot first (Unity-side cache will make this fast once implemented).
|
|
191
|
-
response = await unity_transport.send_with_unity_instance(
|
|
192
|
-
async_send_command_with_retry,
|
|
193
|
-
unity_instance,
|
|
194
|
-
"get_editor_state_v2",
|
|
195
|
-
{},
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
# If Unity returns a structured retry hint or error, surface it directly.
|
|
199
|
-
if isinstance(response, dict) and not response.get("success", True):
|
|
200
|
-
return MCPResponse(**response)
|
|
201
|
-
|
|
202
|
-
# If v2 is unavailable (older plugin), fall back to legacy get_editor_state and map.
|
|
203
|
-
if not (isinstance(response, dict) and isinstance(response.get("data"), dict) and response["data"].get("schema_version")):
|
|
204
|
-
legacy = await unity_transport.send_with_unity_instance(
|
|
205
|
-
async_send_command_with_retry,
|
|
206
|
-
unity_instance,
|
|
207
|
-
"get_editor_state",
|
|
208
|
-
{},
|
|
209
|
-
)
|
|
210
|
-
if isinstance(legacy, dict) and not legacy.get("success", True):
|
|
211
|
-
return MCPResponse(**legacy)
|
|
212
|
-
state_v2 = _build_v2_from_legacy(legacy if isinstance(legacy, dict) else {})
|
|
213
|
-
else:
|
|
214
|
-
state_v2 = response.get("data") if isinstance(response.get("data"), dict) else {}
|
|
215
|
-
# Ensure required v2 marker exists even if Unity returns partial.
|
|
216
|
-
state_v2.setdefault("schema_version", "unity-mcp/editor_state@2")
|
|
217
|
-
state_v2.setdefault("observed_at_unix_ms", _now_unix_ms())
|
|
218
|
-
state_v2.setdefault("sequence", 0)
|
|
219
|
-
|
|
220
|
-
# Ensure the returned snapshot is clearly associated with the targeted instance.
|
|
221
|
-
# (This matters when multiple Unity instances are connected and the client is polling readiness.)
|
|
222
|
-
unity_section = state_v2.get("unity")
|
|
223
|
-
if not isinstance(unity_section, dict):
|
|
224
|
-
unity_section = {}
|
|
225
|
-
state_v2["unity"] = unity_section
|
|
226
|
-
current_instance_id = unity_section.get("instance_id")
|
|
227
|
-
if current_instance_id in (None, ""):
|
|
228
|
-
if unity_instance:
|
|
229
|
-
unity_section["instance_id"] = unity_instance
|
|
230
|
-
else:
|
|
231
|
-
inferred = await _infer_single_instance_id(ctx)
|
|
232
|
-
if inferred:
|
|
233
|
-
unity_section["instance_id"] = inferred
|
|
234
|
-
|
|
235
|
-
# External change detection (server-side): compute per instance based on project root path.
|
|
236
|
-
# This helps detect stale assets when external tools edit the filesystem.
|
|
237
|
-
try:
|
|
238
|
-
instance_id = unity_section.get("instance_id")
|
|
239
|
-
if isinstance(instance_id, str) and instance_id.strip():
|
|
240
|
-
from services.resources.project_info import get_project_info
|
|
241
|
-
|
|
242
|
-
# Cache the project root for this instance (best-effort).
|
|
243
|
-
proj_resp = await get_project_info(ctx)
|
|
244
|
-
proj = proj_resp.model_dump() if hasattr(proj_resp, "model_dump") else proj_resp
|
|
245
|
-
proj_data = proj.get("data") if isinstance(proj, dict) else None
|
|
246
|
-
project_root = proj_data.get("projectRoot") if isinstance(proj_data, dict) else None
|
|
247
|
-
if isinstance(project_root, str) and project_root.strip():
|
|
248
|
-
external_changes_scanner.set_project_root(instance_id, project_root)
|
|
249
|
-
|
|
250
|
-
ext = external_changes_scanner.update_and_get(instance_id)
|
|
251
|
-
|
|
252
|
-
assets = state_v2.get("assets")
|
|
253
|
-
if not isinstance(assets, dict):
|
|
254
|
-
assets = {}
|
|
255
|
-
state_v2["assets"] = assets
|
|
256
|
-
# IMPORTANT: Unity's cached snapshot may include placeholder defaults; the server scanner is authoritative
|
|
257
|
-
# for external changes (filesystem edits outside Unity). Always overwrite these fields from the scanner.
|
|
258
|
-
assets["external_changes_dirty"] = bool(ext.get("external_changes_dirty", False))
|
|
259
|
-
assets["external_changes_last_seen_unix_ms"] = ext.get("external_changes_last_seen_unix_ms")
|
|
260
|
-
# Extra bookkeeping fields (server-only) are safe to add under assets.
|
|
261
|
-
assets["external_changes_dirty_since_unix_ms"] = ext.get("dirty_since_unix_ms")
|
|
262
|
-
assets["external_changes_last_cleared_unix_ms"] = ext.get("last_cleared_unix_ms")
|
|
263
|
-
except Exception:
|
|
264
|
-
# Best-effort; do not fail readiness resource if filesystem scan can't run.
|
|
265
|
-
pass
|
|
266
|
-
|
|
267
|
-
state_v2 = _enrich_advice_and_staleness(state_v2)
|
|
268
|
-
return MCPResponse(success=True, message="Retrieved editor state (v2).", data=state_v2)
|
|
269
|
-
|
|
270
|
-
|
services/tools/test_jobs.py
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""Async Unity Test Runner jobs: start + poll."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from typing import Annotated, Any, Literal
|
|
5
|
-
|
|
6
|
-
from fastmcp import Context
|
|
7
|
-
|
|
8
|
-
from models import MCPResponse
|
|
9
|
-
from services.registry import mcp_for_unity_tool
|
|
10
|
-
from services.tools import get_unity_instance_from_context
|
|
11
|
-
from services.tools.preflight import preflight
|
|
12
|
-
import transport.unity_transport as unity_transport
|
|
13
|
-
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@mcp_for_unity_tool(description="Starts a Unity test run asynchronously and returns a job_id immediately. Preferred over run_tests for long-running suites. Poll with get_test_job for progress.")
|
|
17
|
-
async def run_tests_async(
|
|
18
|
-
ctx: Context,
|
|
19
|
-
mode: Annotated[Literal["EditMode", "PlayMode"], "Unity test mode to run"] = "EditMode",
|
|
20
|
-
test_names: Annotated[list[str] | str, "Full names of specific tests to run"] | None = None,
|
|
21
|
-
group_names: Annotated[list[str] | str, "Same as test_names, except it allows for Regex"] | None = None,
|
|
22
|
-
category_names: Annotated[list[str] | str, "NUnit category names to filter by"] | None = None,
|
|
23
|
-
assembly_names: Annotated[list[str] | str, "Assembly names to filter tests by"] | None = None,
|
|
24
|
-
include_failed_tests: Annotated[bool, "Include details for failed/skipped tests only (default: false)"] = False,
|
|
25
|
-
include_details: Annotated[bool, "Include details for all tests (default: false)"] = False,
|
|
26
|
-
) -> dict[str, Any] | MCPResponse:
|
|
27
|
-
unity_instance = get_unity_instance_from_context(ctx)
|
|
28
|
-
|
|
29
|
-
gate = await preflight(ctx, requires_no_tests=True, wait_for_no_compile=True, refresh_if_dirty=True)
|
|
30
|
-
if isinstance(gate, MCPResponse):
|
|
31
|
-
return gate
|
|
32
|
-
|
|
33
|
-
def _coerce_string_list(value) -> list[str] | None:
|
|
34
|
-
if value is None:
|
|
35
|
-
return None
|
|
36
|
-
if isinstance(value, str):
|
|
37
|
-
return [value] if value.strip() else None
|
|
38
|
-
if isinstance(value, list):
|
|
39
|
-
result = [str(v).strip() for v in value if v and str(v).strip()]
|
|
40
|
-
return result if result else None
|
|
41
|
-
return None
|
|
42
|
-
|
|
43
|
-
params: dict[str, Any] = {"mode": mode}
|
|
44
|
-
if (t := _coerce_string_list(test_names)):
|
|
45
|
-
params["testNames"] = t
|
|
46
|
-
if (g := _coerce_string_list(group_names)):
|
|
47
|
-
params["groupNames"] = g
|
|
48
|
-
if (c := _coerce_string_list(category_names)):
|
|
49
|
-
params["categoryNames"] = c
|
|
50
|
-
if (a := _coerce_string_list(assembly_names)):
|
|
51
|
-
params["assemblyNames"] = a
|
|
52
|
-
if include_failed_tests:
|
|
53
|
-
params["includeFailedTests"] = True
|
|
54
|
-
if include_details:
|
|
55
|
-
params["includeDetails"] = True
|
|
56
|
-
|
|
57
|
-
response = await unity_transport.send_with_unity_instance(
|
|
58
|
-
async_send_command_with_retry,
|
|
59
|
-
unity_instance,
|
|
60
|
-
"run_tests_async",
|
|
61
|
-
params,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
if isinstance(response, dict) and not response.get("success", True):
|
|
65
|
-
return MCPResponse(**response)
|
|
66
|
-
return response if isinstance(response, dict) else MCPResponse(success=False, error=str(response)).model_dump()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@mcp_for_unity_tool(description="Polls an async Unity test job by job_id.")
|
|
70
|
-
async def get_test_job(
|
|
71
|
-
ctx: Context,
|
|
72
|
-
job_id: Annotated[str, "Job id returned by run_tests_async"],
|
|
73
|
-
include_failed_tests: Annotated[bool, "Include details for failed/skipped tests only (default: false)"] = False,
|
|
74
|
-
include_details: Annotated[bool, "Include details for all tests (default: false)"] = False,
|
|
75
|
-
) -> dict[str, Any] | MCPResponse:
|
|
76
|
-
unity_instance = get_unity_instance_from_context(ctx)
|
|
77
|
-
|
|
78
|
-
params: dict[str, Any] = {"job_id": job_id}
|
|
79
|
-
if include_failed_tests:
|
|
80
|
-
params["includeFailedTests"] = True
|
|
81
|
-
if include_details:
|
|
82
|
-
params["includeDetails"] = True
|
|
83
|
-
|
|
84
|
-
response = await unity_transport.send_with_unity_instance(
|
|
85
|
-
async_send_command_with_retry,
|
|
86
|
-
unity_instance,
|
|
87
|
-
"get_test_job",
|
|
88
|
-
params,
|
|
89
|
-
)
|
|
90
|
-
if isinstance(response, dict) and not response.get("success", True):
|
|
91
|
-
return MCPResponse(**response)
|
|
92
|
-
return response if isinstance(response, dict) else MCPResponse(success=False, error=str(response)).model_dump()
|
|
93
|
-
|
|
94
|
-
|
|
File without changes
|