CoplayDev/unity-mcp · error · RuntimeError

op is required; allowed: {allowed}. Use 'op' (aliases accept

Error message

op is required; allowed: {allowed}. Use 'op' (aliases accepted: type/mode/operation).

What it means

In script_apply_edits' local edit applier (_apply_edits_locally), each edit dict must specify an operation. The code reads 'op' with aliases 'operation', 'type', 'mode' (first non-empty wins, case-insensitive, trimmed). If all are absent or empty, this RuntimeError fires listing the five allowed ops: anchor_insert, prepend, append, replace_range, regex_replace. This is the first validation per edit — without an op the applier cannot decide what to do.

Source

Thrown at Server/src/services/tools/script_apply_edits.py:393

    return False


async def _apply_edits_locally(original_text: str, edits: list[dict[str, Any]]) -> str:
    text = original_text
    for edit in edits or []:
        op = (
            (edit.get("op")
             or edit.get("operation")
             or edit.get("type")
             or edit.get("mode")
             or "")
            .strip()
            .lower()
        )

        if not op:
            allowed = "anchor_insert, prepend, append, replace_range, regex_replace"
            raise RuntimeError(
                f"op is required; allowed: {allowed}. Use 'op' (aliases accepted: type/mode/operation)."
            )

        if op == "prepend":
            prepend_text = edit.get("text", "")
            text = (prepend_text if prepend_text.endswith(
                "\n") else prepend_text + "\n") + text
        elif op == "append":
            append_text = edit.get("text", "")
            if not text.endswith("\n"):
                text += "\n"
            text += append_text
            if not text.endswith("\n"):
                text += "\n"
        elif op == "anchor_insert":
            anchor = edit.get("anchor", "")
            position = (edit.get("position") or "before").lower()
            insert_text = edit.get("text", "")

View on GitHub (pinned to c21bf496bc)

Solutions

  1. Add "op": "<one of anchor_insert|prepend|append|replace_range|regex_replace>" to each edit dict.
  2. If you used a different key name, switch to 'op' (or an accepted alias: operation, type, mode).
  3. Validate every edit has a non-empty op before calling script_apply_edits.

Example fix

// before
edits=[{"text": "new code\n"}]
// after
edits=[{"op": "prepend", "text": "new code\n"}]
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED_OPS = {"anchor_insert", "prepend", "append", "replace_range", "regex_replace"}
for i, edit in enumerate(edits):
    op = (edit.get("op") or edit.get("operation") or edit.get("type") or edit.get("mode") or "").strip().lower()
    if not op:
        raise ValueError(f"Edit {i} is missing 'op'. Allowed: {ALLOWED_OPS}")
    if op not in ALLOWED_OPS:
        raise ValueError(f"Edit {i} has unknown op '{op}'. Allowed: {ALLOWED_OPS}")

Type guard

ALLOWED_OPS = {"anchor_insert", "prepend", "append", "replace_range", "regex_replace"}

def edit_has_valid_op(edit: dict) -> bool:
    op = (edit.get("op") or edit.get("operation") or edit.get("type") or edit.get("mode") or "").strip().lower()
    return op in ALLOWED_OPS

Prevention

When it happens

Trigger: An edit dict is missing the op key entirely: {"text": "..."}; all alias keys are empty strings; the caller uses an unrecognized key like 'action' or 'edit_type' instead of op/operation/type/mode; a JSON payload where op was stripped by a schema filter.

Common situations: An LLM omits the op field assuming a default; a caller uses 'action' (common in other APIs) instead of 'op'; a programmatic edit builder forgets to set the operation; a serialization layer drops unknown keys.

Related errors


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