Skip to content

Commit c26ee13

Browse files
committed
MCP: add spec resource (script-edits); tighten script_apply_edits description with canonical fields & examples; alias/wrapper normalization; machine-parsable validation hints; auto applyMode=sequential for mixed insert+replace; echo normalizedEdits
1 parent f1d773b commit c26ee13

File tree

2 files changed

+309
-179
lines changed

2 files changed

+309
-179
lines changed

UnityMcpBridge/UnityMcpServer~/src/server.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,19 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
8181
def asset_creation_strategy() -> str:
8282
"""Guide for discovering and using Unity MCP tools effectively."""
8383
return (
84-
"Available Unity MCP Server Tools:\\n\\n"
85-
"- `manage_editor`: Controls editor state and queries info.\\n"
86-
"- `execute_menu_item`: Executes Unity Editor menu items by path.\\n"
87-
"- `read_console`: Reads or clears Unity console messages, with filtering options.\\n"
88-
"- `manage_scene`: Manages scenes.\\n"
89-
"- `manage_gameobject`: Manages GameObjects in the scene.\\n"
90-
"- `manage_script`: Manages C# script files.\\n"
91-
"- `manage_asset`: Manages prefabs and assets.\\n"
92-
"- `manage_shader`: Manages shaders.\\n\\n"
93-
"Tips:\\n"
94-
"- Create prefabs for reusable GameObjects.\\n"
95-
"- Always include a camera and main light in your scenes.\\n"
84+
"Available Unity MCP Server Tools:\n\n"
85+
"- `manage_editor`: Controls editor state and queries info.\n"
86+
"- `execute_menu_item`: Executes Unity Editor menu items by path.\n"
87+
"- `read_console`: Reads or clears Unity console messages, with filtering options.\n"
88+
"- `manage_scene`: Manages scenes.\n"
89+
"- `manage_gameobject`: Manages GameObjects in the scene.\n"
90+
"- `manage_script`: Manages C# script files.\n"
91+
"- `manage_asset`: Manages prefabs and assets.\n"
92+
"- `manage_shader`: Manages shaders.\n\n"
93+
"Tips:\n"
94+
"- Prefer structured script edits over raw text ranges.\n"
95+
"- For script edits, common aliases are accepted: class_name→className; method_name/target/method→methodName; new_method/newMethod/content→replacement; anchor_method→afterMethodName/beforeMethodName based on position.\n"
96+
"- You can pass uri or full file path for scripts; the server normalizes to name/path.\n"
9697
)
9798

9899
# Resources support: list and read Unity scripts/files
@@ -133,11 +134,62 @@ def list_resources(ctx: Context) -> list[dict]:
133134
assets.append({"uri": f"unity://path/{rel}", "name": p.name})
134135
except Exception:
135136
pass
137+
# Add spec resource so clients (e.g., Claude Desktop) can learn the exact contract
138+
assets.append({
139+
"uri": "unity://spec/script-edits",
140+
"name": "Unity Script Edits – Required JSON"
141+
})
136142
return assets
137143

138144
if hasattr(mcp, "resource") and hasattr(getattr(mcp, "resource"), "read"):
139145
@mcp.resource.read()
140146
def read_resource(ctx: Context, uri: str) -> dict:
147+
# Serve script-edits spec
148+
if uri == "unity://spec/script-edits":
149+
spec_json = (
150+
'{\n'
151+
' "name": "Unity MCP — Script Edits v1",\n'
152+
' "target_tool": "script_apply_edits",\n'
153+
' "canonical_rules": {\n'
154+
' "always_use": ["op","className","methodName","replacement","afterMethodName","beforeMethodName"],\n'
155+
' "never_use": ["new_method","anchor_method","content","newText"],\n'
156+
' "defaults": {\n'
157+
' "className": "← server will default to \'name\' when omitted",\n'
158+
' "position": "end"\n'
159+
' }\n'
160+
' },\n'
161+
' "ops": [\n'
162+
' {"op":"replace_method","required":["className","methodName","replacement"],"optional":["returnType","parametersSignature","attributesContains"]},\n'
163+
' {"op":"insert_method","required":["className","replacement"],"position":{"enum":["start","end","after","before"],"after_requires":"afterMethodName","before_requires":"beforeMethodName"}},\n'
164+
' {"op":"delete_method","required":["className","methodName"]},\n'
165+
' {"op":"anchor_insert","required":["anchor","text"],"notes":"regex; position=before|after"}\n'
166+
' ],\n'
167+
' "examples": [\n'
168+
' {\n'
169+
' "title": "Replace a method",\n'
170+
' "args": {\n'
171+
' "name": "SmartReach",\n'
172+
' "path": "Assets/Scripts/Interaction",\n'
173+
' "edits": [\n'
174+
' {"op":"replace_method","className":"SmartReach","methodName":"HasTarget","replacement":"public bool HasTarget() { return currentTarget != null; }"}\n'
175+
' ],\n'
176+
' "options": { "validate": "standard", "refresh": "immediate" }\n'
177+
' }\n'
178+
' },\n'
179+
' {\n'
180+
' "title": "Insert a method after another",\n'
181+
' "args": {\n'
182+
' "name": "SmartReach",\n'
183+
' "path": "Assets/Scripts/Interaction",\n'
184+
' "edits": [\n'
185+
' {"op":"insert_method","className":"SmartReach","replacement":"public void PrintSeries() { Debug.Log(seriesName); }","position":"after","afterMethodName":"GetCurrentTarget"}\n'
186+
' ]\n'
187+
' }\n'
188+
' }\n'
189+
' ]\n'
190+
'}\n'
191+
)
192+
return {"mimeType": "application/json", "text": spec_json}
141193
p = _resolve_safe_path_from_uri(uri)
142194
if not p or not p.exists():
143195
return {"mimeType": "text/plain", "text": f"Resource not found: {uri}"}

0 commit comments

Comments
 (0)