back to all blogsSee all blog posts

Model Context Protocol Server 1.0 updates and more in 26.0.0.2-beta

image of author
Navaneeth S Nair on Feb 10, 2026
Post available in languages:

This beta release enhances the mcpServer-1.0 feature with role-based authorization, the new _meta field, and key bug fixes. It also adds documentation and tests for displayCustomizedExceptionText, allowing users to replace default Liberty error messages with clearer, custom text.

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

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 role-based authorization, the _meta field, and a few bug fixes.

Prerequisites

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

Implement role-based authorization for MCP tools via annotations

The following new annotations allow you to restrict tool usage through authorization policies:

  1. @DenyAll - Resource is denied. This annotation is the strictest policy.

  2. @RolesAllowed - Resource is allowed for pre-authorised users in a role (the same as a group in Liberty).

  3. @PermitAll - Resource is allowed for anyone (even unauthenticated users).

These security annotations are declared on two levels:

  1. Class level - Every method in the class inherits this class level annotation.

  2. Method level - Overrides any class level annotations.

For complete reference documentation, see the Jakarta Security Annotations specification.

The mcpServer-1.0 feature does not currently implement all of the authorization-server-metadata-discovery requirements specified in the MCP specification. However, you can use any of Liberty’s authentication and authorization mechanisms to authenticate users and assign roles. The mcpServer-1.0 feature can then ensure that users can access only the tools that are permitted for their assigned roles.

Basic authorization example

The following example shows how to configure MCP tools to require authentication and to ensure that users have the appropriate roles to access specific tools. Users authenticate using Basic Authentication, and the list of users and their roles are configured in a basicRegistry in the server.xml file.

Consider an online bookshop that has different users each with different authorization levels:

  • Users - can buy books and track prices

  • Moderators - can manage the books available

  • Admins - can manually ban users

Clients who connect without authenticating can only display the available books or register as a new user.

// common imports for all classes below

import io.openliberty.mcp.annotations.Tool;
import io.openliberty.mcp.annotations.ToolArg;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;

Security Annotation example classes

@ApplicationScoped
public class BookShopTools {

    // Admin Tool
    @RolesAllowed("Admins")
    @Tool(name = "banUser")
    public String banUser(@ToolArg(name = "userName", description = "Name of user to be banned") String userName)  {...}

    // Moderator Tools
    @RolesAllowed("Moderators")
    @Tool(name = "addBook")
    public String addBook(@ToolArg(name = "bookCode", description = "Code to uniquely identify the book") String bookCode)  {...}

    @RolesAllowed("Moderators")
    @Tool(name = "deleteBook")
    public String deleteBook(@ToolArg(name = "bookCode", description = "Code to uniquely identify the book") String bookCode)  {...}

    // User Tools
    @RolesAllowed("Users")
    @Tool(name = "buyBook")
    public String buyBook(@ToolArg(name = "bookCode", description = "Code to uniquely identify the book") String bookCode) {...}

    @RolesAllowed("Users")
    @Tool(name = "trackBookPrice")
    public String trackBookPrice(@ToolArg(name = "bookCode", description = "Code to uniquely identify the book") String bookCode)  {...}
}
@ApplicationScoped
@PermitAll // PermitAll Class level Annotation applies to all class tools, unless overwritten on method level
public class PublicTools {

    @Tool(name = "displayBooks")
    public String displayBooks(@ToolArg(name = "bookCode", description = "Code to uniquely identify the book") String book)  {...}

    @Tool(name = "registerNewUser")
    public String registerNewUser(@ToolArg(name = "userName", description = "The name for the user to be registered") String input,
                                  @ToolArg(name = "password", description = "The password for the user to be registered") String input)  {...}
}

Steps required

  • Create an application with @ApplicationScoped and expose the tools with the required annotations.

  • Create a server.xml file with the users.

  • Ensure that the groups map to the roles created in the tool.

  • Add users to the groups in the server.xml file.

