Java implementation of the Model Context Protocol (MCP) client for the LangChain4j framework, enabling integration with MCP servers for tools, resources, and prompts
The langchain4j-mcp library provides specialized exception classes for handling MCP protocol errors, response validation failures, and registry client issues.
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
}LangChain4jException
├── McpException
├── IllegalResponseException
└── McpRegistryClientExceptionAll MCP exceptions extend dev.langchain4j.exception.LangChain4jException from the core LangChain4j framework.
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();
}public McpException(int errorCode, String errorMessage)Parameters:
errorCode (int) - The numeric error code from the MCP protocolerrorMessage (String) - The error message from the MCP protocolReturns 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 errorsReturns the error message from the MCP protocol.
Returns: String - The error message (never null)
McpException is thrown when:
Operations that throw:
executeTool() - Most commonreadResource()getPrompt()listTools(), listResources(), listPrompts() (less common)checkHealth()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");
}
}
}| Code | Meaning | Common Cause | Recovery Action |
|---|---|---|---|
| -32700 | Parse error | Invalid JSON in request | Fix JSON formatting |
| -32600 | Invalid request | Malformed MCP request | Check MCP protocol version |
| -32601 | Method not found | Tool/method doesn't exist | Refresh tool list, check name |
| -32602 | Invalid params | Wrong arguments | Validate argument schema |
| -32603 | Internal error | Server-side failure | Check server logs, retry |
| -32000 to -32099 | Server error | Custom server errors | Check error message for details |
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);
}public IllegalResponseException(String message)Parameters:
message (String) - Description of the response validation failureIllegalResponseException is thrown when:
Specific Scenarios:
listPrompts() response missing 'prompts' or 'result' elementlistResources() response missing 'resources' or 'result' elementlistResourceTemplates() response missing 'resourceTemplates' or 'result' elementlistTools() response malformed (rare)readResource() response missing contentgetPrompt() response missing messagesimport 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
}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);
}public McpRegistryClientException(String message)Parameters:
message (String) - The error messagepublic McpRegistryClientException(Throwable cause)Parameters:
cause (Throwable) - The underlying exception (uses its message)public McpRegistryClientException(String message, Throwable cause)Parameters:
message (String) - Custom error messagecause (Throwable) - The underlying exceptionMcpRegistryClientException is thrown when:
Operations that throw:
listServers() - Most commongetSpecificServerVersion()getAllVersionsOfServer()healthCheck()ping()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
}
}
}| Cause | Exception Type | Solution |
|---|---|---|
| Network timeout | SocketTimeoutException | Increase timeout, check network |
| Connection refused | ConnectException | Verify URL, check firewall |
| Invalid JSON | JsonProcessingException | Check registry status, verify API version |
| 404 Not Found | HTTP error in cause | Verify server name/version exists |
| 401/403 Auth error | HTTP error in cause | Check authentication headers |
| 500 Server error | HTTP error in cause | Registry may be down, try later |
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);
}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);
}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
}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 closedtry {
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());
}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);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;
}
}
}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");
}
}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
}
}Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-mcp@1.11.0