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
cli/commands/scene.py ADDED
@@ -0,0 +1,231 @@
1
+ """Scene CLI commands."""
2
+
3
+ import sys
4
+ import click
5
+ from typing import Optional, Any
6
+
7
+ from cli.utils.config import get_config
8
+ from cli.utils.output import format_output, print_error, print_success
9
+ from cli.utils.connection import run_command, handle_unity_errors
10
+
11
+
12
+ @click.group()
13
+ def scene():
14
+ """Scene operations - hierarchy, load, save, create scenes."""
15
+ pass
16
+
17
+
18
+ @scene.command("hierarchy")
19
+ @click.option(
20
+ "--parent",
21
+ default=None,
22
+ help="Parent GameObject to list children of (name, path, or instance ID)."
23
+ )
24
+ @click.option(
25
+ "--max-depth", "-d",
26
+ default=None,
27
+ type=int,
28
+ help="Maximum depth to traverse."
29
+ )
30
+ @click.option(
31
+ "--include-transform", "-t",
32
+ is_flag=True,
33
+ help="Include transform data for each node."
34
+ )
35
+ @click.option(
36
+ "--limit", "-l",
37
+ default=50,
38
+ type=int,
39
+ help="Maximum nodes to return."
40
+ )
41
+ @click.option(
42
+ "--cursor", "-c",
43
+ default=0,
44
+ type=int,
45
+ help="Pagination cursor."
46
+ )
47
+ @handle_unity_errors
48
+ def hierarchy(
49
+ parent: Optional[str],
50
+ max_depth: Optional[int],
51
+ include_transform: bool,
52
+ limit: int,
53
+ cursor: int,
54
+ ):
55
+ """Get the scene hierarchy.
56
+
57
+ \b
58
+ Examples:
59
+ unity-mcp scene hierarchy
60
+ unity-mcp scene hierarchy --max-depth 3
61
+ unity-mcp scene hierarchy --parent "Canvas" --include-transform
62
+ unity-mcp scene hierarchy --format json
63
+ """
64
+ config = get_config()
65
+
66
+ params: dict[str, Any] = {
67
+ "action": "get_hierarchy",
68
+ "pageSize": limit,
69
+ "cursor": cursor,
70
+ }
71
+
72
+ if parent:
73
+ params["parent"] = parent
74
+ if max_depth is not None:
75
+ params["maxDepth"] = max_depth
76
+ if include_transform:
77
+ params["includeTransform"] = True
78
+
79
+ result = run_command("manage_scene", params, config)
80
+ click.echo(format_output(result, config.format))
81
+
82
+
83
+ @scene.command("active")
84
+ @handle_unity_errors
85
+ def active():
86
+ """Get information about the active scene."""
87
+ config = get_config()
88
+ result = run_command("manage_scene", {"action": "get_active"}, config)
89
+ click.echo(format_output(result, config.format))
90
+
91
+
92
+ @scene.command("load")
93
+ @click.argument("scene")
94
+ @click.option(
95
+ "--by-index", "-i",
96
+ is_flag=True,
97
+ help="Load by build index instead of path/name."
98
+ )
99
+ @handle_unity_errors
100
+ def load(scene: str, by_index: bool):
101
+ """Load a scene.
102
+
103
+ \b
104
+ Examples:
105
+ unity-mcp scene load "Assets/Scenes/Main.unity"
106
+ unity-mcp scene load "MainScene"
107
+ unity-mcp scene load 0 --by-index
108
+ """
109
+ config = get_config()
110
+
111
+ params: dict[str, Any] = {"action": "load"}
112
+
113
+ if by_index:
114
+ try:
115
+ params["buildIndex"] = int(scene)
116
+ except ValueError:
117
+ print_error(f"Invalid build index: {scene}")
118
+ sys.exit(1)
119
+ else:
120
+ if scene.endswith(".unity"):
121
+ params["path"] = scene
122
+ else:
123
+ params["name"] = scene
124
+
125
+ result = run_command("manage_scene", params, config)
126
+ click.echo(format_output(result, config.format))
127
+ if result.get("success"):
128
+ print_success(f"Loaded scene: {scene}")
129
+
130
+
131
+ @scene.command("save")
132
+ @click.option(
133
+ "--path",
134
+ default=None,
135
+ help="Path to save the scene to (for new scenes)."
136
+ )
137
+ @handle_unity_errors
138
+ def save(path: Optional[str]):
139
+ """Save the current scene.
140
+
141
+ \b
142
+ Examples:
143
+ unity-mcp scene save
144
+ unity-mcp scene save --path "Assets/Scenes/NewScene.unity"
145
+ """
146
+ config = get_config()
147
+
148
+ params: dict[str, Any] = {"action": "save"}
149
+ if path:
150
+ params["path"] = path
151
+
152
+ result = run_command("manage_scene", params, config)
153
+ click.echo(format_output(result, config.format))
154
+ if result.get("success"):
155
+ print_success("Scene saved")
156
+
157
+
158
+ @scene.command("create")
159
+ @click.argument("name")
160
+ @click.option(
161
+ "--path",
162
+ default=None,
163
+ help="Path to create the scene at."
164
+ )
165
+ @handle_unity_errors
166
+ def create(name: str, path: Optional[str]):
167
+ """Create a new scene.
168
+
169
+ \b
170
+ Examples:
171
+ unity-mcp scene create "NewLevel"
172
+ unity-mcp scene create "TestScene" --path "Assets/Scenes/Test"
173
+ """
174
+ config = get_config()
175
+
176
+ params: dict[str, Any] = {
177
+ "action": "create",
178
+ "name": name,
179
+ }
180
+ if path:
181
+ params["path"] = path
182
+
183
+ result = run_command("manage_scene", params, config)
184
+ click.echo(format_output(result, config.format))
185
+ if result.get("success"):
186
+ print_success(f"Created scene: {name}")
187
+
188
+
189
+ @scene.command("build-settings")
190
+ @handle_unity_errors
191
+ def build_settings():
192
+ """Get scenes in build settings."""
193
+ config = get_config()
194
+ result = run_command("manage_scene", {"action": "get_build_settings"}, config)
195
+ click.echo(format_output(result, config.format))
196
+
197
+
198
+ @scene.command("screenshot")
199
+ @click.option(
200
+ "--filename", "-f",
201
+ default=None,
202
+ help="Output filename (default: timestamp)."
203
+ )
204
+ @click.option(
205
+ "--supersize", "-s",
206
+ default=1,
207
+ type=int,
208
+ help="Supersize multiplier (1-4)."
209
+ )
210
+ @handle_unity_errors
211
+ def screenshot(filename: Optional[str], supersize: int):
212
+ """Capture a screenshot of the scene.
213
+
214
+ \b
215
+ Examples:
216
+ unity-mcp scene screenshot
217
+ unity-mcp scene screenshot --filename "level_preview"
218
+ unity-mcp scene screenshot --supersize 2
219
+ """
220
+ config = get_config()
221
+
222
+ params: dict[str, Any] = {"action": "screenshot"}
223
+ if filename:
224
+ params["fileName"] = filename
225
+ if supersize > 1:
226
+ params["superSize"] = supersize
227
+
228
+ result = run_command("manage_scene", params, config)
229
+ click.echo(format_output(result, config.format))
230
+ if result.get("success"):
231
+ print_success("Screenshot captured")
cli/commands/script.py ADDED
@@ -0,0 +1,222 @@
1
+ """Script 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_json_list_or_exit
12
+ from cli.utils.confirmation import confirm_destructive_action
13
+
14
+
15
+ @click.group()
16
+ def script():
17
+ """Script operations - create, read, edit C# scripts."""
18
+ pass
19
+
20
+
21
+ @script.command("create")
22
+ @click.argument("name")
23
+ @click.option(
24
+ "--path", "-p",
25
+ default="Assets/Scripts",
26
+ help="Directory to create the script in."
27
+ )
28
+ @click.option(
29
+ "--type", "-t",
30
+ "script_type",
31
+ type=click.Choice(["MonoBehaviour", "ScriptableObject",
32
+ "Editor", "EditorWindow", "Plain"]),
33
+ default="MonoBehaviour",
34
+ help="Type of script to create."
35
+ )
36
+ @click.option(
37
+ "--namespace", "-n",
38
+ default=None,
39
+ help="Namespace for the script."
40
+ )
41
+ @click.option(
42
+ "--contents", "-c",
43
+ default=None,
44
+ help="Full script contents (overrides template)."
45
+ )
46
+ @handle_unity_errors
47
+ def create(name: str, path: str, script_type: str, namespace: Optional[str], contents: Optional[str]):
48
+ """Create a new C# script.
49
+
50
+ \b
51
+ Examples:
52
+ unity-mcp script create "PlayerController"
53
+ unity-mcp script create "GameManager" --path "Assets/Scripts/Managers"
54
+ unity-mcp script create "EnemyData" --type ScriptableObject
55
+ unity-mcp script create "CustomEditor" --type Editor --namespace "MyGame.Editor"
56
+ """
57
+ config = get_config()
58
+
59
+ params: dict[str, Any] = {
60
+ "action": "create",
61
+ "name": name,
62
+ "path": path,
63
+ "scriptType": script_type,
64
+ }
65
+
66
+ if namespace:
67
+ params["namespace"] = namespace
68
+ if contents:
69
+ params["contents"] = contents
70
+
71
+ result = run_command("manage_script", params, config)
72
+ click.echo(format_output(result, config.format))
73
+ if result.get("success"):
74
+ print_success(f"Created script: {name}.cs")
75
+
76
+
77
+ @script.command("read")
78
+ @click.argument("path")
79
+ @click.option(
80
+ "--start-line", "-s",
81
+ default=None,
82
+ type=int,
83
+ help="Starting line number (1-based)."
84
+ )
85
+ @click.option(
86
+ "--line-count", "-n",
87
+ default=None,
88
+ type=int,
89
+ help="Number of lines to read."
90
+ )
91
+ @handle_unity_errors
92
+ def read(path: str, start_line: Optional[int], line_count: Optional[int]):
93
+ """Read a C# script file.
94
+
95
+ \b
96
+ Examples:
97
+ unity-mcp script read "Assets/Scripts/Player.cs"
98
+ unity-mcp script read "Assets/Scripts/Player.cs" --start-line 10 --line-count 20
99
+ """
100
+ config = get_config()
101
+
102
+ parts = path.rsplit("/", 1)
103
+ filename = parts[-1]
104
+ directory = parts[0] if len(parts) > 1 else "Assets"
105
+ name = filename[:-3] if filename.endswith(".cs") else filename
106
+
107
+ params: dict[str, Any] = {
108
+ "action": "read",
109
+ "name": name,
110
+ "path": directory,
111
+ }
112
+
113
+ if start_line:
114
+ params["startLine"] = start_line
115
+ if line_count:
116
+ params["lineCount"] = line_count
117
+
118
+ result = run_command("manage_script", params, config)
119
+ # For read, just output the content directly
120
+ if result.get("success") and result.get("data"):
121
+ data = result.get("data", {})
122
+ if isinstance(data, dict) and "contents" in data:
123
+ click.echo(data["contents"])
124
+ else:
125
+ click.echo(format_output(result, config.format))
126
+ else:
127
+ click.echo(format_output(result, config.format))
128
+
129
+
130
+ @script.command("delete")
131
+ @click.argument("path")
132
+ @click.option(
133
+ "--force", "-f",
134
+ is_flag=True,
135
+ help="Skip confirmation prompt."
136
+ )
137
+ @handle_unity_errors
138
+ def delete(path: str, force: bool):
139
+ """Delete a C# script.
140
+
141
+ \b
142
+ Examples:
143
+ unity-mcp script delete "Assets/Scripts/OldScript.cs"
144
+ """
145
+ config = get_config()
146
+
147
+ confirm_destructive_action("Delete", "script", path, force)
148
+
149
+ parts = path.rsplit("/", 1)
150
+ filename = parts[-1]
151
+ directory = parts[0] if len(parts) > 1 else "Assets"
152
+ name = filename[:-3] if filename.endswith(".cs") else filename
153
+
154
+ params: dict[str, Any] = {
155
+ "action": "delete",
156
+ "name": name,
157
+ "path": directory,
158
+ }
159
+
160
+ result = run_command("manage_script", params, config)
161
+ click.echo(format_output(result, config.format))
162
+ if result.get("success"):
163
+ print_success(f"Deleted: {path}")
164
+
165
+
166
+ @script.command("edit")
167
+ @click.argument("path")
168
+ @click.option(
169
+ "--edits", "-e",
170
+ required=True,
171
+ help='Edits as JSON array of {startLine, startCol, endLine, endCol, newText}.'
172
+ )
173
+ @handle_unity_errors
174
+ def edit(path: str, edits: str):
175
+ """Apply text edits to a script.
176
+
177
+ \b
178
+ Examples:
179
+ unity-mcp script edit "Assets/Scripts/Player.cs" --edits '[{"startLine": 10, "startCol": 1, "endLine": 10, "endCol": 20, "newText": "// Modified"}]'
180
+ """
181
+ config = get_config()
182
+
183
+ edits_list = parse_json_list_or_exit(edits, "edits")
184
+
185
+ params: dict[str, Any] = {
186
+ "uri": path,
187
+ "edits": edits_list,
188
+ }
189
+
190
+ result = run_command("apply_text_edits", params, config)
191
+ click.echo(format_output(result, config.format))
192
+ if result.get("success"):
193
+ print_success(f"Applied edits to: {path}")
194
+
195
+
196
+ @script.command("validate")
197
+ @click.argument("path")
198
+ @click.option(
199
+ "--level", "-l",
200
+ type=click.Choice(["basic", "standard"]),
201
+ default="basic",
202
+ help="Validation level."
203
+ )
204
+ @handle_unity_errors
205
+ def validate(path: str, level: str):
206
+ """Validate a C# script for errors.
207
+
208
+ \b
209
+ Examples:
210
+ unity-mcp script validate "Assets/Scripts/Player.cs"
211
+ unity-mcp script validate "Assets/Scripts/Player.cs" --level standard
212
+ """
213
+ config = get_config()
214
+
215
+ params: dict[str, Any] = {
216
+ "uri": path,
217
+ "level": level,
218
+ "include_diagnostics": True,
219
+ }
220
+
221
+ result = run_command("validate_script", params, config)
222
+ click.echo(format_output(result, config.format))
cli/commands/shader.py ADDED
@@ -0,0 +1,226 @@
1
+ """Shader CLI commands for managing Unity shaders."""
2
+
3
+ import sys
4
+ import click
5
+ from typing import Optional
6
+
7
+ from cli.utils.config import get_config
8
+ from cli.utils.output import format_output, print_error, print_success
9
+ from cli.utils.connection import run_command, handle_unity_errors
10
+ from cli.utils.confirmation import confirm_destructive_action
11
+
12
+
13
+ @click.group()
14
+ def shader():
15
+ """Shader operations - create, read, update, delete shaders."""
16
+ pass
17
+
18
+
19
+ @shader.command("read")
20
+ @click.argument("path")
21
+ @handle_unity_errors
22
+ def read_shader(path: str):
23
+ """Read a shader file.
24
+
25
+ \\b
26
+ Examples:
27
+ unity-mcp shader read "Assets/Shaders/MyShader.shader"
28
+ """
29
+ config = get_config()
30
+
31
+ # Extract name from path
32
+ import os
33
+ name = os.path.splitext(os.path.basename(path))[0]
34
+ directory = os.path.dirname(path)
35
+
36
+ result = run_command("manage_shader", {
37
+ "action": "read",
38
+ "name": name,
39
+ "path": directory or "Assets/",
40
+ }, config)
41
+
42
+ # If successful, display the contents nicely
43
+ if result.get("success") and result.get("data", {}).get("contents"):
44
+ click.echo(result["data"]["contents"])
45
+ else:
46
+ click.echo(format_output(result, config.format))
47
+
48
+
49
+ @shader.command("create")
50
+ @click.argument("name")
51
+ @click.option(
52
+ "--path", "-p",
53
+ default="Assets/Shaders",
54
+ help="Directory to create shader in."
55
+ )
56
+ @click.option(
57
+ "--contents", "-c",
58
+ default=None,
59
+ help="Shader code (reads from stdin if not provided)."
60
+ )
61
+ @click.option(
62
+ "--file", "-f",
63
+ "file_path",
64
+ default=None,
65
+ type=click.Path(exists=True),
66
+ help="Read shader code from file."
67
+ )
68
+ @handle_unity_errors
69
+ def create_shader(name: str, path: str, contents: Optional[str], file_path: Optional[str]):
70
+ """Create a new shader.
71
+
72
+ \\b
73
+ Examples:
74
+ unity-mcp shader create "MyShader" --path "Assets/Shaders"
75
+ unity-mcp shader create "MyShader" --file local_shader.shader
76
+ echo "Shader code..." | unity-mcp shader create "MyShader"
77
+ """
78
+ config = get_config()
79
+
80
+ # Get contents from file, option, or stdin
81
+ if file_path:
82
+ with open(file_path, 'r') as f:
83
+ shader_contents = f.read()
84
+ elif contents:
85
+ shader_contents = contents
86
+ else:
87
+ # Read from stdin if available
88
+ import sys
89
+ if not sys.stdin.isatty():
90
+ shader_contents = sys.stdin.read()
91
+ else:
92
+ # Provide default shader template
93
+ shader_contents = f'''Shader "Custom/{name}"
94
+ {{
95
+ Properties
96
+ {{
97
+ _Color ("Color", Color) = (1,1,1,1)
98
+ _MainTex ("Albedo (RGB)", 2D) = "white" {{}}
99
+ }}
100
+ SubShader
101
+ {{
102
+ Tags {{ "RenderType"="Opaque" }}
103
+ LOD 200
104
+
105
+ CGPROGRAM
106
+ #pragma surface surf Standard fullforwardshadows
107
+ #pragma target 3.0
108
+
109
+ sampler2D _MainTex;
110
+ fixed4 _Color;
111
+
112
+ struct Input
113
+ {{
114
+ float2 uv_MainTex;
115
+ }};
116
+
117
+ void surf (Input IN, inout SurfaceOutputStandard o)
118
+ {{
119
+ fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
120
+ o.Albedo = c.rgb;
121
+ o.Alpha = c.a;
122
+ }}
123
+ ENDCG
124
+ }}
125
+ FallBack "Diffuse"
126
+ }}
127
+ '''
128
+
129
+ result = run_command("manage_shader", {
130
+ "action": "create",
131
+ "name": name,
132
+ "path": path,
133
+ "contents": shader_contents,
134
+ }, config)
135
+ click.echo(format_output(result, config.format))
136
+ if result.get("success"):
137
+ print_success(f"Created shader: {path}/{name}.shader")
138
+
139
+
140
+ @shader.command("update")
141
+ @click.argument("path")
142
+ @click.option(
143
+ "--contents", "-c",
144
+ default=None,
145
+ help="New shader code."
146
+ )
147
+ @click.option(
148
+ "--file", "-f",
149
+ "file_path",
150
+ default=None,
151
+ type=click.Path(exists=True),
152
+ help="Read shader code from file."
153
+ )
154
+ @handle_unity_errors
155
+ def update_shader(path: str, contents: Optional[str], file_path: Optional[str]):
156
+ """Update an existing shader.
157
+
158
+ \\b
159
+ Examples:
160
+ unity-mcp shader update "Assets/Shaders/MyShader.shader" --file updated.shader
161
+ echo "New shader code" | unity-mcp shader update "Assets/Shaders/MyShader.shader"
162
+ """
163
+ config = get_config()
164
+
165
+ import os
166
+ name = os.path.splitext(os.path.basename(path))[0]
167
+ directory = os.path.dirname(path)
168
+
169
+ # Get contents from file, option, or stdin
170
+ if file_path:
171
+ with open(file_path, 'r') as f:
172
+ shader_contents = f.read()
173
+ elif contents:
174
+ shader_contents = contents
175
+ else:
176
+ import sys
177
+ if not sys.stdin.isatty():
178
+ shader_contents = sys.stdin.read()
179
+ else:
180
+ print_error(
181
+ "No shader contents provided. Use --contents, --file, or pipe via stdin.")
182
+ sys.exit(1)
183
+
184
+ result = run_command("manage_shader", {
185
+ "action": "update",
186
+ "name": name,
187
+ "path": directory or "Assets/",
188
+ "contents": shader_contents,
189
+ }, config)
190
+ click.echo(format_output(result, config.format))
191
+ if result.get("success"):
192
+ print_success(f"Updated shader: {path}")
193
+
194
+
195
+ @shader.command("delete")
196
+ @click.argument("path")
197
+ @click.option(
198
+ "--force", "-f",
199
+ is_flag=True,
200
+ help="Skip confirmation prompt."
201
+ )
202
+ @handle_unity_errors
203
+ def delete_shader(path: str, force: bool):
204
+ """Delete a shader.
205
+
206
+ \\b
207
+ Examples:
208
+ unity-mcp shader delete "Assets/Shaders/OldShader.shader"
209
+ unity-mcp shader delete "Assets/Shaders/OldShader.shader" --force
210
+ """
211
+ config = get_config()
212
+
213
+ confirm_destructive_action("Delete", "shader", path, force)
214
+
215
+ import os
216
+ name = os.path.splitext(os.path.basename(path))[0]
217
+ directory = os.path.dirname(path)
218
+
219
+ result = run_command("manage_shader", {
220
+ "action": "delete",
221
+ "name": name,
222
+ "path": directory or "Assets/",
223
+ }, config)
224
+ click.echo(format_output(result, config.format))
225
+ if result.get("success"):
226
+ print_success(f"Deleted shader: {path}")