Skip to content

Commit cf0fd0a

Browse files
authored
Merge branch 'modelcontextprotocol:main' into StreamableHttpServerTransportProvider
2 parents 11a7d3e + b701a36 commit cf0fd0a

File tree

10 files changed

+611
-85
lines changed

10 files changed

+611
-85
lines changed

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebClientStreamableHttpAsyncClientTests.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package io.modelcontextprotocol.client;
22

3-
import com.fasterxml.jackson.databind.ObjectMapper;
43
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
54
import io.modelcontextprotocol.spec.McpClientTransport;
65
import org.junit.jupiter.api.Timeout;
7-
import org.springframework.web.reactive.function.client.WebClient;
86
import org.testcontainers.containers.GenericContainer;
97
import org.testcontainers.containers.wait.strategy.Wait;
10-
import org.testcontainers.images.builder.ImageFromDockerfile;
8+
9+
import org.springframework.web.reactive.function.client.WebClient;
1110

1211
@Timeout(15)
1312
public class WebClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncClientTests {

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebFluxSseMcpAsyncClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
import java.time.Duration;
88

9-
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
10-
import io.modelcontextprotocol.spec.McpClientTransport;
119
import org.junit.jupiter.api.Timeout;
10+
import org.springframework.web.reactive.function.client.WebClient;
1211
import org.testcontainers.containers.GenericContainer;
1312
import org.testcontainers.containers.wait.strategy.Wait;
1413

15-
import org.springframework.web.reactive.function.client.WebClient;
14+
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
15+
import io.modelcontextprotocol.spec.McpClientTransport;
1616

1717
/**
1818
* Tests for the {@link McpAsyncClient} with {@link WebFluxSseClientTransport}.

mcp-test/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@
5454
<artifactId>junit-jupiter-api</artifactId>
5555
<version>${junit.version}</version>
5656
</dependency>
57+
<dependency>
58+
<groupId>org.junit.jupiter</groupId>
59+
<artifactId>junit-jupiter-params</artifactId>
60+
<version>${junit.version}</version>
61+
</dependency>
5762
<dependency>
5863
<groupId>org.mockito</groupId>
5964
<artifactId>mockito-core</artifactId>

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 126 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44

55
package io.modelcontextprotocol.client;
66

7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.assertj.core.api.Assertions.assertThatCode;
9+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
10+
import static org.assertj.core.api.Assertions.fail;
11+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
12+
713
import java.time.Duration;
14+
import java.util.ArrayList;
815
import java.util.Map;
916
import java.util.Objects;
1017
import java.util.concurrent.atomic.AtomicBoolean;
@@ -13,9 +20,16 @@
1320
import java.util.function.Consumer;
1421
import java.util.function.Function;
1522

23+
import org.junit.jupiter.api.AfterEach;
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.ValueSource;
28+
1629
import io.modelcontextprotocol.spec.McpClientTransport;
1730
import io.modelcontextprotocol.spec.McpError;
1831
import io.modelcontextprotocol.spec.McpSchema;
32+
import io.modelcontextprotocol.spec.McpSchema.BlobResourceContents;
1933
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
2034
import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities;
2135
import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
@@ -24,25 +38,19 @@
2438
import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
2539
import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest;
2640
import io.modelcontextprotocol.spec.McpSchema.Prompt;
41+
import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
2742
import io.modelcontextprotocol.spec.McpSchema.Resource;
43+
import io.modelcontextprotocol.spec.McpSchema.ResourceContents;
2844
import io.modelcontextprotocol.spec.McpSchema.Root;
2945
import io.modelcontextprotocol.spec.McpSchema.SubscribeRequest;
46+
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
3047
import io.modelcontextprotocol.spec.McpSchema.Tool;
3148
import io.modelcontextprotocol.spec.McpSchema.UnsubscribeRequest;
3249
import io.modelcontextprotocol.spec.McpTransport;
33-
import org.junit.jupiter.api.AfterEach;
34-
import org.junit.jupiter.api.BeforeEach;
35-
import org.junit.jupiter.api.Disabled;
36-
import org.junit.jupiter.api.Test;
3750
import reactor.core.publisher.Flux;
3851
import reactor.core.publisher.Mono;
3952
import reactor.test.StepVerifier;
4053

41-
import static org.assertj.core.api.Assertions.assertThat;
42-
import static org.assertj.core.api.Assertions.assertThatCode;
43-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
44-
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
45-
4654
/**
4755
* Test suite for the {@link McpAsyncClient} that can be used with different
4856
* {@link McpTransport} implementations.
@@ -208,6 +216,64 @@ void testCallToolWithInvalidTool() {
208216
});
209217
}
210218

219+
@ParameterizedTest
220+
@ValueSource(strings = { "success", "error", "debug" })
221+
void testCallToolWithMessageAnnotations(String messageType) {
222+
McpClientTransport transport = createMcpTransport();
223+
224+
withClient(transport, mcpAsyncClient -> {
225+
StepVerifier.create(mcpAsyncClient.initialize()
226+
.then(mcpAsyncClient.callTool(new McpSchema.CallToolRequest("annotatedMessage",
227+
Map.of("messageType", messageType, "includeImage", true)))))
228+
.consumeNextWith(result -> {
229+
assertThat(result).isNotNull();
230+
assertThat(result.isError()).isNotEqualTo(true);
231+
assertThat(result.content()).isNotEmpty();
232+
assertThat(result.content()).allSatisfy(content -> {
233+
switch (content.type()) {
234+
case "text":
235+
McpSchema.TextContent textContent = assertInstanceOf(McpSchema.TextContent.class,
236+
content);
237+
assertThat(textContent.text()).isNotEmpty();
238+
assertThat(textContent.annotations()).isNotNull();
239+
240+
switch (messageType) {
241+
case "error":
242+
assertThat(textContent.annotations().priority()).isEqualTo(1.0);
243+
assertThat(textContent.annotations().audience())
244+
.containsOnly(McpSchema.Role.USER, McpSchema.Role.ASSISTANT);
245+
break;
246+
case "success":
247+
assertThat(textContent.annotations().priority()).isEqualTo(0.7);
248+
assertThat(textContent.annotations().audience())
249+
.containsExactly(McpSchema.Role.USER);
250+
break;
251+
case "debug":
252+
assertThat(textContent.annotations().priority()).isEqualTo(0.3);
253+
assertThat(textContent.annotations().audience())
254+
.containsExactly(McpSchema.Role.ASSISTANT);
255+
break;
256+
default:
257+
throw new IllegalStateException("Unexpected value: " + content.type());
258+
}
259+
break;
260+
case "image":
261+
McpSchema.ImageContent imageContent = assertInstanceOf(McpSchema.ImageContent.class,
262+
content);
263+
assertThat(imageContent.data()).isNotEmpty();
264+
assertThat(imageContent.annotations()).isNotNull();
265+
assertThat(imageContent.annotations().priority()).isEqualTo(0.5);
266+
assertThat(imageContent.annotations().audience()).containsExactly(McpSchema.Role.USER);
267+
break;
268+
default:
269+
fail("Unexpected content type: " + content.type());
270+
}
271+
});
272+
})
273+
.verifyComplete();
274+
});
275+
}
276+
211277
@Test
212278
void testListResourcesWithoutInitialization() {
213279
verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(null), "listing resources");
@@ -345,18 +411,59 @@ void testRemoveNonExistentRoot() {
345411
}
346412

347413
@Test
348-
@Disabled
349414
void testReadResource() {
350-
withClient(createMcpTransport(), mcpAsyncClient -> {
351-
StepVerifier.create(mcpAsyncClient.listResources()).consumeNextWith(resources -> {
352-
if (!resources.resources().isEmpty()) {
353-
Resource firstResource = resources.resources().get(0);
354-
StepVerifier.create(mcpAsyncClient.readResource(firstResource)).consumeNextWith(result -> {
355-
assertThat(result).isNotNull();
356-
assertThat(result.contents()).isNotNull();
357-
}).verifyComplete();
415+
withClient(createMcpTransport(), client -> {
416+
Flux<McpSchema.ReadResourceResult> resources = client.initialize()
417+
.then(client.listResources(null))
418+
.flatMapMany(r -> Flux.fromIterable(r.resources()))
419+
.flatMap(r -> client.readResource(r));
420+
421+
StepVerifier.create(resources).recordWith(ArrayList::new).consumeRecordedWith(readResourceResults -> {
422+
423+
for (ReadResourceResult result : readResourceResults) {
424+
425+
assertThat(result).isNotNull();
426+
assertThat(result.contents()).isNotNull().isNotEmpty();
427+
428+
// Validate each content item
429+
for (ResourceContents content : result.contents()) {
430+
assertThat(content).isNotNull();
431+
assertThat(content.uri()).isNotNull().isNotEmpty();
432+
assertThat(content.mimeType()).isNotNull().isNotEmpty();
433+
434+
// Validate content based on its type with more comprehensive
435+
// checks
436+
switch (content.mimeType()) {
437+
case "text/plain" -> {
438+
TextResourceContents textContent = assertInstanceOf(TextResourceContents.class,
439+
content);
440+
assertThat(textContent.text()).isNotNull().isNotEmpty();
441+
assertThat(textContent.uri()).isNotEmpty();
442+
}
443+
case "application/octet-stream" -> {
444+
BlobResourceContents blobContent = assertInstanceOf(BlobResourceContents.class,
445+
content);
446+
assertThat(blobContent.blob()).isNotNull().isNotEmpty();
447+
assertThat(blobContent.uri()).isNotNull().isNotEmpty();
448+
// Validate base64 encoding format
449+
assertThat(blobContent.blob()).matches("^[A-Za-z0-9+/]*={0,2}$");
450+
}
451+
default -> {
452+
453+
// Still validate basic properties
454+
if (content instanceof TextResourceContents textContent) {
455+
assertThat(textContent.text()).isNotNull();
456+
}
457+
else if (content instanceof BlobResourceContents blobContent) {
458+
assertThat(blobContent.blob()).isNotNull();
459+
}
460+
}
461+
}
462+
}
358463
}
359-
}).verifyComplete();
464+
})
465+
.expectNextCount(10) // Expect 10 elements
466+
.verifyComplete();
360467
});
361468
}
362469

0 commit comments

Comments
 (0)