CoplayDev/unity-mcp · error · ValueError
'commands' must be a non-empty list of command specification
Error message
'commands' must be a non-empty list of command specifications
What it means
Input validation in the batch_execute MCP tool: the `commands` argument must be a non-empty Python list. The check `not isinstance(commands, list) or not commands` rejects None, dicts, tuples, strings, and empty lists. batch_execute proxies a list of {tool, params} sub-commands to Unity's batch transporter, so an empty list has nothing to execute and a non-list is a caller contract violation.
Source
Thrown at Server/src/services/tools/batch_execute.py:91
title="Batch Execute",
destructiveHint=True,
),
)
async def batch_execute(
ctx: Context,
commands: Annotated[list[dict[str, Any]], "List of commands with 'tool' and 'params' keys."],
parallel: Annotated[bool | None,
"Attempt to run read-only commands in parallel"] = None,
fail_fast: Annotated[bool | None,
"Stop processing after the first failure"] = None,
max_parallelism: Annotated[int | None,
"Hint for the maximum number of parallel workers"] = None,
) -> dict[str, Any]:
"""Proxy the batch_execute tool to the Unity Editor transporter."""
unity_instance = await get_unity_instance_from_context(ctx)
if not isinstance(commands, list) or not commands:
raise ValueError(
"'commands' must be a non-empty list of command specifications")
max_commands = await _get_max_commands_from_editor_state(ctx)
if len(commands) > max_commands:
raise ValueError(
f"batch_execute supports up to {max_commands} commands (configured in Unity); received {len(commands)}"
)
normalized_commands: list[dict[str, Any]] = []
for index, command in enumerate(commands):
if not isinstance(command, dict):
raise ValueError(
f"Command at index {index} must be an object with 'tool' and 'params' keys")
tool_name = command.get("tool")
params = command.get("params", {})
if not tool_name or not isinstance(tool_name, str):View on GitHub (pinned to c21bf496bc)
Solutions
- Ensure commands is a list with at least one {"tool": "...", "params": {...}} entry.
- If building the list dynamically, guard against empty before calling: only call batch_execute when len(commands) > 0.
- Use the correct shape: [{"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube"}}].
Example fix
// before (caller)
await batch_execute(ctx, commands=[])
// after
await batch_execute(ctx, commands=[{"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube"}}]) Defensive patterns
Strategy: validation
Validate before calling
# Validate commands before calling batch_execute
def validate_batch_commands(commands: list) -> None:
if not isinstance(commands, list) or not commands:
raise ValueError("commands must be a non-empty list")
for i, cmd in enumerate(commands):
if not isinstance(cmd, dict):
raise ValueError(f"Command {i} must be a dict")
if not cmd.get("tool") or not isinstance(cmd.get("tool"), str):
raise ValueError(f"Command {i} missing valid 'tool'")
params = cmd.get("params", {})
if params is None:
params = {}
if not isinstance(params, dict):
raise ValueError(f"Command {i} params must be a dict")
validate_batch_commands(commands) Type guard
def is_valid_command_list(commands: object) -> bool:
if not isinstance(commands, list) or len(commands) == 0:
return False
return all(
isinstance(c, dict)
and isinstance(c.get("tool"), str)
and c.get("tool", "") != ""
and (c.get("params", {}) is None or isinstance(c.get("params"), dict))
for c in commands
) Prevention
- Always build commands as a list of {"tool": str, "params": dict} objects.
- Guard dynamically-built lists: only call batch_execute when len(commands) > 0.
- Validate the full command shape before submission to catch errors early.
When it happens
Trigger: An AI assistant or caller passes commands=null, commands={}, commands="tool_name", or commands=[]; a JSON-RPC payload where 'commands' is omitted (None after default) or explicitly empty; a caller that builds the list dynamically and it ended up empty due to a filtering bug.
Common situations: An LLM generates an empty batch by mistake; a programmatic caller passes a single tool name as a string instead of a list of dicts; a pipeline that filters commands down to zero entries before calling batch_execute; a caller confusing batch_execute with a single-command tool.
Related errors
- Command at index {index} must be an object with 'tool' and '
- Command at index {index} is missing a valid 'tool' name
- Command '{tool_name}' must specify parameters as an object/d
- batch_execute supports up to {max_commands} commands (config
- Command '{tool_name}' at index {index} contains 'unity_insta
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/f8cb9637577763f9.
Report an issue: GitHub.