feat!: enforce required MCP spec fields in McpSchema; lenient wire deserialization (#928)

Every wire-serialized record in McpSchema now validates spec-required
fields at construction time. Wire deserialization is intentionally
lenient: a missing required field is replaced with a documented default
and a WARN is logged, instead of failing the parse. A required-first
builder convention is introduced across the schema so it is no longer
possible to obtain a builder that is missing required state.

Required-field guards
---------------------
Every wire record's compact constructor asserts non-null (and non-empty
for String identifiers like name, uri, uriTemplate, version) on its
spec-required components. Passing null now throws
IllegalArgumentException at construction time instead of silently
producing invalid JSON via @JsonInclude(NON_ABSENT). This applies to
the JSON-RPC envelopes, lifecycle types, resource/prompt/tool requests
and results, sampling and elicitation, content records, root, complete,
logging and progress notifications, and the two CompleteReference
implementations. See MIGRATION-2.0.md for the full list.

Lenient wire deserialization
----------------------------
For each of those records (except JSONRPCResponse.JSONRPCError, which
still fails fast) a @JsonCreator static `fromJson` factory substitutes
a documented default for any absent required field — "" for strings,
[] for collections, {} for maps, 0 / 0.0 for numerics, INFO for
LoggingLevel, USER for SamplingMessage.role, ASSISTANT for
CreateMessageResult.role, CANCEL for ElicitResult.action, {values=[]}
for CompleteResult.completion — and logs a WARN naming the field and
the value used. Application code can still observe a malformed message
and react, but the SDK no longer halts the conversation.

Builder convention
------------------
Records that have a builder gain a required-first factory method
`builder(req1, req2, …)`; setters for required fields are removed from
the builder so it cannot be left in an invalid state. Existing no-arg
`builder()` factories and required-field setters are kept where source
compatibility demands it but are marked @Deprecated. New builders are
also added for several records that previously had none (ProgressNotification,
JSONRPCError, CompleteRequest, list/result types, content records, ...).

JSON-RPC envelope ergonomics
----------------------------
Previously every JSON-RPC envelope had to be constructed via the
canonical record constructor and the literal "2.0" string had to be
threaded through every call site, e.g.

    new JSONRPCRequest("2.0", "tools/call", id, params)
    new JSONRPCResponse("2.0", id, result, null)
    new JSONRPCResponse("2.0", id, null, new JSONRPCError(code, message, null))

Now:

    new JSONRPCRequest("tools/call", id, params)              // jsonrpc defaulted
    new JSONRPCNotification("notifications/initialized")       // params optional
    JSONRPCResponse.result(id, result)                        // factory
    JSONRPCResponse.error(id, new JSONRPCError(code, message)) // 2-arg error

JSONRPCResponse's compact constructor additionally enforces the
JSON-RPC invariant that exactly one of `result` / `error` is set —
previously the SDK could build envelopes that violated the protocol.

CompleteReference changes
-------------------------
- PromptReference.equals/hashCode now key on `name` only (previously
  derived from identifier()+type()). Two refs with the same name but
  different titles now compare equal — code using PromptReference as a
  map/set key should be audited.
- PromptReference's compact constructor pins `type` to "ref/prompt"
  and logs a WARN if the caller supplies a different non-null value.
  The legacy two-arg `PromptReference(String type, String name)`
  constructor remains @Deprecated.
- ResourceReference's record components are reduced from (type, uri)
  to (uri) — positional construction breaks. The legacy
  `ResourceReference(String type, String uri)` constructor stays
  @Deprecated and ignores `type`.
- CompleteReference.identifier() is @Deprecated and now returns null
  via a default method on the interface.

Tests / refactor
----------------
Conformance harness, integration tests, sample apps, and internal
callers were migrated to the new builder factories and convenience
constructors. No semantic changes to client/server runtime behaviour
beyond the McpSchema changes above.

Docs
----
- MIGRATION-2.0.md: required-field section now covers the broader
  record set and the lenient-deserialization behaviour; PromptReference
  WARN behaviour and ResourceReference component reduction are
  documented; the JSON-RPC envelope section is rewritten to compare the
  pre-2.0 surface with the new one.
- CONTRIBUTING.md: the "Evolving wire-serialized records" recipe is
  split into two cases — adding a new optional field (existing rules)
  and adding/maintaining a spec-required field (new rules covering
  Assert in compact constructor, @JsonCreator fromJson with defaults
  and WARN, required-first builder factory).

Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
This commit is contained in:
Dariusz Jędrzejczyk 2026-04-20 17:20:38 +02:00 • committed by Christian Tzolov
commit 22a0efa577
12 changed files with 4258 additions and 1688 deletions

View file

