mcpforunityserver 9.3.0b20260129104751__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 +84 -0
- cli/commands/asset.py +280 -0
- cli/commands/audio.py +125 -0
- cli/commands/batch.py +171 -0
- cli/commands/code.py +182 -0
- cli/commands/component.py +190 -0
- cli/commands/editor.py +447 -0
- cli/commands/gameobject.py +487 -0
- cli/commands/instance.py +93 -0
- cli/commands/lighting.py +123 -0
- cli/commands/material.py +239 -0
- cli/commands/prefab.py +248 -0
- cli/commands/scene.py +231 -0
- cli/commands/script.py +222 -0
- cli/commands/shader.py +226 -0
- cli/commands/texture.py +540 -0
- cli/commands/tool.py +58 -0
- cli/commands/ui.py +258 -0
- cli/commands/vfx.py +421 -0
- cli/main.py +281 -0
- cli/utils/__init__.py +31 -0
- cli/utils/config.py +58 -0
- cli/utils/confirmation.py +37 -0
- cli/utils/connection.py +258 -0
- cli/utils/constants.py +23 -0
- cli/utils/output.py +195 -0
- cli/utils/parsers.py +112 -0
- cli/utils/suggestions.py +34 -0
- core/__init__.py +0 -0
- core/config.py +52 -0
- core/logging_decorator.py +37 -0
- core/telemetry.py +551 -0
- core/telemetry_decorator.py +164 -0
- main.py +713 -0
- mcpforunityserver-9.3.0b20260129104751.dist-info/METADATA +216 -0
- mcpforunityserver-9.3.0b20260129104751.dist-info/RECORD +103 -0
- mcpforunityserver-9.3.0b20260129104751.dist-info/WHEEL +5 -0
- mcpforunityserver-9.3.0b20260129104751.dist-info/entry_points.txt +3 -0
- mcpforunityserver-9.3.0b20260129104751.dist-info/licenses/LICENSE +21 -0
- mcpforunityserver-9.3.0b20260129104751.dist-info/top_level.txt +7 -0
- models/__init__.py +4 -0
- models/models.py +56 -0
- models/unity_response.py +47 -0
- services/__init__.py +0 -0
- services/custom_tool_service.py +499 -0
- services/registry/__init__.py +22 -0
- services/registry/resource_registry.py +53 -0
- services/registry/tool_registry.py +51 -0
- services/resources/__init__.py +86 -0
- services/resources/active_tool.py +47 -0
- services/resources/custom_tools.py +57 -0
- services/resources/editor_state.py +304 -0
- services/resources/gameobject.py +243 -0
- services/resources/layers.py +29 -0
- services/resources/menu_items.py +34 -0
- services/resources/prefab.py +191 -0
- services/resources/prefab_stage.py +39 -0
- services/resources/project_info.py +39 -0
- services/resources/selection.py +55 -0
- services/resources/tags.py +30 -0
- services/resources/tests.py +87 -0
- services/resources/unity_instances.py +122 -0
- services/resources/windows.py +47 -0
- services/state/external_changes_scanner.py +245 -0
- services/tools/__init__.py +83 -0
- services/tools/batch_execute.py +93 -0
- services/tools/debug_request_context.py +86 -0
- services/tools/execute_custom_tool.py +43 -0
- services/tools/execute_menu_item.py +32 -0
- services/tools/find_gameobjects.py +110 -0
- services/tools/find_in_file.py +181 -0
- services/tools/manage_asset.py +119 -0
- services/tools/manage_components.py +131 -0
- services/tools/manage_editor.py +64 -0
- services/tools/manage_gameobject.py +260 -0
- services/tools/manage_material.py +111 -0
- services/tools/manage_prefabs.py +174 -0
- services/tools/manage_scene.py +111 -0
- services/tools/manage_script.py +645 -0
- services/tools/manage_scriptable_object.py +87 -0
- services/tools/manage_shader.py +71 -0
- services/tools/manage_texture.py +581 -0
- services/tools/manage_vfx.py +120 -0
- services/tools/preflight.py +110 -0
- services/tools/read_console.py +151 -0
- services/tools/refresh_unity.py +153 -0
- services/tools/run_tests.py +317 -0
- services/tools/script_apply_edits.py +1006 -0
- services/tools/set_active_instance.py +117 -0
- services/tools/utils.py +348 -0
- transport/__init__.py +0 -0
- transport/legacy/port_discovery.py +329 -0
- transport/legacy/stdio_port_registry.py +65 -0
- transport/legacy/unity_connection.py +888 -0
- transport/models.py +63 -0
- transport/plugin_hub.py +585 -0
- transport/plugin_registry.py +126 -0
- transport/unity_instance_middleware.py +232 -0
- transport/unity_transport.py +63 -0
- utils/focus_nudge.py +589 -0
- utils/module_discovery.py +55 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from typing import Annotated, Any, Literal
|
|
2
|
+
|
|
3
|
+
from fastmcp import Context
|
|
4
|
+
from mcp.types import ToolAnnotations
|
|
5
|
+
|
|
6
|
+
from services.registry import mcp_for_unity_tool
|
|
7
|
+
from services.tools import get_unity_instance_from_context
|
|
8
|
+
from services.tools.utils import coerce_bool, normalize_vector3
|
|
9
|
+
from transport.unity_transport import send_with_unity_instance
|
|
10
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
11
|
+
from services.tools.preflight import preflight
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Required parameters for each action
|
|
15
|
+
REQUIRED_PARAMS = {
|
|
16
|
+
"get_info": ["prefab_path"],
|
|
17
|
+
"get_hierarchy": ["prefab_path"],
|
|
18
|
+
"create_from_gameobject": ["target", "prefab_path"],
|
|
19
|
+
"modify_contents": ["prefab_path"],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@mcp_for_unity_tool(
|
|
24
|
+
description=(
|
|
25
|
+
"Manages Unity Prefab assets via headless operations (no UI, no prefab stages). "
|
|
26
|
+
"Actions: get_info, get_hierarchy, create_from_gameobject, modify_contents. "
|
|
27
|
+
"Use modify_contents for headless prefab editing - ideal for automated workflows. "
|
|
28
|
+
"Use manage_asset action=search filterType=Prefab to list prefabs."
|
|
29
|
+
),
|
|
30
|
+
annotations=ToolAnnotations(
|
|
31
|
+
title="Manage Prefabs",
|
|
32
|
+
destructiveHint=True,
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
async def manage_prefabs(
|
|
36
|
+
ctx: Context,
|
|
37
|
+
action: Annotated[
|
|
38
|
+
Literal[
|
|
39
|
+
"create_from_gameobject",
|
|
40
|
+
"get_info",
|
|
41
|
+
"get_hierarchy",
|
|
42
|
+
"modify_contents",
|
|
43
|
+
],
|
|
44
|
+
"Prefab operation to perform.",
|
|
45
|
+
],
|
|
46
|
+
prefab_path: Annotated[str, "Prefab asset path (e.g., Assets/Prefabs/MyPrefab.prefab)."] | None = None,
|
|
47
|
+
target: Annotated[str, "Target GameObject: scene object for create_from_gameobject, or object within prefab for modify_contents (name or path like 'Parent/Child')."] | None = None,
|
|
48
|
+
allow_overwrite: Annotated[bool, "Allow replacing existing prefab."] | None = None,
|
|
49
|
+
search_inactive: Annotated[bool, "Include inactive GameObjects in search."] | None = None,
|
|
50
|
+
unlink_if_instance: Annotated[bool, "Unlink from existing prefab before creating new one."] | None = None,
|
|
51
|
+
# modify_contents parameters
|
|
52
|
+
position: Annotated[list[float] | dict[str, float] | str, "New local position [x, y, z] or {x, y, z} for modify_contents."] | None = None,
|
|
53
|
+
rotation: Annotated[list[float] | dict[str, float] | str, "New local rotation (euler angles) [x, y, z] or {x, y, z} for modify_contents."] | None = None,
|
|
54
|
+
scale: Annotated[list[float] | dict[str, float] | str, "New local scale [x, y, z] or {x, y, z} for modify_contents."] | None = None,
|
|
55
|
+
name: Annotated[str, "New name for the target object in modify_contents."] | None = None,
|
|
56
|
+
tag: Annotated[str, "New tag for the target object in modify_contents."] | None = None,
|
|
57
|
+
layer: Annotated[str, "New layer name for the target object in modify_contents."] | None = None,
|
|
58
|
+
set_active: Annotated[bool, "Set active state of target object in modify_contents."] | None = None,
|
|
59
|
+
parent: Annotated[str, "New parent object name/path within prefab for modify_contents."] | None = None,
|
|
60
|
+
components_to_add: Annotated[list[str], "Component types to add in modify_contents."] | None = None,
|
|
61
|
+
components_to_remove: Annotated[list[str], "Component types to remove in modify_contents."] | None = None,
|
|
62
|
+
) -> dict[str, Any]:
|
|
63
|
+
# Back-compat: map 'name' → 'target' for create_from_gameobject (Unity accepts both)
|
|
64
|
+
if action == "create_from_gameobject" and target is None and name is not None:
|
|
65
|
+
target = name
|
|
66
|
+
|
|
67
|
+
# Validate required parameters
|
|
68
|
+
required = REQUIRED_PARAMS.get(action, [])
|
|
69
|
+
for param_name in required:
|
|
70
|
+
# Use updated local value for target after back-compat mapping
|
|
71
|
+
param_value = target if param_name == "target" else locals().get(param_name)
|
|
72
|
+
# Check for None and empty/whitespace strings
|
|
73
|
+
if param_value is None or (isinstance(param_value, str) and not param_value.strip()):
|
|
74
|
+
return {
|
|
75
|
+
"success": False,
|
|
76
|
+
"message": f"Action '{action}' requires parameter '{param_name}'."
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
80
|
+
|
|
81
|
+
# Preflight check for operations to ensure Unity is ready
|
|
82
|
+
try:
|
|
83
|
+
gate = await preflight(ctx, wait_for_no_compile=True, refresh_if_dirty=True)
|
|
84
|
+
if gate is not None:
|
|
85
|
+
return gate.model_dump()
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
return {
|
|
88
|
+
"success": False,
|
|
89
|
+
"message": f"Unity preflight check failed: {exc}"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
# Build parameters dictionary
|
|
94
|
+
params: dict[str, Any] = {"action": action}
|
|
95
|
+
|
|
96
|
+
# Handle prefab path parameter
|
|
97
|
+
if prefab_path:
|
|
98
|
+
params["prefabPath"] = prefab_path
|
|
99
|
+
|
|
100
|
+
if target:
|
|
101
|
+
params["target"] = target
|
|
102
|
+
|
|
103
|
+
allow_overwrite_val = coerce_bool(allow_overwrite)
|
|
104
|
+
if allow_overwrite_val is not None:
|
|
105
|
+
params["allowOverwrite"] = allow_overwrite_val
|
|
106
|
+
|
|
107
|
+
search_inactive_val = coerce_bool(search_inactive)
|
|
108
|
+
if search_inactive_val is not None:
|
|
109
|
+
params["searchInactive"] = search_inactive_val
|
|
110
|
+
|
|
111
|
+
unlink_if_instance_val = coerce_bool(unlink_if_instance)
|
|
112
|
+
if unlink_if_instance_val is not None:
|
|
113
|
+
params["unlinkIfInstance"] = unlink_if_instance_val
|
|
114
|
+
|
|
115
|
+
# modify_contents parameters
|
|
116
|
+
if position is not None:
|
|
117
|
+
position_value, position_error = normalize_vector3(position, "position")
|
|
118
|
+
if position_error:
|
|
119
|
+
return {"success": False, "message": position_error}
|
|
120
|
+
params["position"] = position_value
|
|
121
|
+
if rotation is not None:
|
|
122
|
+
rotation_value, rotation_error = normalize_vector3(rotation, "rotation")
|
|
123
|
+
if rotation_error:
|
|
124
|
+
return {"success": False, "message": rotation_error}
|
|
125
|
+
params["rotation"] = rotation_value
|
|
126
|
+
if scale is not None:
|
|
127
|
+
scale_value, scale_error = normalize_vector3(scale, "scale")
|
|
128
|
+
if scale_error:
|
|
129
|
+
return {"success": False, "message": scale_error}
|
|
130
|
+
params["scale"] = scale_value
|
|
131
|
+
if name is not None:
|
|
132
|
+
params["name"] = name
|
|
133
|
+
if tag is not None:
|
|
134
|
+
params["tag"] = tag
|
|
135
|
+
if layer is not None:
|
|
136
|
+
params["layer"] = layer
|
|
137
|
+
set_active_val = coerce_bool(set_active)
|
|
138
|
+
if set_active_val is not None:
|
|
139
|
+
params["setActive"] = set_active_val
|
|
140
|
+
if parent is not None:
|
|
141
|
+
params["parent"] = parent
|
|
142
|
+
if components_to_add is not None:
|
|
143
|
+
params["componentsToAdd"] = components_to_add
|
|
144
|
+
if components_to_remove is not None:
|
|
145
|
+
params["componentsToRemove"] = components_to_remove
|
|
146
|
+
|
|
147
|
+
# Send command to Unity
|
|
148
|
+
response = await send_with_unity_instance(
|
|
149
|
+
async_send_command_with_retry, unity_instance, "manage_prefabs", params
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Return Unity response directly; ensure success field exists
|
|
153
|
+
# Handle MCPResponse objects (returned on error) by converting to dict
|
|
154
|
+
if hasattr(response, 'model_dump'):
|
|
155
|
+
return response.model_dump()
|
|
156
|
+
if isinstance(response, dict):
|
|
157
|
+
if "success" not in response:
|
|
158
|
+
response["success"] = False
|
|
159
|
+
return response
|
|
160
|
+
return {
|
|
161
|
+
"success": False,
|
|
162
|
+
"message": f"Unexpected response type: {type(response).__name__}"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
except TimeoutError:
|
|
166
|
+
return {
|
|
167
|
+
"success": False,
|
|
168
|
+
"message": "Unity connection timeout. Please check if Unity is running and responsive."
|
|
169
|
+
}
|
|
170
|
+
except Exception as exc:
|
|
171
|
+
return {
|
|
172
|
+
"success": False,
|
|
173
|
+
"message": f"Error managing prefabs: {exc}"
|
|
174
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import Annotated, Literal, Any
|
|
2
|
+
|
|
3
|
+
from fastmcp import Context
|
|
4
|
+
from mcp.types import ToolAnnotations
|
|
5
|
+
|
|
6
|
+
from services.registry import mcp_for_unity_tool
|
|
7
|
+
from services.tools import get_unity_instance_from_context
|
|
8
|
+
from services.tools.utils import coerce_int, coerce_bool
|
|
9
|
+
from transport.unity_transport import send_with_unity_instance
|
|
10
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
11
|
+
from services.tools.preflight import preflight
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@mcp_for_unity_tool(
|
|
15
|
+
description="Performs CRUD operations on Unity scenes. Read-only actions: get_hierarchy, get_active, get_build_settings, screenshot. Modifying actions: create, load, save.",
|
|
16
|
+
annotations=ToolAnnotations(
|
|
17
|
+
title="Manage Scene",
|
|
18
|
+
destructiveHint=True,
|
|
19
|
+
),
|
|
20
|
+
)
|
|
21
|
+
async def manage_scene(
|
|
22
|
+
ctx: Context,
|
|
23
|
+
action: Annotated[Literal[
|
|
24
|
+
"create",
|
|
25
|
+
"load",
|
|
26
|
+
"save",
|
|
27
|
+
"get_hierarchy",
|
|
28
|
+
"get_active",
|
|
29
|
+
"get_build_settings",
|
|
30
|
+
"screenshot",
|
|
31
|
+
], "Perform CRUD operations on Unity scenes, and capture a screenshot."],
|
|
32
|
+
name: Annotated[str, "Scene name."] | None = None,
|
|
33
|
+
path: Annotated[str, "Scene path."] | None = None,
|
|
34
|
+
build_index: Annotated[int | str,
|
|
35
|
+
"Unity build index (quote as string, e.g., '0')."] | None = None,
|
|
36
|
+
screenshot_file_name: Annotated[str,
|
|
37
|
+
"Screenshot file name (optional). Defaults to timestamp when omitted."] | None = None,
|
|
38
|
+
screenshot_super_size: Annotated[int | str,
|
|
39
|
+
"Screenshot supersize multiplier (integer ≥1). Optional."] | None = None,
|
|
40
|
+
# --- get_hierarchy paging/safety ---
|
|
41
|
+
parent: Annotated[str | int,
|
|
42
|
+
"Optional parent GameObject reference (name/path/instanceID) to list direct children."] | None = None,
|
|
43
|
+
page_size: Annotated[int | str,
|
|
44
|
+
"Page size for get_hierarchy paging."] | None = None,
|
|
45
|
+
cursor: Annotated[int | str,
|
|
46
|
+
"Opaque cursor for paging (offset)."] | None = None,
|
|
47
|
+
max_nodes: Annotated[int | str,
|
|
48
|
+
"Hard cap on returned nodes per request (safety)."] | None = None,
|
|
49
|
+
max_depth: Annotated[int | str,
|
|
50
|
+
"Accepted for forward-compatibility; current paging returns a single level."] | None = None,
|
|
51
|
+
max_children_per_node: Annotated[int | str,
|
|
52
|
+
"Child paging hint (safety)."] | None = None,
|
|
53
|
+
include_transform: Annotated[bool | str,
|
|
54
|
+
"If true, include local transform in node summaries."] | None = None,
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
# Get active instance from session state
|
|
57
|
+
# Removed session_state import
|
|
58
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
59
|
+
gate = await preflight(ctx, wait_for_no_compile=True, refresh_if_dirty=True)
|
|
60
|
+
if gate is not None:
|
|
61
|
+
return gate.model_dump()
|
|
62
|
+
try:
|
|
63
|
+
coerced_build_index = coerce_int(build_index, default=None)
|
|
64
|
+
coerced_super_size = coerce_int(screenshot_super_size, default=None)
|
|
65
|
+
coerced_page_size = coerce_int(page_size, default=None)
|
|
66
|
+
coerced_cursor = coerce_int(cursor, default=None)
|
|
67
|
+
coerced_max_nodes = coerce_int(max_nodes, default=None)
|
|
68
|
+
coerced_max_depth = coerce_int(max_depth, default=None)
|
|
69
|
+
coerced_max_children_per_node = coerce_int(
|
|
70
|
+
max_children_per_node, default=None)
|
|
71
|
+
coerced_include_transform = coerce_bool(
|
|
72
|
+
include_transform, default=None)
|
|
73
|
+
|
|
74
|
+
params: dict[str, Any] = {"action": action}
|
|
75
|
+
if name:
|
|
76
|
+
params["name"] = name
|
|
77
|
+
if path:
|
|
78
|
+
params["path"] = path
|
|
79
|
+
if coerced_build_index is not None:
|
|
80
|
+
params["buildIndex"] = coerced_build_index
|
|
81
|
+
if screenshot_file_name:
|
|
82
|
+
params["fileName"] = screenshot_file_name
|
|
83
|
+
if coerced_super_size is not None:
|
|
84
|
+
params["superSize"] = coerced_super_size
|
|
85
|
+
|
|
86
|
+
# get_hierarchy paging/safety params (optional)
|
|
87
|
+
if parent is not None:
|
|
88
|
+
params["parent"] = parent
|
|
89
|
+
if coerced_page_size is not None:
|
|
90
|
+
params["pageSize"] = coerced_page_size
|
|
91
|
+
if coerced_cursor is not None:
|
|
92
|
+
params["cursor"] = coerced_cursor
|
|
93
|
+
if coerced_max_nodes is not None:
|
|
94
|
+
params["maxNodes"] = coerced_max_nodes
|
|
95
|
+
if coerced_max_depth is not None:
|
|
96
|
+
params["maxDepth"] = coerced_max_depth
|
|
97
|
+
if coerced_max_children_per_node is not None:
|
|
98
|
+
params["maxChildrenPerNode"] = coerced_max_children_per_node
|
|
99
|
+
if coerced_include_transform is not None:
|
|
100
|
+
params["includeTransform"] = coerced_include_transform
|
|
101
|
+
|
|
102
|
+
# Use centralized retry helper with instance routing
|
|
103
|
+
response = await send_with_unity_instance(async_send_command_with_retry, unity_instance, "manage_scene", params)
|
|
104
|
+
|
|
105
|
+
# Preserve structured failure data; unwrap success into a friendlier shape
|
|
106
|
+
if isinstance(response, dict) and response.get("success"):
|
|
107
|
+
return {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")}
|
|
108
|
+
return response if isinstance(response, dict) else {"success": False, "message": str(response)}
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
return {"success": False, "message": f"Python error managing scene: {str(e)}"}
|