diff --git a/.gitignore b/.gitignore
index 0e2cbb0..be9fc7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,7 @@ CONTRIBUTING.md.meta
.DS_Store*
# Unity test project lock files
TestProjects/UnityMCPTests/Packages/packages-lock.json
+
+# Backup artifacts
+*.backup
+*.backup.meta
diff --git a/TestProjects/UnityMCPTests/Assets/Editor.meta b/TestProjects/UnityMCPTests/Assets/Editor.meta
new file mode 100644
index 0000000..79828f3
--- /dev/null
+++ b/TestProjects/UnityMCPTests/Assets/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 46421b2ea84fe4b1a903e2483cff3958
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs b/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs
index ab99643..9bab3e3 100644
--- a/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs
+++ b/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs
@@ -1,11 +1,15 @@
-using System.Collections;
-using System.Collections.Generic;
using UnityEngine;
+using System.Collections;
public class Hello : MonoBehaviour
{
+
+ // Use this for initialization
void Start()
{
Debug.Log("Hello World");
}
-}
+
+
+
+}
\ No newline at end of file
diff --git a/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs.meta b/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs.meta
index 6b1e126..b01fea0 100644
--- a/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs.meta
+++ b/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs.meta
@@ -1,11 +1,2 @@
fileFormatVersion: 2
-guid: bebdf68a6876b425494ee770d20f70ef
-MonoImporter:
- externalObjects: {}
- serializedVersion: 2
- defaultReferences: []
- executionOrder: 0
- icon: {instanceID: 0}
- userData:
- assetBundleName:
- assetBundleVariant:
+guid: bebdf68a6876b425494ee770d20f70ef
\ No newline at end of file
diff --git a/UnityMcpBridge/Editor/MCPForUnityBridge.cs b/UnityMcpBridge/Editor/MCPForUnityBridge.cs
index f90b223..1a17584 100644
--- a/UnityMcpBridge/Editor/MCPForUnityBridge.cs
+++ b/UnityMcpBridge/Editor/MCPForUnityBridge.cs
@@ -48,7 +48,7 @@ namespace MCPForUnity.Editor
{
if (IsDebugEnabled())
{
- Debug.Log($"MCP-FOR-UNITY: [{stage}]");
+ McpLog.Info($"[{stage}]", always: false);
}
}
@@ -230,7 +230,10 @@ namespace MCPForUnity.Editor
// Don't restart if already running on a working port
if (isRunning && listener != null)
{
- Debug.Log($"MCP-FOR-UNITY: MCPForUnityBridge already running on port {currentUnityPort}");
+ if (IsDebugEnabled())
+ {
+ Debug.Log($"MCP-FOR-UNITY: MCPForUnityBridge already running on port {currentUnityPort}");
+ }
return;
}
@@ -348,7 +351,7 @@ namespace MCPForUnity.Editor
listener?.Stop();
listener = null;
EditorApplication.update -= ProcessCommands;
- Debug.Log("MCP-FOR-UNITY: MCPForUnityBridge stopped.");
+ if (IsDebugEnabled()) Debug.Log("MCP-FOR-UNITY: MCPForUnityBridge stopped.");
}
catch (Exception ex)
{
@@ -389,7 +392,7 @@ namespace MCPForUnity.Editor
{
if (isRunning)
{
- Debug.LogError($"Listener error: {ex.Message}");
+ if (IsDebugEnabled()) Debug.LogError($"Listener error: {ex.Message}");
}
}
}
@@ -403,8 +406,11 @@ namespace MCPForUnity.Editor
// Framed I/O only; legacy mode removed
try
{
- var ep = client.Client?.RemoteEndPoint?.ToString() ?? "unknown";
- Debug.Log($"UNITY-MCP: Client connected {ep}");
+ if (IsDebugEnabled())
+ {
+ var ep = client.Client?.RemoteEndPoint?.ToString() ?? "unknown";
+ Debug.Log($"UNITY-MCP: Client connected {ep}");
+ }
}
catch { }
// Strict framing: always require FRAMING=1 and frame all I/O
@@ -423,11 +429,11 @@ namespace MCPForUnity.Editor
#else
await stream.WriteAsync(handshakeBytes, 0, handshakeBytes.Length, cts.Token).ConfigureAwait(false);
#endif
- Debug.Log("UNITY-MCP: Sent handshake FRAMING=1 (strict)");
+ if (IsDebugEnabled()) MCPForUnity.Editor.Helpers.McpLog.Info("Sent handshake FRAMING=1 (strict)", always: false);
}
catch (Exception ex)
{
- Debug.LogWarning($"UNITY-MCP: Handshake failed: {ex.Message}");
+ if (IsDebugEnabled()) MCPForUnity.Editor.Helpers.McpLog.Warn($"Handshake failed: {ex.Message}");
return; // abort this client
}
@@ -440,8 +446,11 @@ namespace MCPForUnity.Editor
try
{
- var preview = commandText.Length > 120 ? commandText.Substring(0, 120) + "…" : commandText;
- Debug.Log($"UNITY-MCP: recv framed: {preview}");
+ if (IsDebugEnabled())
+ {
+ var preview = commandText.Length > 120 ? commandText.Substring(0, 120) + "…" : commandText;
+ MCPForUnity.Editor.Helpers.McpLog.Info($"recv framed: {preview}", always: false);
+ }
}
catch { }
string commandId = Guid.NewGuid().ToString();
@@ -470,7 +479,20 @@ namespace MCPForUnity.Editor
}
catch (Exception ex)
{
- Debug.LogError($"Client handler error: {ex.Message}");
+ // Treat common disconnects/timeouts as benign; only surface hard errors
+ string msg = ex.Message ?? string.Empty;
+ bool isBenign =
+ msg.IndexOf("Connection closed before reading expected bytes", StringComparison.OrdinalIgnoreCase) >= 0
+ || msg.IndexOf("Read timed out", StringComparison.OrdinalIgnoreCase) >= 0
+ || ex is System.IO.IOException;
+ if (isBenign)
+ {
+ if (IsDebugEnabled()) MCPForUnity.Editor.Helpers.McpLog.Info($"Client handler: {msg}", always: false);
+ }
+ else
+ {
+ MCPForUnity.Editor.Helpers.McpLog.Error($"Client handler error: {msg}");
+ }
break;
}
}
diff --git a/UnityMcpBridge/Editor/Tools/ExecuteMenuItem.cs b/UnityMcpBridge/Editor/Tools/ExecuteMenuItem.cs
index e51d773..5adb476 100644
--- a/UnityMcpBridge/Editor/Tools/ExecuteMenuItem.cs
+++ b/UnityMcpBridge/Editor/Tools/ExecuteMenuItem.cs
@@ -68,6 +68,8 @@ namespace MCPForUnity.Editor.Tools
{
// Try both naming conventions: snake_case and camelCase
string menuPath = @params["menu_path"]?.ToString() ?? @params["menuPath"]?.ToString();
+ // Optional future param retained for API compatibility; not used in synchronous mode
+ // int timeoutMs = Math.Max(0, (@params["timeout_ms"]?.ToObject() ?? 2000));
// string alias = @params["alias"]?.ToString(); // TODO: Implement alias mapping based on refactor plan requirements.
// JObject parameters = @params["parameters"] as JObject; // TODO: Investigate parameter passing (often not directly supported by ExecuteMenuItem).
@@ -94,42 +96,29 @@ namespace MCPForUnity.Editor.Tools
try
{
- // Attempt to execute the menu item on the main thread using delayCall for safety.
- EditorApplication.delayCall += () =>
- {
- try
- {
- bool executed = EditorApplication.ExecuteMenuItem(menuPath);
- // Log potential failure inside the delayed call.
- if (!executed)
- {
- Debug.LogError(
- $"[ExecuteMenuItem] Failed to find or execute menu item via delayCall: '{menuPath}'. It might be invalid, disabled, or context-dependent."
- );
- }
- }
- catch (Exception delayEx)
- {
- Debug.LogError(
- $"[ExecuteMenuItem] Exception during delayed execution of '{menuPath}': {delayEx}"
- );
- }
- };
+ // Trace incoming execute requests
+ Debug.Log($"[ExecuteMenuItem] Request to execute menu: '{menuPath}'");
- // Report attempt immediately, as execution is delayed.
- return Response.Success(
- $"Attempted to execute menu item: '{menuPath}'. Check Unity logs for confirmation or errors."
+ // Execute synchronously. This code runs on the Editor main thread in our bridge path.
+ bool executed = EditorApplication.ExecuteMenuItem(menuPath);
+ if (executed)
+ {
+ Debug.Log($"[ExecuteMenuItem] Executed successfully: '{menuPath}'");
+ return Response.Success(
+ $"Executed menu item: '{menuPath}'",
+ new { executed = true, menuPath }
+ );
+ }
+ Debug.LogWarning($"[ExecuteMenuItem] Failed (not found/disabled): '{menuPath}'");
+ return Response.Error(
+ $"Failed to execute menu item (not found or disabled): '{menuPath}'",
+ new { executed = false, menuPath }
);
}
catch (Exception e)
{
- // Catch errors during setup phase.
- Debug.LogError(
- $"[ExecuteMenuItem] Failed to setup execution for '{menuPath}': {e}"
- );
- return Response.Error(
- $"Error setting up execution for menu item '{menuPath}': {e.Message}"
- );
+ Debug.LogError($"[ExecuteMenuItem] Error executing '{menuPath}': {e}");
+ return Response.Error($"Error executing menu item '{menuPath}': {e.Message}");
}
}
diff --git a/UnityMcpBridge/Editor/Tools/ManageScript.cs b/UnityMcpBridge/Editor/Tools/ManageScript.cs
index 0337f74..7ec6afe 100644
--- a/UnityMcpBridge/Editor/Tools/ManageScript.cs
+++ b/UnityMcpBridge/Editor/Tools/ManageScript.cs
@@ -193,10 +193,10 @@ namespace MCPForUnity.Editor.Tools
namespaceName
);
case "read":
- Debug.LogWarning("manage_script.read is deprecated; prefer resources/read. Serving read for backward compatibility.");
+ McpLog.Warn("manage_script.read is deprecated; prefer resources/read. Serving read for backward compatibility.");
return ReadScript(fullPath, relativePath);
case "update":
- Debug.LogWarning("manage_script.update is deprecated; prefer apply_text_edits. Serving update for backward compatibility.");
+ McpLog.Warn("manage_script.update is deprecated; prefer apply_text_edits. Serving update for backward compatibility.");
return UpdateScript(fullPath, relativePath, name, contents);
case "delete":
return DeleteScript(fullPath, relativePath);
@@ -356,11 +356,11 @@ namespace MCPForUnity.Editor.Tools
var uri = $"unity://path/{relativePath}";
var ok = Response.Success(
$"Script '{name}.cs' created successfully at '{relativePath}'.",
- new { uri, scheduledRefresh = true }
+ new { uri, scheduledRefresh = false }
);
- // Schedule heavy work AFTER replying
- ManageScriptRefreshHelpers.ScheduleScriptRefresh(relativePath);
+ ManageScriptRefreshHelpers.ImportAndRequestCompile(relativePath);
+
return ok;
}
catch (Exception e)
@@ -650,7 +650,7 @@ namespace MCPForUnity.Editor.Tools
spans = spans.OrderByDescending(t => t.start).ToList();
for (int i = 1; i < spans.Count; i++)
{
- if (spans[i].end > spans[i - 1].start)
+ if (spans[i].end > spans[i - 1].start)
{
var conflict = new[] { new { startA = spans[i].start, endA = spans[i].end, startB = spans[i - 1].start, endB = spans[i - 1].end } };
return Response.Error("overlap", new { status = "overlap", conflicts = conflict, hint = "Sort ranges descending by start and compute from the same snapshot." });
@@ -763,19 +763,18 @@ namespace MCPForUnity.Editor.Tools
string.Equals(refreshModeFromCaller, "sync", StringComparison.OrdinalIgnoreCase);
if (immediate)
{
- EditorApplication.delayCall += () =>
- {
- AssetDatabase.ImportAsset(
- relativePath,
- ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate
- );
+ McpLog.Info($"[ManageScript] ApplyTextEdits: immediate refresh for '{relativePath}'");
+ AssetDatabase.ImportAsset(
+ relativePath,
+ ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate
+ );
#if UNITY_EDITOR
- UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
+ UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
#endif
- };
}
else
{
+ McpLog.Info($"[ManageScript] ApplyTextEdits: debounced refresh scheduled for '{relativePath}'");
ManageScriptRefreshHelpers.ScheduleScriptRefresh(relativePath);
}
@@ -786,7 +785,8 @@ namespace MCPForUnity.Editor.Tools
uri = $"unity://path/{relativePath}",
path = relativePath,
editsApplied = spans.Count,
- sha256 = newSha
+ sha256 = newSha,
+ scheduledRefresh = !immediate
}
);
}
@@ -1326,7 +1326,7 @@ namespace MCPForUnity.Editor.Tools
if (ordered[i].start + ordered[i].length > ordered[i - 1].start)
{
var conflict = new[] { new { startA = ordered[i].start, endA = ordered[i].start + ordered[i].length, startB = ordered[i - 1].start, endB = ordered[i - 1].start + ordered[i - 1].length } };
- return Response.Error("overlap", new { status = "overlap", conflicts = conflict, hint = "Apply in descending order against the same precondition snapshot." });
+ return Response.Error("overlap", new { status = "overlap", conflicts = conflict, hint = "Sort ranges descending by start and compute from the same snapshot." });
}
}
return Response.Error("overlap", new { status = "overlap" });
@@ -1421,17 +1421,8 @@ namespace MCPForUnity.Editor.Tools
if (immediate)
{
- // Force on main thread
- EditorApplication.delayCall += () =>
- {
- AssetDatabase.ImportAsset(
- relativePath,
- ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate
- );
-#if UNITY_EDITOR
- UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
-#endif
- };
+ McpLog.Info($"[ManageScript] EditScript: immediate refresh for '{relativePath}'", always: false);
+ ManageScriptRefreshHelpers.ImportAndRequestCompile(relativePath);
}
else
{
@@ -2620,5 +2611,15 @@ static class ManageScriptRefreshHelpers
{
RefreshDebounce.Schedule(relPath, TimeSpan.FromMilliseconds(200));
}
+
+ public static void ImportAndRequestCompile(string relPath, bool synchronous = true)
+ {
+ var opts = ImportAssetOptions.ForceUpdate;
+ if (synchronous) opts |= ImportAssetOptions.ForceSynchronousImport;
+ AssetDatabase.ImportAsset(relPath, opts);
+#if UNITY_EDITOR
+ UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
+#endif
+ }
}
diff --git a/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs b/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
index f9235fd..84113f7 100644
--- a/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
+++ b/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
@@ -1771,7 +1771,7 @@ namespace MCPForUnity.Editor.Windows
{
if (debugLogsEnabled)
{
- UnityEngine.Debug.Log($"MCP for Unity: Auto-updated MCP config for '{mcpClient.name}' to new path: {pythonDir}");
+ MCPForUnity.Editor.Helpers.McpLog.Info($"Auto-updated MCP config for '{mcpClient.name}' to new path: {pythonDir}", always: false);
}
mcpClient.SetStatus(McpStatus.Configured);
}
@@ -1971,7 +1971,7 @@ namespace MCPForUnity.Editor.Windows
if (debugLogsEnabled)
{
- UnityEngine.Debug.Log($"Checking Claude config at: {configPath}");
+ MCPForUnity.Editor.Helpers.McpLog.Info($"Checking Claude config at: {configPath}", always: false);
}
if (!File.Exists(configPath))
diff --git a/UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py b/UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py
new file mode 100644
index 0000000..e224844
--- /dev/null
+++ b/UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py
@@ -0,0 +1,8 @@
+"""
+Deprecated: Sentinel flipping is handled inside Unity via the MCP menu
+'MCP/Flip Reload Sentinel'. This module remains only as a compatibility shim.
+All functions are no-ops to prevent accidental external writes.
+"""
+
+def flip_reload_sentinel(*args, **kwargs) -> str:
+ return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'"
diff --git a/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py b/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py
index 9aad124..d4e9ad4 100644
--- a/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py
+++ b/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py
@@ -306,6 +306,38 @@ def register_manage_script_tools(mcp: FastMCP):
data.setdefault("normalizedEdits", normalized_edits)
if warnings:
data.setdefault("warnings", warnings)
+ if resp.get("success") and (options or {}).get("force_sentinel_reload"):
+ # Optional: flip sentinel via menu if explicitly requested
+ try:
+ import threading, time, json, glob, os
+ def _latest_status() -> dict | None:
+ try:
+ files = sorted(glob.glob(os.path.expanduser("~/.unity-mcp/unity-mcp-status-*.json")), key=os.path.getmtime, reverse=True)
+ if not files:
+ return None
+ with open(files[0], "r") as f:
+ return json.loads(f.read())
+ except Exception:
+ return None
+
+ def _flip_async():
+ try:
+ time.sleep(0.1)
+ st = _latest_status()
+ if st and st.get("reloading"):
+ return
+ send_command_with_retry(
+ "execute_menu_item",
+ {"menuPath": "MCP/Flip Reload Sentinel"},
+ max_retries=0,
+ retry_ms=0,
+ )
+ except Exception:
+ pass
+ threading.Thread(target=_flip_async, daemon=True).start()
+ except Exception:
+ pass
+ return resp
return resp
return {"success": False, "message": str(resp)}
@@ -457,7 +489,7 @@ def register_manage_script_tools(mcp: FastMCP):
"path": path,
"edits": edits,
"precondition_sha256": sha,
- "options": {"refresh": "immediate", "validate": "standard"},
+ "options": {"refresh": "debounced", "validate": "standard"},
}
# Preflight size vs. default cap (256 KiB) to avoid opaque server errors
try:
diff --git a/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py b/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py
index fc50be3..4ed65de 100644
--- a/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py
+++ b/UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py
@@ -2,6 +2,7 @@ from mcp.server.fastmcp import FastMCP, Context
from typing import Dict, Any, List, Tuple
import base64
import re
+import os
from unity_connection import send_command_with_retry
@@ -80,6 +81,37 @@ def _apply_edits_locally(original_text: str, edits: List[Dict[str, Any]]) -> str
return text
+def _trigger_sentinel_async() -> None:
+ """Fire the Unity menu flip on a short-lived background thread.
+
+ This avoids blocking the current request or getting stuck during domain reloads
+ (socket reconnects) when the Editor recompiles.
+ """
+ try:
+ import threading, time
+
+ def _flip():
+ try:
+ import json, glob, os
+ # Small delay so write flushes; prefer early flip to avoid editor-focus second reload
+ time.sleep(0.1)
+ try:
+ files = sorted(glob.glob(os.path.expanduser("~/.unity-mcp/unity-mcp-status-*.json")), key=os.path.getmtime, reverse=True)
+ if files:
+ with open(files[0], "r") as f:
+ st = json.loads(f.read())
+ if st.get("reloading"):
+ return
+ except Exception:
+ pass
+
+ except Exception:
+ pass
+
+ threading.Thread(target=_flip, daemon=True).start()
+ except Exception:
+ pass
+
def _infer_class_name(script_name: str) -> str:
# Default to script name as class name (common Unity pattern)
return (script_name or "").strip()
@@ -470,7 +502,7 @@ def register_manage_script_edits_tools(mcp: FastMCP):
# If everything is structured (method/class/anchor ops), forward directly to Unity's structured editor.
if all_struct:
opts2 = dict(options or {})
- # Do not force sequential; allow server default (atomic) unless caller requests otherwise
+ # For structured edits, prefer immediate refresh to avoid missed reloads when Editor is unfocused
opts2.setdefault("refresh", "immediate")
params_struct: Dict[str, Any] = {
"action": "edit",
@@ -482,6 +514,13 @@ def register_manage_script_edits_tools(mcp: FastMCP):
"options": opts2,
}
resp_struct = send_command_with_retry("manage_script", params_struct)
+ if isinstance(resp_struct, dict) and resp_struct.get("success"):
+ # Optional: flip sentinel only if explicitly requested
+ if (options or {}).get("force_sentinel_reload"):
+ try:
+ _trigger_sentinel_async()
+ except Exception:
+ pass
return _with_norm(resp_struct if isinstance(resp_struct, dict) else {"success": False, "message": str(resp_struct)}, normalized_for_echo, routing="structured")
# 1) read from Unity
@@ -597,18 +636,24 @@ def register_manage_script_edits_tools(mcp: FastMCP):
"scriptType": script_type,
"edits": at_edits,
"precondition_sha256": sha,
- "options": {"refresh": "immediate", "validate": (options or {}).get("validate", "standard"), "applyMode": ("atomic" if len(at_edits) > 1 else (options or {}).get("applyMode", "sequential"))}
+ "options": {"refresh": (options or {}).get("refresh", "debounced"), "validate": (options or {}).get("validate", "standard"), "applyMode": ("atomic" if len(at_edits) > 1 else (options or {}).get("applyMode", "sequential"))}
}
resp_text = send_command_with_retry("manage_script", params_text)
if not (isinstance(resp_text, dict) and resp_text.get("success")):
return _with_norm(resp_text if isinstance(resp_text, dict) else {"success": False, "message": str(resp_text)}, normalized_for_echo, routing="mixed/text-first")
+ # Successful text write; flip sentinel only if explicitly requested
+ if (options or {}).get("force_sentinel_reload"):
+ try:
+ _trigger_sentinel_async()
+ except Exception:
+ pass
except Exception as e:
return _with_norm({"success": False, "message": f"Text edit conversion failed: {e}"}, normalized_for_echo, routing="mixed/text-first")
if struct_edits:
opts2 = dict(options or {})
- # Let server decide; do not force sequential
- opts2.setdefault("refresh", "immediate")
+ # Prefer debounced background refresh unless explicitly overridden
+ opts2.setdefault("refresh", "debounced")
params_struct: Dict[str, Any] = {
"action": "edit",
"name": name,
@@ -619,6 +664,12 @@ def register_manage_script_edits_tools(mcp: FastMCP):
"options": opts2
}
resp_struct = send_command_with_retry("manage_script", params_struct)
+ if isinstance(resp_struct, dict) and resp_struct.get("success"):
+ if (options or {}).get("force_sentinel_reload"):
+ try:
+ _trigger_sentinel_async()
+ except Exception:
+ pass
return _with_norm(resp_struct if isinstance(resp_struct, dict) else {"success": False, "message": str(resp_struct)}, normalized_for_echo, routing="mixed/text-first")
return _with_norm({"success": True, "message": "Applied text edits (no structured ops)"}, normalized_for_echo, routing="mixed/text-first")
@@ -648,9 +699,10 @@ def register_manage_script_edits_tools(mcp: FastMCP):
if op == "anchor_insert":
anchor = e.get("anchor") or ""
position = (e.get("position") or "after").lower()
- # Early regex compile with helpful errors
+ # Early regex compile with helpful errors, honoring ignore_case
try:
- regex_obj = _re.compile(anchor, _re.MULTILINE)
+ flags = _re.MULTILINE | (_re.IGNORECASE if e.get("ignore_case") else 0)
+ regex_obj = _re.compile(anchor, flags)
except Exception as ex:
return _with_norm(_err("bad_regex", f"Invalid anchor regex: {ex}", normalized=normalized_for_echo, routing="text", extra={"hint": "Escape parentheses/braces or use a simpler anchor."}), normalized_for_echo, routing="text")
m = regex_obj.search(base_text)
@@ -734,12 +786,18 @@ def register_manage_script_edits_tools(mcp: FastMCP):
"edits": at_edits,
"precondition_sha256": sha,
"options": {
- "refresh": "immediate",
+ "refresh": (options or {}).get("refresh", "debounced"),
"validate": (options or {}).get("validate", "standard"),
"applyMode": ("atomic" if len(at_edits) > 1 else (options or {}).get("applyMode", "sequential"))
}
}
resp = send_command_with_retry("manage_script", params)
+ if isinstance(resp, dict) and resp.get("success"):
+ if (options or {}).get("force_sentinel_reload"):
+ try:
+ _trigger_sentinel_async()
+ except Exception:
+ pass
return _with_norm(
resp if isinstance(resp, dict) else {"success": False, "message": str(resp)},
normalized_for_echo,
@@ -791,7 +849,7 @@ def register_manage_script_edits_tools(mcp: FastMCP):
# Default refresh/validate for natural usage on text path as well
options = dict(options or {})
options.setdefault("validate", "standard")
- options.setdefault("refresh", "immediate")
+ options.setdefault("refresh", "debounced")
import hashlib
# Compute the SHA of the current file contents for the precondition
@@ -816,10 +874,16 @@ def register_manage_script_edits_tools(mcp: FastMCP):
}
],
"precondition_sha256": sha,
- "options": options or {"validate": "standard", "refresh": "immediate"},
+ "options": options or {"validate": "standard", "refresh": "debounced"},
}
write_resp = send_command_with_retry("manage_script", params)
+ if isinstance(write_resp, dict) and write_resp.get("success"):
+ if (options or {}).get("force_sentinel_reload"):
+ try:
+ _trigger_sentinel_async()
+ except Exception:
+ pass
return _with_norm(
write_resp if isinstance(write_resp, dict)
else {"success": False, "message": str(write_resp)},
diff --git a/UnityMcpBridge/UnityMcpServer~/src/uv.lock b/UnityMcpBridge/UnityMcpServer~/src/uv.lock
index 87a4deb..f5cac0f 100644
--- a/UnityMcpBridge/UnityMcpServer~/src/uv.lock
+++ b/UnityMcpBridge/UnityMcpServer~/src/uv.lock
@@ -162,7 +162,7 @@ cli = [
[[package]]
name = "mcpforunityserver"
-version = "3.0.2"
+version = "3.1.0"
source = { editable = "." }
dependencies = [
{ name = "httpx" },