The following additional configuration is required in the server.xml (See the comments in the XML example for details.):

  • The appSecurity feature

  • The transportSecurity feature

  • Define a user registry

  • Enable TLS (Transport Layer Security) for security

  • Require TLS (Transport Layer Security) to ensure that credentials aren’t sent in cleartext

<server description="Liberty server config for Security Annotations">

  <featureManager>
    <feature>servlet-6.0</feature>
    <feature>cdi-4.0</feature>
    <feature>mcpServer-1.0</feature>
    <!-- The following two features are required for application security to work -->
    <feature>appSecurity-5.0</feature>
    <feature>transportSecurity-1.0</feature>
  </featureManager>

  <include location="../fatTestPorts.xml" />

  <!-- Required for passwords to be encrypted using HTTPS -->
  <keyStore id="defaultKeyStore" password="Liberty" />

  <!-- Required to define https port for TLS (Transport Layer Security) -->
  <httpEndpoint id="defaultHttpEndpoint"
                httpPort="-1"
                httpsPort="9443">
  </httpEndpoint>

  <basicRegistry id="basic" realm="BasicRealm">

       <!-- Basic user authentication setup -->

       <!-- Users -->
       <user name="Sam"   password="{xor}KzosKy8+LDwoMC07aw=="/>
       <user name="Nile"  password="{xor}KzosKy8+LRwoMC07aw=="/>
       <user name="Cloe"  password="{xor}KzosKy8+LEwoMC07aw=="/>
       <user name="Vick"  password="{xor}KzosKy8+LFwoMC07aw=="/>

       <!-- Moderators -->
       <user name="Sally" password="{xor}KzosKy8+LCwoMC07bQ=="/>
       <user name="Harry" password="{xor}KzosKy8+LCwoMC07bQ=="/>

       <!-- Admins -->
       <user name="Bob"   password="{xor}KzosKy8+LCwoMC07bA=="/>
       <user name="Noah"  password="{xor}KzosKy8+LCwoMC07aw=="/>


       <!-- Mapping users to groups for the RolesAllowed annotation (a group is equivalent to a role) -->

       <group name="Users">
           <member name="Nile"/>
           <member name="Sam"/>
           <member name="Cloe"/>
           <member name="Vick"/>
       </group>

       <group name="Moderators">
           <member name="Sally"/>
           <member name="Harry"/>
       </group>

       <group name="Admins">
           <member name="Bob"/>
           <member name="Noah"/>
       </group>

  </basicRegistry>
</server>

It is also possible to add multiple roles to tools: @RolesAllowed("Admins, Moderators"). Doing so grants access to users who have either role.

If a role that is used in @RolesAllowed annotation is not mentioned in the server.xml file, that just means that no user has that role. If it is the only role that is allowed to call the tool, then no users can call the tool.

MCP: Tools can access metadata that the client sends in the _meta field.

The MCP specification reserves the _meta property or parameter so clients and servers can attach additional metadata to their interactions. Key name format: valid _meta key names have two segments:

  • An optional prefix (e.g. "example.com/") and

  • A name ("myKey")

The whole _meta key is seen as "example.com/myKey"

Adding a Tool Meta parameter

The following code shows an example metaKey used to retrieve the value of the metadata. In the example, the metakey is "example.org/location"

    @Tool(name = "noArgsRequest",
          title = "call tool without providing arguments in params",
          description = "return string made from args and metadata",
          structuredContent = false)
    public String noArgsRequest(Meta meta) {
        Jsonb jsonb = JsonbBuilder.create();
        String location = (String) meta.getValue(MetaKey.from("example.org/location"));
        BigDecimal timestamp = (BigDecimal) meta.getValue(MetaKey.from("timestamp"));
        String result = "You have called this tool from " + location + " at timestamp " + timestamp.toString();
        return result;
    }

Returning metadata in a tool response

