Skip to content

Conversation

@Scra3
Copy link
Member

@Scra3 Scra3 commented Feb 10, 2026

Summary

  • Catch tool handler errors in tool-with-logging.ts and return them as { isError: true } results instead of throwing
  • Per the MCP specification, tool execution errors should be reported within the result object, not as protocol-level errors

Problem

When a tool handler throws an error, the MCP SDK catches it and may replace the original message with a generic text. This means the LLM client receives an unhelpful error message and cannot adapt its behavior.

Solution

By returning { content: [{ type: 'text', text: message }], isError: true } instead of throwing, the error message is preserved and forwarded to the LLM as-is.

Test plan

  • yarn workspace @forestadmin/mcp-server test — 483 tests pass
  • yarn workspace @forestadmin/mcp-server lint — 0 errors
  • Updated 24 test assertions across 9 test files

🤖 Generated with Claude Code

@qltysh
Copy link

qltysh bot commented Feb 10, 2026

Qlty

Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (1)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/mcp-server/src/utils/tool-with-logging.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@Scra3 Scra3 changed the base branch from main to fix/mcp-server-diagnostic-logging February 10, 2026 14:24
Base automatically changed from fix/mcp-server-diagnostic-logging to main February 10, 2026 14:34
Per the MCP specification, tool execution errors should be reported
within the result object (isError: true) rather than thrown as
protocol-level errors. This ensures the LLM receives the actual error
message and can handle it appropriately.

See: https://modelcontextprotocol.io/docs/concepts/tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the fix/mcp-server-tool-error-handling branch from 4ba700a to 91ca294 Compare February 10, 2026 14:35
try {
return await handler(args as TArgs, extra);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think JSON.stringify(error) is better than String(error) to avoid returning things like "[object]"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
JSON.stringify returns undefined for undefined/Symbol/Function values,
and throws on circular references. Add fallback to String() for both
cases to ensure error message is always a string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the fix/mcp-server-tool-error-handling branch from 8356eaf to 3be6265 Compare February 11, 2026 15:02
@Scra3
Copy link
Member Author

Scra3 commented Feb 11, 2026

Code review

1 issue found — weak assertions in 4 test files.

Weak expect.any(String) assertions

In the "should return error result when no parsable error found" tests across 4 files, expect.any(String) is used when the output is deterministic ("[object Object]" — the error { unknownProperty: 'some value' } goes through withActivityLogString(error)new Error("[object Object]")).

Files:

Fix: Replace expect.any(String) with '[object Object]' — this is the exact, deterministic output.

Otherwise, no bugs found. Code is correct and CLAUDE.md compliant.

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