Testing with McpAssured
This guide covers testing MCP servers using the McpAssured testing utility, which provides a fluent API for writing comprehensive integration tests.
Overview
McpAssured is a testing library designed specifically for MCP servers. It provides:
-
Fluent API: Readable, chainable test methods
-
Transport Support: Test SSE, Streamable HTTP, and WebSocket transports
-
Type-Safe Assertions: Strongly-typed response validation
-
Batch Testing: Send multiple requests and validate results together
-
Request/Response Inspection: Access raw JSON-RPC messages for advanced scenarios
Adding the Dependency
Add the test dependency to your project:
<dependency>
<groupId>io.quarkiverse.mcp</groupId>
<artifactId>quarkus-mcp-server-test</artifactId>
<scope>test</scope>
</dependency>
Creating Test Clients
McpAssured provides three transport-specific client types.
SSE Transport Client
import io.quarkiverse.mcp.server.test.McpAssured;
import io.quarkiverse.mcp.server.test.McpAssured.McpSseTestClient;
@Test
public void testSseTransport() {
McpSseTestClient client = McpAssured.newConnectedSseClient(); (1)
// Test operations...
client.disconnect(); (2)
}
| 1 | Creates and connects a new SSE client with default configuration |
| 2 | Clean up the connection after testing |
Streamable HTTP Transport Client
import io.quarkiverse.mcp.server.test.McpAssured.McpStreamableTestClient;
@Test
public void testStreamableTransport() {
McpStreamableTestClient client =
McpAssured.newConnectedStreamableClient(); (1)
// Test operations...
client.disconnect();
}
| 1 | Creates and connects a new Streamable HTTP client |
WebSocket Transport Client
import io.quarkiverse.mcp.server.test.McpAssured.McpWebSocketTestClient;
@Test
public void testWebSocketTransport() {
McpWebSocketTestClient client =
McpAssured.newConnectedWebSocketClient(); (1)
// Test operations...
client.disconnect();
}
| 1 | Creates and connects a new WebSocket client |
Testing Tools
Listing Tools
Test that your tools are registered and exposed correctly:
import static org.junit.jupiter.api.Assertions.*;
@Test
public void testToolsList() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.toolsList(page -> {
assertEquals(3, page.size()); (1)
ToolInfo tool = page.findByName("calculate"); (2)
assertEquals("Perform calculations", tool.description());
assertNotNull(tool.inputSchema()); (3)
})
.thenAssertResults(); (4)
}
| 1 | Verify the number of tools |
| 2 | Find a specific tool by name |
| 3 | Verify tool metadata (schema, description, etc.) |
| 4 | Execute assertions |
Calling Tools
Test tool execution and response validation:
@Test
public void testToolCall() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.toolsCall("greet",
Map.of("name", "Alice"), (1)
response -> {
assertFalse(response.isError()); (2)
TextContent content = response.content().get(0).asText(); (3)
assertEquals("Hello, Alice!", content.text());
})
.thenAssertResults();
}
| 1 | Pass tool arguments as a Map |
| 2 | Verify the call succeeded |
| 3 | Extract and validate response content |
Testing Tool Errors
Verify error handling in your tools:
@Test
public void testToolError() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.toolsCall("divide",
Map.of("a", 10, "b", 0), (1)
response -> {
assertTrue(response.isError()); (2)
String errorText = response.content().get(0).asText().text();
assertTrue(errorText.contains("division by zero")); (3)
})
.thenAssertResults();
}
| 1 | Trigger an error condition |
| 2 | Verify the response indicates an error |
| 3 | Check the error message content |
Testing Protocol Errors
Test JSON-RPC protocol errors (McpException):
import io.quarkiverse.mcp.server.JsonRpcErrorCodes;
@Test
public void testProtocolError() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.toolsCall("secureOperation")
.withErrorAssert(error -> { (1)
assertEquals(JsonRpcErrorCodes.SECURITY_ERROR, error.code()); (2)
assertEquals("Unauthorized", error.message());
})
.send()
.thenAssertResults();
}
| 1 | Use withErrorAssert for protocol errors |
| 2 | Validate the JSON-RPC error code and message |
Testing Resources
Listing Resources
@Test
public void testResourcesList() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.resourcesList(page -> {
assertEquals(2, page.size());
ResourceInfo resource = page.findByUri("file:///config.json");
assertEquals("application/json", resource.mimeType());
assertEquals("Configuration", resource.name());
})
.thenAssertResults();
}
Reading Resources
@Test
public void testResourceRead() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.resourcesRead("file:///config.json", response -> {
TextResourceContents contents =
response.contents().get(0).asText();
assertTrue(contents.text().contains("\"enabled\": true"));
assertEquals("file:///config.json", contents.uri());
})
.thenAssertResults();
}
Testing Resource Templates
@Test
public void testResourceTemplates() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.resourcesTemplatesList(page -> {
ResourceTemplateInfo template =
page.findByUriTemplate("file:///{path}");
assertEquals("Dynamic file access", template.description());
})
.resourcesRead("file:///data/users.json", response -> {
// Validate templated resource
assertFalse(response.contents().isEmpty());
})
.thenAssertResults();
}
Testing Prompts
Listing Prompts
@Test
public void testPromptsList() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.promptsList(page -> {
PromptInfo prompt = page.findByName("code_review");
assertEquals("Code Review Assistant", prompt.title());
// Verify prompt arguments
List<PromptArgument> args = prompt.arguments();
assertEquals(2, args.size());
assertTrue(args.stream()
.anyMatch(arg -> arg.name().equals("language")));
})
.thenAssertResults();
}
Getting Prompts
@Test
public void testPromptGet() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.promptsGet("code_review",
Map.of("language", "java", "level", "senior"),
response -> {
assertEquals(1, response.messages().size());
PromptMessage message = response.messages().get(0);
assertEquals(Role.USER, message.role());
String text = message.content().asText().text();
assertTrue(text.contains("java"));
})
.thenAssertResults();
}
Batch Testing
Send multiple requests and validate them together:
@Test
public void testBatchOperations() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.toolsCall("add", Map.of("a", 5, "b", 3),
r -> assertEquals("8", r.content().get(0).asText().text()))
.toolsCall("multiply", Map.of("a", 5, "b", 3),
r -> assertEquals("15", r.content().get(0).asText().text()))
.toolsCall("subtract", Map.of("a", 5, "b", 3),
r -> assertEquals("2", r.content().get(0).asText().text()))
.thenAssertResults(); (1)
}
| 1 | All requests are sent and validated together |
Advanced Client Configuration
Custom Base URI
@Test
public void testCustomUri() {
McpSseTestClient client = McpAssured.newSseClient()
.setBaseUri(URI.create("http://localhost:8081")) (1)
.build()
.connect();
// Test operations...
}
| 1 | Override the default base URI |
Client Capabilities
import io.quarkiverse.mcp.server.ClientCapability;
@Test
public void testClientCapabilities() {
McpSseTestClient client = McpAssured.newSseClient()
.setClientCapabilities(
ClientCapability.SAMPLING, (1)
ClientCapability.ROOTS)
.build()
.connect();
// Server will see these capabilities during initialization
}
| 1 | Specify which client capabilities to advertise |
Custom Headers (HTTP Transports)
@Test
public void testCustomHeaders() {
McpSseTestClient client = McpAssured.newSseClient()
.setAdditionalHeaders(msg -> {
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
headers.add("X-API-Key", "secret123"); (1)
headers.add("X-Request-ID", UUID.randomUUID().toString());
return headers;
})
.build()
.connect();
}
| 1 | Add custom HTTP headers for each request |
Inspecting Raw Messages
Access raw JSON-RPC messages for advanced testing:
@Test
public void testRawMessages() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.toolsCall("echo", Map.of("message", "test"))
.send()
.thenAssertResults();
Snapshot snapshot = client.snapshot(); (1)
assertEquals(1, snapshot.requests().size()); (2)
JsonObject request = snapshot.requests().get(0);
assertEquals("tools/call", request.getString("method"));
assertEquals(1, snapshot.responses().size()); (3)
JsonObject response = snapshot.responses().get(0);
assertNotNull(response.getJsonObject("result"));
}
| 1 | Capture a snapshot of all messages |
| 2 | Inspect raw request messages |
| 3 | Inspect raw response messages |
Testing Server Notifications
Wait for and validate server-initiated notifications:
@Test
public void testProgressNotifications() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
// Trigger an operation that sends progress notifications
client.sendAndForget(
client.newRequest("tools/call")
.put("params", new JsonObject()
.put("name", "long_operation")));
Snapshot snapshot = client.waitForNotifications(2); (1)
assertEquals(2, snapshot.notifications().size());
JsonObject notification = snapshot.notifications().get(0);
assertEquals("notifications/progress", notification.getString("method")); (2)
}
| 1 | Wait for expected number of notifications |
| 2 | Validate notification content |
Testing Metadata
Validate metadata in tools, resources, and responses:
@Test
public void testMetadata() {
McpSseTestClient client = McpAssured.newConnectedSseClient();
client.when()
.toolsList(page -> {
ToolInfo tool = page.findByName("alpha");
JsonObject meta = tool.meta(); (1)
assertNotNull(meta);
assertEquals("high", meta.getString("priceLevel"));
assertEquals(100, meta.getInteger("price"));
})
.toolsCall("alpha", Map.of("price", 1), response -> {
TextContent content = response.content().get(0).asText();
assertEquals(1, content._meta().size()); (2)
Map<MetaKey, Object> responseMeta = response._meta(); (3)
assertTrue(responseMeta.containsKey(new MetaKey("alpha-foo")));
})
.thenAssertResults();
}
| 1 | Tool-level metadata |
| 2 | Content-level metadata |
| 3 | Response-level metadata |