Skip to content

fix(tools): raise on failure so isError=true on the MCP wire#300

Open
sanjibani wants to merge 1 commit into
CursorTouch:mainfrom
sanjibani:fix/iserror-compliance
Open

fix(tools): raise on failure so isError=true on the MCP wire#300
sanjibani wants to merge 1 commit into
CursorTouch:mainfrom
sanjibani:fix/iserror-compliance

Conversation

@sanjibani

Copy link
Copy Markdown

Summary

6 @mcp.tool() handlers in src/windows_mcp/tools/{clipboard,filesystem,notification,process,registry,shell}.py caught except Exception as e: and returned a formatted "Error: ..." string. FastMCP wraps the return value as success content with isError=false, so MCP clients treat the failure as data and the LLM often proceeds as if the call had succeeded.

This is the same isError-compliance gap flagged in the recent MCP security audit (Dayna Blackwell, Tool Poisoning, Rug Pulls, and Prompt Injections — oh my!).

Changes

Each return f'Error ...' is replaced with bare raise so the original exception propagates and FastMCP sets isError=true on the wire while preserving the formatted message in content for the LLM.

The pre-validation returns (e.g. "Error: content parameter is required for write mode.") are kept as-is — those run before any try/except and represent input-validation rejections, not exception swallowing.

Tests

tests/test_iserror_compliance.py uses monkeypatch to force the underlying Windows service to raise, then asserts each tool handler raises ToolError. The test will run on Windows CI (this codebase depends on pywin32 which is Windows-only; the test module is skipped when fastmcp is unavailable on non-Windows).

Trade-off

Bare raise keeps the FastMCP isError=true wire semantics. The exception type is preserved so any upstream exception handler (logging, metrics) that was relying on isinstance(e, SomeError) continues to work.

Reference: https://composio.dev/blog/mcp-security-vulnerabilities

6 @mcp.tool() handlers in src/windows_mcp/tools/{clipboard,filesystem,
notification,process,registry,shell}.py caught
and returned a formatted "Error: ..." string. FastMCP wraps the
return value as success content with isError=false, so MCP clients
(Claude Code, Cursor, etc.) treat the failure as data and the LLM
often proceeds as if the call had succeeded.

This is the same isError-compliance gap flagged in the recent MCP
security audit (Dayna Blackwell, 'Tool Poisoning, Rug Pulls, and
Prompt Injections — oh my!').

Each  is replaced with bare  so the
original exception propagates and FastMCP sets isError=true on the
wire while preserving the formatted message in content for the LLM.

The pre-validation returns (e.g. 'Error: content parameter is required
for write mode.') are kept as-is — those run before any try/except and
represent input-validation rejections, not exception swallowing.

Regression test: tests/test_iserror_compliance.py uses monkeypatch to
force the underlying Windows service to raise, then asserts each tool
handler raises ToolError. Test will run on Windows CI (this codebase
depends on pywin32 which is Windows-only).
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@Jeomon

Jeomon commented Jul 1, 2026

Copy link
Copy Markdown
Member

Can you show a demo without this fix and after the fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants