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.
Files changed (103) 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 +258 -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 +52 -0
  33. core/logging_decorator.py +37 -0
  34. core/telemetry.py +551 -0
  35. core/telemetry_decorator.py +164 -0
  36. main.py +713 -0
  37. mcpforunityserver-9.3.0b20260129104751.dist-info/METADATA +216 -0
  38. mcpforunityserver-9.3.0b20260129104751.dist-info/RECORD +103 -0
  39. mcpforunityserver-9.3.0b20260129104751.dist-info/WHEEL +5 -0
  40. mcpforunityserver-9.3.0b20260129104751.dist-info/entry_points.txt +3 -0
  41. mcpforunityserver-9.3.0b20260129104751.dist-info/licenses/LICENSE +21 -0
  42. mcpforunityserver-9.3.0b20260129104751.dist-info/top_level.txt +7 -0
  43. models/__init__.py +4 -0
  44. models/models.py +56 -0
  45. models/unity_response.py +47 -0
  46. services/__init__.py +0 -0
  47. services/custom_tool_service.py +499 -0
  48. services/registry/__init__.py +22 -0
  49. services/registry/resource_registry.py +53 -0
  50. services/registry/tool_registry.py +51 -0
  51. services/resources/__init__.py +86 -0
  52. services/resources/active_tool.py +47 -0
  53. services/resources/custom_tools.py +57 -0
  54. services/resources/editor_state.py +304 -0
  55. services/resources/gameobject.py +243 -0
  56. services/resources/layers.py +29 -0
  57. services/resources/menu_items.py +34 -0
  58. services/resources/prefab.py +191 -0
  59. services/resources/prefab_stage.py +39 -0
  60. services/resources/project_info.py +39 -0
  61. services/resources/selection.py +55 -0
  62. services/resources/tags.py +30 -0
  63. services/resources/tests.py +87 -0
  64. services/resources/unity_instances.py +122 -0
  65. services/resources/windows.py +47 -0
  66. services/state/external_changes_scanner.py +245 -0
  67. services/tools/__init__.py +83 -0
  68. services/tools/batch_execute.py +93 -0
  69. services/tools/debug_request_context.py +86 -0
  70. services/tools/execute_custom_tool.py +43 -0
  71. services/tools/execute_menu_item.py +32 -0
  72. services/tools/find_gameobjects.py +110 -0
  73. services/tools/find_in_file.py +181 -0
  74. services/tools/manage_asset.py +119 -0
  75. services/tools/manage_components.py +131 -0
  76. services/tools/manage_editor.py +64 -0
  77. services/tools/manage_gameobject.py +260 -0
  78. services/tools/manage_material.py +111 -0
  79. services/tools/manage_prefabs.py +174 -0
  80. services/tools/manage_scene.py +111 -0
  81. services/tools/manage_script.py +645 -0
  82. services/tools/manage_scriptable_object.py +87 -0
  83. services/tools/manage_shader.py +71 -0
  84. services/tools/manage_texture.py +581 -0
  85. services/tools/manage_vfx.py +120 -0
  86. services/tools/preflight.py +110 -0
  87. services/tools/read_console.py +151 -0
  88. services/tools/refresh_unity.py +153 -0
  89. services/tools/run_tests.py +317 -0
  90. services/tools/script_apply_edits.py +1006 -0
  91. services/tools/set_active_instance.py +117 -0
  92. services/tools/utils.py +348 -0
  93. transport/__init__.py +0 -0
  94. transport/legacy/port_discovery.py +329 -0
  95. transport/legacy/stdio_port_registry.py +65 -0
  96. transport/legacy/unity_connection.py +888 -0
  97. transport/models.py +63 -0
  98. transport/plugin_hub.py +585 -0
  99. transport/plugin_registry.py +126 -0
  100. transport/unity_instance_middleware.py +232 -0
  101. transport/unity_transport.py +63 -0
  102. utils/focus_nudge.py +589 -0
  103. utils/module_discovery.py +55 -0
