MCP Server updates in mcpServer-1.0 for Open Liberty 26.0.0.8-beta
This beta release adds several new capabilities to mcpServer-1.0, including operational metrics for monitoring throughput and session lifecycle, configurable server identity and metadata, configurable session and async tool timeouts, support for custom Java types as default values in tool arguments, and full support for multi-WAR EAR deployments.
The Open Liberty 26.0.0.8-beta includes the following beta features (along with all GA features):
See also previous Open Liberty beta blog posts.
MCP Server metrics
The Model Context Protocol (MCP) is an open standard that enables AI applications to access real-time data and business logic from external sources. The Liberty MCP Server feature mcpServer-1.0 allows developers to expose their application’s tools and data for use in agentic AI workflows.
The MCP Server now exports operational metrics, making it possible to monitor operation throughput, duration, and session lifecycle using Liberty’s existing observability stack. Metrics are available via JMX (monitor-1.0) and as OpenTelemetry histograms via mpTelemetry-2.0 or mpTelemetry-2.1.
Add monitor-1.0 alongside mcpServer-1.0 in your server.xml file. Optionally add mpTelemetry-2.0 (or 2.1) to export metrics to an OpenTelemetry collector:
<featureManager>
<feature>mcpServer-1.0</feature>
<feature>monitor-1.0</feature>
<feature>mpTelemetry-2.0</feature>
</featureManager>
Two metrics are exported, following the OpenTelemetry semantic conventions for MCP:
mcp.server.operation.duration (Histogram, unit: seconds)
Tracks the duration of each MCP operation. A new data point is recorded per unique combination of the following attributes:
| Attribute | Required | Description |
|---|---|---|
|
Yes |
The MCP JSON-RPC method name, for example |
|
Conditional |
Set when an error occurs |
|
Conditional |
The name of the tool; set for |
|
Optional |
The prompt name, if applicable |
|
Optional |
The GenAI operation name |
|
Optional |
The JSON-RPC response status code |
|
Optional |
The JSON-RPC protocol version |
|
Optional |
The MCP protocol version, for example |
|
Optional |
Network protocol name, for example |
|
Optional |
Network protocol version, for example |
|
Optional |
Network transport, for example |
|
Optional |
MCP resource URI, if applicable |
mcp.server.session.duration (Histogram, unit: seconds)
Tracks the duration of each MCP session (stateful mode only). A new data point is recorded per unique combination of the following attributes:
| Attribute | Required | Description |
|---|---|---|
|
Conditional |
Set when an error occurs during the session |
|
Optional |
The JSON-RPC protocol version |
|
Optional |
The MCP protocol version, for example |
|
Optional |
Network protocol name, for example |
|
Optional |
Network protocol version, for example |
|
Optional |
Network transport, for example |
Both metrics are also accessible via JMX through McpOperationStatsMXBean (exposes Count, CountDetails, Duration, DurationDetails) and McpSessionStatsMXBean (exposes Count, CountDetails, Duration, DurationDetails). For more information, see the MicroProfile Telemetry documentation.
Configure MCP Server description and metadata
The MCP Server now sends a meaningful serverInfo block to clients during initialization, which MCP clients may display in their UI. Previously, this was hard-coded to a placeholder value. You can now configure the name, title, version, and description using the <info> subelement within <mcpServer>:
<application location="my-app.war">
<mcpServer>
<info name="my-mcp-server"
title="My MCP Server"
version="2.5.0"
description="Provides tools for querying customer data." />
</mcpServer>
</application>
If the <info> subelement is not configured, the server defaults to name="mcp-server" and version="1.0.0".
Configure MCP session timeout and async tool timeout
Two previously hard-coded timeouts are now configurable via <mcpServer> attributes:
-
sessionTimeout— how long an inactive session is kept alive (default:600s) -
asyncTimeout— maximum execution time for an async tool call (default:30s)
<application location="my-app.war">
<mcpServer sessionTimeout="20m" asyncTimeout="60s"/>
</application>
Both attributes accept Liberty’s standard duration format (for example, 30s, 10m, 1h).
Custom types for default values in MCP tool arguments
The @ToolArg(defaultValue = "…") annotation previously only worked with String, primitives, and enums. You can now handle any custom Java type by implementing DefaultValueConverter<T> as a CDI bean. Liberty automatically discovers and calls the converter when a default value string needs to be converted to your custom type.
import io.openliberty.mcp.annotations.DefaultValueConverter;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class DateRangeConverter implements DefaultValueConverter<DateRange> {
@Override
public DateRange convert(String defaultValue) {
String[] parts = defaultValue.split("/");
return new DateRange(LocalDate.parse(parts[0]), LocalDate.parse(parts[1]));
}
}
@Tool(description = "Generate a report for a date range")
public String generateReport(
@ToolArg(description = "Date range (YYYY-MM-DD/YYYY-MM-DD)",
defaultValue = "2024-01-01/2024-12-31")
DateRange range) {
return "Report from " + range.start + " to " + range.end;
}
If multiple converters exist for the same type, the one with the highest @jakarta.annotation.Priority value is called. Converters without @Priority default to priority 0.
Multi-module MCP applications
Liberty now fully supports deploying MCP applications as an EAR with multiple WAR modules. Each WAR module that contains MCP tools gets its own independent MCP server endpoint:
-
Tools declared in one WAR module are only available at that module’s endpoint — they are not visible to other modules.
-
ContentEncoderandToolResponseEncoderbeans from one module are not invoked for requests to another module’s endpoint. -
Sessions and request tracking are isolated per module.
Configure separate endpoints for each WAR module using the moduleName attribute (the WAR file name within the EAR):
<application location="my-app.ear">
<mcpServer path="/custom-mcp" moduleName="war1"/>
<mcpServer path="/custom-mcp-for-war-2" moduleName="war2"/>
</application>
This creates two independent MCP endpoints:
-
http://localhost:9080/war1/custom-mcp -
http://localhost:9080/war2/custom-mcp-for-war-2
Bug fixes
Cancelling a tool call no longer logs an error
Previously, when a client cancelled a running tool call, the server incorrectly logged an ERROR for the expected OperationCancellationException. The client still receives the correct cancellation response, but the error is no longer logged.
@Priority on encoder CDI beans now resolved correctly
When sorting ContentEncoder or ToolResponseEncoder beans by @Priority, the priority was sometimes read from a CDI proxy rather than the actual bean class, causing non-deterministic ordering. The priority is now always resolved from the real bean class at CDI extension time.
Try it now
To try out these features, update your build tools to pull the Open Liberty All Beta Features package instead of the main release. The beta works with Java SE 21, Java SE 17, Java SE 11, and Java SE 8.
If you’re using Maven, you can install the All Beta Features package using:
<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<runtimeArtifact>
<groupId>io.openliberty.beta</groupId>
<artifactId>openliberty-runtime</artifactId>
<version>26.0.0.8-beta</version>
<type>zip</type>
</runtimeArtifact>
</configuration>
</plugin>
You must also add dependencies to your pom.xml file for the beta version of the APIs that are associated with the beta features that you want to try. For example, the following block adds dependencies for two example beta APIs:
<dependency>
<groupId>org.example.spec</groupId>
<artifactId>exampleApi</artifactId>
<version>7.0</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>example.platform</groupId>
<artifactId>example.example-api</artifactId>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
Or for Gradle:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'io.openliberty.tools:liberty-gradle-plugin:4.0.0'
}
}
apply plugin: 'liberty'
dependencies {
libertyRuntime group: 'io.openliberty.beta', name: 'openliberty-runtime', version: '[26.0.0.8-beta,)'
}
Or if you’re using container images:
FROM icr.io/appcafe/open-liberty:beta
Or take a look at our Downloads page.
If you’re using IntelliJ IDEA, Visual Studio Code or Eclipse IDE, you can also take advantage of our open source Liberty developer tools to enable effective development, testing, debugging and application management all from within your IDE.
For more information on using a beta release, refer to the Installing Open Liberty beta releases documentation.
We welcome your feedback
Let us know what you think on our mailing list. If you hit a problem, post a question on StackOverflow. If you hit a bug, please raise an issue.