Skip to content

Rework MCP error handling #422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tzolov
Copy link
Contributor

@tzolov tzolov commented Jul 24, 2025

Refactor exception handling to introduce a proper exception hierarchy, replacing generic McpError usage with specific exception types for better error categorization and handling.

  • Add McpTransportException for transport layer errors
  • Add McpClientInternalException for internal client failures
  • Use standard Java exceptions for validation errors
  • Implement structured error builder with JSON-RPC error codes
  • Change tool not found errors to be returned in CallToolResult
  • Update logging levels for unregistered notification handlers

Motivation and Context

The existing codebase was using McpError as a catch-all exception type for various error scenarios, making it difficult to distinguish between different types of failures. This change addresses several issues:

  • Poor error categorization: Transport errors, validation errors, and protocol errors were all using the same exception type
  • Missing error context: Generic exceptions didn't provide enough context for proper error handling
  • Tool error handling: Tool errors were causing protocol-level failures instead of being visible to LLMs
  • Inconsistent error structure: MCP protocol errors weren't following proper JSON-RPC error format

How Has This Been Tested?

  • All existing unit tests have been updated and pass with the new exception types
  • Integration tests across all transport implementations (SSE, HTTP, Stdio) continue to pass
  • Test coverage includes all error scenarios for each exception type
  • Verified that tool errors are now properly returned as results rather than thrown exceptions

Breaking Changes

Yes, this is a breaking change for applications that catch McpError exceptions. Users will need to update their exception handling code to catch the new specific exception types:

  • McpTransportException for transport-related errors
  • McpClientInternalException for internal client errors
  • IllegalArgumentException for validation errors
  • IllegalStateException for state-related errors
  • McpError now only for actual MCP protocol JSON-RPC errors

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • Bug fix (non-breaking change which fixes an issue) - Tool error handling improvement
  • New feature (non-breaking change which adds functionality)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Key Changes:

  1. New Exception Classes:

    • McpTransportException: For transport layer errors (network, serialization, SSE parsing)
    • McpClientInternalException: For internal client errors (initialization failures, endpoint handling)
  2. McpError Refinement:

    • Now reserved for actual MCP protocol JSON-RPC errors
    • Added builder pattern for proper error structure: McpError.builder(errorCode).message("...").data("...").build()
  3. Standard Java Exceptions:

    • IllegalArgumentException for validation errors (null parameters, invalid arguments)
    • IllegalStateException for state-related errors (missing capabilities, not initialized)
  4. Tool Error Handling Improvement:

    • Tool errors now return CallToolResult with isError(true) instead of throwing exceptions
    • This allows LLMs to see and potentially handle tool errors rather than causing protocol failures

Migration Guide:

Applications catching McpError should update their exception handling:

// Before
catch (McpError e) { ... }

// After - catch specific types
catch (McpTransportException e) { /* handle transport errors */ }
catch (McpClientInternalException e) { /* handle client errors */ }
catch (IllegalArgumentException e) { /* handle validation errors */ }
catch (McpError e) { /* handle protocol errors */ }

This refactoring significantly improves error handling semantics and makes the codebase more maintainable while following Java exception handling best practices.

tzolov added 2 commits July 24, 2025 09:14
BREAKING CHANGE: McpError is no longer used for validation and internal errors.
Client code catching McpError should now catch specific exception types like
IllegalArgumentException, IllegalStateException, McpTransportException,
or McpClientInternalException.

- Add McpTransportException for transport layer errors
- Add McpClientInternalException for internal client failures
- Use standard Java exceptions for validation errors
- Implement structured error builder with JSON-RPC error codes
- Change tool not found errors to be returned in CallToolResult
- Update logging levels for unregistered notification handlers

Signed-off-by: Christian Tzolov <[email protected]>
Signed-off-by: Christian Tzolov <[email protected]>
@@ -469,11 +471,17 @@ private McpServerSession.RequestHandler<CallToolResult> toolsCallRequestHandler(
.findAny();

if (toolSpecification.isEmpty()) {
return Mono.error(new McpError("Tool not found: " + callToolRequest.name()));
// Tool errors should be reported within the result object, not as MCP
Copy link
Contributor Author

@tzolov tzolov Jul 24, 2025

Choose a reason for hiding this comment

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

This aligns the tool response with the spec: https://modelcontextprotocol.io/specification/2025-06-18/server/tools#error-handling

  • Tool errors now return CallToolResult with isError(true) instead of throwing exceptions
  • This allows LLMs to see and potentially handle tool errors rather than causing protocol failures

@@ -14,12 +15,41 @@ public McpError(JSONRPCError jsonRpcError) {
this.jsonRpcError = jsonRpcError;
}

public McpError(Object error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now reserved for actual MCP protocol JSON-RPC errors

public JSONRPCError getJsonRpcError() {
return jsonRpcError;
}

public static Builder builder(int errorCode) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added builder pattern for proper error structure: McpError.builder(errorCode).message("...").data("...").build()

@@ -221,7 +221,7 @@ private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification noti
return Mono.defer(() -> {
var handler = notificationHandlers.get(notification.method());
if (handler == null) {
logger.error("No handler registered for notification method: {}", notification.method());
logger.warn("No handler registered for notification: {}", notification);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Log abnormal behavior without indicating it's an error.
Perhaps consider using debug level logging?

@@ -248,7 +248,7 @@ private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification noti

var handler = notificationHandlers.get(notification.method());
if (handler == null) {
logger.error("No handler registered for notification method: {}", notification.method());
logger.warn("No handler registered for notification: {}", notification);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Log abnormal behavior without indicating it's an error.
Perhaps consider using debug level logging?

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.

1 participant