@ -43,13 +43,16 @@ class LifecycleInitializerPostInitializationHookTests {
private static final McpSchema.ClientCapabilities CLIENT_CAPABILITIES = McpSchema.ClientCapabilities.builder()
.build();
private static final McpSchema.Implementation CLIENT_INFO = new McpSchema.Implementation("test-client", "1.0.0");
private static final McpSchema.Implementation CLIENT_INFO = McpSchema.Implementation.builder("test-client", "1.0.0")
.build();
private static final List<String> PROTOCOL_VERSIONS = List.of("1.0.0", "2.0.0");
private static final McpSchema.InitializeResult MOCK_INIT_RESULT = new McpSchema.InitializeResult("2.0.0",
McpSchema.ServerCapabilities.builder().build(), new McpSchema.Implementation("test-server", "1.0.0"),
"Test instructions");
private static final McpSchema.InitializeResult MOCK_INIT_RESULT = McpSchema.InitializeResult
.builder("2.0.0", McpSchema.ServerCapabilities.builder().build(),
McpSchema.Implementation.builder("test-server", "1.0.0").build())
.instructions("Test instructions")
.build();
@Mock
private McpClientSession mockClientSession;

View file

@ -44,13 +44,16 @@ class LifecycleInitializerTests {
private static final McpSchema.ClientCapabilities CLIENT_CAPABILITIES = McpSchema.ClientCapabilities.builder()
.build();
private static final McpSchema.Implementation CLIENT_INFO = new McpSchema.Implementation("test-client", "1.0.0");
private static final McpSchema.Implementation CLIENT_INFO = McpSchema.Implementation.builder("test-client", "1.0.0")
.build();
private static final List<String> PROTOCOL_VERSIONS = List.of("1.0.0", "2.0.0");
private static final McpSchema.InitializeResult MOCK_INIT_RESULT = new McpSchema.InitializeResult("2.0.0",
McpSchema.ServerCapabilities.builder().build(), new McpSchema.Implementation("test-server", "1.0.0"),
"Test instructions");
private static final McpSchema.InitializeResult MOCK_INIT_RESULT = McpSchema.InitializeResult
.builder("2.0.0", McpSchema.ServerCapabilities.builder().build(),
McpSchema.Implementation.builder("test-server", "1.0.0").build())
.instructions("Test instructions")
.build();
@Mock
private McpClientSession mockClientSession;
@ -148,10 +151,12 @@ class LifecycleInitializerTests {
@Test
void shouldFailForUnsupportedProtocolVersion() {
McpSchema.InitializeResult unsupportedResult = new McpSchema.InitializeResult("999.0.0", // Unsupported
// version
McpSchema.ServerCapabilities.builder().build(), new McpSchema.Implementation("test-server", "1.0.0"),
"Test instructions");
McpSchema.InitializeResult unsupportedResult = McpSchema.InitializeResult.builder("999.0.0", // Unsupported
// version
McpSchema.ServerCapabilities.builder().build(),
McpSchema.Implementation.builder("test-server", "1.0.0").build())
.instructions("Test instructions")
.build();
when(mockClientSession.sendRequest(eq(McpSchema.METHOD_INITIALIZE), any(), any()))
.thenReturn(Mono.just(unsupportedResult));
@ -342,8 +347,11 @@ class LifecycleInitializerTests {
when(mockClientSession.sendRequest(eq(McpSchema.METHOD_INITIALIZE), any(), any())).thenAnswer(invocation -> {
capturedRequest.set((McpSchema.InitializeRequest) invocation.getArgument(1));
return Mono.just(new McpSchema.InitializeResult("4.0.0", McpSchema.ServerCapabilities.builder().build(),
new McpSchema.Implementation("test-server", "1.0.0"), "Test instructions"));
return Mono.just(McpSchema.InitializeResult
.builder("4.0.0", McpSchema.ServerCapabilities.builder().build(),
McpSchema.Implementation.builder("test-server", "1.0.0").build())
.instructions("Test instructions")
.build());
});
StepVerifier.create(initializer.withInitialization("test", init -> Mono.just(init.initializeResult())))

View file

@ -42,16 +42,15 @@ class AsyncToolSpecificationBuilderTest {
@Test
void builderShouldCreateValidAsyncToolSpecification() {
Tool tool = McpSchema.Tool.builder()
.name("test-tool")
.title("A test tool")
.inputSchema(EMPTY_JSON_SCHEMA)
.build();
Tool tool = McpSchema.Tool.builder("test-tool", EMPTY_JSON_SCHEMA).title("A test tool").build();
McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder()
.tool(tool)
.callHandler((exchange, request) -> Mono
.just(CallToolResult.builder().content(List.of(new TextContent("Test result"))).isError(false).build()))
.callHandler((exchange,
request) -> Mono.just(CallToolResult.builder()
.content(List.of(TextContent.builder("Test result").build()))
.isError(false)
.build()))
.build();
assertThat(specification).isNotNull();
@ -69,11 +68,7 @@ class AsyncToolSpecificationBuilderTest {
@Test
void builderShouldThrowExceptionWhenCallToolIsNull() {
Tool tool = McpSchema.Tool.builder()
.name("test-tool")
.title("A test tool")
.inputSchema(EMPTY_JSON_SCHEMA)
.build();
Tool tool = McpSchema.Tool.builder("test-tool", EMPTY_JSON_SCHEMA).title("A test tool").build();
assertThatThrownBy(() -> McpServerFeatures.AsyncToolSpecification.builder().tool(tool).build())
.isInstanceOf(IllegalArgumentException.class)
@ -82,11 +77,7 @@ class AsyncToolSpecificationBuilderTest {
@Test
void builderShouldAllowMethodChaining() {
Tool tool = McpSchema.Tool.builder()
.name("test-tool")
.title("A test tool")
.inputSchema(EMPTY_JSON_SCHEMA)
.build();
Tool tool = McpSchema.Tool.builder("test-tool", EMPTY_JSON_SCHEMA).title("A test tool").build();
McpServerFeatures.AsyncToolSpecification.Builder builder = McpServerFeatures.AsyncToolSpecification.builder();
// Then - verify method chaining returns the same builder instance
@ -98,20 +89,19 @@ class AsyncToolSpecificationBuilderTest {
@Test
void builtSpecificationShouldExecuteCallToolCorrectly() {
Tool tool = McpSchema.Tool.builder()
.name("calculator")
.title("Simple calculator")
.inputSchema(EMPTY_JSON_SCHEMA)
.build();
Tool tool = McpSchema.Tool.builder("calculator", EMPTY_JSON_SCHEMA).title("Simple calculator").build();
String expectedResult = "42";
McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder()
.tool(tool)
.callHandler((exchange, request) -> Mono.just(
CallToolResult.builder().content(List.of(new TextContent(expectedResult))).isError(false).build()))
.callHandler((exchange,
request) -> Mono.just(CallToolResult.builder()
.content(List.of(TextContent.builder(expectedResult).build()))
.isError(false)
.build()))
.build();
CallToolRequest request = new CallToolRequest("calculator", Map.of());
CallToolRequest request = CallToolRequest.builder("calculator").build();
Mono<CallToolResult> resultMono = specification.callHandler().apply(null, request);
StepVerifier.create(resultMono).assertNext(result -> {
@ -125,18 +115,14 @@ class AsyncToolSpecificationBuilderTest {
@Test
void fromSyncShouldConvertSyncToolSpecificationCorrectly() {
Tool tool = McpSchema.Tool.builder()
.name("sync-tool")
.title("A sync tool")
.inputSchema(EMPTY_JSON_SCHEMA)
.build();
Tool tool = McpSchema.Tool.builder("sync-tool", EMPTY_JSON_SCHEMA).title("A sync tool").build();
String expectedResult = "sync result";
// Create a sync tool specification
McpServerFeatures.SyncToolSpecification syncSpec = McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler((exchange, request) -> CallToolResult.builder()
.content(List.of(new TextContent(expectedResult)))
.content(List.of(TextContent.builder(expectedResult).build()))
.isError(false)
.build())
.build();
@ -150,7 +136,7 @@ class AsyncToolSpecificationBuilderTest {
assertThat(asyncSpec.callHandler()).isNotNull();
// Test that the converted async specification works correctly
CallToolRequest request = new CallToolRequest("sync-tool", Map.of("param", "value"));
CallToolRequest request = CallToolRequest.builder("sync-tool").arguments(Map.of("param", "value")).build();
Mono<CallToolResult> resultMono = asyncSpec.callHandler().apply(null, request);
StepVerifier.create(resultMono).assertNext(result -> {
@ -193,7 +179,7 @@ class AsyncToolSpecificationBuilderTest {
@Test
void defaultShouldThrowOnInvalidName() {
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatThrownBy(
() -> McpServer.async(transportProvider).toolCall(invalidTool, (exchange, request) -> null))
@ -204,7 +190,7 @@ class AsyncToolSpecificationBuilderTest {
@Test
void lenientDefaultShouldLogOnInvalidName() {
System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false");
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatCode(() -> McpServer.async(transportProvider).toolCall(invalidTool, (exchange, request) -> null))
.doesNotThrowAnyException();
@ -213,7 +199,7 @@ class AsyncToolSpecificationBuilderTest {
@Test
void lenientConfigurationShouldLogOnInvalidName() {
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatCode(() -> McpServer.async(transportProvider)
.strictToolNameValidation(false)
@ -224,7 +210,7 @@ class AsyncToolSpecificationBuilderTest {
@Test
void serverConfigurationShouldOverrideDefault() {
System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false");
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatThrownBy(() -> McpServer.async(transportProvider)
.strictToolNameValidation(true)

View file

@ -52,7 +52,7 @@ class McpAsyncServerExchangeTests {
clientCapabilities = McpSchema.ClientCapabilities.builder().roots(true).build();
clientInfo = new McpSchema.Implementation("test-client", "1.0.0");
clientInfo = McpSchema.Implementation.builder("test-client", "1.0.0").build();
exchange = new McpAsyncServerExchange("testSessionId", mockSession, clientCapabilities, clientInfo,
McpTransportContext.EMPTY);
@ -61,9 +61,10 @@ class McpAsyncServerExchangeTests {
@Test
void testListRootsWithSinglePage() {
List<McpSchema.Root> roots = Arrays.asList(new McpSchema.Root("file:///home/user/project1", "Project 1"),
new McpSchema.Root("file:///home/user/project2", "Project 2"));
McpSchema.ListRootsResult singlePageResult = new McpSchema.ListRootsResult(roots, null);
List<McpSchema.Root> roots = Arrays.asList(
McpSchema.Root.builder("file:///home/user/project1").name("Project 1").build(),
McpSchema.Root.builder("file:///home/user/project2").name("Project 2").build());
McpSchema.ListRootsResult singlePageResult = McpSchema.ListRootsResult.builder(roots).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), any(McpSchema.PaginatedRequest.class),
any(TypeRef.class)))
@ -78,7 +79,7 @@ class McpAsyncServerExchangeTests {
assertThat(result.nextCursor()).isNull();
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
}).verifyComplete();
}
@ -86,14 +87,18 @@ class McpAsyncServerExchangeTests {
@Test
void testListRootsWithMultiplePages() {
List<McpSchema.Root> page1Roots = Arrays.asList(new McpSchema.Root("file:///home/user/project1", "Project 1"),
new McpSchema.Root("file:///home/user/project2", "Project 2"));
List<McpSchema.Root> page2Roots = Arrays.asList(new McpSchema.Root("file:///home/user/project3", "Project 3"));
List<McpSchema.Root> page1Roots = Arrays.asList(
McpSchema.Root.builder("file:///home/user/project1").name("Project 1").build(),
McpSchema.Root.builder("file:///home/user/project2").name("Project 2").build());
List<McpSchema.Root> page2Roots = Arrays
.asList(McpSchema.Root.builder("file:///home/user/project3").name("Project 3").build());
McpSchema.ListRootsResult page1Result = new McpSchema.ListRootsResult(page1Roots, "cursor1");
McpSchema.ListRootsResult page2Result = new McpSchema.ListRootsResult(page2Roots, null);
McpSchema.ListRootsResult page1Result = McpSchema.ListRootsResult.builder(page1Roots)
.nextCursor("cursor1")
.build();
McpSchema.ListRootsResult page2Result = McpSchema.ListRootsResult.builder(page2Roots).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), eq(new McpSchema.PaginatedRequest(null)),
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), eq(new McpSchema.PaginatedRequest()),
any(TypeRef.class)))
.thenReturn(Mono.just(page1Result));
@ -109,7 +114,7 @@ class McpAsyncServerExchangeTests {
assertThat(result.nextCursor()).isNull();
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
}).verifyComplete();
}
@ -117,7 +122,7 @@ class McpAsyncServerExchangeTests {
@Test
void testListRootsWithEmptyResult() {
McpSchema.ListRootsResult emptyResult = new McpSchema.ListRootsResult(new ArrayList<>(), null);
McpSchema.ListRootsResult emptyResult = McpSchema.ListRootsResult.builder(new ArrayList<>()).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), any(McpSchema.PaginatedRequest.class),
any(TypeRef.class)))
@ -128,7 +133,7 @@ class McpAsyncServerExchangeTests {
assertThat(result.nextCursor()).isNull();
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
}).verifyComplete();
}
@ -136,8 +141,9 @@ class McpAsyncServerExchangeTests {
@Test
void testListRootsWithSpecificCursor() {
List<McpSchema.Root> roots = Arrays.asList(new McpSchema.Root("file:///home/user/project3", "Project 3"));
McpSchema.ListRootsResult result = new McpSchema.ListRootsResult(roots, "nextCursor");
List<McpSchema.Root> roots = Arrays
.asList(McpSchema.Root.builder("file:///home/user/project3").name("Project 3").build());
McpSchema.ListRootsResult result = McpSchema.ListRootsResult.builder(roots).nextCursor("nextCursor").build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), eq(new McpSchema.PaginatedRequest("someCursor")),
any(TypeRef.class)))
@ -167,12 +173,14 @@ class McpAsyncServerExchangeTests {
void testListRootsUnmodifiabilityAfterAccumulation() {
List<McpSchema.Root> page1Roots = new ArrayList<>(
Arrays.asList(new McpSchema.Root("file:///home/user/project1", "Project 1")));
Arrays.asList(McpSchema.Root.builder("file:///home/user/project1").name("Project 1").build()));
List<McpSchema.Root> page2Roots = new ArrayList<>(
Arrays.asList(new McpSchema.Root("file:///home/user/project2", "Project 2")));
Arrays.asList(McpSchema.Root.builder("file:///home/user/project2").name("Project 2").build()));
McpSchema.ListRootsResult page1Result = new McpSchema.ListRootsResult(page1Roots, "cursor1");
McpSchema.ListRootsResult page2Result = new McpSchema.ListRootsResult(page2Roots, null);
McpSchema.ListRootsResult page1Result = McpSchema.ListRootsResult.builder(page1Roots)
.nextCursor("cursor1")
.build();
McpSchema.ListRootsResult page2Result = McpSchema.ListRootsResult.builder(page2Roots).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), eq(new McpSchema.PaginatedRequest(null)),
any(TypeRef.class)))
@ -187,7 +195,7 @@ class McpAsyncServerExchangeTests {
assertThat(result.roots()).hasSize(2);
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
// Verify that clear() also throws UnsupportedOperationException
@ -227,10 +235,9 @@ class McpAsyncServerExchangeTests {
@Test
void testLoggingNotificationWithAllowedLevel() {
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.ERROR)
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.ERROR, "Test error message")
.logger("test-logger")
.data("Test error message")
.build();
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
@ -248,10 +255,9 @@ class McpAsyncServerExchangeTests {
exchange.setMinLoggingLevel(McpSchema.LoggingLevel.DEBUG);
verify(mockSession, times(1)).setMinLoggingLevel(eq(McpSchema.LoggingLevel.DEBUG));
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.DEBUG)
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.DEBUG, "Debug message that should be filtered")
.logger("test-logger")
.data("Debug message that should be filtered")
.build();
when(mockSession.isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.DEBUG))).thenReturn(Boolean.TRUE);
@ -264,10 +270,9 @@ class McpAsyncServerExchangeTests {
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
eq(debugNotification));
McpSchema.LoggingMessageNotification warningNotification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.WARNING)
McpSchema.LoggingMessageNotification warningNotification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.WARNING, "Debug message that should be filtered")
.logger("test-logger")
.data("Debug message that should be filtered")
.build();
StepVerifier.create(exchange.loggingNotification(warningNotification)).verifyComplete();
@ -279,10 +284,9 @@ class McpAsyncServerExchangeTests {
@Test
void testLoggingNotificationWithSessionError() {
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.ERROR)
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.ERROR, "Test error message")
.logger("test-logger")
.data("Test error message")
.build();
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
@ -304,8 +308,8 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithNullCapabilities = new McpAsyncServerExchange("testSessionId", mockSession,
null, clientInfo, McpTransportContext.EMPTY);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your name")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your name", Map.of("type", "object"))
.build();
StepVerifier.create(exchangeWithNullCapabilities.createElicitation(elicitRequest))
@ -328,8 +332,8 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithoutElicitation = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithoutElicitation, clientInfo, McpTransportContext.EMPTY);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your name")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your name", Map.of("type", "object"))
.build();
StepVerifier.create(exchangeWithoutElicitation.createElicitation(elicitRequest)).verifyErrorSatisfies(error -> {
@ -359,17 +363,15 @@ class McpAsyncServerExchangeTests {
java.util.Map.of("type", "number")));
requestedSchema.put("required", java.util.List.of("name"));
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your personal information")
.requestedSchema(requestedSchema)
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your personal information", requestedSchema)
.build();
java.util.Map<String, Object> responseContent = new java.util.HashMap<>();
responseContent.put("name", "John Doe");
responseContent.put("age", 30);
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder()
.message(McpSchema.ElicitResult.Action.ACCEPT)
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.ACCEPT)
.content(responseContent)
.build();
@ -395,12 +397,11 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide sensitive information")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide sensitive information", Map.of("type", "object"))
.build();
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder()
.message(McpSchema.ElicitResult.Action.DECLINE)
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.DECLINE)
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest), any(TypeRef.class)))
@ -422,12 +423,11 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your information")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your information", Map.of("type", "object"))
.build();
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder()
.message(McpSchema.ElicitResult.Action.CANCEL)
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.CANCEL)
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest), any(TypeRef.class)))
@ -449,8 +449,8 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your name")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your name", Map.of("type", "object"))
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest), any(TypeRef.class)))
@ -471,9 +471,10 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithNullCapabilities = new McpAsyncServerExchange("testSessionId", mockSession,
null, clientInfo, McpTransportContext.EMPTY);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello, world!"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello, world!").build())
.build()), 1000)
.build();
StepVerifier.create(exchangeWithNullCapabilities.createMessage(createMessageRequest))
@ -497,9 +498,10 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithoutSampling = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithoutSampling, clientInfo, McpTransportContext.EMPTY);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello, world!"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello, world!").build())
.build()), 1000)
.build();
StepVerifier.create(exchangeWithoutSampling.createMessage(createMessageRequest)).verifyErrorSatisfies(error -> {
@ -522,15 +524,15 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello, world!"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello, world!").build())
.build()), 1000)
.build();
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult.builder()
.role(McpSchema.Role.ASSISTANT)
.content(new McpSchema.TextContent("Hello! How can I help you today?"))
.model("gpt-4")
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult
.builder(McpSchema.Role.ASSISTANT,
McpSchema.TextContent.builder("Hello! How can I help you today?").build(), "gpt-4")
.stopReason(McpSchema.CreateMessageResult.StopReason.END_TURN)
.build();
@ -559,16 +561,20 @@ class McpAsyncServerExchangeTests {
capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY);
// Create request with image content
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER,
new McpSchema.ImageContent(null, "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...",
"image/jpeg"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder(Arrays.asList(
McpSchema.SamplingMessage
.builder(McpSchema.Role.USER,
McpSchema.ImageContent
.builder("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...", "image/jpeg")
.build())
.build()),
1000)
.build();
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult.builder()
.role(McpSchema.Role.ASSISTANT)
.content(new McpSchema.TextContent("I can see an image. It appears to be a photograph."))
.model("gpt-4-vision")
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult
.builder(McpSchema.Role.ASSISTANT,
McpSchema.TextContent.builder("I can see an image. It appears to be a photograph.").build(),
"gpt-4-vision")
.stopReason(McpSchema.CreateMessageResult.StopReason.END_TURN)
.build();
@ -593,9 +599,10 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder(Arrays.asList(
McpSchema.SamplingMessage.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello").build())
.build()),
1000)
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_SAMPLING_CREATE_MESSAGE), eq(createMessageRequest),
@ -617,16 +624,17 @@ class McpAsyncServerExchangeTests {
McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession,
capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER,
new McpSchema.TextContent("What files are available?"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("What files are available?").build())
.build()), 1000)
.includeContext(McpSchema.CreateMessageRequest.ContextInclusionStrategy.ALL_SERVERS)
.build();
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult.builder()
.role(McpSchema.Role.ASSISTANT)
.content(new McpSchema.TextContent("Based on the available context, I can see several files..."))
.model("gpt-4")
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult
.builder(McpSchema.Role.ASSISTANT,
McpSchema.TextContent.builder("Based on the available context, I can see several files...").build(),
"gpt-4")
.stopReason(McpSchema.CreateMessageResult.StopReason.END_TURN)
.build();

