back to all blogsSee all blog posts

Log Throttling and Model Context Protocol Server 1.0 updates in 26.0.0.1-beta

image of author
Navaneeth S Nair on Jan 13, 2026
Post available in languages:

In this beta release, Open Liberty introduces log throttling to prevent excessive log output by limiting repeated high-volume messages within a short time span. It also includes updates to the mcpServer-1.0 feature.

The Open Liberty 26.0.0.1-beta includes the following beta features (along with all GA features):

Log Throttling

Open Liberty Logging is introducing log throttling. Developers previously had no way to throttle or suppress high-volume messages. This new feature helps to prevent excessive log output when the same log events occur repeatedly within a short span of time.

Throttling is enabled by default. While enabled, Liberty tracks each messageID using a sliding window. By default, any messageID that is repeated more than 1,000 times within a five-minute interval is suppressed. A throttle warning is logged when throttling begins.

Log throttling can be configured to throttle messages based on the message or messageID by using the throttleType logging attribute. The number of messages allowed before throttling can be configured by using the throttleMaxMessagesPerWindow attribute.

Log Throttling is disabled by setting throttleMaxMessagesPerWindow to 0.

Currently, these attributes can be configured as follows:

  • In server.xml:

    <logging throttleMaxMessagesPerWindow="5000" throttleType="message" />
  • In bootstrap.properties:

    com.ibm.ws.logging.throttle.max.messages.per.window=5000
    com.ibm.ws.logging.throttle.type=messageID

Difference between message and messageID:

Consider the following example of a log event: TEST0111I: Hello World!.

When throttleType is set to messageID, throttling is exclusively applied based on the number of occurrences of the messageID. In this example, TEST0111I is used for throttling.

When throttleType is set to message, throttling is applied to the entire message. Any variation in the message content is tracked separately. In this example, TEST0111I: Hello World! is used for throttling.

Model Context Protocol Server 1.0 updates

The Model Context Protocol (MCP) is an open standard that enables AI applications to access real-time information from external sources. The Liberty MCP Server feature mcpServer-1.0 allows developers to expose the business logic of their applications, allowing it to be integrated into agentic AI workflows.

This beta release of Open Liberty includes important updates to the mcpServer-1.0 feature, including asynchronous tool support, support for stateless mode, and a few bug fixes.

Prerequisites

To use the mcpServer-1.0 feature, Java 17 must be installed on the system.

Update on the MCP JAR location

The MCP server JAR is located in a new directory, /dev/ibm/api, replacing the location used in the initial beta. For complete instructions on building an MCP server in Liberty, see the set up guide.

Asynchronous Tool Execution Support

MCP tools can now run asynchronously by returning a CompletionStage. If a tool needs to wait for something, such as for data to be returned from a remote system, running asynchronously allows it to wait without occupying a thread.

The tool method can return a CompletionStage quickly, but the response is not returned to the client until the CompletionStage completes and provides the response data.

The following example illustrates how this approach would look for a tool that calls an asynchronous Jakarta REST client:

private Client client = ClientBuilder.newClient();

@Tool
public CompletionStage<String> toolName(@ToolArg(name = "value") int value) {
    return client.target("https://example.com/api")
            .queryParam("value", value)
            .request()
            .rx()
            .get(String.class);
}

Stateless Mode

Parts of the Model Context Protocol require the server to store client information and reuse it for subsequent requests from the same client. This stateful behavior creates challenges when clustering MCP servers. When requests from the same client are handled by different servers, the stored information must be shared across all servers.

One way to address this problem is through session affinity. The ingress or load balancer in front of the MCP servers helps ensure that all requests from a single session are served by the same server. However, this approach requires special support for how MCP identifies sessions, and such support for MCP session affinity is not widely available yet.

This beta introduces another option, called stateless mode. This option disables MCP features that require state to be stored, allowing MCP servers to be clustered easily without any special logic for session affinity.

Currently, enabling stateless mode disables the client’s ability to cancel tool calls. As we add new features, we document which features are disabled or have limitations in stateless mode.

To enable stateless mode in your application, you need to add the following configuration in your server.xml file:

<mcpServer stateless="true"/>

One of the main differences in stateless mode is that the server does not assign a session ID to clients. As a result, subsequent client requests do not include the Mcp-Session-Id header.

Working with structured content in MCP Tools

The MCP specification supports returning structured content from tools.

Returning structured content

It is now possible to return structured JSON from a tool method. To enable this capability, set structuredContent = true on the @Tool annotation and return a POJO from your tool method:

public record Street(String streetName, String roadType) {}

