升级XR插件版本
This commit is contained in:
221
Packages/MCPForUnity/Editor/Tools/BatchExecute.cs
Normal file
221
Packages/MCPForUnity/Editor/Tools/BatchExecute.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes multiple MCP commands within a single Unity-side handler. Commands are executed sequentially
|
||||
/// on the main thread to preserve determinism and Unity API safety.
|
||||
/// </summary>
|
||||
[McpForUnityTool("batch_execute", AutoRegister = false)]
|
||||
public static class BatchExecute
|
||||
{
|
||||
private const int MaxCommandsPerBatch = 25;
|
||||
|
||||
public static async Task<object> HandleCommand(JObject @params)
|
||||
{
|
||||
if (@params == null)
|
||||
{
|
||||
return new ErrorResponse("'commands' payload is required.");
|
||||
}
|
||||
|
||||
var commandsToken = @params["commands"] as JArray;
|
||||
if (commandsToken == null || commandsToken.Count == 0)
|
||||
{
|
||||
return new ErrorResponse("Provide at least one command entry in 'commands'.");
|
||||
}
|
||||
|
||||
if (commandsToken.Count > MaxCommandsPerBatch)
|
||||
{
|
||||
return new ErrorResponse($"A maximum of {MaxCommandsPerBatch} commands are allowed per batch.");
|
||||
}
|
||||
|
||||
bool failFast = @params.Value<bool?>("failFast") ?? false;
|
||||
bool parallelRequested = @params.Value<bool?>("parallel") ?? false;
|
||||
int? maxParallel = @params.Value<int?>("maxParallelism");
|
||||
|
||||
if (parallelRequested)
|
||||
{
|
||||
McpLog.Warn("batch_execute parallel mode requested, but commands will run sequentially on the main thread for safety.");
|
||||
}
|
||||
|
||||
var commandResults = new List<object>(commandsToken.Count);
|
||||
int invocationSuccessCount = 0;
|
||||
int invocationFailureCount = 0;
|
||||
bool anyCommandFailed = false;
|
||||
|
||||
foreach (var token in commandsToken)
|
||||
{
|
||||
if (token is not JObject commandObj)
|
||||
{
|
||||
invocationFailureCount++;
|
||||
anyCommandFailed = true;
|
||||
commandResults.Add(new
|
||||
{
|
||||
tool = (string)null,
|
||||
callSucceeded = false,
|
||||
error = "Command entries must be JSON objects."
|
||||
});
|
||||
if (failFast)
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
string toolName = commandObj["tool"]?.ToString();
|
||||
var rawParams = commandObj["params"] as JObject ?? new JObject();
|
||||
var commandParams = NormalizeParameterKeys(rawParams);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(toolName))
|
||||
{
|
||||
invocationFailureCount++;
|
||||
anyCommandFailed = true;
|
||||
commandResults.Add(new
|
||||
{
|
||||
tool = toolName,
|
||||
callSucceeded = false,
|
||||
error = "Each command must include a non-empty 'tool' field."
|
||||
});
|
||||
if (failFast)
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await CommandRegistry.InvokeCommandAsync(toolName, commandParams).ConfigureAwait(true);
|
||||
bool callSucceeded = DetermineCallSucceeded(result);
|
||||
if (callSucceeded)
|
||||
{
|
||||
invocationSuccessCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
invocationFailureCount++;
|
||||
anyCommandFailed = true;
|
||||
}
|
||||
|
||||
commandResults.Add(new
|
||||
{
|
||||
tool = toolName,
|
||||
callSucceeded,
|
||||
result
|
||||
});
|
||||
|
||||
if (!callSucceeded && failFast)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
invocationFailureCount++;
|
||||
anyCommandFailed = true;
|
||||
commandResults.Add(new
|
||||
{
|
||||
tool = toolName,
|
||||
callSucceeded = false,
|
||||
error = ex.Message
|
||||
});
|
||||
|
||||
if (failFast)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool overallSuccess = !anyCommandFailed;
|
||||
var data = new
|
||||
{
|
||||
results = commandResults,
|
||||
callSuccessCount = invocationSuccessCount,
|
||||
callFailureCount = invocationFailureCount,
|
||||
parallelRequested,
|
||||
parallelApplied = false,
|
||||
maxParallelism = maxParallel
|
||||
};
|
||||
|
||||
return overallSuccess
|
||||
? new SuccessResponse("Batch execution completed.", data)
|
||||
: new ErrorResponse("One or more commands failed.", data);
|
||||
}
|
||||
|
||||
private static bool DetermineCallSucceeded(object result)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result is IMcpResponse response)
|
||||
{
|
||||
return response.Success;
|
||||
}
|
||||
|
||||
if (result is JObject obj)
|
||||
{
|
||||
var successToken = obj["success"];
|
||||
if (successToken != null && successToken.Type == JTokenType.Boolean)
|
||||
{
|
||||
return successToken.Value<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
if (result is JToken token)
|
||||
{
|
||||
var successToken = token["success"];
|
||||
if (successToken != null && successToken.Type == JTokenType.Boolean)
|
||||
{
|
||||
return successToken.Value<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static JObject NormalizeParameterKeys(JObject source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return new JObject();
|
||||
}
|
||||
|
||||
var normalized = new JObject();
|
||||
foreach (var property in source.Properties())
|
||||
{
|
||||
string normalizedName = ToCamelCase(property.Name);
|
||||
normalized[normalizedName] = NormalizeToken(property.Value);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static JArray NormalizeArray(JArray source)
|
||||
{
|
||||
var normalized = new JArray();
|
||||
foreach (var token in source)
|
||||
{
|
||||
normalized.Add(NormalizeToken(token));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static JToken NormalizeToken(JToken token)
|
||||
{
|
||||
return token switch
|
||||
{
|
||||
JObject obj => NormalizeParameterKeys(obj),
|
||||
JArray arr => NormalizeArray(arr),
|
||||
_ => token.DeepClone()
|
||||
};
|
||||
}
|
||||
|
||||
private static string ToCamelCase(string key) => StringCaseUtility.ToCamelCase(key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user