cli/commands/code.py ADDED
@@ -0,0 +1,182 @@
1
+ """Code CLI commands - read source code. search might be implemented later (but can be totally achievable with AI)."""
2
+
3
+ import sys
4
+ import os
5
+ import click
6
+ from typing import Optional, Any
7
+
8
+ from cli.utils.config import get_config
9
+ from cli.utils.output import format_output, print_error, print_info
10
+ from cli.utils.connection import run_command, handle_unity_errors
11
+
12
+
13
+ @click.group()
14
+ def code():
15
+ """Code operations - read source files."""
16
+ pass
17
+
18
+
19
+ @code.command("read")
20
+ @click.argument("path")
21
+ @click.option(
22
+ "--start-line", "-s",
23
+ default=None,
24
+ type=int,
25
+ help="Starting line number (1-based)."
26
+ )
27
+ @click.option(
28
+ "--line-count", "-n",
29
+ default=None,
30
+ type=int,
31
+ help="Number of lines to read."
32
+ )
33
+ @handle_unity_errors
34
+ def read(path: str, start_line: Optional[int], line_count: Optional[int]):
35
+ """Read a source file.
36
+
37
+ \b
38
+ Examples:
39
+ unity-mcp code read "Assets/Scripts/Player.cs"
40
+ unity-mcp code read "Assets/Scripts/Player.cs" --start-line 10 --line-count 20
41
+ """
42
+ config = get_config()
43
+
44
+ # Extract name and directory from path
45
+ parts = path.replace("\\", "/").split("/")
46
+ filename = os.path.splitext(parts[-1])[0]
47
+ directory = "/".join(parts[:-1]) or "Assets"
48
+
49
+ params: dict[str, Any] = {
50
+ "action": "read",
51
+ "name": filename,
52
+ "path": directory,
53
+ }
54
+
55
+ if start_line:
56
+ params["startLine"] = start_line
57
+ if line_count:
58
+ params["lineCount"] = line_count
59
+
60
+ result = run_command("manage_script", params, config)
61
+ # For read, output content directly if available
62
+ if result.get("success") and result.get("data"):
63
+ data = result.get("data", {})
64
+ if isinstance(data, dict) and "contents" in data:
65
+ click.echo(data["contents"])
66
+ else:
67
+ click.echo(format_output(result, config.format))
68
+ else:
69
+ click.echo(format_output(result, config.format))
70
+
71
+
72
+ @code.command("search")
73
+ @click.argument("pattern")
74
+ @click.argument("path")
75
+ @click.option(
76
+ "--max-results", "-n",
77
+ default=50,
78
+ type=int,
79
+ help="Maximum number of results (default: 50)."
80
+ )
81
+ @click.option(
82
+ "--case-sensitive", "-c",
83
+ is_flag=True,
84
+ help="Make search case-sensitive (default: case-insensitive)."
85
+ )
86
+ @handle_unity_errors
87
+ def search(pattern: str, path: str, max_results: int, case_sensitive: bool):
88
+ """Search for patterns in Unity scripts using regex.
89
+
90
+ PATTERN is a regex pattern to search for.
91
+ PATH is the script path (e.g., Assets/Scripts/Player.cs).
92
+
93
+ \\b
94
+ Examples:
95
+ unity-mcp code search "class.*Player" "Assets/Scripts/Player.cs"
96
+ unity-mcp code search "private.*int" "Assets/Scripts/GameManager.cs"
97
+ unity-mcp code search "TODO|FIXME" "Assets/Scripts/Utils.cs"
98
+ """
99
+ import re
100
+ import base64
101
+
102
+ config = get_config()
103
+
104
+ # Extract name and directory from path
105
+ parts = path.replace("\\", "/").split("/")
106
+ filename = os.path.splitext(parts[-1])[0]
107
+ directory = "/".join(parts[:-1]) or "Assets"
108
+
109
+ # Step 1: Read the file via Unity's manage_script
110
+ read_params: dict[str, Any] = {
111
+ "action": "read",
112
+ "name": filename,
113
+ "path": directory,
114
+ }
115
+
116
+ result = run_command("manage_script", read_params, config)
117
+
118
+ # Handle nested response structure: {status, result: {success, data}}
119
+ inner_result = result.get("result", result)
120
+
121
+ if not inner_result.get("success") and result.get("status") != "success":
122
+ click.echo(format_output(result, config.format))
123
+ return
124
+
125
+ # Get file contents from nested data
126
+ data = inner_result.get("data", {})
127
+ contents = data.get("contents")
128
+
129
+ # Handle base64 encoded content
130
+ if not contents and data.get("contentsEncoded") and data.get("encodedContents"):
131
+ try:
132
+ contents = base64.b64decode(
133
+ data["encodedContents"]).decode("utf-8", "replace")
134
+ except (ValueError, TypeError):
135
+ pass
136
+
137
+ if not contents:
138
+ print_error(f"Could not read file content from {path}")
139
+ sys.exit(1)
140
+
141
+ # Step 2: Perform regex search locally
142
+ flags = re.MULTILINE
143
+ if not case_sensitive:
144
+ flags |= re.IGNORECASE
145
+
146
+ try:
147
+ regex = re.compile(pattern, flags)
148
+ except re.error as e:
149
+ print_error(f"Invalid regex pattern: {e}")
150
+ sys.exit(1)
151
+
152
+ found = list(regex.finditer(contents))
153
+
154
+ if not found:
155
+ print_info(f"No matches found for pattern: {pattern}")
156
+ return
157
+
158
+ results = []
159
+ for m in found[:max_results]:
160
+ start_idx = m.start()
161
+
162
+ # Calculate line number
163
+ line_num = contents.count('\n', 0, start_idx) + 1
164
+
165
+ # Get line content
166
+ line_start = contents.rfind('\n', 0, start_idx) + 1
167
+ line_end = contents.find('\n', start_idx)
168
+ if line_end == -1:
169
+ line_end = len(contents)
170
+
171
+ line_content = contents[line_start:line_end].strip()
172
+
173
+ results.append({
174
+ "line": line_num,
175
+ "content": line_content,
176
+ "match": m.group(0),
177
+ })
178
+
179
+ # Display results
180
+ click.echo(f"Found {len(results)} matches (total: {len(found)}):\n")
181
+ for match in results:
182
+ click.echo(f" Line {match['line']}: {match['content']}")
@@ -0,0 +1,190 @@
1
+ """Component CLI commands."""
2
+
3
+ import sys
4
+ import json
5
+ import click
6
+ from typing import Optional, Any
7
+
8
+ from cli.utils.config import get_config
9
+ from cli.utils.output import format_output, print_error, print_success
10
+ from cli.utils.connection import run_command, handle_unity_errors
11
+ from cli.utils.parsers import parse_value_safe, parse_json_dict_or_exit
12
+ from cli.utils.constants import SEARCH_METHOD_CHOICE_BASIC
13
+ from cli.utils.confirmation import confirm_destructive_action
14
+
15
+
16
+ @click.group()
17
+ def component():
18
+ """Component operations - add, remove, modify components on GameObjects."""
19
+ pass
20
+
21
+
22
+ @component.command("add")
23
+ @click.argument("target")
24
+ @click.argument("component_type")
25
+ @click.option(
26
+ "--search-method",
27
+ type=SEARCH_METHOD_CHOICE_BASIC,
28
+ default=None,
29
+ help="How to find the target GameObject."
30
+ )
31
+ @click.option(
32
+ "--properties", "-p",
33
+ default=None,
34
+ help='Initial properties as JSON (e.g., \'{"mass": 5.0}\').'
35
+ )
36
+ @handle_unity_errors
37
+ def add(target: str, component_type: str, search_method: Optional[str], properties: Optional[str]):
38
+ """Add a component to a GameObject.
39
+
40
+ \b
41
+ Examples:
42
+ unity-mcp component add "Player" Rigidbody
43
+ unity-mcp component add "-81840" BoxCollider --search-method by_id
44
+ unity-mcp component add "Enemy" Rigidbody --properties '{"mass": 5.0, "useGravity": true}'
45
+ """
46
+ config = get_config()
47
+
48
+ params: dict[str, Any] = {
49
+ "action": "add",
50
+ "target": target,
51
+ "componentType": component_type,
52
+ }
53
+
54
+ if search_method:
55
+ params["searchMethod"] = search_method
56
+ if properties:
57
+ params["properties"] = parse_json_dict_or_exit(properties, "properties")
58
+
59
+ result = run_command("manage_components", params, config)
60
+ click.echo(format_output(result, config.format))
61
+ if result.get("success"):
62
+ print_success(f"Added {component_type} to '{target}'")
63
+
64
+
65
+ @component.command("remove")
66
+ @click.argument("target")
67
+ @click.argument("component_type")
68
+ @click.option(
69
+ "--search-method",
70
+ type=SEARCH_METHOD_CHOICE_BASIC,
71
+ default=None,
72
+ help="How to find the target GameObject."
73
+ )
74
+ @click.option(
75
+ "--force", "-f",
76
+ is_flag=True,
77
+ help="Skip confirmation prompt."
78
+ )
79
+ @handle_unity_errors
80
+ def remove(target: str, component_type: str, search_method: Optional[str], force: bool):
81
+ """Remove a component from a GameObject.
82
+
83
+ \b
84
+ Examples:
85
+ unity-mcp component remove "Player" Rigidbody
86
+ unity-mcp component remove "-81840" BoxCollider --search-method by_id --force
87
+ """
88
+ config = get_config()
89
+
90
+ confirm_destructive_action("Remove", component_type, target, force, "from")
91
+
92
+ params: dict[str, Any] = {
93
+ "action": "remove",
94
+ "target": target,
95
+ "componentType": component_type,
96
+ }
97
+
98
+ if search_method:
99
+ params["searchMethod"] = search_method
100
+
101
+ result = run_command("manage_components", params, config)
102
+ click.echo(format_output(result, config.format))
103
+ if result.get("success"):
104
+ print_success(f"Removed {component_type} from '{target}'")
105
+
106
+
107
+ @component.command("set")
108
+ @click.argument("target")
109
+ @click.argument("component_type")
110
+ @click.argument("property_name")
111
+ @click.argument("value")
112
+ @click.option(
113
+ "--search-method",
114
+ type=SEARCH_METHOD_CHOICE_BASIC,
115
+ default=None,
116
+ help="How to find the target GameObject."
117
+ )
118
+ @handle_unity_errors
119
+ def set_property(target: str, component_type: str, property_name: str, value: str, search_method: Optional[str]):
120
+ """Set a single property on a component.
121
+
122
+ \b
123
+ Examples:
124
+ unity-mcp component set "Player" Rigidbody mass 5.0
125
+ unity-mcp component set "Enemy" Transform position "[0, 5, 0]"
126
+ unity-mcp component set "-81840" Light intensity 2.5 --search-method by_id
127
+ """
128
+ config = get_config()
129
+
130
+ # Try to parse value as JSON for complex types
131
+ parsed_value = parse_value_safe(value)
132
+
133
+ params: dict[str, Any] = {
134
+ "action": "set_property",
135
+ "target": target,
136
+ "componentType": component_type,
137
+ "property": property_name,
138
+ "value": parsed_value,
139
+ }
140
+
141
+ if search_method:
142
+ params["searchMethod"] = search_method
143
+
144
+ result = run_command("manage_components", params, config)
145
+ click.echo(format_output(result, config.format))
146
+ if result.get("success"):
147
+ print_success(f"Set {component_type}.{property_name} = {value}")
148
+
149
+
150
+ @component.command("modify")
151
+ @click.argument("target")
152
+ @click.argument("component_type")
153
+ @click.option(
154
+ "--properties", "-p",
155
+ required=True,
156
+ help='Properties to set as JSON (e.g., \'{"mass": 5.0, "useGravity": false}\').'
157
+ )
158
+ @click.option(
159
+ "--search-method",
160
+ type=SEARCH_METHOD_CHOICE_BASIC,
161
+ default=None,
162
+ help="How to find the target GameObject."
163
+ )
164
+ @handle_unity_errors
165
+ def modify(target: str, component_type: str, properties: str, search_method: Optional[str]):
166
+ """Set multiple properties on a component at once.
167
+
168
+ \b
169
+ Examples:
170
+ unity-mcp component modify "Player" Rigidbody --properties '{"mass": 5.0, "useGravity": false}'
171
+ unity-mcp component modify "Light" Light --properties '{"intensity": 2.0, "color": [1, 0, 0, 1]}'
172
+ """
173
+ config = get_config()
174
+
175
+ props_dict = parse_json_dict_or_exit(properties, "properties")
176
+
177
+ params: dict[str, Any] = {
178
+ "action": "set_property",
179
+ "target": target,
180
+ "componentType": component_type,
181
+ "properties": props_dict,
182
+ }
183
+
184
+ if search_method:
185
+ params["searchMethod"] = search_method
186
+
187
+ result = run_command("manage_components", params, config)
188
+ click.echo(format_output(result, config.format))
189
+ if result.get("success"):
190
+ print_success(f"Modified {component_type} on '{target}'")