implementation("io.micronaut.mcp:micronaut-mcp-server-java-sdk")
Table of Contents
Micronaut MCP
Integration with MCP (Model Context Protocol).
Version: 0.0.12
1 Introduction
Integration with MCP (Model Context Protocol).
2 Release History
For this project, you can find a list of releases (with release notes) here: https://github.com/micronaut-projects/micronaut-mcp/releases
3 MCP Server
3.1 MCP Server Java SDK
Micronaut MCP Server support is based on the MCP Java SDK.
To use it, add the following dependency:
<dependency>
<groupId>io.micronaut.mcp</groupId>
<artifactId>micronaut-mcp-server-java-sdk</artifactId>
</dependency>
3.2 Server Information
Via configuration, you should define your mcp server name and version:
Property | Type | Description |
---|---|---|
|
java.lang.String |
|
|
java.lang.String |
3.3 Configuration
To create a server, you will need to define the property micronaut.mcp.server.transport
with one of the values STDIO
, HTTP
.
If you define the property micronaut.mcp.server.reactive
with the value true
, you can define primitives (tools, prompts or resources) with call handlers wrapped in a Project Reactor Mono
.
Property | Type | Description |
---|---|---|
|
boolean |
|
|
java.lang.String |
The MCP Server endpoint. It applies to MCP Servers using HTTP transport. It defaults to |
|
Set the MCP Transport. It defaults to HTTP. |
|
|
boolean |
Whether you want to define MCP Primitive handlers using reactive code. Default value {@value #DEFAULT_REACTIVE} |
3.3.1 Transport
The protocol currently defines two standard transport mechanisms for client-server communication: stdio (communication over standard in and standard out) and Streamable HTTP
For stdio transport, define the property micronaut.mcp.server.transport
as STDIO
.
For HTTP transport, define the property micronaut.mcp.server.transport
as HTTP
.
Server to Client communication via an SSE stream is not yet supported. The server responses are currently only of Content-Type : application/json .
|
3.3.1.1 Stdio Sample Configuration
For stdio transport, your configuration may look like
micronaut.mcp.server.transport=STDIO
micronaut.mcp.server.info.name=pgn-resources
micronaut.mcp.server.info.version=1.0.0
micronaut:
mcp:
server:
transport: STDIO
info:
name: 'pgn-resources'
version: '1.0.0'
[micronaut]
[micronaut.mcp]
[micronaut.mcp.server]
transport="STDIO"
[micronaut.mcp.server.info]
name="pgn-resources"
version="1.0.0"
micronaut {
mcp {
server {
transport = "STDIO"
info {
name = "pgn-resources"
version = "1.0.0"
}
}
}
}
{
micronaut {
mcp {
server {
transport = "STDIO"
info {
name = "pgn-resources"
version = "1.0.0"
}
}
}
}
}
{
"micronaut": {
"mcp": {
"server": {
"transport": "STDIO",
"info": {
"name": "pgn-resources",
"version": "1.0.0"
}
}
}
}
}
3.3.1.2 HTTP Sample Configuration
For HTTP transport, your configuration may look like
micronaut.mcp.server.transport=HTTP
micronaut.mcp.server.info.name=pgn-resources
micronaut.mcp.server.info.version=1.0.0
micronaut:
mcp:
server:
transport: HTTP
info:
name: 'pgn-resources'
version: '1.0.0'
[micronaut]
[micronaut.mcp]
[micronaut.mcp.server]
transport="HTTP"
[micronaut.mcp.server.info]
name="pgn-resources"
version="1.0.0"
micronaut {
mcp {
server {
transport = "HTTP"
info {
name = "pgn-resources"
version = "1.0.0"
}
}
}
}
{
micronaut {
mcp {
server {
transport = "HTTP"
info {
name = "pgn-resources"
version = "1.0.0"
}
}
}
}
}
{
"micronaut": {
"mcp": {
"server": {
"transport": "HTTP",
"info": {
"name": "pgn-resources",
"version": "1.0.0"
}
}
}
}
}
3.3.2 Server Instance
Based on the transport and reactive setting, an MCP Java SDK Server class instance is created with @Context
scope.
micronaut.mcp.server.transport |
micronaut.mcp.server.reactive |
Server | Server Specification |
---|---|---|---|
STDIO |
false |
|
|
STDIO |
true |
|
|
HTTP |
false |
|
|
HTTP |
true |
|
|
McpServer.SyncSpecification
, McpServer.AsyncSpecification
, McpServer.StatelessAsyncSpecification
, McpServer.StatelessSyncSpecification
are builder classes. Thus, you can create beans of type BeanCreatedEventListener
to customize the server specification and creation further.
3.3.3 Primitive types per Transport
To create primitives (tools, prompts or resources), you will create beans of a particular type (typically in a bean factory). The bean type depends on the server type selected:
micronaut.mcp.server.transport |
micronaut.mcp.server.reactive |
Prompt Bean Type | Tool Bean Type | Resource Bean Type | Resource Template Bean Type |
---|---|---|---|---|---|
STDIO |
false |
|
|
|
|
STDIO |
true |
|
|
|
|
HTTP |
false |
|
|
|
|
HTTP |
true |
|
|
|
|
3.3.4 Server Capabilities
Based on the primitive beans you defined, Micronaut instantiates a bean of type McpSchema.ServerCapabilities
.
Moreover, you can create a BeanCreatedEventListener
for McpSchema.ServerCapabilities.Builder
to further customize the definition of the capabilities.
3.4 STDIO Transport
If you use Standard Input/Output (stdio) protocol, your server MUST NOT write anything to its stdout .
|
Disable the Micronaut banner and configure Logback not to log to stdout
.
For example, you can configure Logback to log to a file or to stderr
(see <target>System.err</target>
in the configuration file below):
<configuration>
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDERR" />
</root>
</configuration>
3.5 Testing your MCP Server
To test the MCP Server, implementation you can use the MCP Inspector
The MCP inspector is a developer tool for testing and debugging MCP servers.
3.6 MCP Transport Context
Micronaut MCP ships MicronautMcpTransportContext, an extension to io.modelcontextprotocol.common.McpTransportContext
, which allows you to access concepts such as the authenticated user, locale, host, etc.
3.7 Primitives
3.7.1 Tools
Tools: Executable functions that allow models to perform actions or retrieve information
3.7.1.1 Tools Configuration
Property | Type | Description |
---|---|---|
|
boolean |
whether the server will emit notifications when the list of available tools changes. Default value |
3.7.1.2 Search Tool
If you need to expose a Search Tool in your MCP Server, you can do it easily defining a bean of type SearchTool
@Singleton
class MicronautModulesSearch implements SearchTool {
@Override
public SearchResponse search(SearchRequest request, McpTransportContext transportContext) {
return new SearchResponse(List.of(SearchResult.builder()
.id("micronaut-security")
.title("Micronaut Security")
.url("https://micronaut-projects.github.io/micronaut-security/latest/guide")
.build()));
}
}
3.7.1.3 Fetch Tool
If you need to expose a Fetch Tool in your MCP Server, you can do it easily defining a bean of type FetchTool
@Singleton
class MicronautModulesFetch implements FetchTool {
@Override
public Optional<FetchResponse> fetch(FetchRequest request, McpTransportContext transportContext) {
return Optional.of(FetchResponse.builder()
.id("micronaut-security")
.title("Micronaut Security")
.url("https://micronaut-projects.github.io/micronaut-security/latest/guide")
.text("Built-in security features. Authentication providers and strategies, Token Propagation.")
.build());
}
}
3.7.1.4 Tools with Annotations
The preferred way to declare a tool is using a method annotated with Tool in a @Singleton
bean.
package example.micronaut;
import io.micronaut.context.annotation.Requires;
import io.micronaut.mcp.annotations.Tool;
import io.micronaut.mcp.server.context.MicronautMcpTransportContext;
import jakarta.inject.Singleton;
@Singleton
class Tools {
@Tool(description = "Evaluate a chess position using a FEN string.")
String fenEvaluation(String fen,
MicronautMcpTransportContext ctx) {
if (fen.equals("r1bqk2r/ppp2ppp/2n5/1BbpP3/3Nn3/8/PPP2PPP/RNBQK2R w KQkq - 1 8")) {
return "+0.12";
}
return "+0.0";
}
}
By default, the method name is used as the tool name.
You can override this by setting the name
attribute of the @Tool
annotation.
3.7.1.4.1 Tool Hints
You can use annotation hints to inform the client that a tool is read-only. ChatGPT, for example, informs users whether a tool performs write operations.
@Singleton
class HelloWorldTool {
@Tool(title = "Hello World",
annotations = @Tool.ToolAnnotations(readOnlyHint = true,
title = "Hello World",
destructiveHint = false,
idempotentHint = true,
openWorldHint = false,
returnDirect = true))
String helloWorld() {
return "Hello, World!";
}
}
3.7.1.4.2 Tool Annotation Method Parameters
The method parameter names will be automatically used as the tool argument names, unless you use the ToolArg annotation to differentiate the tool argument name from the method parameter name.
package example.micronaut;
import io.micronaut.context.annotation.Requires;
import io.micronaut.mcp.annotations.Tool;
import io.micronaut.mcp.annotations.ToolArg;
import io.modelcontextprotocol.common.McpTransportContext;
import jakarta.inject.Singleton;
@Singleton
class Tools {
@Tool(name = "fenEvaluation", description = "Evaluate a chess position using a FEN string.")
String forsythEdwardsNotationEvaluation(@ToolArg(name = "fen") String forsythEdwardsNotation,
McpTransportContext ctx) {
if (forsythEdwardsNotation.equals("r1bqk2r/ppp2ppp/2n5/1BbpP3/3Nn3/8/PPP2PPP/RNBQK2R w KQkq - 1 8")) {
return "+0.12";
}
return "+0.0";
}
}
In a method annotated with @Tool
you can bind parameters with types:
-
A class or Java record defined as the tool toolsInputJsonSchema.
-
io.modelcontextprotocol.spec.McpSchema.CallToolRequest
.
3.7.1.4.3 Tool Annotation Method Return Type
In a method annotated with @Tool
, you can use as a return type:
-
io.modelcontextprotocol.spec.McpSchema.CallToolResult
-
String
-
A class or Java record which will be serialized to JSON. If the return type is annotated with
@JsonSchema
and it is defined as the tool output it will be used as structured content.
3.7.1.5 Tools Input JSON Schema
A tool definition includes an input JSON Schema.
You can leverage Micronaut JSON Schema to generate a JSON Schema at compilation and use it as your tool input JSON Schema.
For example, you can use a Java record to define your tool input:
package io.micronaut.mcp.server.stateless.sync.tools.jsonschema;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.jsonschema.JsonSchema;
/**
*
* @param fen A Chess position in Forsyth–Edwards Notation
*/
@JsonSchema
@Introspected
public record FenEvaluationRequest(String fen) {
}
Then, just use the Java record as the method parameter:
package example.micronaut;
import io.micronaut.context.annotation.Requires;
import io.micronaut.mcp.annotations.Tool;
import io.micronaut.mcp.server.context.MicronautMcpTransportContext;
import jakarta.inject.Singleton;
@Singleton
class Tools {
@Tool(description = "Evaluate a chess position using a FEN string.")
String fenEvaluation(FenEvaluationRequest req,
MicronautMcpTransportContext ctx) {
if (req.fen().equals("r1bqk2r/ppp2ppp/2n5/1BbpP3/3Nn3/8/PPP2PPP/RNBQK2R w KQkq - 1 8")) {
return "+0.12";
}
return "+0.0";
}
}
3.7.1.6 Tools Output JSON Schema
A tool definition includes an input JSON Schema.
You can leverage Micronaut JSON Schema to generate a JSON Schema at compilation and use it as your tool output JSON Schema.
For example, you can use a Java record to define your tool output:
package io.micronaut.mcp.server.stateless.sync.tools.jsonschema.output;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.jsonschema.JsonSchema;
import jakarta.validation.constraints.NotBlank;
@Introspected
@JsonSchema
public record FenEvaluationResponse(
@NonNull @NotBlank String fen,
@NonNull @NotBlank String evaluation
) {
}
Then, use the Java record as the method return type:
package example.micronaut;
import io.micronaut.context.annotation.Requires;
import io.micronaut.mcp.annotations.Tool;
import io.micronaut.mcp.server.context.MicronautMcpTransportContext;
import io.micronaut.mcp.server.stateless.sync.tools.jsonschema.FenEvaluationRequest;
import jakarta.inject.Singleton;
@Singleton
class Tools {
@Tool(description = "Evaluate a chess position using a FEN string.")
FenEvaluationResponse fenEvaluation(FenEvaluationRequest req,
MicronautMcpTransportContext ctx) {
String fen = req.fen();
if (fen.equals("r1bqk2r/ppp2ppp/2n5/1BbpP3/3Nn3/8/PPP2PPP/RNBQK2R w KQkq - 1 8")) {
return new FenEvaluationResponse(fen, "+0.12");
}
return new FenEvaluationResponse(fen, "+0.0");
}
}
3.7.1.7 Tools with a Factory
Alternatively, you can define tools by registering beans (typically in a bean factory).
The following example assumes you set SYNC as your server type. Because of that, the following example creates defining tools with the class McpStatelessServerFeatures.SyncToolSpecification . If you used a different server type, you should use a different class.
|
package example.micronaut;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.modelcontextprotocol.server.McpStatelessServerFeatures;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.spec.McpSchema;
import jakarta.inject.Singleton;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
@Factory
class ToolsFactory {
@Singleton
McpStatelessServerFeatures.SyncToolSpecification getAlertsTools() {
return McpStatelessServerFeatures.SyncToolSpecification.builder()
.tool(tool())
.callHandler(callHandler())
.build();
}
private McpSchema.Tool tool() {
return McpSchema.Tool.builder()
.name("fenEvaluation")
.description("Evaluate a chess position using a FEN string.")
.inputSchema(inputSchema())
.build();
}
private McpSchema.JsonSchema inputSchema() {
McpSchema.JsonSchema fenSchema = new McpSchema.JsonSchema("string", null,null, null, null, null);
return new McpSchema.JsonSchema("object", Map.of("fen", fenSchema), List.of("fen"), null, null, null);
}
private BiFunction<McpTransportContext, McpSchema.CallToolRequest, McpSchema.CallToolResult> callHandler() {
return (exchange, req) -> {
String content = evaluation(req.arguments().get("fen").toString());
return new McpSchema.CallToolResult(content, false);
};
}
private String evaluation(String fen) {
if (fen.equals("r1bqk2r/ppp2ppp/2n5/1BbpP3/3Nn3/8/PPP2PPP/RNBQK2R w KQkq - 1 8")) {
return "+0.12";
}
return "+0.0";
}
}
3.7.2 Prompts
Prompts: Pre-defined templates or instructions that guide language model interactions
3.7.2.1 Classpath Prompts
It is possible to define prompts by placing a text file in the classpath and defining the prompt attributes and path via configuration:
micronaut.mcp.classpath-prompts.introspection-testing.name=introspection-testing
micronaut.mcp.classpath-prompts.introspection-testing.title=Introspection-Testing
micronaut.mcp.classpath-prompts.introspection-testing.description=Test whether a class is introspected in a Micronaut application
micronaut.mcp.classpath-prompts.introspection-testing.path=prompts/introspection-testing.md
micronaut.mcp.classpath-prompts.introspection-testing.arguments[0].name=className
micronaut.mcp.classpath-prompts.introspection-testing.arguments[0].description=The class for which you want to test introspection
micronaut.mcp.classpath-prompts.dev-default-environment.name=dev-default-environment
micronaut.mcp.classpath-prompts.dev-default-environment.title=Development-Default-Environment
micronaut.mcp.classpath-prompts.dev-default-environment.description=Modify a Micronaut application to set dev as the default environment
micronaut.mcp.classpath-prompts.dev-default-environment.path=prompts/dev-default-environment.md
You can define the prompt content with argument interpolation in a text file.
Please, write a test to verify introspection for ${className}
The following tests shows how to test if a class is introspected. The following test verifies if the `CreateGame` class is annotated with `@Introspected`.
```java
@Test
void isAnnotatedWithIntrospected() {
assertDoesNotThrow(() -> BeanIntrospection.getIntrospection(CreateGame.class));
}
```
3.7.2.2 Prompts Configuration
Property | Type | Description |
---|---|---|
|
boolean |
whether the server will emit notifications when the list of available prompts changes. Default value |
3.7.2.3 Prompts with Annotations
The preferred way to define prompts is to annotate a method with Prompt in a @Singleton
bean.
import io.micronaut.mcp.annotations.Prompt;
import io.micronaut.mcp.annotations.PromptArg;
import jakarta.inject.Singleton;
@Singleton
class Prompts {
/**
*
* @return Chess statistics
*/
@Prompt(name = "chess-statistics", description = "Displays statistics for chess games")
String prompt(@PromptArg(description = "Player Name") String name) {
return String.format("You generate chess statistics for %s ....", name);
}
}
3.7.2.3.1 Prompt Annotation Method Parameters
In a method annotated with @Prompt
you can bind parameters with types:
-
io.modelcontextprotocol.spec.McpSchema.GetPromptRequest
3.7.2.3.2 Prompt Annotation Method Return Type
In a method annotated with @Prompt
, you can use as a return type:
-
io.modelcontextprotocol.spec.McpSchema.GetPromptResult
-
String
3.7.2.4 Prompts with a Factory
Alternatively, you define prompts registering beans (typically in a bean factory) using the low-level MCP SDK API.
The following example assumes you set SYNC as your server type. Because of that, the following example creates defining Prompts with the class McpStatelessServerFeatures.SyncPromptSpecification . If you used a different server type, you should use a different class.
|
package example.micronaut;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.modelcontextprotocol.server.McpStatelessServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import jakarta.inject.Singleton;
import java.util.List;
@Factory
class PromptsFactory {
@Singleton
McpStatelessServerFeatures.SyncPromptSpecification prompt() {
return new McpStatelessServerFeatures.SyncPromptSpecification(
new McpSchema.Prompt("chess-statistics", "Displays statistics for chess games",
List.of(new McpSchema.PromptArgument("name", "Player Name", true))), (ex, req) -> {
Object playerNameObj = req.arguments().get("name");
String playerName = playerNameObj != null ? playerNameObj.toString() : "";
McpSchema.TextContent assistantContent = new McpSchema.TextContent(String.format("""
You generate chess statistics for %s ....""", playerName));
McpSchema.PromptMessage assistantMessage = new McpSchema.PromptMessage(McpSchema.Role.ASSISTANT, assistantContent);
return new McpSchema.GetPromptResult("Chess statistics", List.of(assistantMessage), null);
});
}
}
3.7.3 Resources
Resources: Structured data or content that provides additional context to the model
3.7.3.1 Resources Configuration
Property | Type | Description |
---|---|---|
|
boolean |
whether the client can subscribe to be notified of changes to individual resources. Default value |
|
boolean |
whether the server will emit notifications when the list of available resources changes. Default value |
3.7.3.2 Resources with Annotations
The preferred way to declare a resource is using a method annotated with Resource in a @Singleton
bean.
package example.micronaut;
import io.micronaut.mcp.annotations.Resource;
import jakarta.inject.Singleton;
@Singleton
class Resources {
@Resource(
uri = "example://hello",
name = "hello",
title = "Hello",
description = "Hello text",
mimeType = "text/plain"
)
String hello() {
return "Hello World";
}
}
Notes:
-
The
uri
attribute is required and must be unique for the resource. -
By default, the method name is used as the resource
name
. You can override this by setting thename
attribute of the@Resource
annotation. -
You can optionally provide
title
,description
andmimeType
(defaults totext/plain
).
3.7.3.2.1 Resource Annotation Method Parameters
In a method annotated with @Resource
you can bind parameters with types:
-
io.modelcontextprotocol.spec.McpSchema.ReadResourceRequest
3.7.3.2.2 Resource Annotation Method Return Type
In a method annotated with @Resource
, you can use as a return type:
-
io.modelcontextprotocol.spec.McpSchema.ReadResourceResult
-
String
. If the method returns aString
, the server responds with a single text resource content using the providedmimeType
.
3.7.3.3 Resources with a Factory
Alternatively, you define resources registering beans (typically in a bean factory).
The following example assumes you set SYNC as your server type. Because of that, the following example creates defining resources with the class McpStatelessServerFeatures.SyncResourceSpecification . If you used a different server type, you should use a different class.
|
package example.micronaut;
import io.micronaut.context.BeanContext;
import io.micronaut.context.annotation.Context;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.exceptions.ConfigurationException;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.mcp.server.utils.PgnLoader;
import io.micronaut.mcp.server.utils.ResourceLoaderUtils;
import io.modelcontextprotocol.server.McpStatelessServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import jakarta.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Context
@Factory
class ResourcesFactory {
public static final String PGN_MIME_TYPE = "application/x-chess-pgn";
private final PgnLoader pgnLoader;
private final ResourceLoader resourceLoader;
ResourcesFactory(ResourceLoader resourceLoader, PgnLoader pgnLoader) {
this.resourceLoader = resourceLoader;
this.pgnLoader = pgnLoader;
}
@EachBean(PgnFile.class)
@Singleton
McpStatelessServerFeatures.SyncResourceSpecification createPgnSyncResourceSpecification(PgnFile pgnFile) throws IOException {
McpSchema.Resource resource = getResource(pgnFile);
return new McpStatelessServerFeatures.SyncResourceSpecification(resource,
(mcpSyncServerExchange, readResourceRequest) -> readResourceResult(readResourceRequest.uri(), pgnLoader));
}
private static Integer round(String uri) {
int lastSlash = uri.lastIndexOf('/');
String roundStr = uri.substring(lastSlash + 1);
return Integer.parseInt(roundStr);
}
static McpSchema.ReadResourceResult readResourceResult(String uri, PgnLoader pgnLoader) {
Integer round = round(uri);
List<McpSchema.ResourceContents> contents = new ArrayList<>();
pgnLoader.loadPgn(round).ifPresent(text ->
contents.add(new McpSchema.TextResourceContents(uri, PGN_MIME_TYPE, text)));
return new McpSchema.ReadResourceResult(contents);
}
private McpSchema.Resource getResource(PgnFile pgnFile) throws IOException {
return ResourceLoaderUtils.size(resourceLoader, pgnFile.getPath())
.map(size -> {
Integer round = pgnFile.getRound();
String uri = "pgn://round/" + round;
String name = "round" + round + "PgnFideWCC2024";
String title = "PGN of the Round " + round + " game of the World Chess Championship";
String description = title + " between Ding Liren and Gukesh Dommaraju";
return new McpSchema.Resource(uri, name, title, description, PGN_MIME_TYPE, size, null, null);
}).orElseThrow(() -> new ConfigurationException("unable find resource for path " + pgnFile.getPath()));
}
}
3.7.3.4 Resources Templates
Resource templates allow servers to expose parameterized resources using URI templates
3.7.3.4.1 Resource Template with Annotations
The preferred way to declare a resource is using a method annotated with ResourceTemplate in a @Singleton
bean.
@Singleton
class MyResources {
private static final String PGN_MIME_TYPE = "application/x-chess-pgn";
private final PgnLoader pgnLoader;
MyResources(PgnLoader pgnLoader) {
this.pgnLoader = pgnLoader;
}
@ResourceTemplate(uriTemplate = "pgn://round/{round}",
mimeType = PGN_MIME_TYPE,
name = "2024ChessChampionshipRoundPgn",
title = "PGN of a round World Chess Championship 2024",
description = "Given a round, it returns a PGN of the World Chess Championship 2024 between Ding Liren and Gukesh Dommaraju")
String pgn(Integer round) {
return pgnLoader.loadPgn(round)
.orElseThrow(() -> new McpError(new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.RESOURCE_NOT_FOUND, "resource for round not found", null)));
}
}
3.7.3.4.1.1 Resource Annotation Method Parameters
In a method annotated with @ResourceTemplate
you can bind parameters with types:
-
io.modelcontextprotocol.spec.McpSchema.ReadResourceRequest
Additionally, you can add method parameter matching the uri template variable as show in the previous example.
3.7.3.4.1.2 Resource Annotation Method Return Type
In a method annotated with @ResourceTemplate
, you can use as a return type:
-
io.modelcontextprotocol.spec.McpSchema.ReadResourceResult
-
String
. If the method returns aString
, the server responds with a single text resource content using the providedmimeType
.
3.7.3.4.2 Resource Templates with a Factory
Alternatively, you define resources registering beans (typically in a bean factory).
The following example assumes you set SYNC as your server type. Because of that, the following example creates defining resources with the class McpStatelessServerFeatures.SyncResourceTemplateSpecification . If you used a different server type, you should use a different class.
|
package example.micronaut;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.micronaut.mcp.server.utils.PgnLoader;
import io.modelcontextprotocol.server.McpStatelessServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import jakarta.inject.Singleton;
import static io.micronaut.mcp.server.stateless.sync.resources.ResourcesFactory.PGN_MIME_TYPE;
import static io.micronaut.mcp.server.stateless.sync.resources.ResourcesFactory.readResourceResult;
@Factory
class ResourcesTemplatesFactory {
private final PgnLoader pgnLoader;
ResourcesTemplatesFactory( PgnLoader pgnLoader) {
this.pgnLoader = pgnLoader;
}
@Singleton
McpStatelessServerFeatures.SyncResourceTemplateSpecification pgnResourceTemplateSpecification() {
McpSchema.ResourceTemplate resourceTemplate = createPgnResourceTemplate();
return new McpStatelessServerFeatures.SyncResourceTemplateSpecification(resourceTemplate,
(mcpTransportContext, readResourceRequest) -> readResourceResult(readResourceRequest.uri(), pgnLoader));
}
McpSchema.ResourceTemplate createPgnResourceTemplate() {
String uriTemplate = "pgn://round/{round}";
String name = "2024ChessChampionshipRoundPgn";
String title = "PGN of a round World Chess Championship 2024";
String description = "Given a round, it returns a PGN of the World Chess Championship 2024 between Ding Liren and Gukesh Dommaraju";
return new McpSchema.ResourceTemplate(uriTemplate, name, title, description, PGN_MIME_TYPE, null, null);
}
}
3.7.4 Completions
The Model Context Protocol (MCP) provides a standardized way for servers to offer argument autocompletion suggestions for prompts and resource URIs.
3.7.4.1 Prompt Completions with Annotations
The preferred way to declare a prompt completion is using a method annotated with PromptCompletion in a @Singleton
bean.
@Singleton
class MyPromptsCompletions {
@PromptCompletion(name = "code_review")
List<String> languages(String language) {
if (language != null && language.startsWith("py")) {
return List.of("python", "pytorch", "pyside");
}
return Collections.emptyList();
}
}
You need a prompt with the same name as the completion prompt. Moreover, the name of the prompt completion argument must match the name of the prompt argument. |
3.7.4.1.1 PromptCompletion Annotation Method Parameters
In a method annotated with @PromptCompletion
you can bind parameters with types:
-
io.modelcontextprotocol.spec.McpSchema.CompleteRequest
-
io.modelcontextprotocol.spec.McpSchema.CompleteRequest.CompleteArgument
You can also bind the completion argument’s name as a method parameter, as illustrated in the code listing above.
3.7.4.1.2 PromptCompletion Annotation Method Return Type
In a method annotated with @PromptCompletion
, you can use as a return type:
-
io.modelcontextprotocol.spec.McpSchema.CompleteResult
-
List<String>
3.7.4.2 Resource Completions with Annotations
The preferred way to declare a resource completion is using a method annotated with ResourceCompletion in a @Singleton
bean.
@Singleton
class MyResourceCompletions {
@ResourceCompletion(uri = "file:///home/user/documents/{fileName}")
List<String> resourcesCompletions(String fileName) {
return List.of(
"report.pdf",
"data.csv",
"notes.txt"
).stream()
.filter(name -> name.startsWith(fileName))
.toList();
}
}
You can use resource completions, for example, in combination with Resource templates (see the uri for both the resource template and resource completion match):
@Singleton
class MyResourceTemplates {
@ResourceTemplate(
uriTemplate = "file:///home/user/documents/{fileName}",
name = "userDocument",
title = "User Document"
)
String ref(String fileName) {
if (fileName.equals("report.pdf")) {
return "Report PDF";
} else if (fileName.equals("data.csv")) {
return "Data CSV";
} else if (fileName.equals("notes.txt")) {
return "Notes TXT";
}
return "";
}
}
3.7.4.2.1 ResourceCompletion Annotation Method Parameters
In a method annotated with @ResourceCompletion
you can bind parameters with types:
-
io.modelcontextprotocol.spec.McpSchema.CompleteRequest
-
io.modelcontextprotocol.spec.McpSchema.CompleteRequest.CompleteArgument
You can also bind the completion argument’s name as a method parameter, as illustrated in the code listing above.
3.7.4.2.2 ResourceCompletion Annotation Method Return Type
In a method annotated with @ResourceCompletion
, you can use as a return type:
-
io.modelcontextprotocol.spec.McpSchema.CompleteResult
-
List<String>
4 MCP Client
You can configure multiple clients via configuration:
Property | Type | Description |
---|---|---|
|
java.net.URI |
The MCP Server URL |
|
java.time.Duration |
The duration to wait for server responses before timing out requests. |
|
boolean |
Whether to log requests. Default value |
|
boolean |
Whether to log responses. Default value |
4.1 Java SDK MCP Client
To use MCP Clients powered by the MCP Java SDK, use the following dependency:
implementation("io.micronaut.mcp:micronaut-mcp-client-java-sdk")
<dependency>
<groupId>io.micronaut.mcp</groupId>
<artifactId>micronaut-mcp-client-java-sdk</artifactId>
</dependency>
Additionally, if you are building an MCP Server, you can test it by injecting a bean of type io.modelcontextprotocol.client.McpSyncClient
or io.modelcontextprotocol.client.McpAsyncClient
in your tests. The MCP Client pointing to your MCP Server has a name qualifier with value embeddedServer
.
4.2 Langchain4j MCP Client
To use an MCP Client powered by LangChain4j, use the following dependency:
implementation("io.micronaut.mcp:micronaut-mcp-client-langchain4j")
<dependency>
<groupId>io.micronaut.mcp</groupId>
<artifactId>micronaut-mcp-client-langchain4j</artifactId>
</dependency>
Additionally, if you are building an MCP Server, you can test it by injecting a bean of type dev.langchain4j.mcp.client.McpClient
in your tests.
The MCP Client pointing to your MCP Server has a name qualifier with value embeddedServer
.
5 Repository
You can find the source code of this project in this repository: