CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-mcp

Java implementation of the Model Context Protocol (MCP) client for the LangChain4j framework, enabling integration with MCP servers for tools, resources, and prompts

Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

The langchain4j-mcp library provides specialized exception classes for handling MCP protocol errors, response validation failures, and registry client issues.

Quick Reference

try (McpClient client = DefaultMcpClient.builder()
        .transport(transport)
        .build()) {

    client.executeTool(request);

} catch (McpException e) {
    // MCP protocol error with error code
    int code = e.errorCode();          // -32601, -32602, etc.
    String message = e.errorMessage(); // Error description

} catch (IllegalResponseException e) {
    // Malformed server response
    String problem = e.getMessage();

} catch (Exception e) {
    // Network, timeout, or other errors
}

Exception Hierarchy

LangChain4jException
├── McpException
├── IllegalResponseException
└── McpRegistryClientException

All MCP exceptions extend dev.langchain4j.exception.LangChain4jException from the core LangChain4j framework.

McpException

Protocol-level errors returned by MCP servers with error codes and messages from the MCP protocol.

package dev.langchain4j.mcp.client;

public class McpException extends LangChain4jException {
    McpException(int errorCode, String errorMessage);
    int errorCode();
    String errorMessage();
}

Constructor

public McpException(int errorCode, String errorMessage)

Parameters:

  • errorCode (int) - The numeric error code from the MCP protocol
  • errorMessage (String) - The error message from the MCP protocol

Methods

errorCode()

Returns the MCP protocol error code.

Returns: int - The numeric error code Common Codes:

  • -32700 - Parse error (invalid JSON)
  • -32600 - Invalid request
  • -32601 - Method not found
  • -32602 - Invalid parameters
  • -32603 - Internal error
  • -32000 to -32099 - Server-defined errors

errorMessage()

Returns the error message from the MCP protocol.

Returns: String - The error message (never null)

When Thrown

McpException is thrown when:

  • Tool execution fails: Tool not found, invalid arguments, execution error
  • Resource access fails: Resource not found, permission denied
  • Prompt operations fail: Prompt not found, required arguments missing
  • Health check fails: Server unhealthy or not responding
  • Protocol errors: Any MCP protocol-level error

Operations that throw:

  • executeTool() - Most common
  • readResource()
  • getPrompt()
  • listTools(), listResources(), listPrompts() (less common)
  • checkHealth()

Usage Example

import dev.langchain4j.mcp.client.McpException;

try {
    ToolExecutionResult result = client.executeTool(request);

} catch (McpException e) {
    System.err.println("MCP Error Code: " + e.errorCode());
    System.err.println("MCP Error Message: " + e.errorMessage());

    // Handle specific error codes
    switch (e.errorCode()) {
        case -32601 -> {
            // Method/tool not found
            System.err.println("Tool does not exist on server");
            // Refresh tool list?
            client.evictToolListCache();
        }
        case -32602 -> {
            // Invalid parameters
            System.err.println("Invalid tool arguments");
            // Check argument format and types
        }
        case -32603 -> {
            // Internal server error
            System.err.println("Server internal error");
            // Check server logs, maybe retry?
        }
        default -> {
            // Other errors
            System.err.println("Unexpected MCP error");
        }
    }
}

Error Code Reference

CodeMeaningCommon CauseRecovery Action
-32700Parse errorInvalid JSON in requestFix JSON formatting
-32600Invalid requestMalformed MCP requestCheck MCP protocol version
-32601Method not foundTool/method doesn't existRefresh tool list, check name
-32602Invalid paramsWrong argumentsValidate argument schema
-32603Internal errorServer-side failureCheck server logs, retry
-32000 to -32099Server errorCustom server errorsCheck error message for details

IllegalResponseException

Thrown when MCP server responses don't match the expected format or structure, indicating a protocol violation or malformed response.

package dev.langchain4j.mcp.client;

public class IllegalResponseException extends LangChain4jException {
    IllegalResponseException(String message);
}

Constructor

public IllegalResponseException(String message)

Parameters:

  • message (String) - Description of the response validation failure

When Thrown

IllegalResponseException is thrown when:

  • Missing required fields: Response lacks 'result', 'prompts', 'resources', etc.
  • Invalid structure: Response doesn't conform to MCP protocol specification
  • Type mismatches: Expected array but got object, etc.
  • Null required values: Required fields are null

Specific Scenarios:

  • listPrompts() response missing 'prompts' or 'result' element
  • listResources() response missing 'resources' or 'result' element
  • listResourceTemplates() response missing 'resourceTemplates' or 'result' element
  • listTools() response malformed (rare)
  • readResource() response missing content
  • getPrompt() response missing messages

Usage Example

import dev.langchain4j.mcp.client.IllegalResponseException;

try {
    List<McpResource> resources = client.listResources();

} catch (IllegalResponseException e) {
    System.err.println("Malformed MCP response: " + e.getMessage());

    // Possible causes:
    // 1. Server doesn't fully implement MCP protocol
    // 2. Protocol version mismatch
    // 3. Server bug or corrupted response

    // Recovery actions:
    // - Check server logs
    // - Verify MCP protocol version compatibility
    // - Update server or client
    // - Report bug to server maintainer
}

Prevention

  1. Use compatible protocol versions: Ensure client and server use compatible MCP versions
  2. Test with official servers: Validate against reference implementations
  3. Check server documentation: Verify server MCP compliance
  4. Update libraries: Keep langchain4j-mcp up to date

McpRegistryClientException

Represents errors during MCP registry client operations, such as HTTP communication failures or JSON parsing errors.

package dev.langchain4j.mcp.registryclient;

public class McpRegistryClientException extends LangChain4jException {
    McpRegistryClientException(String message);
    McpRegistryClientException(Throwable cause);
    McpRegistryClientException(String message, Throwable cause);
}

Constructors

public McpRegistryClientException(String message)

Parameters:

  • message (String) - The error message
public McpRegistryClientException(Throwable cause)

Parameters:

  • cause (Throwable) - The underlying exception (uses its message)
public McpRegistryClientException(String message, Throwable cause)

Parameters:

  • message (String) - Custom error message
  • cause (Throwable) - The underlying exception

When Thrown

McpRegistryClientException is thrown when:

  • HTTP request fails: Network error, timeout, connection refused
  • JSON parsing fails: Invalid JSON from registry
  • Server returns error: Registry API returns error response
  • Invalid data: Unexpected or malformed registry data

Operations that throw:

  • listServers() - Most common
  • getSpecificServerVersion()
  • getAllVersionsOfServer()
  • healthCheck()
  • ping()

Usage Example

import dev.langchain4j.mcp.registryclient.McpRegistryClientException;
import dev.langchain4j.mcp.registryclient.McpRegistryClient;
import dev.langchain4j.mcp.registryclient.DefaultMcpRegistryClient;

McpRegistryClient registryClient = DefaultMcpRegistryClient.builder()
    .baseUrl("https://registry.example.com")
    .build();

try {
    McpServerList servers = registryClient.listServers(request);

} catch (McpRegistryClientException e) {
    System.err.println("Registry client error: " + e.getMessage());

    // Check underlying cause
    Throwable cause = e.getCause();
    if (cause != null) {
        System.err.println("Caused by: " + cause.getClass().getSimpleName());
        System.err.println("Details: " + cause.getMessage());

        if (cause instanceof java.net.SocketTimeoutException) {
            System.err.println("Registry request timed out");
            // Increase timeout or check network
        } else if (cause instanceof java.net.ConnectException) {
            System.err.println("Cannot connect to registry");
            // Check registry URL and network connectivity
        } else if (cause instanceof com.fasterxml.jackson.core.JsonProcessingException) {
            System.err.println("Invalid JSON from registry");
            // Registry may be down or returning errors
        }
    }
}

Common Causes and Solutions

CauseException TypeSolution
Network timeoutSocketTimeoutExceptionIncrease timeout, check network
Connection refusedConnectExceptionVerify URL, check firewall
Invalid JSONJsonProcessingExceptionCheck registry status, verify API version
404 Not FoundHTTP error in causeVerify server name/version exists
401/403 Auth errorHTTP error in causeCheck authentication headers
500 Server errorHTTP error in causeRegistry may be down, try later

Error Handling Best Practices

1. Handle Specific Exceptions

try (McpClient client = DefaultMcpClient.builder()
        .transport(transport)
        .build()) {

    ToolExecutionResult result = client.executeTool(request);

} catch (McpException e) {
    // Handle protocol-level errors with error codes
    handleMcpError(e);

} catch (IllegalResponseException e) {
    // Handle malformed responses
    handleMalformedResponse(e);

} catch (Exception e) {
    // Handle other errors (network, timeout, etc.)
    handleUnexpectedError(e);
}

2. Implement Retry Logic

int maxRetries = 3;
ToolExecutionResult result = null;

for (int attempt = 0; attempt < maxRetries; attempt++) {
    try {
        result = client.executeTool(request);
        break; // Success

    } catch (McpException e) {
        // Only retry on transient errors
        if (isRetryable(e) && attempt < maxRetries - 1) {
            long waitMs = (long) Math.pow(2, attempt) * 1000; // Exponential backoff
            Thread.sleep(waitMs);
        } else {
            throw e;
        }
    }
}

private boolean isRetryable(McpException e) {
    int code = e.errorCode();
    // Retry on internal errors, not on invalid method/params
    return code == -32603 || (code >= -32000 && code <= -32099);
}

3. Log with Context

try {
    ToolExecutionResult result = client.executeTool(request);

} catch (McpException e) {
    logger.error("MCP tool execution failed: {} (code: {})",
                 e.errorMessage(), e.errorCode());
    logger.error("Tool: {}, Arguments: {}",
                 request.name(), request.arguments());

} catch (IllegalResponseException e) {
    logger.error("Invalid MCP response: {}", e.getMessage());
    logger.error("Operation: listResources"); // Or other operation
}

4. Resource Cleanup

Always close clients properly, even when exceptions occur:

McpClient client = null;

try {
    client = DefaultMcpClient.builder()
        .transport(transport)
        .build();

    // Use client
    client.executeTool(request);

} catch (McpException | IllegalResponseException e) {
    // Handle exceptions
    handleError(e);

} finally {
    if (client != null) {
        try {
            client.close();
        } catch (Exception e) {
            logger.warn("Error closing client: {}", e.getMessage());
        }
    }
}

Preferred: Use try-with-resources:

try (McpClient client = DefaultMcpClient.builder()
        .transport(transport)
        .build()) {

    // Use client
    client.executeTool(request);

} catch (McpException e) {
    handleError(e);
} // Automatically closed

5. User-Friendly Error Messages

try {
    ToolExecutionResult result = client.executeTool(request);

} catch (McpException e) {
    String userMessage = switch (e.errorCode()) {
        case -32601 -> "The requested operation is not available. Please try a different action.";
        case -32602 -> "Invalid input provided. Please check your request and try again.";
        case -32603 -> "A server error occurred. Please try again in a moment.";
        default -> "An error occurred while processing your request. Please try again.";
    };

    // Show user-friendly message to user
    System.err.println(userMessage);

    // Log technical details
    logger.error("MCP error: {} (code: {})", e.errorMessage(), e.errorCode());
}

6. Graceful Degradation

List<ToolSpecification> tools;

try {
    tools = client.listTools();

} catch (McpException | IllegalResponseException e) {
    logger.warn("Failed to fetch tools from MCP server: {}", e.getMessage());

    // Fall back to default/cached tools
    tools = getDefaultTools();
}

// Continue with tools (either fresh or fallback)
processTools(tools);

7. Circuit Breaker Pattern

class McpClientWithCircuitBreaker {
    private final McpClient delegate;
    private final CircuitBreaker circuitBreaker;

    ToolExecutionResult executeTool(ToolExecutionRequest request) {
        if (circuitBreaker.isOpen()) {
            throw new IllegalStateException("Circuit breaker is open");
        }

        try {
            ToolExecutionResult result = delegate.executeTool(request);
            circuitBreaker.recordSuccess();
            return result;

        } catch (McpException | IllegalResponseException e) {
            circuitBreaker.recordFailure();
            throw e;
        }
    }
}

Complete Error Handling Example

