dcc-mcp-unity 0.2.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.
- dcc_mcp_unity/__init__.py +6 -0
- dcc_mcp_unity/__version__.py +3 -0
- dcc_mcp_unity/bridge.py +64 -0
- dcc_mcp_unity/dispatcher.py +21 -0
- dcc_mcp_unity/install.py +67 -0
- dcc_mcp_unity/server.py +101 -0
- dcc_mcp_unity/skills/unity-diagnostics/SKILL.md +24 -0
- dcc_mcp_unity/skills/unity-diagnostics/metadata/depends.md +3 -0
- dcc_mcp_unity/skills/unity-diagnostics/scripts/read_console.py +16 -0
- dcc_mcp_unity/skills/unity-diagnostics/tools.yaml +21 -0
- dcc_mcp_unity/skills/unity-project/SKILL.md +27 -0
- dcc_mcp_unity/skills/unity-project/metadata/depends.md +3 -0
- dcc_mcp_unity/skills/unity-project/scripts/inspect_project.py +14 -0
- dcc_mcp_unity/skills/unity-project/scripts/refresh_assets.py +14 -0
- dcc_mcp_unity/skills/unity-project/tools.yaml +31 -0
- dcc_mcp_unity/skills/unity-scene/SKILL.md +27 -0
- dcc_mcp_unity/skills/unity-scene/metadata/depends.md +3 -0
- dcc_mcp_unity/skills/unity-scene/scripts/create_game_object.py +18 -0
- dcc_mcp_unity/skills/unity-scene/scripts/inspect_scene.py +15 -0
- dcc_mcp_unity/skills/unity-scene/scripts/save_scene.py +14 -0
- dcc_mcp_unity/skills/unity-scene/scripts/set_transform.py +31 -0
- dcc_mcp_unity/skills/unity-scene/tools.yaml +95 -0
- dcc_mcp_unity/unity_package/Editor/DccMcp.Unity.Editor.asmdef +9 -0
- dcc_mcp_unity/unity_package/Editor/DccMcpBridge.cs +358 -0
- dcc_mcp_unity/unity_package/Editor/DccMcpCommands.cs +406 -0
- dcc_mcp_unity/unity_package/Editor/DccMcpConsole.cs +200 -0
- dcc_mcp_unity/unity_package/LICENSE.md +21 -0
- dcc_mcp_unity/unity_package/README.md +7 -0
- dcc_mcp_unity/unity_package/package.json +18 -0
- dcc_mcp_unity-0.2.0.dist-info/METADATA +105 -0
- dcc_mcp_unity-0.2.0.dist-info/RECORD +34 -0
- dcc_mcp_unity-0.2.0.dist-info/WHEEL +4 -0
- dcc_mcp_unity-0.2.0.dist-info/entry_points.txt +6 -0
- dcc_mcp_unity-0.2.0.dist-info/licenses/LICENSE +21 -0
dcc_mcp_unity/bridge.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Loopback WebSocket bridge shared by the MCP server and Unity package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from dcc_mcp_core.bridge import DccBridge
|
|
12
|
+
|
|
13
|
+
_bridge: DccBridge | None = None
|
|
14
|
+
_lock = threading.Lock()
|
|
15
|
+
_HOST_REQUEST_LIFETIME_SECONDS = 55
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _bridge_timeout() -> float:
|
|
19
|
+
value = float(os.environ.get("DCC_MCP_UNITY_BRIDGE_TIMEOUT", "60"))
|
|
20
|
+
if not math.isfinite(value) or value < 60:
|
|
21
|
+
raise ValueError(
|
|
22
|
+
"DCC_MCP_UNITY_BRIDGE_TIMEOUT must be a finite value of at least 60 seconds"
|
|
23
|
+
)
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_bridge() -> DccBridge:
|
|
28
|
+
"""Return the process-wide bridge, creating it without starting it."""
|
|
29
|
+
global _bridge
|
|
30
|
+
with _lock:
|
|
31
|
+
if _bridge is None:
|
|
32
|
+
port = int(os.environ.get("DCC_MCP_UNITY_BRIDGE_PORT", "3852"))
|
|
33
|
+
_bridge = DccBridge(
|
|
34
|
+
host="127.0.0.1",
|
|
35
|
+
port=port,
|
|
36
|
+
timeout=_bridge_timeout(),
|
|
37
|
+
server_name="dcc-mcp-unity",
|
|
38
|
+
)
|
|
39
|
+
os.environ.setdefault("DCC_MCP_UNITY_BRIDGE_URL", f"ws://127.0.0.1:{port}")
|
|
40
|
+
return _bridge
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def start_bridge() -> DccBridge:
|
|
44
|
+
bridge = get_bridge()
|
|
45
|
+
bridge.connect(wait_for_dcc=False)
|
|
46
|
+
return bridge
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def stop_bridge() -> None:
|
|
50
|
+
global _bridge
|
|
51
|
+
with _lock:
|
|
52
|
+
bridge, _bridge = _bridge, None
|
|
53
|
+
if bridge is not None:
|
|
54
|
+
bridge.disconnect()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def call_host(method: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
58
|
+
"""Invoke one typed command in the connected Unity Editor."""
|
|
59
|
+
request_params = dict(params or {})
|
|
60
|
+
request_params["_dcc_mcp_deadline_unix_ms"] = int(
|
|
61
|
+
(time.time() + _HOST_REQUEST_LIFETIME_SECONDS) * 1000
|
|
62
|
+
)
|
|
63
|
+
result = get_bridge().call(method, **request_params)
|
|
64
|
+
return result if isinstance(result, dict) else {"value": result}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""In-process skill dispatcher for the external Unity Editor bridge."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UnityBridgeDispatcher:
|
|
9
|
+
"""Run Python wrappers inline; Unity performs host work on its editor update loop."""
|
|
10
|
+
|
|
11
|
+
def dispatch_callable(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
|
12
|
+
for key in (
|
|
13
|
+
"affinity",
|
|
14
|
+
"context",
|
|
15
|
+
"action_name",
|
|
16
|
+
"skill_name",
|
|
17
|
+
"execution",
|
|
18
|
+
"timeout_hint_secs",
|
|
19
|
+
):
|
|
20
|
+
kwargs.pop(key, None)
|
|
21
|
+
return func(*args, **kwargs)
|
dcc_mcp_unity/install.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Install the bundled UPM package into a Unity project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.dcc-mcp.unity"
|
|
11
|
+
MIN_UNITY_VERSION = (2021, 3)
|
|
12
|
+
_EDITOR_VERSION_PATTERN = re.compile(r"^m_EditorVersion:\s*(\S+)\s*$", re.MULTILINE)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_unity_version(project: Path) -> str:
|
|
16
|
+
"""Return the exact Editor version recorded by a Unity project."""
|
|
17
|
+
version_file = project / "ProjectSettings" / "ProjectVersion.txt"
|
|
18
|
+
try:
|
|
19
|
+
contents = version_file.read_text(encoding="utf-8-sig")
|
|
20
|
+
except OSError as exc:
|
|
21
|
+
raise ValueError(f"Unity project version file is unavailable: {version_file}") from exc
|
|
22
|
+
|
|
23
|
+
match = _EDITOR_VERSION_PATTERN.search(contents)
|
|
24
|
+
if match is None:
|
|
25
|
+
raise ValueError(f"Unity project version is missing from: {version_file}")
|
|
26
|
+
return match.group(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _require_supported_unity_version(version: str) -> None:
|
|
30
|
+
match = re.match(r"^(\d+)\.(\d+)", version)
|
|
31
|
+
if match is None:
|
|
32
|
+
raise ValueError(f"unsupported Unity version format: {version}")
|
|
33
|
+
parsed = (int(match.group(1)), int(match.group(2)))
|
|
34
|
+
if parsed < MIN_UNITY_VERSION:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Unity {version} is unsupported; DCC-MCP Unity requires Unity 2021.3 or newer"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def install_package(project: Path, *, overwrite: bool = False) -> Path:
|
|
41
|
+
"""Copy the bundled package below the target project's Packages directory."""
|
|
42
|
+
project = project.resolve()
|
|
43
|
+
if not (project / "Assets").is_dir() or not (project / "ProjectSettings").is_dir():
|
|
44
|
+
raise ValueError(f"not a Unity project: {project}")
|
|
45
|
+
_require_supported_unity_version(read_unity_version(project))
|
|
46
|
+
|
|
47
|
+
source = Path(__file__).resolve().parent / "unity_package"
|
|
48
|
+
target = project / "Packages" / PACKAGE_NAME
|
|
49
|
+
if target.exists():
|
|
50
|
+
if not overwrite:
|
|
51
|
+
raise FileExistsError(f"package already exists: {target}")
|
|
52
|
+
shutil.rmtree(target)
|
|
53
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
shutil.copytree(source, target)
|
|
55
|
+
return target
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> None:
|
|
59
|
+
parser = argparse.ArgumentParser(description="Install DCC-MCP Unity into a Unity project.")
|
|
60
|
+
parser.add_argument("project", type=Path)
|
|
61
|
+
parser.add_argument("--overwrite", action="store_true")
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
print(install_package(args.project, overwrite=args.overwrite))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
main()
|
dcc_mcp_unity/server.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Unity MCP server composition and lifecycle."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import signal
|
|
7
|
+
import threading
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from dcc_mcp_core import DccServerOptions, HostExecutionBridge
|
|
12
|
+
from dcc_mcp_core.host import QueueDispatcher, StandaloneHost
|
|
13
|
+
from dcc_mcp_core.server_base import DccServerBase
|
|
14
|
+
|
|
15
|
+
from .__version__ import __version__
|
|
16
|
+
from .bridge import start_bridge, stop_bridge
|
|
17
|
+
from .dispatcher import UnityBridgeDispatcher
|
|
18
|
+
|
|
19
|
+
_server: Optional["UnityMcpServer"] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UnityMcpServer(DccServerBase):
|
|
23
|
+
"""DCC-MCP server backed by the bundled Unity Editor package."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, port: Optional[int] = None) -> None:
|
|
26
|
+
self._host_dispatcher = QueueDispatcher()
|
|
27
|
+
self._host_driver = StandaloneHost(
|
|
28
|
+
self._host_dispatcher,
|
|
29
|
+
thread_name="dcc-mcp-unity-host",
|
|
30
|
+
)
|
|
31
|
+
execution_bridge = HostExecutionBridge(
|
|
32
|
+
dispatcher=UnityBridgeDispatcher(),
|
|
33
|
+
host_dispatcher=self._host_dispatcher,
|
|
34
|
+
default_thread_affinity="main",
|
|
35
|
+
default_execution="sync",
|
|
36
|
+
default_timeout_hint_secs=60,
|
|
37
|
+
)
|
|
38
|
+
options = DccServerOptions.from_env(
|
|
39
|
+
"unity",
|
|
40
|
+
Path(__file__).resolve().parent / "skills",
|
|
41
|
+
port=port,
|
|
42
|
+
server_name="dcc-mcp-unity",
|
|
43
|
+
server_version=__version__,
|
|
44
|
+
execution_bridge=execution_bridge,
|
|
45
|
+
)
|
|
46
|
+
super().__init__(options=options)
|
|
47
|
+
|
|
48
|
+
def start(self, **kwargs: Any) -> Any:
|
|
49
|
+
start_bridge()
|
|
50
|
+
try:
|
|
51
|
+
self._host_driver.start()
|
|
52
|
+
return super().start(**kwargs)
|
|
53
|
+
except Exception:
|
|
54
|
+
self._host_driver.stop()
|
|
55
|
+
stop_bridge()
|
|
56
|
+
raise
|
|
57
|
+
|
|
58
|
+
def stop(self) -> None:
|
|
59
|
+
try:
|
|
60
|
+
super().stop()
|
|
61
|
+
finally:
|
|
62
|
+
try:
|
|
63
|
+
self._host_driver.stop()
|
|
64
|
+
finally:
|
|
65
|
+
stop_bridge()
|
|
66
|
+
|
|
67
|
+
def _version_string(self) -> str:
|
|
68
|
+
return os.environ.get("DCC_MCP_UNITY_VERSION", "unknown")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def start_server(port: Optional[int] = None) -> UnityMcpServer:
|
|
72
|
+
global _server
|
|
73
|
+
if _server is None or not _server.is_running:
|
|
74
|
+
_server = UnityMcpServer(port)
|
|
75
|
+
_server.register_builtin_actions()
|
|
76
|
+
_server.start()
|
|
77
|
+
return _server
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def stop_server() -> None:
|
|
81
|
+
global _server
|
|
82
|
+
if _server is not None:
|
|
83
|
+
_server.stop()
|
|
84
|
+
_server = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> None:
|
|
88
|
+
"""Run the standalone adapter until interrupted."""
|
|
89
|
+
stopped = threading.Event()
|
|
90
|
+
signal.signal(signal.SIGINT, lambda *_: stopped.set())
|
|
91
|
+
if hasattr(signal, "SIGTERM"):
|
|
92
|
+
signal.signal(signal.SIGTERM, lambda *_: stopped.set())
|
|
93
|
+
start_server()
|
|
94
|
+
try:
|
|
95
|
+
stopped.wait()
|
|
96
|
+
finally:
|
|
97
|
+
stop_server()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-diagnostics
|
|
3
|
+
description: >-
|
|
4
|
+
Domain skill — Read a bounded snapshot of Unity Console messages captured
|
|
5
|
+
after the Editor bridge loads. Use to verify operations and diagnose compile,
|
|
6
|
+
import, or runtime errors. Not for clearing logs or arbitrary Editor access.
|
|
7
|
+
license: MIT
|
|
8
|
+
compatibility: "Unity 2021.3+; dcc-mcp-core 0.19.45+"
|
|
9
|
+
allowed-tools: "python"
|
|
10
|
+
metadata:
|
|
11
|
+
dcc-mcp:
|
|
12
|
+
dcc: unity
|
|
13
|
+
layer: domain
|
|
14
|
+
version: "0.2.0" # x-release-please-version
|
|
15
|
+
search-hint: "Unity Console logs errors warnings stack trace diagnostics"
|
|
16
|
+
tags: "unity,console,logs,diagnostics,game-development"
|
|
17
|
+
tools: tools.yaml
|
|
18
|
+
depends: "dcc-diagnostics"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Unity Diagnostics
|
|
22
|
+
|
|
23
|
+
Read the captured Unity Console after an operation or when the Editor reports a failure. Results
|
|
24
|
+
are bounded and may be truncated; increase `limit` up to 200 or narrow by severity when needed.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
|
|
3
|
+
from dcc_mcp_unity.bridge import call_host
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@skill_entry
|
|
7
|
+
def main(limit: int = 100, severity: str = "all", **_kwargs):
|
|
8
|
+
result = call_host("editor.read_console", {"limit": limit, "severity": severity})
|
|
9
|
+
count = len(result.get("entries", []))
|
|
10
|
+
return skill_success(f"Read {count} captured Unity Console entries.", **result)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
from dcc_mcp_core.skill import run_main
|
|
15
|
+
|
|
16
|
+
run_main(main)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
- name: read_console
|
|
3
|
+
description: Return a bounded, chronological snapshot of Unity Console messages captured since the Editor package loaded. Filter by severity and use after operations or failures. Not for historical logs from before package initialization.
|
|
4
|
+
input_schema:
|
|
5
|
+
type: object
|
|
6
|
+
properties:
|
|
7
|
+
limit: {type: integer, minimum: 1, maximum: 200, default: 100}
|
|
8
|
+
severity: {type: string, enum: [all, error, warning, log], default: all}
|
|
9
|
+
additionalProperties: false
|
|
10
|
+
output_schema: {type: object}
|
|
11
|
+
read_only: true
|
|
12
|
+
destructive: false
|
|
13
|
+
idempotent: true
|
|
14
|
+
execution: sync
|
|
15
|
+
affinity: main
|
|
16
|
+
enforce_thread_affinity: true
|
|
17
|
+
timeout_hint_secs: 60
|
|
18
|
+
source_file: scripts/read_console.py
|
|
19
|
+
next-tools:
|
|
20
|
+
on-success: [unity_project__inspect_project, unity_scene__inspect_scene]
|
|
21
|
+
on-failure: [dcc_diagnostics__screenshot, dcc_diagnostics__audit_log]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-project
|
|
3
|
+
description: >-
|
|
4
|
+
Domain skill — Inspect the open Unity project and refresh its Asset Database.
|
|
5
|
+
Use for project identity, Unity version, Editor state, active scene, build
|
|
6
|
+
scenes, and asset import refresh. Not for GameObject edits — use unity-scene.
|
|
7
|
+
license: MIT
|
|
8
|
+
compatibility: "Unity 2021.3+; dcc-mcp-core 0.19.45+"
|
|
9
|
+
allowed-tools: "python"
|
|
10
|
+
metadata:
|
|
11
|
+
dcc-mcp:
|
|
12
|
+
dcc: unity
|
|
13
|
+
layer: domain
|
|
14
|
+
version: "0.2.0" # x-release-please-version
|
|
15
|
+
search-hint: "Unity project version build scenes Asset Database refresh"
|
|
16
|
+
tags: "unity,project,assets,game-development"
|
|
17
|
+
tools: tools.yaml
|
|
18
|
+
depends: "dcc-diagnostics"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Unity Project
|
|
22
|
+
|
|
23
|
+
Inspect before assuming the active project, scene, Unity version, or Play/compile state. Asset
|
|
24
|
+
refresh is an explicit mutation because it may import changed files and update generated metadata.
|
|
25
|
+
|
|
26
|
+
Stop if the returned project path is not the intended target. Do not automatically retry a timed-out
|
|
27
|
+
refresh; inspect the project again first.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
|
|
3
|
+
from dcc_mcp_unity.bridge import call_host
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@skill_entry
|
|
7
|
+
def main(**_kwargs):
|
|
8
|
+
return skill_success("Unity project inspected.", **call_host("project.inspect"))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
from dcc_mcp_core.skill import run_main
|
|
13
|
+
|
|
14
|
+
run_main(main)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
|
|
3
|
+
from dcc_mcp_unity.bridge import call_host
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@skill_entry
|
|
7
|
+
def main(**_kwargs):
|
|
8
|
+
return skill_success("Unity assets refreshed.", **call_host("assets.refresh"))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
from dcc_mcp_core.skill import run_main
|
|
13
|
+
|
|
14
|
+
run_main(main)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
- name: inspect_project
|
|
3
|
+
description: Return Unity project identity, editor version and readiness state, active scene, and enabled build scenes. Use before project-sensitive work and stop mutations while Unity reports compiling, updating, or playing. Not for hierarchy inspection; use inspect_scene.
|
|
4
|
+
input_schema: {type: object, properties: {}, additionalProperties: false}
|
|
5
|
+
output_schema: {type: object}
|
|
6
|
+
read_only: true
|
|
7
|
+
destructive: false
|
|
8
|
+
idempotent: true
|
|
9
|
+
execution: sync
|
|
10
|
+
affinity: main
|
|
11
|
+
enforce_thread_affinity: true
|
|
12
|
+
timeout_hint_secs: 60
|
|
13
|
+
source_file: scripts/inspect_project.py
|
|
14
|
+
next-tools:
|
|
15
|
+
on-success: [unity_scene__inspect_scene, unity_project__refresh_assets]
|
|
16
|
+
on-failure: [dcc_diagnostics__screenshot, dcc_diagnostics__audit_log]
|
|
17
|
+
- name: refresh_assets
|
|
18
|
+
description: Ask Unity AssetDatabase to discover and import changed project assets. Use after external file changes. Not for saving scene edits; use save_scene.
|
|
19
|
+
input_schema: {type: object, properties: {}, additionalProperties: false}
|
|
20
|
+
output_schema: {type: object}
|
|
21
|
+
read_only: false
|
|
22
|
+
destructive: false
|
|
23
|
+
idempotent: true
|
|
24
|
+
execution: sync
|
|
25
|
+
affinity: main
|
|
26
|
+
enforce_thread_affinity: true
|
|
27
|
+
timeout_hint_secs: 60
|
|
28
|
+
source_file: scripts/refresh_assets.py
|
|
29
|
+
next-tools:
|
|
30
|
+
on-success: [unity_project__inspect_project]
|
|
31
|
+
on-failure: [dcc_diagnostics__screenshot, dcc_diagnostics__audit_log]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-scene
|
|
3
|
+
description: >-
|
|
4
|
+
Domain skill — Inspect and edit the active Unity scene with typed, undoable
|
|
5
|
+
GameObject operations. Use for hierarchy, object creation, transforms, and
|
|
6
|
+
scene saves. Not for asset imports — use unity-project.
|
|
7
|
+
license: MIT
|
|
8
|
+
compatibility: "Unity 2021.3+; dcc-mcp-core 0.19.45+"
|
|
9
|
+
allowed-tools: "python"
|
|
10
|
+
metadata:
|
|
11
|
+
dcc-mcp:
|
|
12
|
+
dcc: unity
|
|
13
|
+
layer: domain
|
|
14
|
+
version: "0.2.0" # x-release-please-version
|
|
15
|
+
search-hint: "Unity scene hierarchy GameObject transform save Undo"
|
|
16
|
+
tags: "unity,scene,gameobject,transform,game-development"
|
|
17
|
+
tools: tools.yaml
|
|
18
|
+
depends: "dcc-diagnostics"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Unity Scene
|
|
22
|
+
|
|
23
|
+
Inspect the hierarchy immediately before using instance IDs; Unity instance IDs are scoped to the
|
|
24
|
+
current Editor session. Object creation and transform edits register with Unity Undo.
|
|
25
|
+
|
|
26
|
+
Do not automatically retry a timed-out mutation. Inspect the project and scene first because the
|
|
27
|
+
Editor may have completed the original request near the timeout boundary.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
|
|
3
|
+
from dcc_mcp_unity.bridge import call_host
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@skill_entry
|
|
7
|
+
def main(name: str, parent_instance_id: int = 0, **_kwargs):
|
|
8
|
+
result = call_host(
|
|
9
|
+
"scene.create_game_object",
|
|
10
|
+
{"name": name, "parent_instance_id": parent_instance_id},
|
|
11
|
+
)
|
|
12
|
+
return skill_success(f"Created Unity GameObject {name}.", **result)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
from dcc_mcp_core.skill import run_main
|
|
17
|
+
|
|
18
|
+
run_main(main)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
|
|
3
|
+
from dcc_mcp_unity.bridge import call_host
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@skill_entry
|
|
7
|
+
def main(max_nodes: int = 1000, **_kwargs):
|
|
8
|
+
result = call_host("scene.inspect", {"max_nodes": max_nodes})
|
|
9
|
+
return skill_success("Unity scene inspected.", **result)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
from dcc_mcp_core.skill import run_main
|
|
14
|
+
|
|
15
|
+
run_main(main)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
|
|
3
|
+
from dcc_mcp_unity.bridge import call_host
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@skill_entry
|
|
7
|
+
def main(**_kwargs):
|
|
8
|
+
return skill_success("Unity scenes saved.", **call_host("scene.save"))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
from dcc_mcp_core.skill import run_main
|
|
13
|
+
|
|
14
|
+
run_main(main)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
4
|
+
|
|
5
|
+
from dcc_mcp_unity.bridge import call_host
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@skill_entry
|
|
9
|
+
def main(
|
|
10
|
+
instance_id: int,
|
|
11
|
+
position: Optional[List[float]] = None,
|
|
12
|
+
rotation_euler: Optional[List[float]] = None,
|
|
13
|
+
scale: Optional[List[float]] = None,
|
|
14
|
+
**_kwargs,
|
|
15
|
+
):
|
|
16
|
+
params = {"instance_id": instance_id}
|
|
17
|
+
for name, value in (
|
|
18
|
+
("position", position),
|
|
19
|
+
("rotation_euler", rotation_euler),
|
|
20
|
+
("scale", scale),
|
|
21
|
+
):
|
|
22
|
+
if value is not None:
|
|
23
|
+
params[name] = value
|
|
24
|
+
result = call_host("scene.set_transform", params)
|
|
25
|
+
return skill_success(f"Updated Unity transform {instance_id}.", **result)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
from dcc_mcp_core.skill import run_main
|
|
30
|
+
|
|
31
|
+
run_main(main)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
- name: inspect_scene
|
|
3
|
+
description: Return a bounded active Unity scene hierarchy up to eight levels deep with session-scoped GameObject instance IDs. Use immediately before choosing targets and lower max_nodes for smaller responses. Not for project metadata; use inspect_project.
|
|
4
|
+
input_schema:
|
|
5
|
+
type: object
|
|
6
|
+
properties:
|
|
7
|
+
max_nodes: {type: integer, minimum: 1, maximum: 5000, default: 1000}
|
|
8
|
+
additionalProperties: false
|
|
9
|
+
output_schema: {type: object}
|
|
10
|
+
read_only: true
|
|
11
|
+
destructive: false
|
|
12
|
+
idempotent: true
|
|
13
|
+
execution: sync
|
|
14
|
+
affinity: main
|
|
15
|
+
enforce_thread_affinity: true
|
|
16
|
+
timeout_hint_secs: 60
|
|
17
|
+
source_file: scripts/inspect_scene.py
|
|
18
|
+
next-tools:
|
|
19
|
+
on-success: [unity_scene__create_game_object, unity_scene__set_transform]
|
|
20
|
+
on-failure: [dcc_diagnostics__screenshot, dcc_diagnostics__audit_log]
|
|
21
|
+
- name: create_game_object
|
|
22
|
+
description: Create one undoable GameObject in the active Unity scene. Use a parent instance ID from a fresh inspect_scene result, or omit it for a scene root. Not for prefab or asset creation.
|
|
23
|
+
input_schema:
|
|
24
|
+
type: object
|
|
25
|
+
required: [name]
|
|
26
|
+
properties:
|
|
27
|
+
name: {type: string, minLength: 1, maxLength: 120}
|
|
28
|
+
parent_instance_id: {type: integer, default: 0}
|
|
29
|
+
additionalProperties: false
|
|
30
|
+
output_schema: {type: object}
|
|
31
|
+
read_only: false
|
|
32
|
+
destructive: false
|
|
33
|
+
idempotent: false
|
|
34
|
+
execution: sync
|
|
35
|
+
affinity: main
|
|
36
|
+
enforce_thread_affinity: true
|
|
37
|
+
timeout_hint_secs: 60
|
|
38
|
+
source_file: scripts/create_game_object.py
|
|
39
|
+
next-tools:
|
|
40
|
+
on-success: [unity_scene__set_transform, unity_scene__save_scene]
|
|
41
|
+
on-failure: [unity_scene__inspect_scene, dcc_diagnostics__audit_log]
|
|
42
|
+
- name: set_transform
|
|
43
|
+
description: Set selected world position, world Euler rotation, or local scale on a Unity GameObject through Undo. Use a session-scoped instance ID from a fresh inspect_scene. Not for RectTransform layout properties.
|
|
44
|
+
input_schema:
|
|
45
|
+
type: object
|
|
46
|
+
required: [instance_id]
|
|
47
|
+
properties:
|
|
48
|
+
instance_id: {type: integer}
|
|
49
|
+
position:
|
|
50
|
+
type: array
|
|
51
|
+
items: {type: number}
|
|
52
|
+
minItems: 3
|
|
53
|
+
maxItems: 3
|
|
54
|
+
rotation_euler:
|
|
55
|
+
type: array
|
|
56
|
+
items: {type: number}
|
|
57
|
+
minItems: 3
|
|
58
|
+
maxItems: 3
|
|
59
|
+
scale:
|
|
60
|
+
type: array
|
|
61
|
+
items: {type: number}
|
|
62
|
+
minItems: 3
|
|
63
|
+
maxItems: 3
|
|
64
|
+
anyOf:
|
|
65
|
+
- required: [position]
|
|
66
|
+
- required: [rotation_euler]
|
|
67
|
+
- required: [scale]
|
|
68
|
+
additionalProperties: false
|
|
69
|
+
output_schema: {type: object}
|
|
70
|
+
read_only: false
|
|
71
|
+
destructive: true
|
|
72
|
+
idempotent: true
|
|
73
|
+
execution: sync
|
|
74
|
+
affinity: main
|
|
75
|
+
enforce_thread_affinity: true
|
|
76
|
+
timeout_hint_secs: 60
|
|
77
|
+
source_file: scripts/set_transform.py
|
|
78
|
+
next-tools:
|
|
79
|
+
on-success: [unity_scene__save_scene, unity_scene__inspect_scene]
|
|
80
|
+
on-failure: [unity_scene__inspect_scene, dcc_diagnostics__audit_log]
|
|
81
|
+
- name: save_scene
|
|
82
|
+
description: Save all currently open Unity scenes. Use after verified scene mutations. Not for importing assets; use refresh_assets.
|
|
83
|
+
input_schema: {type: object, properties: {}, additionalProperties: false}
|
|
84
|
+
output_schema: {type: object}
|
|
85
|
+
read_only: false
|
|
86
|
+
destructive: true
|
|
87
|
+
idempotent: true
|
|
88
|
+
execution: sync
|
|
89
|
+
affinity: main
|
|
90
|
+
enforce_thread_affinity: true
|
|
91
|
+
timeout_hint_secs: 60
|
|
92
|
+
source_file: scripts/save_scene.py
|
|
93
|
+
next-tools:
|
|
94
|
+
on-success: [unity_scene__inspect_scene]
|
|
95
|
+
on-failure: [dcc_diagnostics__screenshot, dcc_diagnostics__audit_log]
|