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
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}'")
|