import dev.langchain4j.mcp.client.*;
import dev.langchain4j.mcp.registryclient.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class McpErrorHandlingExample {
    private static final Logger logger = LoggerFactory.getLogger(McpErrorHandlingExample.class);

    public static void main(String[] args) {
        // Create client with error handling
        try (McpClient client = createClient()) {

            // Execute tool with comprehensive error handling
            ToolExecutionResult result = executeToolSafely(client, request);

            System.out.println("Success: " + result.text());

        } catch (Exception e) {
            logger.error("Failed to create or use MCP client", e);
            System.exit(1);
        }
    }

    private static McpClient createClient() {
        try {
            StdioMcpTransport transport = StdioMcpTransport.builder()
                .command(List.of("node", "mcp-server.js"))
                .build();

            return DefaultMcpClient.builder()
                .transport(transport)
                .toolExecutionTimeout(Duration.ofMinutes(2))
                .build();

        } catch (Exception e) {
            logger.error("Failed to create MCP client", e);
            throw new RuntimeException("Client creation failed", e);
        }
    }

    private static ToolExecutionResult executeToolSafely(
            McpClient client,
            ToolExecutionRequest request) {

        int maxRetries = 3;

        for (int attempt = 0; attempt < maxRetries; attempt++) {
            try {
                return client.executeTool(request);

            } catch (McpException e) {
                logger.warn("MCP error on attempt {}: {} (code: {})",
                           attempt + 1, e.errorMessage(), e.errorCode());

                // Handle specific error codes
                switch (e.errorCode()) {
                    case -32601 -> {
                        // Method not found - don't retry
                        logger.error("Tool '{}' not found", request.name());
                        throw new IllegalArgumentException("Tool not found", e);
                    }
                    case -32602 -> {
                        // Invalid params - don't retry
                        logger.error("Invalid parameters for tool '{}'", request.name());
                        throw new IllegalArgumentException("Invalid parameters", e);
                    }
                    case -32603 -> {
                        // Internal error - retry with backoff
                        if (attempt < maxRetries - 1) {
                            long waitMs = (long) Math.pow(2, attempt) * 1000;
                            logger.info("Retrying after {}ms", waitMs);
                            try {
                                Thread.sleep(waitMs);
                            } catch (InterruptedException ie) {
                                Thread.currentThread().interrupt();
                                throw new RuntimeException("Interrupted during retry", ie);
                            }
                        } else {
                            throw new RuntimeException("Max retries exceeded", e);
                        }
                    }
                    default -> {
                        // Unknown error code
                        logger.error("Unknown MCP error code: {}", e.errorCode());
                        throw new RuntimeException("MCP error: " + e.errorMessage(), e);
                    }
                }

            } catch (IllegalResponseException e) {
                // Protocol violation - don't retry
                logger.error("Invalid response from MCP server: {}", e.getMessage());
                throw new RuntimeException("Protocol error", e);

            } catch (Exception e) {
                // Network, timeout, or other errors
                logger.error("Unexpected error on attempt {}: {}", attempt + 1, e.getMessage());

                if (attempt < maxRetries - 1) {
                    logger.info("Retrying...");
                } else {
                    throw new RuntimeException("Failed after " + maxRetries + " attempts", e);
                }
            }
        }

        throw new IllegalStateException("Should not reach here");
    }
}

Testing Exception Handling

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class McpExceptionHandlingTest {

    @Test
    void testMcpExceptionHandling() {
        McpClient client = createTestClient();

        McpException exception = assertThrows(McpException.class, () -> {
            client.executeTool(invalidRequest);
        });

        assertEquals(-32602, exception.errorCode());
        assertTrue(exception.errorMessage().contains("Invalid parameters"));
    }

    @Test
    void testIllegalResponseExceptionHandling() {
        McpClient client = createMalformedResponseClient();

        IllegalResponseException exception = assertThrows(
            IllegalResponseException.class,
            () -> client.listResources()
        );

        assertTrue(exception.getMessage().contains("resources"));
    }

    @Test
    void testRetryLogic() {
        AtomicInteger attempts = new AtomicInteger(0);
        McpClient client = createUnstableClient(attempts);

        // Should succeed after retries
        ToolExecutionResult result = executeWithRetry(client, request);
        assertNotNull(result);
        assertTrue(attempts.get() > 1); // Verified retry happened
    }
}

Related Documentation

  • Client - Client API methods that throw exceptions
  • Registry - Registry client error handling
  • Logging and Listeners - Error event handling

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-mcp

docs

client.md

data-models.md

exceptions.md

index.md

logging-listeners.md

registry.md

resources-as-tools.md

tool-provider.md

transports.md

tile.json