diff --git a/.claude/skills/unity-mcp-skill/SKILL.md b/.claude/skills/unity-mcp-skill/SKILL.md
index cd12f7fc9..bda21f49b 100644
--- a/.claude/skills/unity-mcp-skill/SKILL.md
+++ b/.claude/skills/unity-mcp-skill/SKILL.md
@@ -53,7 +53,7 @@ batch_execute(
)
```
-**Max 25 commands per batch.** Use `fail_fast=True` for dependent operations. Batches are not transactional (no rollback on partial failure).
+**Max 25 commands per batch by default (configurable in Unity MCP Tools window, max 100).** Use `fail_fast=True` for dependent operations.
### 3. Use `screenshot` in manage_scene to Verify Visual Results
diff --git a/.claude/skills/unity-mcp-skill/references/workflows.md b/.claude/skills/unity-mcp-skill/references/workflows.md
index 631c4a0e4..545b5c518 100644
--- a/.claude/skills/unity-mcp-skill/references/workflows.md
+++ b/.claude/skills/unity-mcp-skill/references/workflows.md
@@ -829,7 +829,7 @@ batch_execute(fail_fast=True, commands=[
### Complete Example: Main Menu Screen
-Combines multiple templates into a full menu screen in two batch calls (25 command limit per batch).
+Combines multiple templates into a full menu screen in two batch calls (default 25 command limit per batch, configurable in Unity MCP Tools window up to 100).
```python
# Batch 1: Canvas + EventSystem + Panel + Title
diff --git a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
index 5f99d3e5a..f1360984b 100644
--- a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
+++ b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
@@ -63,5 +63,7 @@ internal static class EditorPrefKeys
internal const string CustomerUuid = "MCPForUnity.CustomerUUID";
internal const string ApiKey = "MCPForUnity.ApiKey";
+
+ internal const string BatchExecuteMaxCommands = "MCPForUnity.BatchExecute.MaxCommands";
}
}
diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
index fc450bada..c45469413 100644
--- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
+++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
@@ -201,6 +201,8 @@ public static JObject GetPackageJson()
/// Gets the package source for the MCP server (used with uvx --from).
/// Checks for EditorPrefs override first (supports git URLs, file:// paths, etc.),
/// then falls back to PyPI package reference.
+ /// When the override is a local path, auto-corrects to the "Server" subdirectory
+ /// if the path doesn't contain pyproject.toml but Server/pyproject.toml exists.
///
/// Package source string for uvx --from argument
public static string GetMcpServerPackageSource()
@@ -209,7 +211,14 @@ public static string GetMcpServerPackageSource()
string sourceOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
if (!string.IsNullOrEmpty(sourceOverride))
{
- return sourceOverride;
+ string resolved = ResolveLocalServerPath(sourceOverride);
+ // Persist the corrected path so future reads are consistent
+ if (resolved != sourceOverride)
+ {
+ EditorPrefs.SetString(EditorPrefKeys.GitUrlOverride, resolved);
+ McpLog.Info($"Auto-corrected server source override from '{sourceOverride}' to '{resolved}'");
+ }
+ return resolved;
}
// Default to PyPI package (avoids Windows long path issues with git clone)
@@ -223,6 +232,59 @@ public static string GetMcpServerPackageSource()
return $"mcpforunityserver=={version}";
}
+ ///
+ /// Validates and auto-corrects a local server source path to ensure it points to the
+ /// directory containing pyproject.toml. If the path points to a parent directory
+ /// (e.g. the repo root "unity-mcp") instead of the Python package directory ("Server"),
+ /// this checks for a "Server" subdirectory with pyproject.toml and returns that path.
+ /// Non-local paths (URLs, PyPI references) are returned unchanged.
+ ///
+ internal static string ResolveLocalServerPath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return path;
+
+ // Skip non-local paths (git URLs, PyPI package names, etc.)
+ if (path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
+ path.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
+ path.StartsWith("git+", StringComparison.OrdinalIgnoreCase) ||
+ path.StartsWith("ssh://", StringComparison.OrdinalIgnoreCase))
+ {
+ return path;
+ }
+
+ // If it looks like a PyPI package reference (no path separators), skip
+ if (!path.Contains('/') && !path.Contains('\\') && !path.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
+ {
+ return path;
+ }
+
+ // Strip file:// prefix for filesystem checks, preserve for return value
+ string checkPath = path;
+ string prefix = string.Empty;
+ if (checkPath.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
+ {
+ prefix = checkPath.Substring(0, 7); // preserve original casing
+ checkPath = checkPath.Substring(7);
+ }
+
+ // Already correct — pyproject.toml exists at this path
+ if (System.IO.File.Exists(System.IO.Path.Combine(checkPath, "pyproject.toml")))
+ {
+ return path;
+ }
+
+ // Check if "Server" subdirectory contains pyproject.toml
+ string serverSubDir = System.IO.Path.Combine(checkPath, "Server");
+ if (System.IO.File.Exists(System.IO.Path.Combine(serverSubDir, "pyproject.toml")))
+ {
+ return prefix + serverSubDir;
+ }
+
+ // Return as-is; uvx will report the error if the path is truly invalid
+ return path;
+ }
+
///
/// Deprecated: Use GetMcpServerPackageSource() instead.
/// Kept for backwards compatibility.
diff --git a/MCPForUnity/Editor/Services/EditorStateCache.cs b/MCPForUnity/Editor/Services/EditorStateCache.cs
index 24fec0f14..9dd85d529 100644
--- a/MCPForUnity/Editor/Services/EditorStateCache.cs
+++ b/MCPForUnity/Editor/Services/EditorStateCache.cs
@@ -75,6 +75,9 @@ private sealed class EditorStateSnapshot
[JsonProperty("transport")]
public EditorStateTransport Transport { get; set; }
+
+ [JsonProperty("settings")]
+ public EditorStateSettings Settings { get; set; }
}
private sealed class EditorStateUnity
@@ -239,6 +242,12 @@ private sealed class EditorStateTransport
public long? LastMessageUnixMs { get; set; }
}
+ private sealed class EditorStateSettings
+ {
+ [JsonProperty("batch_execute_max_commands")]
+ public int BatchExecuteMaxCommands { get; set; }
+ }
+
static EditorStateCache()
{
try
@@ -482,6 +491,10 @@ private static JObject BuildSnapshot(string reason)
{
UnityBridgeConnected = null,
LastMessageUnixMs = null
+ },
+ Settings = new EditorStateSettings
+ {
+ BatchExecuteMaxCommands = Tools.BatchExecute.GetMaxCommandsPerBatch()
}
};
diff --git a/MCPForUnity/Editor/Tools/BatchExecute.cs b/MCPForUnity/Editor/Tools/BatchExecute.cs
index d9df336d6..66dc2b39b 100644
--- a/MCPForUnity/Editor/Tools/BatchExecute.cs
+++ b/MCPForUnity/Editor/Tools/BatchExecute.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
+using UnityEditor;
namespace MCPForUnity.Editor.Tools
{
@@ -13,7 +15,20 @@ namespace MCPForUnity.Editor.Tools
[McpForUnityTool("batch_execute", AutoRegister = false)]
public static class BatchExecute
{
- private const int MaxCommandsPerBatch = 25;
+ /// Default limit when no EditorPrefs override is set.
+ internal const int DefaultMaxCommandsPerBatch = 25;
+
+ /// Hard ceiling to prevent extreme editor freezes regardless of user setting.
+ internal const int AbsoluteMaxCommandsPerBatch = 100;
+
+ ///
+ /// Returns the user-configured max commands per batch, clamped between 1 and .
+ ///
+ internal static int GetMaxCommandsPerBatch()
+ {
+ int configured = EditorPrefs.GetInt(EditorPrefKeys.BatchExecuteMaxCommands, DefaultMaxCommandsPerBatch);
+ return Math.Clamp(configured, 1, AbsoluteMaxCommandsPerBatch);
+ }
public static async Task