mcpforunityserver 9.4.0b20260203025228__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 +254 -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 +67 -0
- core/constants.py +4 -0
- core/logging_decorator.py +37 -0
- core/telemetry.py +551 -0
- core/telemetry_decorator.py +164 -0
- main.py +845 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/METADATA +328 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/RECORD +105 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/WHEEL +5 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/entry_points.txt +3 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/licenses/LICENSE +21 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/top_level.txt +7 -0
- models/__init__.py +4 -0
- models/models.py +56 -0
- models/unity_response.py +70 -0
- services/__init__.py +0 -0
- services/api_key_service.py +235 -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 +48 -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 +30 -0
- services/resources/menu_items.py +35 -0
- services/resources/prefab.py +191 -0
- services/resources/prefab_stage.py +40 -0
- services/resources/project_info.py +40 -0
- services/resources/selection.py +56 -0
- services/resources/tags.py +31 -0
- services/resources/tests.py +88 -0
- services/resources/unity_instances.py +125 -0
- services/resources/windows.py +48 -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 +209 -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 +120 -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 +910 -0
- transport/models.py +68 -0
- transport/plugin_hub.py +787 -0
- transport/plugin_registry.py +182 -0
- transport/unity_instance_middleware.py +262 -0
- transport/unity_transport.py +94 -0
- utils/focus_nudge.py +589 -0
- utils/module_discovery.py +55 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Resources for reading GameObject data from Unity scenes.
|
|
3
|
+
|
|
4
|
+
These resources provide read-only access to:
|
|
5
|
+
- Single GameObject data (mcpforunity://scene/gameobject/{id})
|
|
6
|
+
- All components on a GameObject (mcpforunity://scene/gameobject/{id}/components)
|
|
7
|
+
- Single component on a GameObject (mcpforunity://scene/gameobject/{id}/component/{name})
|
|
8
|
+
"""
|
|
9
|
+
from typing import Any
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
from fastmcp import Context
|
|
12
|
+
|
|
13
|
+
from models import MCPResponse
|
|
14
|
+
from services.registry import mcp_for_unity_resource
|
|
15
|
+
from services.tools import get_unity_instance_from_context
|
|
16
|
+
from transport.unity_transport import send_with_unity_instance
|
|
17
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _normalize_response(response: dict | Any) -> MCPResponse:
|
|
21
|
+
"""Normalize Unity transport response to MCPResponse."""
|
|
22
|
+
if isinstance(response, dict):
|
|
23
|
+
return MCPResponse(**response)
|
|
24
|
+
return response
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _validate_instance_id(instance_id: str) -> tuple[int | None, MCPResponse | None]:
|
|
28
|
+
"""
|
|
29
|
+
Validate and convert instance_id string to int.
|
|
30
|
+
Returns (id_int, None) on success or (None, error_response) on failure.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
return int(instance_id), None
|
|
34
|
+
except ValueError:
|
|
35
|
+
return None, MCPResponse(success=False, error=f"Invalid instance ID: {instance_id}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# =============================================================================
|
|
39
|
+
# Static Helper Resource (shows in UI)
|
|
40
|
+
# =============================================================================
|
|
41
|
+
|
|
42
|
+
@mcp_for_unity_resource(
|
|
43
|
+
uri="mcpforunity://scene/gameobject-api",
|
|
44
|
+
name="gameobject_api",
|
|
45
|
+
description="Documentation for GameObject resources. Use find_gameobjects tool to get instance IDs, then access resources below.\n\nURI: mcpforunity://scene/gameobject-api"
|
|
46
|
+
)
|
|
47
|
+
async def get_gameobject_api_docs(_ctx: Context) -> MCPResponse:
|
|
48
|
+
"""
|
|
49
|
+
Returns documentation for the GameObject resource API.
|
|
50
|
+
|
|
51
|
+
This is a helper resource that explains how to use the parameterized
|
|
52
|
+
GameObject resources which require an instance ID.
|
|
53
|
+
"""
|
|
54
|
+
docs = {
|
|
55
|
+
"overview": "GameObject resources provide read-only access to Unity scene objects.",
|
|
56
|
+
"workflow": [
|
|
57
|
+
"1. Use find_gameobjects tool to search for GameObjects and get instance IDs",
|
|
58
|
+
"2. Use the instance ID to access detailed data via resources below"
|
|
59
|
+
],
|
|
60
|
+
"best_practices": [
|
|
61
|
+
"⚡ Use batch_execute for multiple operations: Combine create/modify/component calls into one batch_execute call for 10-100x better performance",
|
|
62
|
+
"Example: Creating 5 cubes → 1 batch_execute with 5 manage_gameobject commands instead of 5 separate calls",
|
|
63
|
+
"Example: Adding components to 3 objects → 1 batch_execute with 3 manage_components commands"
|
|
64
|
+
],
|
|
65
|
+
"resources": {
|
|
66
|
+
"mcpforunity://scene/gameobject/{instance_id}": {
|
|
67
|
+
"description": "Get basic GameObject data (name, tag, layer, transform, component type list)",
|
|
68
|
+
"example": "mcpforunity://scene/gameobject/-81840",
|
|
69
|
+
"returns": ["instanceID", "name", "tag", "layer", "transform", "componentTypes", "path", "parent", "children"]
|
|
70
|
+
},
|
|
71
|
+
"mcpforunity://scene/gameobject/{instance_id}/components": {
|
|
72
|
+
"description": "Get all components with full property serialization (paginated)",
|
|
73
|
+
"example": "mcpforunity://scene/gameobject/-81840/components",
|
|
74
|
+
"parameters": {
|
|
75
|
+
"page_size": "Number of components per page (default: 25)",
|
|
76
|
+
"cursor": "Pagination offset (default: 0)",
|
|
77
|
+
"include_properties": "Include full property data (default: true)"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"mcpforunity://scene/gameobject/{instance_id}/component/{component_name}": {
|
|
81
|
+
"description": "Get a single component by type name with full properties",
|
|
82
|
+
"example": "mcpforunity://scene/gameobject/-81840/component/Camera",
|
|
83
|
+
"note": "Use the component type name (e.g., 'Camera', 'Rigidbody', 'Transform')"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"related_tools": {
|
|
87
|
+
"find_gameobjects": "Search for GameObjects by name, tag, layer, component, or path",
|
|
88
|
+
"manage_components": "Add, remove, or modify components on GameObjects",
|
|
89
|
+
"manage_gameobject": "Create, modify, or delete GameObjects"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return MCPResponse(success=True, data=docs)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TransformData(BaseModel):
|
|
96
|
+
"""Transform component data."""
|
|
97
|
+
position: dict[str, float] = {"x": 0.0, "y": 0.0, "z": 0.0}
|
|
98
|
+
localPosition: dict[str, float] = {"x": 0.0, "y": 0.0, "z": 0.0}
|
|
99
|
+
rotation: dict[str, float] = {"x": 0.0, "y": 0.0, "z": 0.0}
|
|
100
|
+
localRotation: dict[str, float] = {"x": 0.0, "y": 0.0, "z": 0.0}
|
|
101
|
+
scale: dict[str, float] = {"x": 1.0, "y": 1.0, "z": 1.0}
|
|
102
|
+
lossyScale: dict[str, float] = {"x": 1.0, "y": 1.0, "z": 1.0}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class GameObjectData(BaseModel):
|
|
106
|
+
"""Data for a single GameObject (without full component serialization)."""
|
|
107
|
+
instanceID: int
|
|
108
|
+
name: str
|
|
109
|
+
tag: str = "Untagged"
|
|
110
|
+
layer: int = 0
|
|
111
|
+
layerName: str = "Default"
|
|
112
|
+
active: bool = True
|
|
113
|
+
activeInHierarchy: bool = True
|
|
114
|
+
isStatic: bool = False
|
|
115
|
+
transform: TransformData = TransformData()
|
|
116
|
+
parent: int | None = None
|
|
117
|
+
children: list[int] = []
|
|
118
|
+
componentTypes: list[str] = []
|
|
119
|
+
path: str = ""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# TODO: Use these typed response classes for better type safety once
|
|
123
|
+
# we update the endpoints to validate response structure more strictly.
|
|
124
|
+
class GameObjectResponse(MCPResponse):
|
|
125
|
+
"""Response containing GameObject data."""
|
|
126
|
+
data: GameObjectData | None = None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@mcp_for_unity_resource(
|
|
130
|
+
uri="mcpforunity://scene/gameobject/{instance_id}",
|
|
131
|
+
name="gameobject",
|
|
132
|
+
description="Get detailed information about a single GameObject by instance ID. Returns name, tag, layer, active state, transform data, parent/children IDs, and component type list (no full component properties).\n\nURI: mcpforunity://scene/gameobject/{instance_id}"
|
|
133
|
+
)
|
|
134
|
+
async def get_gameobject(ctx: Context, instance_id: str) -> MCPResponse:
|
|
135
|
+
"""Get GameObject data by instance ID."""
|
|
136
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
137
|
+
|
|
138
|
+
id_int, error = _validate_instance_id(instance_id)
|
|
139
|
+
if error:
|
|
140
|
+
return error
|
|
141
|
+
|
|
142
|
+
response = await send_with_unity_instance(
|
|
143
|
+
async_send_command_with_retry,
|
|
144
|
+
unity_instance,
|
|
145
|
+
"get_gameobject",
|
|
146
|
+
{"instanceID": id_int}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return _normalize_response(response)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ComponentsData(BaseModel):
|
|
153
|
+
"""Data for components on a GameObject."""
|
|
154
|
+
gameObjectID: int
|
|
155
|
+
gameObjectName: str
|
|
156
|
+
components: list[Any] = []
|
|
157
|
+
cursor: int = 0
|
|
158
|
+
pageSize: int = 25
|
|
159
|
+
nextCursor: int | None = None
|
|
160
|
+
totalCount: int = 0
|
|
161
|
+
hasMore: bool = False
|
|
162
|
+
includeProperties: bool = True
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ComponentsResponse(MCPResponse):
|
|
166
|
+
"""Response containing components data."""
|
|
167
|
+
data: ComponentsData | None = None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@mcp_for_unity_resource(
|
|
171
|
+
uri="mcpforunity://scene/gameobject/{instance_id}/components",
|
|
172
|
+
name="gameobject_components",
|
|
173
|
+
description="Get all components on a GameObject with full property serialization. Supports pagination with pageSize and cursor parameters.\n\nURI: mcpforunity://scene/gameobject/{instance_id}/components"
|
|
174
|
+
)
|
|
175
|
+
async def get_gameobject_components(
|
|
176
|
+
ctx: Context,
|
|
177
|
+
instance_id: str,
|
|
178
|
+
page_size: int = 25,
|
|
179
|
+
cursor: int = 0,
|
|
180
|
+
include_properties: bool = True
|
|
181
|
+
) -> MCPResponse:
|
|
182
|
+
"""Get all components on a GameObject."""
|
|
183
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
184
|
+
|
|
185
|
+
id_int, error = _validate_instance_id(instance_id)
|
|
186
|
+
if error:
|
|
187
|
+
return error
|
|
188
|
+
|
|
189
|
+
response = await send_with_unity_instance(
|
|
190
|
+
async_send_command_with_retry,
|
|
191
|
+
unity_instance,
|
|
192
|
+
"get_gameobject_components",
|
|
193
|
+
{
|
|
194
|
+
"instanceID": id_int,
|
|
195
|
+
"pageSize": page_size,
|
|
196
|
+
"cursor": cursor,
|
|
197
|
+
"includeProperties": include_properties
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return _normalize_response(response)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class SingleComponentData(BaseModel):
|
|
205
|
+
"""Data for a single component."""
|
|
206
|
+
gameObjectID: int
|
|
207
|
+
gameObjectName: str
|
|
208
|
+
component: Any = None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class SingleComponentResponse(MCPResponse):
|
|
212
|
+
"""Response containing single component data."""
|
|
213
|
+
data: SingleComponentData | None = None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@mcp_for_unity_resource(
|
|
217
|
+
uri="mcpforunity://scene/gameobject/{instance_id}/component/{component_name}",
|
|
218
|
+
name="gameobject_component",
|
|
219
|
+
description="Get a specific component on a GameObject by type name. Returns the fully serialized component with all properties.\n\nURI: mcpforunity://scene/gameobject/{instance_id}/component/{component_name}"
|
|
220
|
+
)
|
|
221
|
+
async def get_gameobject_component(
|
|
222
|
+
ctx: Context,
|
|
223
|
+
instance_id: str,
|
|
224
|
+
component_name: str
|
|
225
|
+
) -> MCPResponse:
|
|
226
|
+
"""Get a specific component on a GameObject."""
|
|
227
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
228
|
+
|
|
229
|
+
id_int, error = _validate_instance_id(instance_id)
|
|
230
|
+
if error:
|
|
231
|
+
return error
|
|
232
|
+
|
|
233
|
+
response = await send_with_unity_instance(
|
|
234
|
+
async_send_command_with_retry,
|
|
235
|
+
unity_instance,
|
|
236
|
+
"get_gameobject_component",
|
|
237
|
+
{
|
|
238
|
+
"instanceID": id_int,
|
|
239
|
+
"componentName": component_name
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return _normalize_response(response)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from fastmcp import Context
|
|
2
|
+
|
|
3
|
+
from models import MCPResponse
|
|
4
|
+
from models.unity_response import parse_resource_response
|
|
5
|
+
from services.registry import mcp_for_unity_resource
|
|
6
|
+
from services.tools import get_unity_instance_from_context
|
|
7
|
+
from transport.unity_transport import send_with_unity_instance
|
|
8
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LayersResponse(MCPResponse):
|
|
12
|
+
"""Dictionary of layer indices to layer names."""
|
|
13
|
+
data: dict[int, str] = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@mcp_for_unity_resource(
|
|
17
|
+
uri="mcpforunity://project/layers",
|
|
18
|
+
name="project_layers",
|
|
19
|
+
description="All layers defined in the project's TagManager with their indices (0-31). Read this before using add_layer or remove_layer tools.\n\nURI: mcpforunity://project/layers"
|
|
20
|
+
)
|
|
21
|
+
async def get_layers(ctx: Context) -> LayersResponse | MCPResponse:
|
|
22
|
+
"""Get all project layers with their indices."""
|
|
23
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
24
|
+
response = await send_with_unity_instance(
|
|
25
|
+
async_send_command_with_retry,
|
|
26
|
+
unity_instance,
|
|
27
|
+
"get_layers",
|
|
28
|
+
{}
|
|
29
|
+
)
|
|
30
|
+
return parse_resource_response(response, LayersResponse)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from fastmcp import Context
|
|
2
|
+
|
|
3
|
+
from models import MCPResponse
|
|
4
|
+
from models.unity_response import parse_resource_response
|
|
5
|
+
from services.registry import mcp_for_unity_resource
|
|
6
|
+
from services.tools import get_unity_instance_from_context
|
|
7
|
+
from transport.unity_transport import send_with_unity_instance
|
|
8
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GetMenuItemsResponse(MCPResponse):
|
|
12
|
+
data: list[str] = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@mcp_for_unity_resource(
|
|
16
|
+
uri="mcpforunity://menu-items",
|
|
17
|
+
name="menu_items",
|
|
18
|
+
description="Provides a list of all menu items.\n\nURI: mcpforunity://menu-items"
|
|
19
|
+
)
|
|
20
|
+
async def get_menu_items(ctx: Context) -> GetMenuItemsResponse | MCPResponse:
|
|
21
|
+
"""Provides a list of all menu items.
|
|
22
|
+
"""
|
|
23
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
24
|
+
params = {
|
|
25
|
+
"refresh": True,
|
|
26
|
+
"search": "",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
response = await send_with_unity_instance(
|
|
30
|
+
async_send_command_with_retry,
|
|
31
|
+
unity_instance,
|
|
32
|
+
"get_menu_items",
|
|
33
|
+
params,
|
|
34
|
+
)
|
|
35
|
+
return parse_resource_response(response, GetMenuItemsResponse)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Resources for reading Prefab data from Unity.
|
|
3
|
+
|
|
4
|
+
These resources provide read-only access to:
|
|
5
|
+
- Prefab info by asset path (mcpforunity://prefab/{path})
|
|
6
|
+
- Prefab hierarchy by asset path (mcpforunity://prefab/{path}/hierarchy)
|
|
7
|
+
- Currently open prefab stage (mcpforunity://editor/prefab-stage - see prefab_stage.py)
|
|
8
|
+
"""
|
|
9
|
+
from typing import Any
|
|
10
|
+
from urllib.parse import unquote
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from fastmcp import Context
|
|
13
|
+
|
|
14
|
+
from models import MCPResponse
|
|
15
|
+
from services.registry import mcp_for_unity_resource
|
|
16
|
+
from services.tools import get_unity_instance_from_context
|
|
17
|
+
from transport.unity_transport import send_with_unity_instance
|
|
18
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _normalize_response(response: dict | MCPResponse | Any) -> MCPResponse:
|
|
22
|
+
"""Normalize Unity transport response to MCPResponse."""
|
|
23
|
+
if isinstance(response, dict):
|
|
24
|
+
return MCPResponse(**response)
|
|
25
|
+
if isinstance(response, MCPResponse):
|
|
26
|
+
return response
|
|
27
|
+
# Fallback: wrap unexpected types in an error response
|
|
28
|
+
return MCPResponse(success=False, error=f"Unexpected response type: {type(response).__name__}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _decode_prefab_path(encoded_path: str) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Decode a URL-encoded prefab path.
|
|
34
|
+
Handles paths like 'Assets%2FPrefabs%2FMyPrefab.prefab' -> 'Assets/Prefabs/MyPrefab.prefab'
|
|
35
|
+
"""
|
|
36
|
+
return unquote(encoded_path)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Static Helper Resource (shows in UI)
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
@mcp_for_unity_resource(
|
|
44
|
+
uri="mcpforunity://prefab-api",
|
|
45
|
+
name="prefab_api",
|
|
46
|
+
description="Documentation for Prefab resources. Use manage_asset action=search filterType=Prefab to find prefabs, then access resources below.\n\nURI: mcpforunity://prefab-api"
|
|
47
|
+
)
|
|
48
|
+
async def get_prefab_api_docs(_ctx: Context) -> MCPResponse:
|
|
49
|
+
"""
|
|
50
|
+
Returns documentation for the Prefab resource API.
|
|
51
|
+
|
|
52
|
+
This is a helper resource that explains how to use the parameterized
|
|
53
|
+
Prefab resources which require an asset path.
|
|
54
|
+
"""
|
|
55
|
+
docs = {
|
|
56
|
+
"overview": "Prefab resources provide read-only access to Unity prefab assets.",
|
|
57
|
+
"workflow": [
|
|
58
|
+
"1. Use manage_asset action=search filterType=Prefab to find prefabs",
|
|
59
|
+
"2. Use the asset path to access detailed data via resources below",
|
|
60
|
+
"3. Use manage_prefabs tool for prefab stage operations (open, save, close)"
|
|
61
|
+
],
|
|
62
|
+
"path_encoding": {
|
|
63
|
+
"note": "Prefab paths must be URL-encoded when used in resource URIs",
|
|
64
|
+
"example": "Assets/Prefabs/MyPrefab.prefab -> Assets%2FPrefabs%2FMyPrefab.prefab"
|
|
65
|
+
},
|
|
66
|
+
"resources": {
|
|
67
|
+
"mcpforunity://prefab/{encoded_path}": {
|
|
68
|
+
"description": "Get prefab asset info (type, root name, components, variant info)",
|
|
69
|
+
"example": "mcpforunity://prefab/Assets%2FPrefabs%2FPlayer.prefab",
|
|
70
|
+
"returns": ["assetPath", "guid", "prefabType", "rootObjectName", "rootComponentTypes", "childCount", "isVariant", "parentPrefab"]
|
|
71
|
+
},
|
|
72
|
+
"mcpforunity://prefab/{encoded_path}/hierarchy": {
|
|
73
|
+
"description": "Get full prefab hierarchy with nested prefab information",
|
|
74
|
+
"example": "mcpforunity://prefab/Assets%2FPrefabs%2FPlayer.prefab/hierarchy",
|
|
75
|
+
"returns": ["prefabPath", "total", "items (with name, instanceId, path, componentTypes, prefab nesting info)"]
|
|
76
|
+
},
|
|
77
|
+
"mcpforunity://editor/prefab-stage": {
|
|
78
|
+
"description": "Get info about the currently open prefab stage (if any)",
|
|
79
|
+
"returns": ["isOpen", "assetPath", "prefabRootName", "mode", "isDirty"]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"related_tools": {
|
|
83
|
+
"manage_prefabs": "Open/close prefab stages, save changes, create prefabs from GameObjects",
|
|
84
|
+
"manage_asset": "Search for prefab assets, get asset info",
|
|
85
|
+
"manage_gameobject": "Modify GameObjects in open prefab stage",
|
|
86
|
+
"manage_components": "Add/remove/modify components on prefab GameObjects"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return MCPResponse(success=True, data=docs)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# =============================================================================
|
|
93
|
+
# Prefab Info Resource
|
|
94
|
+
# =============================================================================
|
|
95
|
+
|
|
96
|
+
# TODO: Use these typed response classes for better type safety once
|
|
97
|
+
# we update the endpoints to validate response structure more strictly.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class PrefabInfoData(BaseModel):
|
|
101
|
+
"""Data for a prefab asset."""
|
|
102
|
+
assetPath: str
|
|
103
|
+
guid: str = ""
|
|
104
|
+
prefabType: str = "Regular"
|
|
105
|
+
rootObjectName: str = ""
|
|
106
|
+
rootComponentTypes: list[str] = []
|
|
107
|
+
childCount: int = 0
|
|
108
|
+
isVariant: bool = False
|
|
109
|
+
parentPrefab: str | None = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class PrefabInfoResponse(MCPResponse):
|
|
113
|
+
"""Response containing prefab info data."""
|
|
114
|
+
data: PrefabInfoData | None = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@mcp_for_unity_resource(
|
|
118
|
+
uri="mcpforunity://prefab/{encoded_path}",
|
|
119
|
+
name="prefab_info",
|
|
120
|
+
description="Get detailed information about a prefab asset by URL-encoded path. Returns prefab type, root object name, component types, child count, and variant info.\n\nURI: mcpforunity://prefab/{encoded_path}"
|
|
121
|
+
)
|
|
122
|
+
async def get_prefab_info(ctx: Context, encoded_path: str) -> MCPResponse:
|
|
123
|
+
"""Get prefab asset info by path."""
|
|
124
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
125
|
+
|
|
126
|
+
# Decode the URL-encoded path
|
|
127
|
+
decoded_path = _decode_prefab_path(encoded_path)
|
|
128
|
+
|
|
129
|
+
response = await send_with_unity_instance(
|
|
130
|
+
async_send_command_with_retry,
|
|
131
|
+
unity_instance,
|
|
132
|
+
"manage_prefabs",
|
|
133
|
+
{
|
|
134
|
+
"action": "get_info",
|
|
135
|
+
"prefabPath": decoded_path
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return _normalize_response(response)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# =============================================================================
|
|
143
|
+
# Prefab Hierarchy Resource
|
|
144
|
+
# =============================================================================
|
|
145
|
+
|
|
146
|
+
class PrefabHierarchyItem(BaseModel):
|
|
147
|
+
"""Single item in prefab hierarchy."""
|
|
148
|
+
name: str
|
|
149
|
+
instanceId: int
|
|
150
|
+
path: str
|
|
151
|
+
activeSelf: bool = True
|
|
152
|
+
childCount: int = 0
|
|
153
|
+
componentTypes: list[str] = []
|
|
154
|
+
prefab: dict[str, Any] = {}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class PrefabHierarchyData(BaseModel):
|
|
158
|
+
"""Data for prefab hierarchy."""
|
|
159
|
+
prefabPath: str
|
|
160
|
+
total: int = 0
|
|
161
|
+
items: list[PrefabHierarchyItem] = []
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class PrefabHierarchyResponse(MCPResponse):
|
|
165
|
+
"""Response containing prefab hierarchy data."""
|
|
166
|
+
data: PrefabHierarchyData | None = None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@mcp_for_unity_resource(
|
|
170
|
+
uri="mcpforunity://prefab/{encoded_path}/hierarchy",
|
|
171
|
+
name="prefab_hierarchy",
|
|
172
|
+
description="Get the full hierarchy of a prefab with nested prefab information. Returns all GameObjects with their components and nesting depth.\n\nURI: mcpforunity://prefab/{encoded_path}/hierarchy"
|
|
173
|
+
)
|
|
174
|
+
async def get_prefab_hierarchy(ctx: Context, encoded_path: str) -> MCPResponse:
|
|
175
|
+
"""Get prefab hierarchy by path."""
|
|
176
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
177
|
+
|
|
178
|
+
# Decode the URL-encoded path
|
|
179
|
+
decoded_path = _decode_prefab_path(encoded_path)
|
|
180
|
+
|
|
181
|
+
response = await send_with_unity_instance(
|
|
182
|
+
async_send_command_with_retry,
|
|
183
|
+
unity_instance,
|
|
184
|
+
"manage_prefabs",
|
|
185
|
+
{
|
|
186
|
+
"action": "get_hierarchy",
|
|
187
|
+
"prefabPath": decoded_path
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return _normalize_response(response)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from fastmcp import Context
|
|
3
|
+
|
|
4
|
+
from models import MCPResponse
|
|
5
|
+
from models.unity_response import parse_resource_response
|
|
6
|
+
from services.registry import mcp_for_unity_resource
|
|
7
|
+
from services.tools import get_unity_instance_from_context
|
|
8
|
+
from transport.unity_transport import send_with_unity_instance
|
|
9
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PrefabStageData(BaseModel):
|
|
13
|
+
"""Prefab stage data fields."""
|
|
14
|
+
isOpen: bool = False
|
|
15
|
+
assetPath: str | None = None
|
|
16
|
+
prefabRootName: str | None = None
|
|
17
|
+
mode: str | None = None
|
|
18
|
+
isDirty: bool = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PrefabStageResponse(MCPResponse):
|
|
22
|
+
"""Information about the current prefab editing context."""
|
|
23
|
+
data: PrefabStageData = PrefabStageData()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mcp_for_unity_resource(
|
|
27
|
+
uri="mcpforunity://editor/prefab-stage",
|
|
28
|
+
name="editor_prefab_stage",
|
|
29
|
+
description="Current prefab editing context if a prefab is open in isolation mode. Returns isOpen=false if no prefab is being edited.\n\nURI: mcpforunity://editor/prefab-stage"
|
|
30
|
+
)
|
|
31
|
+
async def get_prefab_stage(ctx: Context) -> PrefabStageResponse | MCPResponse:
|
|
32
|
+
"""Get current prefab stage information."""
|
|
33
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
34
|
+
response = await send_with_unity_instance(
|
|
35
|
+
async_send_command_with_retry,
|
|
36
|
+
unity_instance,
|
|
37
|
+
"get_prefab_stage",
|
|
38
|
+
{}
|
|
39
|
+
)
|
|
40
|
+
return parse_resource_response(response, PrefabStageResponse)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from fastmcp import Context
|
|
3
|
+
|
|
4
|
+
from models import MCPResponse
|
|
5
|
+
from models.unity_response import parse_resource_response
|
|
6
|
+
from services.registry import mcp_for_unity_resource
|
|
7
|
+
from services.tools import get_unity_instance_from_context
|
|
8
|
+
from transport.unity_transport import send_with_unity_instance
|
|
9
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ProjectInfoData(BaseModel):
|
|
13
|
+
"""Project info data fields."""
|
|
14
|
+
projectRoot: str = ""
|
|
15
|
+
projectName: str = ""
|
|
16
|
+
unityVersion: str = ""
|
|
17
|
+
platform: str = ""
|
|
18
|
+
assetsPath: str = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ProjectInfoResponse(MCPResponse):
|
|
22
|
+
"""Static project configuration information."""
|
|
23
|
+
data: ProjectInfoData = ProjectInfoData()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mcp_for_unity_resource(
|
|
27
|
+
uri="mcpforunity://project/info",
|
|
28
|
+
name="project_info",
|
|
29
|
+
description="Static project information including root path, Unity version, and platform. This data rarely changes.\n\nURI: mcpforunity://project/info"
|
|
30
|
+
)
|
|
31
|
+
async def get_project_info(ctx: Context) -> ProjectInfoResponse | MCPResponse:
|
|
32
|
+
"""Get static project configuration information."""
|
|
33
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
34
|
+
response = await send_with_unity_instance(
|
|
35
|
+
async_send_command_with_retry,
|
|
36
|
+
unity_instance,
|
|
37
|
+
"get_project_info",
|
|
38
|
+
{}
|
|
39
|
+
)
|
|
40
|
+
return parse_resource_response(response, ProjectInfoResponse)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from fastmcp import Context
|
|
3
|
+
|
|
4
|
+
from models import MCPResponse
|
|
5
|
+
from models.unity_response import parse_resource_response
|
|
6
|
+
from services.registry import mcp_for_unity_resource
|
|
7
|
+
from services.tools import get_unity_instance_from_context
|
|
8
|
+
from transport.unity_transport import send_with_unity_instance
|
|
9
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SelectionObjectInfo(BaseModel):
|
|
13
|
+
"""Information about a selected object."""
|
|
14
|
+
name: str | None = None
|
|
15
|
+
type: str | None = None
|
|
16
|
+
instanceID: int | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SelectionGameObjectInfo(BaseModel):
|
|
20
|
+
"""Information about a selected GameObject."""
|
|
21
|
+
name: str | None = None
|
|
22
|
+
instanceID: int | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SelectionData(BaseModel):
|
|
26
|
+
"""Selection data fields."""
|
|
27
|
+
activeObject: str | None = None
|
|
28
|
+
activeGameObject: str | None = None
|
|
29
|
+
activeTransform: str | None = None
|
|
30
|
+
activeInstanceID: int = 0
|
|
31
|
+
count: int = 0
|
|
32
|
+
objects: list[SelectionObjectInfo] = []
|
|
33
|
+
gameObjects: list[SelectionGameObjectInfo] = []
|
|
34
|
+
assetGUIDs: list[str] = []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SelectionResponse(MCPResponse):
|
|
38
|
+
"""Detailed information about the current editor selection."""
|
|
39
|
+
data: SelectionData = SelectionData()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@mcp_for_unity_resource(
|
|
43
|
+
uri="mcpforunity://editor/selection",
|
|
44
|
+
name="editor_selection",
|
|
45
|
+
description="Detailed information about currently selected objects in the editor, including GameObjects, assets, and their properties.\n\nURI: mcpforunity://editor/selection"
|
|
46
|
+
)
|
|
47
|
+
async def get_selection(ctx: Context) -> SelectionResponse | MCPResponse:
|
|
48
|
+
"""Get detailed editor selection information."""
|
|
49
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
50
|
+
response = await send_with_unity_instance(
|
|
51
|
+
async_send_command_with_retry,
|
|
52
|
+
unity_instance,
|
|
53
|
+
"get_selection",
|
|
54
|
+
{}
|
|
55
|
+
)
|
|
56
|
+
return parse_resource_response(response, SelectionResponse)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
from fastmcp import Context
|
|
3
|
+
|
|
4
|
+
from models import MCPResponse
|
|
5
|
+
from models.unity_response import parse_resource_response
|
|
6
|
+
from services.registry import mcp_for_unity_resource
|
|
7
|
+
from services.tools import get_unity_instance_from_context
|
|
8
|
+
from transport.unity_transport import send_with_unity_instance
|
|
9
|
+
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TagsResponse(MCPResponse):
|
|
13
|
+
"""List of all tags in the project."""
|
|
14
|
+
data: list[str] = Field(default_factory=list)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp_for_unity_resource(
|
|
18
|
+
uri="mcpforunity://project/tags",
|
|
19
|
+
name="project_tags",
|
|
20
|
+
description="All tags defined in the project's TagManager. Read this before using add_tag or remove_tag tools.\n\nURI: mcpforunity://project/tags"
|
|
21
|
+
)
|
|
22
|
+
async def get_tags(ctx: Context) -> TagsResponse | MCPResponse:
|
|
23
|
+
"""Get all project tags."""
|
|
24
|
+
unity_instance = get_unity_instance_from_context(ctx)
|
|
25
|
+
response = await send_with_unity_instance(
|
|
26
|
+
async_send_command_with_retry,
|
|
27
|
+
unity_instance,
|
|
28
|
+
"get_tags",
|
|
29
|
+
{}
|
|
30
|
+
)
|
|
31
|
+
return parse_resource_response(response, TagsResponse)
|