Quarkus build-time deployment extension for Model Context Protocol (MCP) client integration, handling configuration processing, synthetic bean generation, and framework integration
Runtime classes and integration points for MCP client functionality, authentication, health checks, and observability.
While the deployment module handles build-time processing, the runtime module (referenced in this document) provides classes that execute at application runtime. Application developers interact with these classes for authentication, health monitoring, log handling, and metrics collection.
Service Provider Interface for providing authentication credentials to MCP HTTP clients.
package io.quarkiverse.langchain4j.mcp.auth;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* SPI for providing authentication credentials to MCP HTTP/WebSocket clients.
* Implement this interface to inject Authorization headers into MCP requests.
*
* Use @McpClientName qualifier to associate provider with specific client(s).
*/
public interface McpClientAuthProvider {
/**
* Provides the Authorization header value for an HTTP request.
*
* @param input Request context containing method, URI, and headers
* @return Authorization header value (e.g., "Bearer token123")
*/
String getAuthorization(Input input);
/**
* Resolves the auth provider for a specific MCP client.
*
* @param mcpClientName Client name
* @return Resolved provider, if any
*/
static Optional<McpClientAuthProvider> resolve(String mcpClientName) {
// Implementation provided by runtime
}
/**
* Request context for authentication.
*/
interface Input {
/**
* HTTP method (GET, POST, etc.)
*
* @return HTTP method
*/
String method();
/**
* Request URI
*
* @return Request URI
*/
URI uri();
/**
* Request headers
*
* @return Map of header names to values
*/
Map<String, List<Object>> headers();
}
}Implementation - Basic:
import io.quarkiverse.langchain4j.mcp.auth.McpClientAuthProvider;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class StaticTokenProvider implements McpClientAuthProvider {
@Override
public String getAuthorization(Input input) {
return "Bearer " + System.getenv("MCP_API_TOKEN");
}
}Implementation - Per-Client:
import io.quarkiverse.langchain4j.mcp.auth.McpClientAuthProvider;
import io.quarkiverse.langchain4j.mcp.runtime.McpClientName;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@McpClientName("github")
public class GitHubAuthProvider implements McpClientAuthProvider {
@Override
public String getAuthorization(Input input) {
return "Bearer " + System.getenv("GITHUB_TOKEN");
}
}
@ApplicationScoped
@McpClientName("gitlab")
public class GitLabAuthProvider implements McpClientAuthProvider {
@Override
public String getAuthorization(Input input) {
return "Bearer " + System.getenv("GITLAB_TOKEN");
}
}Implementation - Dynamic:
import io.quarkiverse.langchain4j.mcp.auth.McpClientAuthProvider;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class DatabaseTokenProvider implements McpClientAuthProvider {
@Inject
TokenRepository tokenRepository;
@Inject
SecurityContext securityContext;
@Override
public String getAuthorization(Input input) {
String userId = securityContext.getCurrentUserId();
String token = tokenRepository.getTokenForUser(userId);
return "Bearer " + token;
}
}Resolution Rules:
@McpClientName, use it@McpClientNameMicroProfile Health readiness check for MCP clients.
package io.quarkiverse.langchain4j.mcp.runtime;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;
import jakarta.enterprise.context.ApplicationScoped;
/**
* Readiness health check for MCP clients.
* Automatically registered when:
* - quarkus-smallrye-health extension is present
* - quarkus.langchain4j.mcp.health.enabled=true (default)
*
* Checks health by:
* - Sending MCP ping and waiting for pong (all transports)
* - Calling MicroProfile health endpoint (HTTP transports, if configured)
*/
@Readiness
@ApplicationScoped
public class McpClientHealthCheck implements HealthCheck {
/**
* Performs health check on all configured MCP clients.
*
* @return Aggregated health check response
*/
@Override
public HealthCheckResponse call() {
// Implementation provided by runtime
}
}Response Format:
Healthy:
{
"status": "UP",
"checks": [
{
"name": "MCP clients health check",
"status": "UP",
"data": {
"github": "OK",
"filesystem": "OK"
}
}
]
}Unhealthy:
{
"status": "DOWN",
"checks": [
{
"name": "MCP clients health check",
"status": "DOWN",
"data": {
"github": "OK",
"filesystem": "Connection refused"
}
}
]
}Configuration:
# Enable health check (default: true)
quarkus.langchain4j.mcp.health.enabled=true
# Ping timeout (per client)
quarkus.langchain4j.mcp.github.ping-timeout=10s
# Use MicroProfile health endpoint (HTTP only)
quarkus.langchain4j.mcp.remote.microprofile-health-check=true
quarkus.langchain4j.mcp.remote.microprofile-health-check-path=/q/healthHealth Check Behavior:
microprofile-health-check=true:
Default handler for log messages received from MCP servers.
package io.quarkiverse.langchain4j.mcp.runtime;
import dev.langchain4j.mcp.client.logging.McpLogMessageHandler;
import dev.langchain4j.mcp.client.logging.McpLogMessage;
import jakarta.enterprise.context.ApplicationScoped;
/**
* Default log handler for MCP client log events.
* Automatically registered for all MCP clients.
*
* Behavior:
* - Logs messages via JBoss Logger with appropriate level
* - Fires CDI events for custom handling
*/
@ApplicationScoped
public class QuarkusDefaultMcpLogHandler implements McpLogMessageHandler {
/**
* Handles log message from MCP server.
*
* @param message Log message from server
*/
@Override
public void handleLogMessage(McpLogMessage message) {
// Implementation provided by runtime:
// 1. Log to JBoss Logger
// 2. Fire CDI event with @McpClientName qualifier
}
}Observing Log Events:
import dev.langchain4j.mcp.client.logging.McpLogMessage;
import dev.langchain4j.mcp.client.logging.McpLogLevel;
import io.quarkiverse.langchain4j.mcp.runtime.McpClientName;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class McpLogObserver {
// Observe logs from specific client
public void onGitHubLog(@Observes @McpClientName("github") McpLogMessage log) {
if (log.level() == McpLogLevel.ERROR) {
alertOps("GitHub MCP error: " + log.data());
}
}
// Observe logs from all clients
public void onAnyLog(@Observes McpLogMessage log) {
storeInDatabase(log);
}
// Observe specific log levels
public void onError(@Observes McpLogMessage log) {
if (log.level() == McpLogLevel.ERROR ||
log.level() == McpLogLevel.CRITICAL) {
handleError(log);
}
}
}McpLogMessage API:
package dev.langchain4j.mcp.client.logging;
import com.fasterxml.jackson.databind.JsonNode;
/**
* Log message from MCP server.
*/
interface McpLogMessage {
/**
* Log message data as JSON.
*
* @return Message data
*/
JsonNode data();
/**
* Log level.
*
* @return Log level
*/
McpLogLevel level();
/**
* Logger name from MCP server.
*
* @return Logger name
*/
String logger();
}
/**
* MCP log levels (from MCP specification).
*/
enum McpLogLevel {
DEBUG,
INFO,
NOTICE,
WARNING,
ERROR,
CRITICAL,
ALERT,
EMERGENCY
}Quarkus-integrated tool provider that aggregates tools from MCP clients.
package io.quarkiverse.langchain4j.mcp.runtime;
import dev.langchain4j.mcp.client.tool.McpToolProvider;
import jakarta.enterprise.context.ApplicationScoped;
/**
* Tool provider integrating MCP clients with LangChain4j AI services.
*
* Features:
* - Aggregates tools from all configured MCP clients
* - Filters tools based on @McpToolBox annotation
* - Integrates with OpenTelemetry for tracing (if available)
* - Supports resource-as-tools conversion (if enabled)
*
* Automatically generated when:
* - quarkus.langchain4j.mcp.generate-tool-provider=true (default)
* - At least one MCP client is configured
*/
@ApplicationScoped
public class QuarkusMcpToolProvider extends McpToolProvider {
// Implementation provided by runtime
// Constructor is package-private
}Usage (automatically injected into AI services with @McpToolBox):
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.mcp.runtime.McpToolBox;
import dev.langchain4j.service.UserMessage;
@RegisterAiService
public interface Assistant {
@McpToolBox("github")
String chat(@UserMessage String message);
}Resource-as-Tools:
When quarkus.langchain4j.mcp.expose-resources-as-tools=true:
// Synthetic tools automatically created:
/**
* Lists all available resources from MCP server.
*/
String list_resources();
/**
* Retrieves content of specific resource.
*
* @param uri Resource URI
*/
String get_resource(String uri);Micrometer-based metrics listener for MCP operations.
package io.quarkiverse.langchain4j.mcp.runtime;
import dev.langchain4j.mcp.client.McpClientListener;
/**
* Micrometer metrics listener for MCP operations.
*
* Requirements:
* - quarkus-micrometer-registry-* extension present
* - quarkus.langchain4j.mcp.{client}.metrics.enabled=true
*
* Metrics recorded:
* - mcp.client.tool.call.duration - Tool execution timer
* - mcp.client.resource.get.duration - Resource retrieval timer
* - mcp.client.prompt.get.duration - Prompt retrieval timer
*
* Tags:
* - mcp_client: Client name
* - tool_name: Tool name (tool calls only)
* - outcome: success/failure/error
*/
public class MetricsMcpListener implements McpClientListener {
// Implementation provided by runtime
}Configuration:
# Enable metrics for specific client
quarkus.langchain4j.mcp.github.metrics.enabled=true
# Micrometer must be present
# Example: Prometheus registry
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>Metrics Example (Prometheus format):
# Tool execution duration
mcp_client_tool_call_duration_seconds_sum{mcp_client="github",tool_name="search_repositories",outcome="success"} 1.234
mcp_client_tool_call_duration_seconds_count{mcp_client="github",tool_name="search_repositories",outcome="success"} 5
# Resource retrieval duration
mcp_client_resource_get_duration_seconds_sum{mcp_client="filesystem",outcome="success"} 0.123
mcp_client_resource_get_duration_seconds_count{mcp_client="filesystem",outcome="success"} 10
# Prompt retrieval duration
mcp_client_prompt_get_duration_seconds_sum{mcp_client="github",outcome="success"} 0.045
mcp_client_prompt_get_duration_seconds_count{mcp_client="github",outcome="success"} 3JSON-RPC service for DevUI to interact with MCP clients.
package io.quarkiverse.langchain4j.mcp.runtime.devui;
import java.util.List;
/**
* JSON-RPC service providing DevUI backend for MCP clients.
* Enables interactive exploration and testing of MCP tools.
*
* Access via: http://localhost:8080/q/dev-ui (development mode only)
*/
public class McpClientsJsonRpcService {
/**
* Retrieves information about all configured MCP clients.
*
* @return List of client information
*/
public List<McpClientInfo> clientInfos() {
// Implementation provided by runtime
}
/**
* Executes a tool and returns the result.
*
* @param clientName MCP client name
* @param toolName Tool name
* @param arguments Tool arguments as JSON string
* @return Tool execution result as JSON string
*/
public String executeTool(String clientName, String toolName, String arguments) {
// Implementation provided by runtime
}
}DevUI Data Models:
package io.quarkiverse.langchain4j.mcp.runtime.devui.json;
import java.util.List;
/**
* MCP client information for DevUI.
*/
public class McpClientInfo {
public String getCdiName();
public void setCdiName(String cdiName);
public List<McpToolInfo> getTools();
public void setTools(List<McpToolInfo> tools);
}
/**
* MCP tool information for DevUI.
*/
public class McpToolInfo {
public String getName();
public void setName(String name);
public String getDescription();
public void setDescription(String description);
public String getExampleInput();
public void setExampleInput(String exampleInput);
public List<McpToolArgInfo> getArgs();
public void setArgs(List<McpToolArgInfo> args);
}
/**
* Tool argument information for DevUI.
*/
public class McpToolArgInfo {
public String getName();
public void setName(String name);
public String getType();
public void setType(String type);
public String getDescription();
public void setDescription(String description);
public boolean isRequired();
public void setRequired(boolean required);
}DevUI Features:
Bytecode recorder for runtime initialization (used by build processors).
package io.quarkiverse.langchain4j.mcp.runtime;
import io.quarkus.runtime.annotations.Recorder;
import java.util.function.Supplier;
import java.util.function.Function;
/**
* Recorder for generating runtime initialization bytecode.
* Used internally by build processors - not for direct application use.
*/
@Recorder
public class McpRecorder {
/**
* Records Claude Desktop config contents at static init.
*/
public void claudeConfigContents(Map<String, LocalLaunchParams> contents);
/**
* Creates supplier for MCP client bean.
*/
public Supplier<McpClient> mcpClientSupplier(
String clientName,
McpTransportType transportType,
ShutdownContextBuildItem shutdown,
Vertx vertx,
boolean metricsEnabled
);
/**
* Creates function for ToolProvider bean.
*/
public Function<SyntheticCreationalContext<ToolProvider>, ToolProvider>
toolProviderFunction(Set<String> mcpClientNames);
/**
* Creates supplier for registry client bean.
*/
public Supplier<McpRegistryClient> mcpRegistryClientSupplier(String key);
}@ApplicationScoped
public class StrategyAuthProvider implements McpClientAuthProvider {
@Inject
AuthStrategyResolver resolver;
@Override
public String getAuthorization(Input input) {
AuthStrategy strategy = resolver.resolveStrategy(input.uri());
return strategy.getAuthorizationHeader();
}
}@ApplicationScoped
public class McpMonitor {
@Inject
AlertService alertService;
@Inject
MetricsCollector metrics;
public void onError(@Observes McpLogMessage log) {
if (log.level() == McpLogLevel.ERROR) {
alertService.sendAlert("MCP Error", log.data().toString());
metrics.incrementErrorCount(log.logger());
}
}
}import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;
@Readiness
@ApplicationScoped
public class CustomMcpHealthCheck implements HealthCheck {
@Inject
@McpClientName("critical")
McpClient criticalClient;
@Override
public HealthCheckResponse call() {
try {
criticalClient.checkHealth();
return HealthCheckResponse.up("critical-mcp");
} catch (Exception e) {
return HealthCheckResponse.down("critical-mcp");
}
}
}@RegisterAiService
public interface ResourceAssistant {
@SystemMessage("""
You have access to resources from MCP servers.
Use list_resources to discover available resources.
Use get_resource to retrieve resource content.
""")
@McpToolBox
String chat(@UserMessage String message);
}Automatic when quarkus-opentelemetry is present:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>Traces include:
# Global logging
quarkus.langchain4j.log-requests=true
quarkus.langchain4j.log-responses=true
# Per-client logging
quarkus.langchain4j.mcp.github.log-requests=true
quarkus.langchain4j.mcp.github.log-responses=true
# MCP-specific debug logging
quarkus.log.category."dev.langchain4j.mcp".level=DEBUG
quarkus.log.category."io.quarkiverse.langchain4j.mcp".level=DEBUGAutomatic configuration for native images:
No additional configuration required for native compilation.
Install with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-mcp-deployment@1.7.0