Fix everything-server-based integration tests (#756)
* Pin `npx @modelcontextprotocol/server-everything` version to `2025.12.18`. * Replace `tzolov/mcp-everything-server` Docker image with `node:lts-alpine` + `npx` command. * Handle HTTP 202 special case. * Fix test assertions. The recent rollout of everything-server broke integration tests which take the latest version from the node registry. This PR unifies the everything-server usage - the Testcontainers Docker setup uses the same version as the STDIO npx-based tests and no longer relies on tzolov/mcp-everything-server. Signed-off-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
This commit is contained in:
parent
f7a460fb50
commit
af65356416
22 changed files with 144 additions and 139 deletions
|
|
@ -491,7 +491,9 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
|||
.firstValue(HttpHeaders.CONTENT_LENGTH)
|
||||
.orElse(null);
|
||||
|
||||
if (contentType.isBlank() || "0".equals(contentLength)) {
|
||||
// For empty content or HTTP code 202 (ACCEPTED), assume success
|
||||
if (contentType.isBlank() || "0".equals(contentLength) || statusCode == 202) {
|
||||
// if (contentType.isBlank() || "0".equals(contentLength)) {
|
||||
logger.debug("No body returned for POST in session {}", sessionRepresentation);
|
||||
// No content type means no response body, so we can just
|
||||
// return an empty stream
|
||||
|
|
|
|||
|
|
@ -48,10 +48,9 @@ public abstract class AbstractMcpAsyncClientResiliencyTests {
|
|||
static Network network = Network.newNetwork();
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("everything-server")
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public abstract class AbstractMcpAsyncClientTests {
|
|||
}
|
||||
|
||||
protected Duration getInitializationTimeout() {
|
||||
return Duration.ofSeconds(2);
|
||||
return Duration.ofSeconds(20);
|
||||
}
|
||||
|
||||
McpAsyncClient client(McpClientTransport transport) {
|
||||
|
|
@ -503,57 +503,64 @@ public abstract class AbstractMcpAsyncClientTests {
|
|||
|
||||
@Test
|
||||
void testReadResource() {
|
||||
AtomicInteger resourceCount = new AtomicInteger();
|
||||
withClient(createMcpTransport(), client -> {
|
||||
Flux<McpSchema.ReadResourceResult> resources = client.initialize()
|
||||
.then(client.listResources(null))
|
||||
.flatMapMany(r -> Flux.fromIterable(r.resources()))
|
||||
.flatMapMany(r -> {
|
||||
List<Resource> l = r.resources();
|
||||
resourceCount.set(l.size());
|
||||
return Flux.fromIterable(l);
|
||||
})
|
||||
.flatMap(r -> client.readResource(r));
|
||||
|
||||
StepVerifier.create(resources).recordWith(ArrayList::new).consumeRecordedWith(readResourceResults -> {
|
||||
StepVerifier.create(resources)
|
||||
.recordWith(ArrayList::new)
|
||||
.thenConsumeWhile(res -> true)
|
||||
.consumeRecordedWith(readResourceResults -> {
|
||||
assertThat(readResourceResults.size()).isEqualTo(resourceCount.get());
|
||||
for (ReadResourceResult result : readResourceResults) {
|
||||
|
||||
for (ReadResourceResult result : readResourceResults) {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.contents()).isNotNull().isNotEmpty();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.contents()).isNotNull().isNotEmpty();
|
||||
// Validate each content item
|
||||
for (ResourceContents content : result.contents()) {
|
||||
assertThat(content).isNotNull();
|
||||
assertThat(content.uri()).isNotNull().isNotEmpty();
|
||||
assertThat(content.mimeType()).isNotNull().isNotEmpty();
|
||||
|
||||
// Validate each content item
|
||||
for (ResourceContents content : result.contents()) {
|
||||
assertThat(content).isNotNull();
|
||||
assertThat(content.uri()).isNotNull().isNotEmpty();
|
||||
assertThat(content.mimeType()).isNotNull().isNotEmpty();
|
||||
|
||||
// Validate content based on its type with more comprehensive
|
||||
// checks
|
||||
switch (content.mimeType()) {
|
||||
case "text/plain" -> {
|
||||
TextResourceContents textContent = assertInstanceOf(TextResourceContents.class,
|
||||
content);
|
||||
assertThat(textContent.text()).isNotNull().isNotEmpty();
|
||||
assertThat(textContent.uri()).isNotEmpty();
|
||||
}
|
||||
case "application/octet-stream" -> {
|
||||
BlobResourceContents blobContent = assertInstanceOf(BlobResourceContents.class,
|
||||
content);
|
||||
assertThat(blobContent.blob()).isNotNull().isNotEmpty();
|
||||
assertThat(blobContent.uri()).isNotNull().isNotEmpty();
|
||||
// Validate base64 encoding format
|
||||
assertThat(blobContent.blob()).matches("^[A-Za-z0-9+/]*={0,2}$");
|
||||
}
|
||||
default -> {
|
||||
|
||||
// Still validate basic properties
|
||||
if (content instanceof TextResourceContents textContent) {
|
||||
assertThat(textContent.text()).isNotNull();
|
||||
// Validate content based on its type with more comprehensive
|
||||
// checks
|
||||
switch (content.mimeType()) {
|
||||
case "text/plain" -> {
|
||||
TextResourceContents textContent = assertInstanceOf(TextResourceContents.class,
|
||||
content);
|
||||
assertThat(textContent.text()).isNotNull().isNotEmpty();
|
||||
assertThat(textContent.uri()).isNotEmpty();
|
||||
}
|
||||
else if (content instanceof BlobResourceContents blobContent) {
|
||||
assertThat(blobContent.blob()).isNotNull();
|
||||
case "application/octet-stream" -> {
|
||||
BlobResourceContents blobContent = assertInstanceOf(BlobResourceContents.class,
|
||||
content);
|
||||
assertThat(blobContent.blob()).isNotNull().isNotEmpty();
|
||||
assertThat(blobContent.uri()).isNotNull().isNotEmpty();
|
||||
// Validate base64 encoding format
|
||||
assertThat(blobContent.blob()).matches("^[A-Za-z0-9+/]*={0,2}$");
|
||||
}
|
||||
default -> {
|
||||
|
||||
// Still validate basic properties
|
||||
if (content instanceof TextResourceContents textContent) {
|
||||
assertThat(textContent.text()).isNotNull();
|
||||
}
|
||||
else if (content instanceof BlobResourceContents blobContent) {
|
||||
assertThat(blobContent.blob()).isNotNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.expectNextCount(10) // Expect 10 elements
|
||||
})
|
||||
.verifyComplete();
|
||||
});
|
||||
}
|
||||
|
|
@ -693,7 +700,6 @@ public abstract class AbstractMcpAsyncClientTests {
|
|||
assertThat(result.capabilities()).isNotNull();
|
||||
}).verifyComplete());
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
// Logging Tests
|
||||
// ---------------------------------------
|
||||
|
|
@ -773,7 +779,7 @@ public abstract class AbstractMcpAsyncClientTests {
|
|||
if (!(content instanceof McpSchema.TextContent text))
|
||||
return;
|
||||
|
||||
assertThat(text.text()).endsWith(response); // Prefixed
|
||||
assertThat(text.text()).contains(response);
|
||||
});
|
||||
|
||||
// Verify sampling request parameters received in our callback
|
||||
|
|
|
|||
|
|
@ -625,7 +625,7 @@ public abstract class AbstractMcpSyncClientTests {
|
|||
if (!(content instanceof McpSchema.TextContent text))
|
||||
return;
|
||||
|
||||
assertThat(text.text()).endsWith(response); // Prefixed
|
||||
assertThat(text.text()).contains(response);
|
||||
});
|
||||
|
||||
// Verify sampling request parameters received in our callback
|
||||
|
|
|
|||
|
|
@ -17,10 +17,9 @@ public class HttpClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncCl
|
|||
|
||||
private static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -30,10 +30,9 @@ public class HttpClientStreamableHttpSyncClientTests extends AbstractMcpSyncClie
|
|||
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -36,10 +36,9 @@ public class HttpSseMcpAsyncClientLostConnectionTests {
|
|||
static Network network = Network.newNetwork();
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js sse")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 sse")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("everything-server")
|
||||
|
|
|
|||
|
|
@ -23,10 +23,9 @@ class HttpSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
|
|||
|
||||
private static String host = "http://localhost:3004";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js sse")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 sse")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -36,10 +36,9 @@ class HttpSseMcpSyncClientTests extends AbstractMcpSyncClientTests {
|
|||
|
||||
static String host = "http://localhost:3003";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js sse")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 sse")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ public final class ServerParameterUtils {
|
|||
public static ServerParameters createServerParameters() {
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
return ServerParameters.builder("cmd.exe")
|
||||
.args("/c", "npx.cmd", "-y", "@modelcontextprotocol/server-everything", "stdio")
|
||||
.args("/c", "npx.cmd", "-y", "@modelcontextprotocol/server-everything@2025.12.18", "stdio")
|
||||
.build();
|
||||
}
|
||||
return ServerParameters.builder("npx").args("-y", "@modelcontextprotocol/server-everything", "stdio").build();
|
||||
return ServerParameters.builder("npx")
|
||||
.args("-y", "@modelcontextprotocol/server-everything@2025.12.18", "stdio")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ class HttpClientSseClientTransportTests {
|
|||
static String host = "http://localhost:3001";
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js sse")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 sse")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ class HttpClientStreamableHttpTransportTest {
|
|||
.create(Map.of("test-transport-context-key", "some-value"));
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -318,7 +318,8 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
long contentLength = response.headers().contentLength().orElse(-1);
|
||||
// Existing SDKs consume notifications with no response body nor
|
||||
// content type
|
||||
if (contentType.isEmpty() || contentLength == 0) {
|
||||
if (contentType.isEmpty() || contentLength == 0
|
||||
|| response.statusCode().equals(HttpStatus.ACCEPTED)) {
|
||||
logger.trace("Message was successfully sent via POST for session {}",
|
||||
sessionRepresentation);
|
||||
// signal the caller that the message was successfully
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@ public class WebClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncCli
|
|||
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@ public class WebClientStreamableHttpSyncClientTests extends AbstractMcpSyncClien
|
|||
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -26,13 +26,12 @@ class WebFluxSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
|
|||
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js sse")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 sse")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404).forPort(3001));
|
||||
|
||||
@Override
|
||||
protected McpClientTransport createMcpTransport() {
|
||||
|
|
|
|||
|
|
@ -25,10 +25,9 @@ class WebFluxSseMcpSyncClientTests extends AbstractMcpSyncClientTests {
|
|||
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js sse")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 sse")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ class WebClientStreamableHttpTransportTest {
|
|||
static WebClient.Builder builder;
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ class WebFluxSseClientTransportTests {
|
|||
static String host = "http://localhost:3001";
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js sse")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 sse")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withExposedPorts(3001)
|
||||
.waitingFor(Wait.forHttp("/").forStatusCode(404));
|
||||
|
|
|
|||
|
|
@ -47,10 +47,9 @@ public abstract class AbstractMcpAsyncClientResiliencyTests {
|
|||
static Network network = Network.newNetwork();
|
||||
static String host = "http://localhost:3001";
|
||||
|
||||
// Uses the https://github.com/tzolov/mcp-everything-server-docker-image
|
||||
@SuppressWarnings("resource")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v3")
|
||||
.withCommand("node dist/index.js streamableHttp")
|
||||
static GenericContainer<?> container = new GenericContainer<>("docker.io/node:lts-alpine3.23")
|
||||
.withCommand("npx -y @modelcontextprotocol/server-everything@2025.12.18 streamableHttp")
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("everything-server")
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue