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.
Files changed (105) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +3 -0
  3. cli/commands/animation.py +84 -0
  4. cli/commands/asset.py +280 -0
  5. cli/commands/audio.py +125 -0
  6. cli/commands/batch.py +171 -0
  7. cli/commands/code.py +182 -0
  8. cli/commands/component.py +190 -0
  9. cli/commands/editor.py +447 -0
  10. cli/commands/gameobject.py +487 -0
  11. cli/commands/instance.py +93 -0
  12. cli/commands/lighting.py +123 -0
  13. cli/commands/material.py +239 -0
  14. cli/commands/prefab.py +248 -0
  15. cli/commands/scene.py +231 -0
  16. cli/commands/script.py +222 -0
  17. cli/commands/shader.py +226 -0
  18. cli/commands/texture.py +540 -0
  19. cli/commands/tool.py +58 -0
  20. cli/commands/ui.py +258 -0
  21. cli/commands/vfx.py +421 -0
  22. cli/main.py +281 -0
  23. cli/utils/__init__.py +31 -0
  24. cli/utils/config.py +58 -0
  25. cli/utils/confirmation.py +37 -0
  26. cli/utils/connection.py +254 -0
  27. cli/utils/constants.py +23 -0
  28. cli/utils/output.py +195 -0
  29. cli/utils/parsers.py +112 -0
  30. cli/utils/suggestions.py +34 -0
  31. core/__init__.py +0 -0
  32. core/config.py +67 -0
  33. core/constants.py +4 -0
  34. core/logging_decorator.py +37 -0
  35. core/telemetry.py +551 -0
  36. core/telemetry_decorator.py +164 -0
  37. main.py +845 -0
  38. mcpforunityserver-9.4.0b20260203025228.dist-info/METADATA +328 -0
  39. mcpforunityserver-9.4.0b20260203025228.dist-info/RECORD +105 -0
  40. mcpforunityserver-9.4.0b20260203025228.dist-info/WHEEL +5 -0
  41. mcpforunityserver-9.4.0b20260203025228.dist-info/entry_points.txt +3 -0
  42. mcpforunityserver-9.4.0b20260203025228.dist-info/licenses/LICENSE +21 -0
  43. mcpforunityserver-9.4.0b20260203025228.dist-info/top_level.txt +7 -0
  44. models/__init__.py +4 -0
  45. models/models.py +56 -0
  46. models/unity_response.py +70 -0
  47. services/__init__.py +0 -0
  48. services/api_key_service.py +235 -0
  49. services/custom_tool_service.py +499 -0
  50. services/registry/__init__.py +22 -0
  51. services/registry/resource_registry.py +53 -0
  52. services/registry/tool_registry.py +51 -0
  53. services/resources/__init__.py +86 -0
  54. services/resources/active_tool.py +48 -0
  55. services/resources/custom_tools.py +57 -0
  56. services/resources/editor_state.py +304 -0
  57. services/resources/gameobject.py +243 -0
  58. services/resources/layers.py +30 -0
  59. services/resources/menu_items.py +35 -0
  60. services/resources/prefab.py +191 -0
  61. services/resources/prefab_stage.py +40 -0
  62. services/resources/project_info.py +40 -0
  63. services/resources/selection.py +56 -0
  64. services/resources/tags.py +31 -0
  65. services/resources/tests.py +88 -0
  66. services/resources/unity_instances.py +125 -0
  67. services/resources/windows.py +48 -0
  68. services/state/external_changes_scanner.py +245 -0
  69. services/tools/__init__.py +83 -0
  70. services/tools/batch_execute.py +93 -0
  71. services/tools/debug_request_context.py +86 -0
  72. services/tools/execute_custom_tool.py +43 -0
  73. services/tools/execute_menu_item.py +32 -0
  74. services/tools/find_gameobjects.py +110 -0
  75. services/tools/find_in_file.py +181 -0
  76. services/tools/manage_asset.py +119 -0
  77. services/tools/manage_components.py +131 -0
  78. services/tools/manage_editor.py +64 -0
  79. services/tools/manage_gameobject.py +260 -0
  80. services/tools/manage_material.py +111 -0
  81. services/tools/manage_prefabs.py +209 -0
  82. services/tools/manage_scene.py +111 -0
  83. services/tools/manage_script.py +645 -0
  84. services/tools/manage_scriptable_object.py +87 -0
  85. services/tools/manage_shader.py +71 -0
  86. services/tools/manage_texture.py +581 -0
  87. services/tools/manage_vfx.py +120 -0
  88. services/tools/preflight.py +110 -0
  89. services/tools/read_console.py +151 -0
  90. services/tools/refresh_unity.py +153 -0
  91. services/tools/run_tests.py +317 -0
  92. services/tools/script_apply_edits.py +1006 -0
  93. services/tools/set_active_instance.py +120 -0
  94. services/tools/utils.py +348 -0
  95. transport/__init__.py +0 -0
  96. transport/legacy/port_discovery.py +329 -0
  97. transport/legacy/stdio_port_registry.py +65 -0
  98. transport/legacy/unity_connection.py +910 -0
  99. transport/models.py +68 -0
  100. transport/plugin_hub.py +787 -0
  101. transport/plugin_registry.py +182 -0
  102. transport/unity_instance_middleware.py +262 -0
  103. transport/unity_transport.py +94 -0
  104. utils/focus_nudge.py +589 -0
  105. 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)