A method can return any metadata by using the ToolResponse object:

   @Tool(name = "addPersonToListToolResponse",
         title = "adds person to people list",
         description = "adds person to people list",
         structuredContent = true)
    public @Schema(value = "{ ... }",description = "Returns list of person object")
    ToolResponse addPersonToListToolResponse(@ToolArg(name = "employeeList", description = "List of people") List<Person> employeeList,
                                             @ToolArg(name = "person", description = "Person object") Optional<Person> person) {
        Person personInstance = person.get();
        employeeList.add(personInstance);
        Jsonb jsonb = JsonbBuilder.create();
        Map<MetaKey, Object> _meta = new HashMap<>();
        _meta.put(MetaKey.from("timestamp"), 1762860699);
        _meta.put(MetaKey.from("example.org/location"), "Hursley");
        _meta.put(MetaKey.from("example.org/person"), personInstance);
        return new ToolResponse(false, List.of(new TextContent(jsonb.toJson(employeeList))), employeeList, _meta);
    }

The _meta field enables protocol extensions without breaking compatibility. The following examples illustrate common use cases:

1) Cost Tracking Extension

Track API costs for expensive operations.

How it works:

  • Server reports the cost of executing a tool in the result metadata.

  • Client tracks total spending across operations.

2) Rate Limiting Extension

Prevent resource exhaustion and ensure fair access.

How it works:

  • Metadata shows the remaining quota before each call for time window

  • If limit is exceeded, the error includes retry time in _meta

3) Caching Hints Extension

Optimize performance through intelligent caching.

How it works:

  • Server includes caching information in the tool call result

  • Client can implement client-side caching

4) Bringing it all together

All three of the preceding extensions might be built into your MCP server as extensions:

{
  "name": "ToolForMetaDataExample",
  "description": "ToolDescription",
  "inputSchema": { ... },
  "_meta": {
    // Rate Limiting Extension - com.example.rateLimit namespace
    "com.example.rateLimit/requests": 100,
    "com.example.rateLimit/period": "hour",
    "com.example.rateLimit/remaining": 47,
    // Cost Tracking Extension - com.example.cost namespace
    "com.example.cost/estimatedCost": 0.02,
    "com.example.cost/currency": "USD",
    // Caching Hints Extension - com.example.cache namespace
    "com.example.cache/cacheable": true,
    "com.example.cache/cacheKey": "LondonWeather",
    "com.example.cache/ttl": 300
  }
}

Notable bug fixes in MCP 1.0

1) MCP Server feature used ISO-8859-1 and did not handle non-Latin characters

  • MCP requests and responses are now encoded correctly using UTF-8.

2) Fix asynchronous MCP Tool error handling

  • Previously, when an asynchronous tool returned a failed completion stage, the system did not verify whether the exception was a business or technical error. This resulted in all failures being reported as internal errors.

  • The system now differentiates between exception types. This categorization ensures that business errors are returned to the client, while technical errors are reported in the Liberty server log.

displayCustomizedExceptionText property

This beta release adds documentation and tests for the displayCustomizedExceptionText configuration, which allows users to override Liberty’s default error messages (such as SRVE0218E: Forbidden and SRVE0232E: An exception occurred) with clearer, user-defined messages.

The feature is enabled through simple server.xml file configuration, where custom messages can be mapped to specific HTTP status codes (403 and 500).

Testing ensures that these custom messages correctly replace Liberty’s defaults across all supported platforms, confirming that the configured text is returned consistently in all scenarios.

<webContainer displaycustomizedexceptiontext="Custom error message"/>

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.12.0</version>
    <configuration>
        <runtimeArtifact>
          <groupId>io.openliberty.beta</groupId>
          <artifactId>openliberty-runtime</artifactId>
          <version>26.0.0.2-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.10.0'
    }
}
apply plugin: 'liberty'
dependencies {
    libertyRuntime group: 'io.openliberty.beta', name: 'openliberty-runtime', version: '[26.0.0.2-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.