feat: add progress token support and extend Request interface (#352)

- Add PaginatedRequest and ReadResourceRequest to Request sealed interface
- Add default meta() and progressToken() methods to Request interface
- Add _meta parameter support to request classes:
  - ReadResourceRequest, GetPromptRequest, CallToolRequest
  - CreateMessageRequest, ElicitRequest, CompleteRequest, PaginatedRequest
- Add builder methods for progress token configuration
- Add message field to ProgressNotification
- Maintain backward compatibility with existing constructors
- Add _meta field to the InitialRequest record

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Christian Tzolov 2025-06-29 09:29:48 +02:00
commit c0b398c650
2 changed files with 1321 additions and 847 deletions

View file

@ -142,7 +142,16 @@ public final class McpSchema {
} }
public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, ElicitRequest, public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, ElicitRequest,
CompleteRequest, GetPromptRequest { CompleteRequest, GetPromptRequest, PaginatedRequest, ReadResourceRequest {
Map<String, Object> meta();
default String progressToken() {
if (meta() != null && meta().containsKey("progressToken")) {
return meta().get("progressToken").toString();
}
return null;
}
} }
@ -237,7 +246,12 @@ public final class McpSchema {
public record InitializeRequest( // @formatter:off public record InitializeRequest( // @formatter:off
@JsonProperty("protocolVersion") String protocolVersion, @JsonProperty("protocolVersion") String protocolVersion,
@JsonProperty("capabilities") ClientCapabilities capabilities, @JsonProperty("capabilities") ClientCapabilities capabilities,
@JsonProperty("clientInfo") Implementation clientInfo) implements Request { @JsonProperty("clientInfo") Implementation clientInfo,
@JsonProperty("_meta") Map<String, Object> meta) implements Request {
public InitializeRequest(String protocolVersion, ClientCapabilities capabilities, Implementation clientInfo) {
this(protocolVersion, capabilities, clientInfo, null);
}
} // @formatter:on } // @formatter:on
@JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonInclude(JsonInclude.Include.NON_ABSENT)
@ -630,7 +644,12 @@ public final class McpSchema {
@JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record ReadResourceRequest( // @formatter:off public record ReadResourceRequest( // @formatter:off
@JsonProperty("uri") String uri){ @JsonProperty("uri") String uri,
@JsonProperty("_meta") Map<String, Object> meta) implements Request {
public ReadResourceRequest(String uri) {
this(uri, null);
}
} // @formatter:on } // @formatter:on
@JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonInclude(JsonInclude.Include.NON_ABSENT)
@ -786,7 +805,12 @@ public final class McpSchema {
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record GetPromptRequest(// @formatter:off public record GetPromptRequest(// @formatter:off
@JsonProperty("name") String name, @JsonProperty("name") String name,
@JsonProperty("arguments") Map<String, Object> arguments) implements Request { @JsonProperty("arguments") Map<String, Object> arguments,
@JsonProperty("_meta") Map<String, Object> meta) implements Request {
public GetPromptRequest(String name, Map<String, Object> arguments) {
this(name, arguments, null);
}
}// @formatter:off }// @formatter:off
/** /**
@ -894,10 +918,14 @@ public final class McpSchema {
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record CallToolRequest(// @formatter:off public record CallToolRequest(// @formatter:off
@JsonProperty("name") String name, @JsonProperty("name") String name,
@JsonProperty("arguments") Map<String, Object> arguments) implements Request { @JsonProperty("arguments") Map<String, Object> arguments,
@JsonProperty("_meta") Map<String, Object> meta) implements Request {
public CallToolRequest(String name, String jsonArguments) { public CallToolRequest(String name, String jsonArguments) {
this(name, parseJsonArguments(jsonArguments)); this(name, parseJsonArguments(jsonArguments), null);
}
public CallToolRequest(String name, Map<String, Object> arguments) {
this(name, arguments, null);
} }
private static Map<String, Object> parseJsonArguments(String jsonArguments) { private static Map<String, Object> parseJsonArguments(String jsonArguments) {
@ -908,6 +936,49 @@ public final class McpSchema {
throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e); throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e);
} }
} }
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String name;
private Map<String, Object> arguments;
private Map<String, Object> meta;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder arguments(Map<String, Object> arguments) {
this.arguments = arguments;
return this;
}
public Builder arguments(String jsonArguments) {
this.arguments = parseJsonArguments(jsonArguments);
return this;
}
public Builder meta(Map<String, Object> meta) {
this.meta = meta;
return this;
}
public Builder progressToken(String progressToken) {
if (this.meta == null) {
this.meta = new HashMap<>();
}
this.meta.put("progressToken", progressToken);
return this;
}
public CallToolRequest build() {
Assert.hasText(name, "name must not be empty");
return new CallToolRequest(name, arguments, meta);
}
}
}// @formatter:off }// @formatter:off
/** /**
@ -1103,7 +1174,18 @@ public final class McpSchema {
@JsonProperty("temperature") Double temperature, @JsonProperty("temperature") Double temperature,
@JsonProperty("maxTokens") int maxTokens, @JsonProperty("maxTokens") int maxTokens,
@JsonProperty("stopSequences") List<String> stopSequences, @JsonProperty("stopSequences") List<String> stopSequences,
@JsonProperty("metadata") Map<String, Object> metadata) implements Request { @JsonProperty("metadata") Map<String, Object> metadata,
@JsonProperty("_meta") Map<String, Object> meta) implements Request {
// backwards compatibility constructor
public CreateMessageRequest(List<SamplingMessage> messages, ModelPreferences modelPreferences,
String systemPrompt, ContextInclusionStrategy includeContext,
Double temperature, int maxTokens, List<String> stopSequences,
Map<String, Object> metadata) {
this(messages, modelPreferences, systemPrompt, includeContext, temperature, maxTokens,
stopSequences, metadata, null);
}
public enum ContextInclusionStrategy { public enum ContextInclusionStrategy {
@JsonProperty("none") NONE, @JsonProperty("none") NONE,
@ -1124,6 +1206,7 @@ public final class McpSchema {
private int maxTokens; private int maxTokens;
private List<String> stopSequences; private List<String> stopSequences;
private Map<String, Object> metadata; private Map<String, Object> metadata;
private Map<String, Object> meta;
public Builder messages(List<SamplingMessage> messages) { public Builder messages(List<SamplingMessage> messages) {
this.messages = messages; this.messages = messages;
@ -1165,9 +1248,22 @@ public final class McpSchema {
return this; return this;
} }
public Builder meta(Map<String, Object> meta) {
this.meta = meta;
return this;
}
public Builder progressToken(String progressToken) {
if (this.meta == null) {
this.meta = new HashMap<>();
}
this.meta.put("progressToken", progressToken);
return this;
}
public CreateMessageRequest build() { public CreateMessageRequest build() {
return new CreateMessageRequest(messages, modelPreferences, systemPrompt, return new CreateMessageRequest(messages, modelPreferences, systemPrompt,
includeContext, temperature, maxTokens, stopSequences, metadata); includeContext, temperature, maxTokens, stopSequences, metadata, meta);
} }
} }
}// @formatter:on }// @formatter:on
@ -1253,7 +1349,13 @@ public final class McpSchema {
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record ElicitRequest(// @formatter:off public record ElicitRequest(// @formatter:off
@JsonProperty("message") String message, @JsonProperty("message") String message,
@JsonProperty("requestedSchema") Map<String, Object> requestedSchema) implements Request { @JsonProperty("requestedSchema") Map<String, Object> requestedSchema,
@JsonProperty("_meta") Map<String, Object> meta) implements Request {
// backwards compatibility constructor
public ElicitRequest(String message, Map<String, Object> requestedSchema) {
this(message, requestedSchema, null);
}
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();
@ -1262,6 +1364,7 @@ public final class McpSchema {
public static class Builder { public static class Builder {
private String message; private String message;
private Map<String, Object> requestedSchema; private Map<String, Object> requestedSchema;
private Map<String, Object> meta;
public Builder message(String message) { public Builder message(String message) {
this.message = message; this.message = message;
@ -1273,8 +1376,21 @@ public final class McpSchema {
return this; return this;
} }
public Builder meta(Map<String, Object> meta) {
this.meta = meta;
return this;
}
public Builder progressToken(String progressToken) {
if (this.meta == null) {
this.meta = new HashMap<>();
}
this.meta.put("progressToken", progressToken);
return this;
}
public ElicitRequest build() { public ElicitRequest build() {
return new ElicitRequest(message, requestedSchema); return new ElicitRequest(message, requestedSchema, meta);
} }
} }
}// @formatter:on }// @formatter:on
@ -1320,7 +1436,20 @@ public final class McpSchema {
// --------------------------- // ---------------------------
@JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record PaginatedRequest(@JsonProperty("cursor") String cursor) { public record PaginatedRequest(// @formatter:off
@JsonProperty("cursor") String cursor,
@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on
public PaginatedRequest(String cursor) {
this(cursor, null);
}
/**
* Creates a new paginated request with an empty cursor.
*/
public PaginatedRequest() {
this(null);
}
} }
@JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonInclude(JsonInclude.Include.NON_ABSENT)
@ -1331,11 +1460,24 @@ public final class McpSchema {
// --------------------------- // ---------------------------
// Progress and Logging // Progress and Logging
// --------------------------- // ---------------------------
/**
* The Model Context Protocol (MCP) supports optional progress tracking for
* long-running operations through notification messages. Either side can send
* progress notifications to provide updates about operation status.
*
* @param progressToken A unique token to identify the progress notification. MUST be
* unique across all active requests.
* @param progress A value indicating the current progress.
* @param total An optional total amount of work to be done, if known.
* @param message An optional message providing additional context about the progress.
*/
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record ProgressNotification(// @formatter:off public record ProgressNotification(// @formatter:off
@JsonProperty("progressToken") String progressToken, @JsonProperty("progressToken") String progressToken,
@JsonProperty("progress") double progress, @JsonProperty("progress") Double progress,
@JsonProperty("total") Double total) { @JsonProperty("total") Double total,
@JsonProperty("message") String message) {
}// @formatter:on }// @formatter:on
/** /**
@ -1469,7 +1611,12 @@ public final class McpSchema {
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record CompleteRequest(// @formatter:off public record CompleteRequest(// @formatter:off
@JsonProperty("ref") McpSchema.CompleteReference ref, @JsonProperty("ref") McpSchema.CompleteReference ref,
@JsonProperty("argument") CompleteArgument argument) implements Request { @JsonProperty("argument") CompleteArgument argument,
@JsonProperty("_meta") Map<String, Object> meta) implements Request {
public CompleteRequest(McpSchema.CompleteReference ref, CompleteArgument argument) {
this(ref, argument, null);
}
public record CompleteArgument( public record CompleteArgument(
@JsonProperty("name") String name, @JsonProperty("name") String name,

View file

@ -3,22 +3,23 @@
*/ */
package io.modelcontextprotocol.spec; package io.modelcontextprotocol.spec;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents; import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
import net.javacrumbs.jsonunit.core.Option; import net.javacrumbs.jsonunit.core.Option;
/** /**
@ -106,6 +107,43 @@ public class McpSchemaTests {
assertThat(audioContent.mimeType()).isEqualTo("audio/wav"); assertThat(audioContent.mimeType()).isEqualTo("audio/wav");
} }
@Test
void testCreateMessageRequestWithMeta() throws Exception {
McpSchema.TextContent content = new McpSchema.TextContent("User message");
McpSchema.SamplingMessage message = new McpSchema.SamplingMessage(McpSchema.Role.USER, content);
McpSchema.ModelHint hint = new McpSchema.ModelHint("gpt-4");
McpSchema.ModelPreferences preferences = new McpSchema.ModelPreferences(Collections.singletonList(hint), 0.3,
0.7, 0.9);
Map<String, Object> metadata = new HashMap<>();
metadata.put("session", "test-session");
Map<String, Object> meta = new HashMap<>();
meta.put("progressToken", "create-message-token-456");
McpSchema.CreateMessageRequest request = McpSchema.CreateMessageRequest.builder()
.messages(Collections.singletonList(message))
.modelPreferences(preferences)
.systemPrompt("You are a helpful assistant")
.includeContext(McpSchema.CreateMessageRequest.ContextInclusionStrategy.THIS_SERVER)
.temperature(0.7)
.maxTokens(1000)
.stopSequences(Arrays.asList("STOP", "END"))
.metadata(metadata)
.meta(meta)
.build();
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.containsEntry("_meta", Map.of("progressToken", "create-message-token-456"));
// Test Request interface methods
assertThat(request.meta()).isEqualTo(meta);
assertThat(request.progressToken()).isEqualTo("create-message-token-456");
}
@Test @Test
void testEmbeddedResource() throws Exception { void testEmbeddedResource() throws Exception {
McpSchema.TextResourceContents resourceContents = new McpSchema.TextResourceContents("resource://test", McpSchema.TextResourceContents resourceContents = new McpSchema.TextResourceContents("resource://test",
@ -444,6 +482,36 @@ public class McpSchemaTests {
{"uri":"resource://test"}""")); {"uri":"resource://test"}"""));
} }
@Test
void testReadResourceRequestWithMeta() throws Exception {
Map<String, Object> meta = new HashMap<>();
meta.put("progressToken", "read-resource-token-123");
McpSchema.ReadResourceRequest request = new McpSchema.ReadResourceRequest("resource://test", meta);
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(json("""
{"uri":"resource://test","_meta":{"progressToken":"read-resource-token-123"}}"""));
// Test Request interface methods
assertThat(request.meta()).isEqualTo(meta);
assertThat(request.progressToken()).isEqualTo("read-resource-token-123");
}
@Test
void testReadResourceRequestDeserialization() throws Exception {
McpSchema.ReadResourceRequest request = mapper.readValue("""
{"uri":"resource://test","_meta":{"progressToken":"test-token"}}""",
McpSchema.ReadResourceRequest.class);
assertThat(request.uri()).isEqualTo("resource://test");
assertThat(request.meta()).containsEntry("progressToken", "test-token");
assertThat(request.progressToken()).isEqualTo("test-token");
}
@Test @Test
void testReadResourceResult() throws Exception { void testReadResourceResult() throws Exception {
McpSchema.TextResourceContents contents1 = new McpSchema.TextResourceContents("resource://test1", "text/plain", McpSchema.TextResourceContents contents1 = new McpSchema.TextResourceContents("resource://test1", "text/plain",
@ -529,6 +597,30 @@ public class McpSchemaTests {
.isEqualTo(request); .isEqualTo(request);
} }
@Test
void testGetPromptRequestWithMeta() throws Exception {
Map<String, Object> arguments = new HashMap<>();
arguments.put("arg1", "value1");
arguments.put("arg2", 42);
Map<String, Object> meta = new HashMap<>();
meta.put("progressToken", "token123");
McpSchema.GetPromptRequest request = new McpSchema.GetPromptRequest("test-prompt", arguments, meta);
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(
json("""
{"name":"test-prompt","arguments":{"arg1":"value1","arg2":42},"_meta":{"progressToken":"token123"}}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isEqualTo(meta);
assertThat(request.progressToken()).isEqualTo("token123");
}
@Test @Test
void testGetPromptResult() throws Exception { void testGetPromptResult() throws Exception {
McpSchema.TextContent content1 = new McpSchema.TextContent("System message"); McpSchema.TextContent content1 = new McpSchema.TextContent("System message");
@ -775,6 +867,65 @@ public class McpSchemaTests {
{"name":"test-tool","arguments":{"name":"test","value":42}}""")); {"name":"test-tool","arguments":{"name":"test","value":42}}"""));
} }
@Test
void testCallToolRequestWithMeta() throws Exception {
McpSchema.CallToolRequest request = McpSchema.CallToolRequest.builder()
.name("test-tool")
.arguments(Map.of("name", "test", "value", 42))
.progressToken("tool-progress-123")
.build();
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(
json("""
{"name":"test-tool","arguments":{"name":"test","value":42},"_meta":{"progressToken":"tool-progress-123"}}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isEqualTo(Map.of("progressToken", "tool-progress-123"));
assertThat(request.progressToken()).isEqualTo("tool-progress-123");
}
@Test
void testCallToolRequestBuilderWithJsonArguments() throws Exception {
Map<String, Object> meta = new HashMap<>();
meta.put("progressToken", "json-builder-789");
McpSchema.CallToolRequest request = McpSchema.CallToolRequest.builder().name("test-tool").arguments("""
{
"name": "test",
"value": 42
}
""").meta(meta).build();
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(
json("""
{"name":"test-tool","arguments":{"name":"test","value":42},"_meta":{"progressToken":"json-builder-789"}}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isEqualTo(meta);
assertThat(request.progressToken()).isEqualTo("json-builder-789");
}
@Test
void testCallToolRequestBuilderNameRequired() {
Map<String, Object> arguments = new HashMap<>();
arguments.put("name", "test");
McpSchema.CallToolRequest.Builder builder = McpSchema.CallToolRequest.builder().arguments(arguments);
assertThatThrownBy(builder::build).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("name must not be empty");
}
@Test @Test
void testCallToolResult() throws Exception { void testCallToolResult() throws Exception {
McpSchema.TextContent content = new McpSchema.TextContent("Tool execution result"); McpSchema.TextContent content = new McpSchema.TextContent("Tool execution result");
@ -991,6 +1142,141 @@ public class McpSchemaTests {
{"action":"accept","content":{"foo":"bar"}}""")); {"action":"accept","content":{"foo":"bar"}}"""));
} }
@Test
void testElicitRequestWithMeta() throws Exception {
Map<String, Object> requestedSchema = Map.of("type", "object", "required", List.of("name"), "properties",
Map.of("name", Map.of("type", "string")));
Map<String, Object> meta = new HashMap<>();
meta.put("progressToken", "elicit-token-789");
McpSchema.ElicitRequest request = McpSchema.ElicitRequest.builder()
.message("Please provide your name")
.requestedSchema(requestedSchema)
.meta(meta)
.build();
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.containsEntry("_meta", Map.of("progressToken", "elicit-token-789"));
// Test Request interface methods
assertThat(request.meta()).isEqualTo(meta);
assertThat(request.progressToken()).isEqualTo("elicit-token-789");
}
// Pagination Tests
@Test
void testPaginatedRequestNoArgs() throws Exception {
McpSchema.PaginatedRequest request = new McpSchema.PaginatedRequest();
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(json("""
{}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isNull();
assertThat(request.progressToken()).isNull();
}
@Test
void testPaginatedRequestWithCursor() throws Exception {
McpSchema.PaginatedRequest request = new McpSchema.PaginatedRequest("cursor123");
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(json("""
{"cursor":"cursor123"}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isNull();
assertThat(request.progressToken()).isNull();
}
@Test
void testPaginatedRequestWithMeta() throws Exception {
Map<String, Object> meta = new HashMap<>();
meta.put("progressToken", "pagination-progress-456");
McpSchema.PaginatedRequest request = new McpSchema.PaginatedRequest("cursor123", meta);
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(json("""
{"cursor":"cursor123","_meta":{"progressToken":"pagination-progress-456"}}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isEqualTo(meta);
assertThat(request.progressToken()).isEqualTo("pagination-progress-456");
}
@Test
void testPaginatedRequestDeserialization() throws Exception {
McpSchema.PaginatedRequest request = mapper.readValue("""
{"cursor":"test-cursor","_meta":{"progressToken":"test-token"}}""", McpSchema.PaginatedRequest.class);
assertThat(request.cursor()).isEqualTo("test-cursor");
assertThat(request.meta()).containsEntry("progressToken", "test-token");
assertThat(request.progressToken()).isEqualTo("test-token");
}
// Complete Request Tests
@Test
void testCompleteRequest() throws Exception {
McpSchema.PromptReference promptRef = new McpSchema.PromptReference("test-prompt");
McpSchema.CompleteRequest.CompleteArgument argument = new McpSchema.CompleteRequest.CompleteArgument("arg1",
"partial-value");
McpSchema.CompleteRequest request = new McpSchema.CompleteRequest(promptRef, argument);
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(
json("""
{"ref":{"type":"ref/prompt","name":"test-prompt"},"argument":{"name":"arg1","value":"partial-value"}}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isNull();
assertThat(request.progressToken()).isNull();
}
@Test
void testCompleteRequestWithMeta() throws Exception {
McpSchema.ResourceReference resourceRef = new McpSchema.ResourceReference("file:///test.txt");
McpSchema.CompleteRequest.CompleteArgument argument = new McpSchema.CompleteRequest.CompleteArgument("path",
"/partial/path");
Map<String, Object> meta = new HashMap<>();
meta.put("progressToken", "complete-progress-789");
McpSchema.CompleteRequest request = new McpSchema.CompleteRequest(resourceRef, argument, meta);
String value = mapper.writeValueAsString(request);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(
json("""
{"ref":{"type":"ref/resource","uri":"file:///test.txt"},"argument":{"name":"path","value":"/partial/path"},"_meta":{"progressToken":"complete-progress-789"}}"""));
// Test that it implements Request interface methods
assertThat(request.meta()).isEqualTo(meta);
assertThat(request.progressToken()).isEqualTo("complete-progress-789");
}
// Roots Tests // Roots Tests
@Test @Test
@ -1024,4 +1310,45 @@ public class McpSchemaTests {
} }
// Progress Notification Tests
@Test
void testProgressNotificationWithMessage() throws Exception {
McpSchema.ProgressNotification notification = new McpSchema.ProgressNotification("progress-token-123", 0.5, 1.0,
"Processing file 1 of 2");
String value = mapper.writeValueAsString(notification);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(
json("""
{"progressToken":"progress-token-123","progress":0.5,"total":1.0,"message":"Processing file 1 of 2"}"""));
}
@Test
void testProgressNotificationDeserialization() throws Exception {
McpSchema.ProgressNotification notification = mapper.readValue("""
{"progressToken":"token-456","progress":0.75,"total":1.0,"message":"Almost done"}""",
McpSchema.ProgressNotification.class);
assertThat(notification.progressToken()).isEqualTo("token-456");
assertThat(notification.progress()).isEqualTo(0.75);
assertThat(notification.total()).isEqualTo(1.0);
assertThat(notification.message()).isEqualTo("Almost done");
}
@Test
void testProgressNotificationWithoutMessage() throws Exception {
McpSchema.ProgressNotification notification = new McpSchema.ProgressNotification("progress-token-789", 0.25,
null, null);
String value = mapper.writeValueAsString(notification);
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
.isObject()
.isEqualTo(json("""
{"progressToken":"progress-token-789","progress":0.25}"""));
}
} }