View file

@ -53,7 +53,7 @@ class McpSyncServerExchangeTests {
clientCapabilities = McpSchema.ClientCapabilities.builder().roots(true).build();
clientInfo = new McpSchema.Implementation("test-client", "1.0.0");
clientInfo = McpSchema.Implementation.builder("test-client", "1.0.0").build();
asyncExchange = new McpAsyncServerExchange("testSessionId", mockSession, clientCapabilities, clientInfo,
McpTransportContext.EMPTY);
@ -63,9 +63,10 @@ class McpSyncServerExchangeTests {
@Test
void testListRootsWithSinglePage() {
List<McpSchema.Root> roots = Arrays.asList(new McpSchema.Root("file:///home/user/project1", "Project 1"),
new McpSchema.Root("file:///home/user/project2", "Project 2"));
McpSchema.ListRootsResult singlePageResult = new McpSchema.ListRootsResult(roots, null);
List<McpSchema.Root> roots = Arrays.asList(
McpSchema.Root.builder("file:///home/user/project1").name("Project 1").build(),
McpSchema.Root.builder("file:///home/user/project2").name("Project 2").build());
McpSchema.ListRootsResult singlePageResult = McpSchema.ListRootsResult.builder(roots).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), any(McpSchema.PaginatedRequest.class),
any(TypeRef.class)))
@ -81,19 +82,23 @@ class McpSyncServerExchangeTests {
assertThat(result.nextCursor()).isNull();
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
void testListRootsWithMultiplePages() {
List<McpSchema.Root> page1Roots = Arrays.asList(new McpSchema.Root("file:///home/user/project1", "Project 1"),
new McpSchema.Root("file:///home/user/project2", "Project 2"));
List<McpSchema.Root> page2Roots = Arrays.asList(new McpSchema.Root("file:///home/user/project3", "Project 3"));
List<McpSchema.Root> page1Roots = Arrays.asList(
McpSchema.Root.builder("file:///home/user/project1").name("Project 1").build(),
McpSchema.Root.builder("file:///home/user/project2").name("Project 2").build());
List<McpSchema.Root> page2Roots = Arrays
.asList(McpSchema.Root.builder("file:///home/user/project3").name("Project 3").build());
McpSchema.ListRootsResult page1Result = new McpSchema.ListRootsResult(page1Roots, "cursor1");
McpSchema.ListRootsResult page2Result = new McpSchema.ListRootsResult(page2Roots, null);
McpSchema.ListRootsResult page1Result = McpSchema.ListRootsResult.builder(page1Roots)
.nextCursor("cursor1")
.build();
McpSchema.ListRootsResult page2Result = McpSchema.ListRootsResult.builder(page2Roots).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), eq(new McpSchema.PaginatedRequest(null)),
any(TypeRef.class)))
@ -112,14 +117,14 @@ class McpSyncServerExchangeTests {
assertThat(result.nextCursor()).isNull();
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
void testListRootsWithEmptyResult() {
McpSchema.ListRootsResult emptyResult = new McpSchema.ListRootsResult(new ArrayList<>(), null);
McpSchema.ListRootsResult emptyResult = McpSchema.ListRootsResult.builder(new ArrayList<>()).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), any(McpSchema.PaginatedRequest.class),
any(TypeRef.class)))
@ -131,15 +136,16 @@ class McpSyncServerExchangeTests {
assertThat(result.nextCursor()).isNull();
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
void testListRootsWithSpecificCursor() {
List<McpSchema.Root> roots = Arrays.asList(new McpSchema.Root("file:///home/user/project3", "Project 3"));
McpSchema.ListRootsResult result = new McpSchema.ListRootsResult(roots, "nextCursor");
List<McpSchema.Root> roots = Arrays
.asList(McpSchema.Root.builder("file:///home/user/project3").name("Project 3").build());
McpSchema.ListRootsResult result = McpSchema.ListRootsResult.builder(roots).nextCursor("nextCursor").build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), eq(new McpSchema.PaginatedRequest("someCursor")),
any(TypeRef.class)))
@ -167,12 +173,14 @@ class McpSyncServerExchangeTests {
void testListRootsUnmodifiabilityAfterAccumulation() {
List<McpSchema.Root> page1Roots = new ArrayList<>(
Arrays.asList(new McpSchema.Root("file:///home/user/project1", "Project 1")));
Arrays.asList(McpSchema.Root.builder("file:///home/user/project1").name("Project 1").build()));
List<McpSchema.Root> page2Roots = new ArrayList<>(
Arrays.asList(new McpSchema.Root("file:///home/user/project2", "Project 2")));
Arrays.asList(McpSchema.Root.builder("file:///home/user/project2").name("Project 2").build()));
McpSchema.ListRootsResult page1Result = new McpSchema.ListRootsResult(page1Roots, "cursor1");
McpSchema.ListRootsResult page2Result = new McpSchema.ListRootsResult(page2Roots, null);
McpSchema.ListRootsResult page1Result = McpSchema.ListRootsResult.builder(page1Roots)
.nextCursor("cursor1")
.build();
McpSchema.ListRootsResult page2Result = McpSchema.ListRootsResult.builder(page2Roots).build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), eq(new McpSchema.PaginatedRequest(null)),
any(TypeRef.class)))
@ -188,7 +196,7 @@ class McpSyncServerExchangeTests {
assertThat(result.roots()).hasSize(2);
// Verify that the returned list is unmodifiable
assertThatThrownBy(() -> result.roots().add(new McpSchema.Root("file:///test", "Test")))
assertThatThrownBy(() -> result.roots().add(McpSchema.Root.builder("file:///test").name("Test").build()))
.isInstanceOf(UnsupportedOperationException.class);
// Verify that clear() also throws UnsupportedOperationException
@ -221,10 +229,9 @@ class McpSyncServerExchangeTests {
@Test
void testLoggingNotificationWithAllowedLevel() {
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.ERROR)
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.ERROR, "Test error message")
.logger("test-logger")
.data("Test error message")
.build();
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
@ -243,10 +250,9 @@ class McpSyncServerExchangeTests {
asyncExchange.setMinLoggingLevel(McpSchema.LoggingLevel.DEBUG);
verify(mockSession, times(1)).setMinLoggingLevel(McpSchema.LoggingLevel.DEBUG);
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.DEBUG)
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.DEBUG, "Debug message that should be filtered")
.logger("test-logger")
.data("Debug message that should be filtered")
.build();
when(mockSession.isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.DEBUG))).thenReturn(Boolean.TRUE);
@ -259,10 +265,9 @@ class McpSyncServerExchangeTests {
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
eq(debugNotification));
McpSchema.LoggingMessageNotification warningNotification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.WARNING)
McpSchema.LoggingMessageNotification warningNotification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.WARNING, "Debug message that should be filtered")
.logger("test-logger")
.data("Debug message that should be filtered")
.build();
exchange.loggingNotification(warningNotification);
@ -275,10 +280,9 @@ class McpSyncServerExchangeTests {
@Test
void testLoggingNotificationWithSessionError() {
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
.level(McpSchema.LoggingLevel.ERROR)
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification
.builder(McpSchema.LoggingLevel.ERROR, "Test error message")
.logger("test-logger")
.data("Test error message")
.build();
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
@ -301,8 +305,8 @@ class McpSyncServerExchangeTests {
McpSyncServerExchange exchangeWithNullCapabilities = new McpSyncServerExchange(
asyncExchangeWithNullCapabilities);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your name")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your name", Map.of("type", "object"))
.build();
assertThatThrownBy(() -> exchangeWithNullCapabilities.createElicitation(elicitRequest))
@ -324,8 +328,8 @@ class McpSyncServerExchangeTests {
mockSession, capabilitiesWithoutElicitation, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithoutElicitation = new McpSyncServerExchange(asyncExchangeWithoutElicitation);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your name")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your name", Map.of("type", "object"))
.build();
assertThatThrownBy(() -> exchangeWithoutElicitation.createElicitation(elicitRequest))
@ -355,17 +359,15 @@ class McpSyncServerExchangeTests {
java.util.Map.of("type", "number")));
requestedSchema.put("required", java.util.List.of("name"));
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your personal information")
.requestedSchema(requestedSchema)
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your personal information", requestedSchema)
.build();
java.util.Map<String, Object> responseContent = new java.util.HashMap<>();
responseContent.put("name", "John Doe");
responseContent.put("age", 30);
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder()
.message(McpSchema.ElicitResult.Action.ACCEPT)
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.ACCEPT)
.content(responseContent)
.build();
@ -392,12 +394,11 @@ class McpSyncServerExchangeTests {
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithElicitation = new McpSyncServerExchange(asyncExchangeWithElicitation);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide sensitive information")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide sensitive information", Map.of("type", "object"))
.build();
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder()
.message(McpSchema.ElicitResult.Action.DECLINE)
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.DECLINE)
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest), any(TypeRef.class)))
@ -420,12 +421,11 @@ class McpSyncServerExchangeTests {
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithElicitation = new McpSyncServerExchange(asyncExchangeWithElicitation);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your information")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your information", Map.of("type", "object"))
.build();
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder()
.message(McpSchema.ElicitResult.Action.CANCEL)
McpSchema.ElicitResult expectedResult = McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.CANCEL)
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest), any(TypeRef.class)))
@ -448,8 +448,8 @@ class McpSyncServerExchangeTests {
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithElicitation = new McpSyncServerExchange(asyncExchangeWithElicitation);
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder()
.message("Please provide your name")
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
.builder("Please provide your name", Map.of("type", "object"))
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest), any(TypeRef.class)))
@ -472,9 +472,10 @@ class McpSyncServerExchangeTests {
McpSyncServerExchange exchangeWithNullCapabilities = new McpSyncServerExchange(
asyncExchangeWithNullCapabilities);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello, world!"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello, world!").build())
.build()), 1000)
.build();
assertThatThrownBy(() -> exchangeWithNullCapabilities.createMessage(createMessageRequest))
@ -497,9 +498,10 @@ class McpSyncServerExchangeTests {
capabilitiesWithoutSampling, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithoutSampling = new McpSyncServerExchange(asyncExchangeWithoutSampling);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello, world!"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello, world!").build())
.build()), 1000)
.build();
assertThatThrownBy(() -> exchangeWithoutSampling.createMessage(createMessageRequest))
@ -522,15 +524,15 @@ class McpSyncServerExchangeTests {
capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello, world!"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello, world!").build())
.build()), 1000)
.build();
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult.builder()
.role(McpSchema.Role.ASSISTANT)
.content(new McpSchema.TextContent("Hello! How can I help you today?"))
.model("gpt-4")
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult
.builder(McpSchema.Role.ASSISTANT,
McpSchema.TextContent.builder("Hello! How can I help you today?").build(), "gpt-4")
.stopReason(McpSchema.CreateMessageResult.StopReason.END_TURN)
.build();
@ -560,16 +562,20 @@ class McpSyncServerExchangeTests {
McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling);
// Create request with image content
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER,
new McpSchema.ImageContent(null, "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...",
"image/jpeg"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder(Arrays.asList(
McpSchema.SamplingMessage
.builder(McpSchema.Role.USER,
McpSchema.ImageContent
.builder("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...", "image/jpeg")
.build())
.build()),
1000)
.build();
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult.builder()
.role(McpSchema.Role.ASSISTANT)
.content(new McpSchema.TextContent("I can see an image. It appears to be a photograph."))
.model("gpt-4-vision")
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult
.builder(McpSchema.Role.ASSISTANT,
McpSchema.TextContent.builder("I can see an image. It appears to be a photograph.").build(),
"gpt-4-vision")
.stopReason(McpSchema.CreateMessageResult.StopReason.END_TURN)
.build();
@ -595,9 +601,10 @@ class McpSyncServerExchangeTests {
capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays
.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder(Arrays.asList(
McpSchema.SamplingMessage.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("Hello").build())
.build()),
1000)
.build();
when(mockSession.sendRequest(eq(McpSchema.METHOD_SAMPLING_CREATE_MESSAGE), eq(createMessageRequest),
@ -620,16 +627,17 @@ class McpSyncServerExchangeTests {
capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY);
McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling);
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder()
.messages(Arrays.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER,
new McpSchema.TextContent("What files are available?"))))
McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest
.builder(Arrays.asList(McpSchema.SamplingMessage
.builder(McpSchema.Role.USER, McpSchema.TextContent.builder("What files are available?").build())
.build()), 1000)
.includeContext(McpSchema.CreateMessageRequest.ContextInclusionStrategy.ALL_SERVERS)
.build();
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult.builder()
.role(McpSchema.Role.ASSISTANT)
.content(new McpSchema.TextContent("Based on the available context, I can see several files..."))
.model("gpt-4")
McpSchema.CreateMessageResult expectedResult = McpSchema.CreateMessageResult
.builder(McpSchema.Role.ASSISTANT,
McpSchema.TextContent.builder("Based on the available context, I can see several files...").build(),
"gpt-4")
.stopReason(McpSchema.CreateMessageResult.StopReason.END_TURN)
.build();

