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
- Add "op": "<one of anchor_insert|prepend|append|replace_range|regex_replace>" to each edit dict.
- If you used a different key name, switch to 'op' (or an accepted alias: operation, type, mode).
- 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
- Always include 'op' in every edit dict with one of the five allowed values.
- Use the accepted alias keys (operation/type/mode) only if 'op' is unavailable — prefer 'op'.
- Validate all edits have a recognized op before calling script_apply_edits.
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
- unknown edit op: {op}; allowed: {allowed}. Use 'op' (aliases
- 'commands' must be a non-empty list of command specification
- batch_execute supports up to {max_commands} commands (config
- Command at index {index} must be an object with 'tool' and '
- Command at index {index} is missing a valid 'tool' name
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/64db8cdf5337cd94.
Report an issue: GitHub.