MP032: undocumented-side-effect¶
The tool's name implies mutation, but the description reads as read-only.
At a glance¶
| Category | consistency |
| Default severity | error |
| Auto-fixable | no |
| LLM-gated | yes |
| Stable since | v0.1.0 |
What this rule checks¶
mcpolish looks at each tool's name for a mutating verb: create, write, delete, drop, insert, update, patch, put, post, send, push, publish, kill, terminate, destroy, remove. If the name has one of those, mcpolish checks whether the description acknowledges the side effect.
If the description sounds purely read-only (uses read, fetch, get, return, query, inspect, view, browse, search but no mutating verbs), mcpolish sends both to an LLM judge. The judge confirms whether the side effect is undocumented. A MISSING: <reason> reply fires MP032.
Why it matters¶
Li et al. (2026) found 1,326 of 10,240 servers had a tool that performed an undocumented mutating operation. The agent reads a description, decides the tool is safe to call without confirmation, and ends up triggering an irreversible action. This is a real, recurring failure mode in agentic systems.
MP032 catches the obvious pattern: a delete-named tool whose description sounds like a read.
Example: code that triggers this rule¶
@mcp.tool()
def delete_record(rid: str) -> dict:
"""Use this when the user wants to read a record by id.
Args:
rid: Record id. Example: "r_1".
Returns: the record's contents as a dict.
"""
return {"id": rid}
Name says delete_record. Description says "read a record". With --llm, MP032 fires with a message naming the missing side effect.
Example: how to fix it¶
Either acknowledge the side effect:
"""Use this when the user has confirmed they want to permanently remove a
record. Cannot be undone. Returns a dict with the removed id.
Args:
rid: Record id to remove. Example: "r_1".
"""
Or rename the tool so the name does not imply mutation:
Configuration¶
MP032 is off by default. Enable with:
To disable:
When to disable this rule¶
If your project uses mutating verbs in tool names for unrelated reasons (e.g. delete_filter in a builder pattern), the LLM judge usually distinguishes those from real side effects. If false positives accumulate, disable.
How the check works under the hood¶
mcpolish runs two regexes: one for mutating verbs in the name, one for read-flavoured verbs in the description. Both must match. If both match, mcpolish sends a focused prompt to the LLM judge with the tool name and description.
Verdicts cache as in the other LLM-gated rules.
Related rules¶
- MP030 param-type-mismatch: another consistency check.
- MP031 param-meaning-mismatch: the parameter-level LLM judge.
- MP041 instruction-in-description: tool-poisoning rather than honest mistakes.
References¶
- Li et al., 2026. arXiv:2602.03580, section 4.