Quarkus extension for integrating Model Context Protocol (MCP) client capabilities with LangChain4j
Health checks, Micrometer metrics, request/response logging, OpenTelemetry tracing, and CDI events for MCP server logs. Comprehensive observability support for monitoring MCP client operations.
MicroProfile health check for verifying MCP client connectivity.
package io.quarkiverse.langchain4j.mcp.runtime;
public class McpClientHealthCheck implements HealthCheck {
/**
* Create a health check for all configured MCP clients.
*
* @param mcpRuntimeConfig the MCP runtime configuration
*/
public McpClientHealthCheck(McpRuntimeConfiguration mcpRuntimeConfig) { ... }
/**
* Performs health check for all configured MCP clients.
* Checks readiness by pinging servers or using MicroProfile health endpoints.
*
* @return HealthCheckResponse indicating UP or DOWN status
*/
public HealthCheckResponse call() { ... }
}Features:
@Readiness checkConfiguration:
# Disable health checks globally
quarkus.langchain4j.mcp.mp-health-enabled=false
# Disable for specific client
quarkus.langchain4j.mcp.github.microprofile-health-check=false
# Custom health check path
quarkus.langchain4j.mcp.github.microprofile-health-check-path=/health/readyUsage:
Health check is automatically available at Quarkus health endpoints:
# Check readiness
curl http://localhost:8080/q/health/ready
# Check liveness
curl http://localhost:8080/q/health/liveResponse includes MCP client status:
{
"status": "UP",
"checks": [
{
"name": "MCP Clients Health Check",
"status": "UP",
"data": {
"github": "UP",
"filesystem": "UP",
"database": "UP"
}
}
]
}Micrometer metrics for MCP operations.
package io.quarkiverse.langchain4j.mcp.runtime;
public class MetricsMcpListener implements McpClientListener {
/**
* Create a metrics listener for the specified MCP client.
*
* @param mcpClientKey the MCP client name
*/
public MetricsMcpListener(String mcpClientKey) { ... }
// Lifecycle methods from McpClientListener interface
void beforeExecuteTool(McpCallContext context);
void afterExecuteTool(McpCallContext context, ToolExecutionResult result, Map<String, Object> rawResult);
void onExecuteToolError(McpCallContext context, Throwable error);
void beforeResourceGet(McpCallContext context);
void afterResourceGet(McpCallContext context, McpReadResourceResult result, Map<String, Object> rawResult);
void onResourceGetError(McpCallContext context, Throwable error);
void beforePromptGet(McpCallContext context);
void afterPromptGet(McpCallContext context, McpGetPromptResult result, Map<String, Object> rawResult);
void onPromptGetError(McpCallContext context, Throwable error);
}Metrics Published:
Tool Call Duration (mcp.client.tool.call.duration)
mcp_client (client name), outcome (success/failure), tool_name (tool name)Resource Get Duration (mcp.client.resource.get.duration)
mcp_client (client name), outcome (success/failure)Prompt Get Duration (mcp.client.prompt.get.duration)
mcp_client (client name), outcome (success/failure)Configuration:
# Enable metrics for specific clients
quarkus.langchain4j.mcp.github.metrics-enabled=true
quarkus.langchain4j.mcp.database.metrics-enabled=trueAccessing Metrics:
Metrics are available through Micrometer endpoints (if quarkus-micrometer is present):
# Prometheus format
curl http://localhost:8080/q/metrics
# Example output:
# mcp_client_tool_call_duration_seconds_count{mcp_client="github",outcome="success",tool_name="list_repos"} 42
# mcp_client_tool_call_duration_seconds_sum{mcp_client="github",outcome="success",tool_name="list_repos"} 2.1Configurable logging for MCP requests and responses.
Configuration:
# Enable request logging
quarkus.langchain4j.mcp.github.log-requests=true
# Enable response logging
quarkus.langchain4j.mcp.github.log-responses=true
# Enable both
quarkus.langchain4j.mcp.database.log-requests=true
quarkus.langchain4j.mcp.database.log-responses=trueLog Output:
Requests and responses are logged at INFO level:
INFO [io.qua.lan.mcp.run.htt.McpHttpClientLogger] Request: POST https://api.github.com/mcp
INFO [io.qua.lan.mcp.run.htt.McpHttpClientLogger] Headers: {Authorization: Bearer ghp_***}
INFO [io.qua.lan.mcp.run.htt.McpHttpClientLogger] Body: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_repos"}}
INFO [io.qua.lan.mcp.run.htt.McpHttpClientLogger] Response: 200 OK
INFO [io.qua.lan.mcp.run.htt.McpHttpClientLogger] Body: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}Handler for MCP server log messages.
package io.quarkiverse.langchain4j.mcp.runtime;
public class QuarkusDefaultMcpLogHandler implements McpLogMessageHandler {
/**
* Create a log handler for the specified MCP client.
*
* @param clientName the MCP client name
*/
public QuarkusDefaultMcpLogHandler(String clientName) { ... }
/**
* Handle log messages from MCP servers.
* Logs via JBoss logging and fires CDI events.
*
* @param message the log message from the MCP server
*/
void handleLogMessage(McpLogMessage message);
}Features:
@McpClientName qualifierLog Levels Mapping:
| MCP Level | Java Level |
|---|---|
| DEBUG | DEBUG |
| INFO | INFO |
| NOTICE | INFO |
| WARNING | WARN |
| ERROR | ERROR |
| CRITICAL | ERROR |
| ALERT | ERROR |
| EMERGENCY | ERROR |
Observing Server Logs via CDI Events:
import io.quarkiverse.langchain4j.mcp.runtime.McpClientName;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import dev.langchain4j.mcp.client.protocol.LoggingLevel;
@ApplicationScoped
public class McpLogObserver {
// Observe logs from all MCP servers
public void onLog(@Observes McpLogMessage logMessage) {
System.out.println("MCP Log: " + logMessage.getMessage());
}
// Observe logs from specific MCP server
public void onGitHubLog(@Observes @McpClientName("github") McpLogMessage logMessage) {
if (logMessage.getLevel() == LoggingLevel.ERROR) {
System.err.println("GitHub MCP Error: " + logMessage.getMessage());
}
}
}Automatic tracing integration for tool executions.
Features:
Usage:
When OpenTelemetry is on the classpath, tool executions are automatically traced:
@RegisterAiService
public interface TracedAssistant {
@McpToolBox("github")
String createIssue(@UserMessage String message);
// Tool calls automatically create spans in the trace
}Trace Example:
Trace: request-123
Span: AI Service Call
Span: Tool Execution [github:create_issue]
- tool_name: create_issue
- mcp_client: github
- outcome: success
- duration: 1.2sJAX-RS client interface for MicroProfile health endpoints.
package io.quarkiverse.langchain4j.mcp.runtime.http;
public interface McpMicroProfileHealthCheck {
@GET
@Produces(MediaType.APPLICATION_JSON)
String healthCheck();
}Used internally by McpClientHealthCheck to check HTTP-based MCP servers that expose MicroProfile health endpoints.
# Health checks
quarkus.langchain4j.mcp.mp-health-enabled=true
quarkus.langchain4j.mcp.github.microprofile-health-check=true
quarkus.langchain4j.mcp.github.microprofile-health-check-path=/q/health
# Metrics
quarkus.langchain4j.mcp.github.metrics-enabled=true
quarkus.langchain4j.mcp.filesystem.metrics-enabled=true
quarkus.langchain4j.mcp.database.metrics-enabled=true
# Logging
quarkus.langchain4j.mcp.github.log-requests=true
quarkus.langchain4j.mcp.github.log-responses=true
quarkus.langchain4j.mcp.database.log-requests=true
quarkus.langchain4j.mcp.database.log-responses=true
# Application logging level
quarkus.log.category."io.quarkiverse.langchain4j.mcp".level=INFO# Health checks enabled
quarkus.langchain4j.mcp.mp-health-enabled=true
# Metrics enabled for critical clients
quarkus.langchain4j.mcp.payment-api.metrics-enabled=true
quarkus.langchain4j.mcp.order-api.metrics-enabled=true
# Request/response logging disabled (use tracing instead)
quarkus.langchain4j.mcp.payment-api.log-requests=false
quarkus.langchain4j.mcp.payment-api.log-responses=false
# OpenTelemetry tracing enabled
quarkus.otel.enabled=true
quarkus.otel.exporter.otlp.endpoint=http://jaeger:4317# All observability features enabled for debugging
quarkus.langchain4j.mcp.mp-health-enabled=true
quarkus.langchain4j.mcp.github.metrics-enabled=true
quarkus.langchain4j.mcp.github.log-requests=true
quarkus.langchain4j.mcp.github.log-responses=true
# Verbose logging
quarkus.log.category."io.quarkiverse.langchain4j.mcp".level=DEBUG# Log only requests for one client
quarkus.langchain4j.mcp.debug-api.log-requests=true
quarkus.langchain4j.mcp.debug-api.log-responses=false
# Log only responses for another client
quarkus.langchain4j.mcp.monitor-api.log-requests=false
quarkus.langchain4j.mcp.monitor-api.log-responses=truequarkus.micrometer.export.prometheus.enabled=true
quarkus.langchain4j.mcp.github.metrics-enabled=trueExpose metrics at /q/metrics and scrape with Prometheus:
scrape_configs:
- job_name: 'quarkus-mcp'
static_configs:
- targets: ['app:8080']
metrics_path: '/q/metrics'quarkus.otel.enabled=true
quarkus.otel.exporter.otlp.endpoint=http://jaeger:4317
quarkus.otel.traces.sampler=always_onquarkus.log.console.json=true
quarkus.langchain4j.mcp.github.log-requests=true
quarkus.langchain4j.mcp.github.log-responses=trueShip logs to Elasticsearch and visualize in Kibana.
apiVersion: v1
kind: Pod
metadata:
name: mcp-app
spec:
containers:
- name: app
image: mcp-app:latest
livenessProbe:
httpGet:
path: /q/health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /q/health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5Install with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-mcp