View file

@ -41,24 +41,12 @@ public class ResourceTemplateListingTest {
void testResourceListingWithMixedResources() {
// Create resource list with both regular and template resources
List<McpSchema.Resource> allResources = List.of(
McpSchema.Resource.builder()
.uri("file:///test/doc1.txt")
.name("Document 1")
McpSchema.Resource.builder("file:///test/doc1.txt", "Document 1").mimeType("text/plain").build(),
McpSchema.Resource.builder("file:///test/doc2.txt", "Document 2").mimeType("text/plain").build(),
McpSchema.Resource.builder("file:///test/{type}/document.txt", "Typed Document")
.mimeType("text/plain")
.build(),
McpSchema.Resource.builder()
.uri("file:///test/doc2.txt")
.name("Document 2")
.mimeType("text/plain")
.build(),
McpSchema.Resource.builder()
.uri("file:///test/{type}/document.txt")
.name("Typed Document")
.mimeType("text/plain")
.build(),
McpSchema.Resource.builder()
.uri("file:///users/{userId}/files/{fileId}")
.name("User File")
McpSchema.Resource.builder("file:///users/{userId}/files/{fileId}", "User File")
.mimeType("text/plain")
.build());
@ -77,20 +65,18 @@ public class ResourceTemplateListingTest {
void testResourceTemplatesListedSeparately() {
// Create mixed resources
List<McpSchema.Resource> resources = List.of(
McpSchema.Resource.builder()
.uri("file:///test/regular.txt")
.name("Regular Resource")
McpSchema.Resource.builder("file:///test/regular.txt", "Regular Resource")
.mimeType("text/plain")
.build(),
McpSchema.Resource.builder()
.uri("file:///test/user/{userId}/profile.txt")
.name("User Profile")
McpSchema.Resource.builder("file:///test/user/{userId}/profile.txt", "User Profile")
.mimeType("text/plain")
.build());
// Create explicit resource template
McpSchema.ResourceTemplate explicitTemplate = new McpSchema.ResourceTemplate(
"file:///test/document/{docId}/content.txt", "Document Template", null, "text/plain", null);
McpSchema.ResourceTemplate explicitTemplate = McpSchema.ResourceTemplate
.builder("file:///test/document/{docId}/content.txt", "Document Template")
.mimeType("text/plain")
.build();
// Filter regular resources (those without template parameters)
List<McpSchema.Resource> regularResources = resources.stream()
@ -100,8 +86,11 @@ public class ResourceTemplateListingTest {
// Extract template resources (those with template parameters)
List<McpSchema.ResourceTemplate> templateResources = resources.stream()
.filter(resource -> resource.uri().contains("{"))
.map(resource -> new McpSchema.ResourceTemplate(resource.uri(), resource.name(), resource.description(),
resource.mimeType(), resource.annotations()))
.map(resource -> McpSchema.ResourceTemplate.builder(resource.uri(), resource.name())
.description(resource.description())
.mimeType(resource.mimeType())
.annotations(resource.annotations())
.build())
.collect(Collectors.toList());
// Verify regular resources list

View file

@ -39,12 +39,12 @@ class SyncToolSpecificationBuilderTest {
@Test
void builderShouldCreateValidSyncToolSpecification() {
Tool tool = Tool.builder().name("test-tool").title("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
Tool tool = Tool.builder("test-tool", EMPTY_JSON_SCHEMA).title("A test tool").build();
McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler((exchange, request) -> CallToolResult.builder()
.content(List.of(new TextContent("Test result")))
.content(List.of(TextContent.builder("Test result").build()))
.isError(false)
.build())
.build();
@ -63,7 +63,7 @@ class SyncToolSpecificationBuilderTest {
@Test
void builderShouldThrowExceptionWhenCallToolIsNull() {
Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
Tool tool = Tool.builder("test-tool", EMPTY_JSON_SCHEMA).description("A test tool").build();
assertThatThrownBy(() -> McpServerFeatures.SyncToolSpecification.builder().tool(tool).build())
.isInstanceOf(IllegalArgumentException.class)
@ -72,7 +72,7 @@ class SyncToolSpecificationBuilderTest {
@Test
void builderShouldAllowMethodChaining() {
Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build();
Tool tool = Tool.builder("test-tool", EMPTY_JSON_SCHEMA).description("A test tool").build();
McpServerFeatures.SyncToolSpecification.Builder builder = McpServerFeatures.SyncToolSpecification.builder();
// Then - verify method chaining returns the same builder instance
@ -84,11 +84,7 @@ class SyncToolSpecificationBuilderTest {
@Test
void builtSpecificationShouldExecuteCallToolCorrectly() {
Tool tool = Tool.builder()
.name("calculator")
.description("Simple calculator")
.inputSchema(EMPTY_JSON_SCHEMA)
.build();
Tool tool = Tool.builder("calculator", EMPTY_JSON_SCHEMA).description("Simple calculator").build();
String expectedResult = "42";
McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder()
@ -96,13 +92,13 @@ class SyncToolSpecificationBuilderTest {
.callHandler((exchange, request) -> {
// Simple test implementation
return CallToolResult.builder()
.content(List.of(new TextContent(expectedResult)))
.content(List.of(TextContent.builder(expectedResult).build()))
.isError(false)
.build();
})
.build();
CallToolRequest request = new CallToolRequest("calculator", Map.of());
CallToolRequest request = CallToolRequest.builder("calculator").build();
CallToolResult result = specification.callHandler().apply(null, request);
assertThat(result).isNotNull();
@ -138,7 +134,7 @@ class SyncToolSpecificationBuilderTest {
@Test
void defaultShouldThrowOnInvalidName() {
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatThrownBy(
() -> McpServer.sync(transportProvider).toolCall(invalidTool, (exchange, request) -> null))
@ -149,7 +145,7 @@ class SyncToolSpecificationBuilderTest {
@Test
void lenientDefaultShouldLogOnInvalidName() {
System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false");
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatCode(() -> McpServer.sync(transportProvider).toolCall(invalidTool, (exchange, request) -> null))
.doesNotThrowAnyException();
@ -158,7 +154,7 @@ class SyncToolSpecificationBuilderTest {
@Test
void lenientConfigurationShouldLogOnInvalidName() {
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatCode(() -> McpServer.sync(transportProvider)
.strictToolNameValidation(false)
@ -169,7 +165,7 @@ class SyncToolSpecificationBuilderTest {
@Test
void serverConfigurationShouldOverrideDefault() {
System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false");
Tool invalidTool = Tool.builder().name("invalid tool name").build();
Tool invalidTool = Tool.builder("invalid tool name", EMPTY_JSON_SCHEMA).build();
assertThatThrownBy(() -> McpServer.sync(transportProvider)
.strictToolNameValidation(true)

View file

@ -20,7 +20,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testValidStringId() {
assertDoesNotThrow(() -> {
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", "string-id", null);
var request = new McpSchema.JSONRPCRequest("test/method", "string-id");
assertEquals("string-id", request.id());
});
}
@ -28,7 +28,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testValidIntegerId() {
assertDoesNotThrow(() -> {
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", 123, null);
var request = new McpSchema.JSONRPCRequest("test/method", 123);
assertEquals(123, request.id());
});
}
@ -36,7 +36,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testValidLongId() {
assertDoesNotThrow(() -> {
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", 123L, null);
var request = new McpSchema.JSONRPCRequest("test/method", 123L);
assertEquals(123L, request.id());
});
}
@ -44,7 +44,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testNullIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", null, null);
new McpSchema.JSONRPCRequest("test/method", null);
});
assertTrue(exception.getMessage().contains("MCP requests MUST include an ID"));
@ -54,7 +54,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testDoubleIdTypeThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", 123.45, null);
new McpSchema.JSONRPCRequest("test/method", 123.45);
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
@ -63,7 +63,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testBooleanIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", true, null);
new McpSchema.JSONRPCRequest("test/method", true);
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
@ -72,7 +72,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testArrayIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", new String[] { "array" }, null);
new McpSchema.JSONRPCRequest("test/method", new String[] { "array" });
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
@ -81,7 +81,7 @@ public class JSONRPCRequestMcpValidationTest {
@Test
public void testObjectIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", new Object(), null);
new McpSchema.JSONRPCRequest("test/method", new Object());
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));

View file

@ -55,8 +55,7 @@ class McpClientSessionTests {
// Verify response handling
StepVerifier.create(responseMono).then(() -> {
McpSchema.JSONRPCRequest request = transport.getLastSentMessageAsRequest();
transport.simulateIncomingMessage(
new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), responseData, null));
transport.simulateIncomingMessage(McpSchema.JSONRPCResponse.result(request.id(), responseData));
}).consumeNextWith(response -> {
// Verify the request was sent
McpSchema.JSONRPCMessage sentMessage = transport.getLastSentMessageAsRequest();
@ -84,9 +83,8 @@ class McpClientSessionTests {
McpSchema.JSONRPCRequest request = transport.getLastSentMessageAsRequest();
// Simulate error response
McpSchema.JSONRPCResponse.JSONRPCError error = new McpSchema.JSONRPCResponse.JSONRPCError(
McpSchema.ErrorCodes.METHOD_NOT_FOUND, "Method not found", null);
transport.simulateIncomingMessage(
new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null, error));
McpSchema.ErrorCodes.METHOD_NOT_FOUND, "Method not found");
transport.simulateIncomingMessage(McpSchema.JSONRPCResponse.error(request.id(), error));
}).expectError(McpError.class).verify();
session.close();
@ -140,8 +138,7 @@ class McpClientSessionTests {
var session = new McpClientSession(TIMEOUT, transport, requestHandlers, Map.of(), Function.identity());
// Simulate incoming request
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, ECHO_METHOD,
"test-id", echoMessage);
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(ECHO_METHOD, "test-id", echoMessage);
transport.simulateIncomingMessage(request);
// Verify response
@ -166,8 +163,8 @@ class McpClientSessionTests {
// Simulate incoming notification from the server
Map<String, Object> notificationParams = Map.of("status", "ready");
McpSchema.JSONRPCNotification notification = new McpSchema.JSONRPCNotification(McpSchema.JSONRPC_VERSION,
TEST_NOTIFICATION, notificationParams);
McpSchema.JSONRPCNotification notification = new McpSchema.JSONRPCNotification(TEST_NOTIFICATION,
notificationParams);
transport.simulateIncomingMessage(notification);
@ -186,8 +183,7 @@ class McpClientSessionTests {
Function.identity());
// Simulate incoming request for unknown method
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, "unknown.method",
"test-id", null);
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest("unknown.method", "test-id");
transport.simulateIncomingMessage(request);
// Verify error response
@ -214,8 +210,7 @@ class McpClientSessionTests {
Function.identity());
// Simulate incoming request that will trigger the error
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod,
"test-id", null);
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(testMethod, "test-id");
transport.simulateIncomingMessage(request);
// Verify: The response should contain the custom error from McpError
@ -242,8 +237,7 @@ class McpClientSessionTests {
Function.identity());
// Simulate incoming request that will trigger the error
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod,
"test-id", null);
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(testMethod, "test-id");
transport.simulateIncomingMessage(request);
// Verify: The response should contain INTERNAL_ERROR with aggregated exception
@ -276,8 +270,7 @@ class McpClientSessionTests {
Function.identity());
// Simulate incoming request that will trigger the error
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod,
"test-id", null);
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(testMethod, "test-id");
transport.simulateIncomingMessage(request);
// Verify: The response should contain INTERNAL_ERROR with full exception chain

View file

@ -18,10 +18,8 @@ class PromptReferenceEqualsTest {
@Test
void testEqualsWithSameIdentifierAndType() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Test Title");
McpSchema.PromptReference ref2 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Different Title");
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt").title("Different Title").build();
assertTrue(ref1.equals(ref2), "PromptReferences with same identifier and type should be equal");
assertEquals(ref1.hashCode(), ref2.hashCode(), "Equal objects should have same hash code");
@ -29,35 +27,22 @@ class PromptReferenceEqualsTest {
@Test
void testEqualsWithDifferentIdentifier() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt-1",
"Test Title");
McpSchema.PromptReference ref2 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt-2",
"Test Title");
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt-1").title("Test Title").build();
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt-2").title("Test Title").build();
assertFalse(ref1.equals(ref2), "PromptReferences with different identifiers should not be equal");
}
@Test
void testEqualsWithDifferentType() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Test Title");
McpSchema.PromptReference ref2 = new McpSchema.PromptReference("ref/other", "test-prompt", "Test Title");
assertFalse(ref1.equals(ref2), "PromptReferences with different types should not be equal");
}
@Test
void testEqualsWithNull() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Test Title");
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
assertFalse(ref1.equals(null), "PromptReference should not be equal to null");
}
@Test
void testEqualsWithDifferentClass() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Test Title");
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
String other = "not a PromptReference";
assertFalse(ref1.equals(other), "PromptReference should not be equal to different class");
@ -65,17 +50,16 @@ class PromptReferenceEqualsTest {
@Test
void testEqualsWithSameInstance() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Test Title");
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
assertTrue(ref1.equals(ref1), "PromptReference should be equal to itself");
}
@Test
void testEqualsIgnoresTitle() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt", "Title 1");
McpSchema.PromptReference ref2 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt", "Title 2");
McpSchema.PromptReference ref3 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt", null);
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Title 1").build();
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt").title("Title 2").build();
McpSchema.PromptReference ref3 = new PromptReference("test-prompt");
assertTrue(ref1.equals(ref2), "PromptReferences should be equal regardless of title");
assertTrue(ref1.equals(ref3), "PromptReferences should be equal even when one has null title");
@ -84,14 +68,11 @@ class PromptReferenceEqualsTest {
@Test
void testHashCodeConsistency() {
McpSchema.PromptReference ref1 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Test Title");
McpSchema.PromptReference ref2 = new McpSchema.PromptReference(PromptReference.TYPE, "test-prompt",
"Different Title");
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt").title("Different Title").build();
assertEquals(ref1.hashCode(), ref2.hashCode(), "Objects that are equal should have the same hash code");
// Call hashCode multiple times to ensure consistency
int hashCode1 = ref1.hashCode();
int hashCode2 = ref1.hashCode();
assertEquals(hashCode1, hashCode2, "Hash code should be consistent across multiple calls");

View file

@ -105,13 +105,13 @@ class GsonMcpJsonMapperTests {
var gsonMapper = new GsonMcpJsonMapper();
// Tool builder parsing of input/output schema strings
var tool = McpSchema.Tool.builder().name("echo").description("Echo tool").inputSchema(gsonMapper, """
var tool = McpSchema.Tool.builder("echo", gsonMapper, """
{
"type": "object",
"properties": { "x": { "type": "integer" } },
"required": ["x"]
}
""").outputSchema(gsonMapper, """
""").description("Echo tool").outputSchema(gsonMapper, """
{
"type": "object",
"properties": { "y": { "type": "string" } }

View file

@ -16,10 +16,7 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
/**
* Tests for {@link ToolInputValidator}.
@ -33,13 +30,9 @@ class ToolInputValidatorTests {
private final Map<String, Object> inputSchema = Map.of("type", "object", "properties",
Map.of("name", Map.of("type", "string")), "required", List.of("name"));
private final Tool toolWithSchema = Tool.builder()
.name("test-tool")
.description("Test tool")
.inputSchema(inputSchema)
.build();
private final Tool toolWithSchema = Tool.builder("test-tool", inputSchema).description("Test tool").build();
private final Tool toolWithoutSchema = Tool.builder().name("test-tool").description("Test tool").build();
private final Tool toolWithoutSchema = Tool.builder("test-tool").description("Test tool").build();
@Test
void validate_whenDisabled_returnsNull() {
@ -51,10 +44,12 @@ class ToolInputValidatorTests {
@Test
void validate_whenNoSchema_returnsNull() {
when(validator.validate(any(), any())).thenReturn(ValidationResponse.asValid(null));
CallToolResult result = ToolInputValidator.validate(toolWithoutSchema, Map.of("name", "test"), true, validator);
assertThat(result).isNull();
verify(validator, never()).validate(any(), any());
verify(validator).validate(any(), any());
}
@Test