@Tool(name = "getStreet",
      description = "Look up a street by name"
      structuredContent = true)
public Street getStreet(@ToolArg(name = "street", description = "the street name") String streetName){
    //Get street information
    return new Street(streetName, "One way Street");
}

Open Liberty uses Jakarta JSON Binding (JSON-B) to encode the returned Street object into JSON and this is returned to the MCP client in the structuredContent field of the response. For compatibility with earlier versions and older clients that do not support structuredContent, the encoded JSON is also returned as TextContent. In addition, Open Liberty generates a schema for the method’s return type to indicate the expected data structure to the MCP client.

Customizing Serialization

Since Open Liberty uses JSON-B to encode the returned object, you can use the standard JSON-B annotations to customize how your objects are serialized.

Customizing schemas with @Schema

MCP protocol requires tools to declare schemas for their inputs and outputs. These schemas help clients to understand what to send and what to expect in return. Open Liberty generates these schemas automatically by inspecting your Java types - although you can customize them by using the @Schema annotation. The simplest use of @Schema is to add descriptions to your types. These descriptions help clients understand the purpose of each type or field.

@Schema(description = "Represents Street information")
public record Street(
    @Schema(description = "Name of the street")
    String streetName,

    @Schema(description = "type of the road and its rules")
    String roadType
) {}

@Tool(name = "getStreet",
      description = "Look up a street by name"
      structuredContent = true)
public Street getStreet(@ToolArg(name = "street", description = "the street name") String streetName) {
    // Get street information
    return new Street(streetName, "One way Street");
}

If the default schemas do not meet your needs, you can provide a complete schema override. You might need to do an override if you use JSON-B annotations to customize the serialization of the object causing the generated schema to no longer match. A schema override is performed by providing a value to the @Schema annotation.

@Schema("""
{
    "type": "object",
    "required": ["streetName"],
    "properties": {
        "streetName": {
            "type": "string"
        },
        "roadType": {
            "type": "string",
            "enum": [
                "One Way Street",
                "Dual Carriageway",
                "Motorway"
            ]
        }
    }
}
""")
public record Street(String streetName, String roadType) {}

You usually also need to provide an output schema for a tool method that returns a ToolResponse. In this case, Open Liberty does not know what type your tool is going to return, so it cannot generate a detailed schema for it.

@Schema("""
{
    "type": "object",
    "required": ["streetName"],
    "properties": {
        "streetName": {
            "type": "string"
        },
        "roadType": {
            "type": "string",
            "enum": [
                "One Way Street",
                "Dual Carriageway",
                "Motorway"
            ]
        }
    }
}
""")
@Tool(structuredContent = true)
public ToolResponse toolName() {
    Street street = yourService.getStreetInfo();
    return ToolResponse.structuredSuccess(street)
}

Use @Schema(description = "…​") when you want to add helpful context for clients, and @Schema(value = "{…​}") when you have more complex types and the auto-generated schema does not reflect your actual JSON output.

Allowing annotations in MCP Content objects

As specified in the MCP specification, MCP annotations can be added to Content objects returned from tool methods. These annotations are not Java annotations; instead, they are additional fields that provide hints to clients about how to use and display the content.

The annotation includes the following fields:

  • audience field - an enum of type Role that indicates the intended audience for the content.

  • lastModified field - a string containing an ISO 8601-formatted timestamp that indicates when the content object was last modified.

  • priority field - a double value between 0.0 to 1.0 that indicates the importance of the content object.

The following example shows how annotation fields can be added to tools.

@Tool
public TextContent toolName(@ToolArg(name = "input") String input) {
    Content.Annotations annotations = new Content.Annotations(Role.USER,
                                                              Instant.now().toString(),
                                                              1.0);
    return new TextContent("My Generated Text", null, annotations);
}

Result:

{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "content": [
            {
                "annotations": {
                    "audience": "user",
                    "lastModified": "2025-12-11T12:31:04",
                    "priority": 1.0
                },
                "text": "My Generated Text",
                "type": "text"
            }
        ],
        "isError": false
    }
}

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. To enable the MCP server feature, follow the instructions from MCP standalone blog. The beta works with Java SE 25, 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.11.5</version>
    <configuration>
        <runtimeArtifact>
          <groupId>io.openliberty.beta</groupId>
          <artifactId>openliberty-runtime</artifactId>
          <version>26.0.0.1-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:3.9.6'
    }
}
apply plugin: 'liberty'
dependencies {
    libertyRuntime group: 'io.openliberty.beta', name: 'openliberty-runtime', version: '[26.0.0.1-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.