Skip to main content
Glama
BatchExecute.cs6.81 kB
using System; using System.Collections.Generic; using System.Text; 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); invocationSuccessCount++; commandResults.Add(new { tool = toolName, callSucceeded = true, result }); } 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 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) { if (string.IsNullOrEmpty(key) || key.IndexOf('_') < 0) { return key; } var parts = key.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) { return key; } var builder = new StringBuilder(parts[0]); for (int i = 1; i < parts.Length; i++) { var part = parts[i]; if (string.IsNullOrEmpty(part)) { continue; } builder.Append(char.ToUpperInvariant(part[0])); if (part.Length > 1) { builder.Append(part.AsSpan(1)); } } return builder.ToString(); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CoplayDev/unity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server