CoplayDev/unity-mcp · error · TypeError

Command parameters must be a dict for HTTP transport

Error message

Command parameters must be a dict for HTTP transport

What it means

Raised by send_with_unity_instance in HTTP mode when the resolved params value is not a dict. The HTTP request body is built from a JSON object, so a non-dict (list, str, None after coercion, custom object) cannot be serialized into the expected command payload shape.

Source

Thrown at Server/src/transport/unity_transport.py:57

        return None


async def send_with_unity_instance(
    send_fn: Callable[..., Awaitable[T]],
    unity_instance: str | None,
    *args,
    user_id: str | None = None,
    **kwargs,
) -> T:
    if _is_http_transport():
        if not args:
            raise ValueError("HTTP transport requires command arguments")
        command_type = args[0]
        params = args[1] if len(args) > 1 else kwargs.get("params")
        if params is None:
            params = {}
        if not isinstance(params, dict):
            raise TypeError(
                "Command parameters must be a dict for HTTP transport")

        # Auto-resolve user_id from HTTP request API key (remote-hosted mode)
        if user_id is None:
            user_id = await _resolve_user_id_from_request()

        # Auth check
        if config.http_remote_hosted and not user_id:
            return normalize_unity_response(
                MCPResponse(
                    success=False,
                    error="auth_required",
                    message="API key required",
                ).model_dump()
            )

        retry_on_reload = kwargs.pop("retry_on_reload", True)
        if not isinstance(retry_on_reload, bool):

View on GitHub (pinned to c21bf496bc)

Solutions

  1. Pass params as a plain dict (use model_dump() for Pydantic models, or json.loads then re-wrap).
  2. Validate isinstance(params, dict) at the wrapper boundary before calling send_with_unity_instance.
  3. Normalize at the call site: params = dict(params) when params is a mapping-like object.

Example fix

// before
await send_with_unity_instance(send_fn, instance, 'manage_gameobject', some_model)
// after
await send_with_unity_instance(send_fn, instance, 'manage_gameobject', some_model.model_dump())
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(params, dict):
    params = params.model_dump() if hasattr(params, 'model_dump') else dict(params)
if not isinstance(params, dict):
    raise TypeError('params must be a dict')

Type guard

def is_command_params(v) -> bool:
    return isinstance(v, dict)

Try / catch

try:
    await send_with_unity_instance(send_fn, instance, cmd, params)
except TypeError as e:
    if 'must be a dict' in str(e):
        await send_with_unity_instance(send_fn, instance, cmd, dict(params))

Prevention

When it happens

Trigger: args[1] (or kwargs['params']) is present but isinstance(params, dict) is false. The guard at unity_transport.py:55-57 fires.

Common situations: A tool wrapper passed a Pydantic model or a JSON string instead of a plain dict; params came from json.loads of an array; a refactor changed the params type without adapting the call site.

Related errors


AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13). Data as JSON: /api/errors/c35ee55e5a593ff4. Report an issue: GitHub.