This is an automated email from the ASF dual-hosted git repository.
rustyrazorblade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cassandra-easy-stress.git
The following commit(s) were added to refs/heads/main by this push:
new 3b12228 [MCP] Add Streamable HTTP transport (#106)
3b12228 is described below
commit 3b1222846d4a99d2d18f65de3588bd919ac6187b
Author: Yuki Morishita <[email protected]>
AuthorDate: Sat Oct 10 04:26:34 2026 +0900
[MCP] Add Streamable HTTP transport (#106)
Patch by Yuki Morishita, Reviewed by Jon Haddad for #106
---
README.md | 2 +-
build.gradle.kts | 4 +
gradle/libs.versions.toml | 2 +-
.../apache/cassandra/easystress/commands/Server.kt | 77 +++++-----
.../easystress/server/StressTestManager.kt | 19 +--
.../easystress/server/ToolInputSchemaGenerator.kt | 10 +-
.../easystress/server/tools/FieldsTool.kt | 15 +-
.../easystress/server/tools/ListWorkloadsTool.kt | 15 +-
.../easystress/server/tools/WorkloadInfoTool.kt | 17 ++-
.../cassandra/easystress/commands/MCPServerTest.kt | 149 ++++++++++++++++---
.../easystress/server/StressTestManagerTest.kt | 160 +++++++++++----------
.../easystress/server/tools/FieldsToolTest.kt | 30 ++--
.../server/tools/ListWorkloadsToolTest.kt | 26 ++--
.../server/tools/WorkloadInfoToolTest.kt | 34 ++---
14 files changed, 344 insertions(+), 216 deletions(-)
diff --git a/README.md b/README.md
index bdf60b0..8d78d20 100644
--- a/README.md
+++ b/README.md
@@ -345,7 +345,7 @@ To test the MCP server integration with Claude Code:
1. Add the MCP server (assuming it's running locally):
```bash
- claude mcp add -t sse cassandra-easy-stress http://localhost:9000/sse
+ claude mcp add -t http cassandra-easy-stress http://localhost:9000/mcp
```
2. Run the MCP integration test prompt:
diff --git a/build.gradle.kts b/build.gradle.kts
index 160320a..e6f806d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -16,6 +16,7 @@
* limitations under the License.
*/
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.netflix.gradle.plugins.deb.Deb
import com.netflix.gradle.plugins.rpm.Rpm
import io.gitlab.arturbosch.detekt.Detekt
@@ -346,3 +347,6 @@ tasks.withType<Detekt>().configureEach {
tasks.withType<DetektCreateBaselineTask>().configureEach {
jvmTarget = "21"
}
+tasks.withType<ShadowJar>().configureEach {
+ isZip64 = true
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5d9f65f..949d613 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -32,7 +32,7 @@ junit-params = "6.1.3"
assertj = "3.9.1"
mockk = "1.14.3"
coroutines-test = "1.9.0"
-mcp-sdk = "0.7.2"
+mcp-sdk = "0.15.0"
ktor = "3.3.0"
testcontainers = "2.0.5"
diff --git a/src/main/kotlin/org/apache/cassandra/easystress/commands/Server.kt
b/src/main/kotlin/org/apache/cassandra/easystress/commands/Server.kt
index bc29299..45fcabc 100644
--- a/src/main/kotlin/org/apache/cassandra/easystress/commands/Server.kt
+++ b/src/main/kotlin/org/apache/cassandra/easystress/commands/Server.kt
@@ -19,24 +19,19 @@ package org.apache.cassandra.easystress.commands
import com.beust.jcommander.Parameter
import com.beust.jcommander.Parameters
-import io.ktor.serialization.kotlinx.json.json
-import io.ktor.server.application.install
import io.ktor.server.cio.CIO
import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.EmbeddedServer
import io.ktor.server.engine.embeddedServer
-import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
-import io.ktor.server.sse.heartbeat
import io.ktor.sse.ServerSentEvent
-import io.modelcontextprotocol.kotlin.sdk.Implementation
-import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
-import io.modelcontextprotocol.kotlin.sdk.server.mcp
-import kotlinx.serialization.json.Json
+import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import org.apache.cassandra.easystress.server.StressTestManager
import org.apache.cassandra.easystress.server.tools.FieldsTool
import org.apache.cassandra.easystress.server.tools.ListWorkloadsTool
@@ -58,7 +53,7 @@ import kotlin.time.Duration.Companion.seconds
*
* The server provides:
* - HTTP endpoint on configurable port (default: 9000)
- * - Server-Sent Events (SSE) for MCP communication
+ * - Streamable HTTP on `/mcp` for MCP communication
* - JSON-based tool invocation and responses
* - Thread-safe execution of concurrent tool calls
*
@@ -72,7 +67,7 @@ import kotlin.time.Duration.Companion.seconds
*
* Usage:
* ```
- * cassandra-easy-stress mcp [-p 9000]
+ * cassandra-easy-stress server [-p 9000]
* ```
*
* The server runs indefinitely until interrupted. Use Ctrl+C or call stop()
to terminate.
@@ -140,18 +135,14 @@ class Server : IStressCommand {
)
/**
- * Creates and configures the embedded Ktor server.
+ * Creates an MCP server instance with all available tools registered.
*
- * Configures:
- * - CIO engine for async I/O
- * - JSON content negotiation with lenient parsing
- * - Basic HTTP route for health checks
- * - MCP protocol support with SSE transport
- * - Registration of all available tools
+ * The Streamable HTTP transport calls this factory, possibly more than
once. Every
+ * instance shares [tools], so all clients see the same
[StressTestManager].
*
- * @return Configured but not yet started embedded server instance
+ * @return MCP server advertising the tools capability
*/
- private fun getServer(): EmbeddedServer<CIOApplicationEngine,
CIOApplicationEngine.Configuration> {
+ private fun createMcpServer(): Server {
val server =
Server(
serverInfo =
@@ -174,19 +165,32 @@ class Server : IStressCommand {
)
// Register all tools with the server
- server.addTools(
- tools,
- )
+ server.addTools(tools)
+ return server
+ }
- return embeddedServer(CIO, port = port) {
- install(ContentNegotiation) {
- json(
- Json {
- prettyPrint = true
- isLenient = true
- ignoreUnknownKeys = true
- },
- )
+ /**
+ * Creates and configures the embedded Ktor server.
+ *
+ * Configures:
+ * - CIO engine for async I/O
+ * - MCP Streamable HTTP transport on `/mcp`, with an SSE heartbeat every
second
+ * - Basic HTTP route on `/` for health checks
+ *
+ * @return Configured but not yet started embedded server instance
+ */
+ private fun getServer(): EmbeddedServer<CIOApplicationEngine,
CIOApplicationEngine.Configuration> =
+ embeddedServer(CIO, port = port) {
+ // Streamable HTTP on /mcp
+ mcpStreamableHttp(
+ path = "/mcp",
+ sseHeartbeatConfig = {
+ period = 1.seconds
+ // Clients parse every data field as JSON-RPC, so the
heartbeat must be a comment.
+ event = ServerSentEvent(comments = "heartbeat")
+ },
+ ) {
+ createMcpServer()
}
routing {
@@ -194,16 +198,5 @@ class Server : IStressCommand {
call.respondText("MCP Server is running")
}
}
-
- // MCP server configuration with SSE
- mcp {
- heartbeat {
- period = 1.seconds
- event = ServerSentEvent("heartbeat")
- }
- // Register all tools
- server
- }
}
- }
}
diff --git
a/src/main/kotlin/org/apache/cassandra/easystress/server/StressTestManager.kt
b/src/main/kotlin/org/apache/cassandra/easystress/server/StressTestManager.kt
index 598bb35..cdad497 100644
---
a/src/main/kotlin/org/apache/cassandra/easystress/server/StressTestManager.kt
+++
b/src/main/kotlin/org/apache/cassandra/easystress/server/StressTestManager.kt
@@ -17,13 +17,13 @@
*/
package org.apache.cassandra.easystress.server
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
-import io.modelcontextprotocol.kotlin.sdk.TextContent
-import io.modelcontextprotocol.kotlin.sdk.Tool
-import io.modelcontextprotocol.kotlin.sdk.Tool.Input
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
@@ -268,7 +268,8 @@ class StressTestManager {
handler = { request: CallToolRequest ->
withContext(Dispatchers.IO) {
try {
- val runCommand =
Json.decodeFromJsonElement(Run.serializer(), request.arguments)
+ val arguments = request.params.arguments ?:
EmptyJsonObject
+ val runCommand =
Json.decodeFromJsonElement(Run.serializer(), arguments)
if (tryStart(runCommand)) {
val jobId = getCurrentJobId() ?: "unknown"
println("Job started: $jobId")
@@ -326,7 +327,7 @@ class StressTestManager {
name = "stop",
title = null,
description = "Stop a currently running stress test",
- inputSchema = Input(properties = EmptyJsonObject),
+ inputSchema = ToolSchema(properties = EmptyJsonObject),
outputSchema = null,
annotations = null,
),
@@ -378,7 +379,7 @@ class StressTestManager {
name = "status",
title = null,
description = "Get status of running stress tests.
Latency numbers are expressed in microseconds.",
- inputSchema = Input(properties = EmptyJsonObject),
+ inputSchema = ToolSchema(properties = EmptyJsonObject),
outputSchema = null,
annotations = null,
),
diff --git
a/src/main/kotlin/org/apache/cassandra/easystress/server/ToolInputSchemaGenerator.kt
b/src/main/kotlin/org/apache/cassandra/easystress/server/ToolInputSchemaGenerator.kt
index 259724f..17d392a 100644
---
a/src/main/kotlin/org/apache/cassandra/easystress/server/ToolInputSchemaGenerator.kt
+++
b/src/main/kotlin/org/apache/cassandra/easystress/server/ToolInputSchemaGenerator.kt
@@ -19,7 +19,7 @@ package org.apache.cassandra.easystress.server
import com.beust.jcommander.DynamicParameter
import com.beust.jcommander.Parameter
-import io.modelcontextprotocol.kotlin.sdk.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
@@ -30,10 +30,10 @@ import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
/**
- * Generates MCP Tool.Input schemas from IStressCommand classes.
+ * Generates MCP ToolSchema from IStressCommand classes.
*
* Directly reads @Parameter and @DynamicParameter annotations using Kotlin
reflection
- * and converts them to MCP Tool.Input format. No intermediate data structures.
+ * and converts them to MCP ToolSchema format. No intermediate data structures.
*
* Usage:
* ```kotlin
@@ -44,7 +44,7 @@ import kotlin.reflect.jvm.javaField
class ToolInputSchemaGenerator(
private val commandClass: KClass<out IStressCommand>,
) {
- fun generateToolInput(): Tool.Input {
+ fun generateToolInput(): ToolSchema {
val commandInstance = createInstance()
val requiredFields = mutableListOf<String>()
@@ -102,7 +102,7 @@ class ToolInputSchemaGenerator(
}
}
- return Tool.Input(
+ return ToolSchema(
properties = properties,
required = requiredFields,
)
diff --git
a/src/main/kotlin/org/apache/cassandra/easystress/server/tools/FieldsTool.kt
b/src/main/kotlin/org/apache/cassandra/easystress/server/tools/FieldsTool.kt
index ebf0462..a59fe8a 100644
--- a/src/main/kotlin/org/apache/cassandra/easystress/server/tools/FieldsTool.kt
+++ b/src/main/kotlin/org/apache/cassandra/easystress/server/tools/FieldsTool.kt
@@ -17,12 +17,13 @@
*/
package org.apache.cassandra.easystress.server.tools
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
-import io.modelcontextprotocol.kotlin.sdk.TextContent
-import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.addJsonObject
@@ -65,11 +66,11 @@ val FieldsTool =
name = "fields",
title = null,
description = "List all available field generators",
- inputSchema = Tool.Input(properties = EmptyJsonObject),
+ inputSchema = ToolSchema(properties = EmptyJsonObject),
outputSchema = null,
annotations = null,
),
- handler = ::handleFields,
+ handler = { request -> handleFields(request) },
)
private suspend fun handleFields(request: CallToolRequest): CallToolResult =
diff --git
a/src/main/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsTool.kt
b/src/main/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsTool.kt
index 03c2ae7..3233744 100644
---
a/src/main/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsTool.kt
+++
b/src/main/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsTool.kt
@@ -17,12 +17,13 @@
*/
package org.apache.cassandra.easystress.server.tools
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
-import io.modelcontextprotocol.kotlin.sdk.TextContent
-import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.addJsonObject
@@ -63,11 +64,11 @@ val ListWorkloadsTool =
name = "list_workloads",
title = null,
description = "List all available workloads",
- inputSchema = Tool.Input(properties = EmptyJsonObject),
+ inputSchema = ToolSchema(properties = EmptyJsonObject),
outputSchema = null,
annotations = null,
),
- handler = ::handleListWorkloads,
+ handler = { request -> handleListWorkloads(request) },
)
private suspend fun handleListWorkloads(request: CallToolRequest):
CallToolResult =
diff --git
a/src/main/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoTool.kt
b/src/main/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoTool.kt
index 200016d..c4c3822 100644
---
a/src/main/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoTool.kt
+++
b/src/main/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoTool.kt
@@ -17,11 +17,13 @@
*/
package org.apache.cassandra.easystress.server.tools
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.TextContent
-import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
@@ -85,7 +87,7 @@ val WorkloadInfoTool =
title = null,
description = "Get details of a specific workload",
inputSchema =
- Tool.Input(
+ ToolSchema(
properties =
buildJsonObject {
putJsonObject("workload") {
@@ -98,12 +100,13 @@ val WorkloadInfoTool =
outputSchema = null,
annotations = null,
),
- handler = ::handleWorkloadInfo,
+ handler = { request -> handleWorkloadInfo(request) },
)
private suspend fun handleWorkloadInfo(request: CallToolRequest):
CallToolResult =
try {
- val workloadName =
Json.decodeFromJsonElement<WorkloadInfoRequest>(request.arguments).workload
+ val arguments = request.params.arguments ?: EmptyJsonObject
+ val workloadName =
Json.decodeFromJsonElement<WorkloadInfoRequest>(arguments).workload
logger.info { "Info request for $workloadName" }
diff --git
a/src/test/kotlin/org/apache/cassandra/easystress/commands/MCPServerTest.kt
b/src/test/kotlin/org/apache/cassandra/easystress/commands/MCPServerTest.kt
index fdaf309..347d52e 100644
--- a/src/test/kotlin/org/apache/cassandra/easystress/commands/MCPServerTest.kt
+++ b/src/test/kotlin/org/apache/cassandra/easystress/commands/MCPServerTest.kt
@@ -20,9 +20,10 @@ package org.apache.cassandra.easystress.commands
import com.beust.jcommander.DynamicParameter
import com.beust.jcommander.Parameter
import com.beust.jcommander.Parameters
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonObject
@@ -91,7 +92,7 @@ class MCPServerTest {
}
@Test
- fun `should have MCP SSE endpoint`() {
+ fun `should have MCP Streamable HTTP endpoint`() {
server = Server()
server!!.port = 8182
@@ -108,9 +109,8 @@ class MCPServerTest {
// Give the server a moment to start
Thread.sleep(2000)
- // The MCP SDK automatically sets up SSE at the root with proper
headers
- // We just verify the server is running, as the MCP endpoint is
handled by the SDK
- val serverRunning =
+ // Verify root endpoint
+ val rootRunning =
try {
val url = URL("http://localhost:8182/")
val connection = url.openConnection() as HttpURLConnection
@@ -123,14 +123,116 @@ class MCPServerTest {
} catch (e: Exception) {
false
}
+ assertThat(rootRunning).isTrue()
- assertThat(serverRunning).isTrue()
+ // Verify /mcp endpoint responds
+ val mcpEndpointReachable =
+ try {
+ val url = URL("http://localhost:8182/mcp")
+ val connection = url.openConnection() as HttpURLConnection
+ connection.requestMethod = "GET"
+ connection.connectTimeout = 1000
+ connection.readTimeout = 1000
+ val responseCode = connection.responseCode
+ connection.disconnect()
+ // GET on /mcp without session or query may return 400 or 405
or 200, which confirms the route is active
+ responseCode > 0
+ } catch (e: Exception) {
+ false
+ }
+ assertThat(mcpEndpointReachable).isTrue()
// Stop the server
server!!.stop()
serverThread.join(5000)
}
+ @Test
+ fun `GET stream should carry heartbeats as comments and only JSON-RPC in
data fields`() {
+ server = Server()
+ server!!.port = 8183
+
+ val serverThread =
+ Thread {
+ try {
+ server!!.execute()
+ } catch (e: Exception) {
+ // Expected when stopping
+ }
+ }
+ serverThread.start()
+
+ // Give the server a moment to start
+ Thread.sleep(2000)
+
+ val initialize =
+ mcpPost(
+ 8183,
+ null,
+
"""{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","""
+
+
""""capabilities":{},"clientInfo":{"name":"test","version":"1"}}}""",
+ )
+ val sessionId = initialize.getHeaderField("mcp-session-id")
+ initialize.disconnect()
+ assertThat(sessionId).isNotBlank()
+
+ val initialized = mcpPost(8183, sessionId,
"""{"jsonrpc":"2.0","method":"notifications/initialized"}""")
+ assertThat(initialized.responseCode).isEqualTo(202)
+ initialized.disconnect()
+
+ val stream = URL("http://localhost:8183/mcp").openConnection() as
HttpURLConnection
+ stream.requestMethod = "GET"
+ stream.setRequestProperty("Accept", "text/event-stream")
+ stream.setRequestProperty("Mcp-Session-Id", sessionId)
+ stream.setRequestProperty("MCP-Protocol-Version", "2025-06-18")
+ stream.connectTimeout = 1000
+ stream.readTimeout = 3000
+ assertThat(stream.responseCode).isEqualTo(200)
+
+ // Strict clients parse every data field as a JSON-RPC message, so a
heartbeat
+ // sent as data makes them drop the connection.
+ val dataFields = mutableListOf<String>()
+ var heartbeats = 0
+ val deadline = System.currentTimeMillis() + 3000
+ stream.inputStream.bufferedReader().use { reader ->
+ while (heartbeats < 2 && System.currentTimeMillis() < deadline) {
+ val line = reader.readLine() ?: break
+ when {
+ line.startsWith("data:") ->
dataFields.add(line.removePrefix("data:").trim())
+ line.startsWith(":") -> heartbeats++
+ }
+ }
+ }
+ stream.disconnect()
+
+ assertThat(heartbeats).isGreaterThanOrEqualTo(2)
+ assertThat(dataFields.filter { it.isNotEmpty() }).allSatisfy { data ->
+ val message = Json.parseToJsonElement(data).jsonObject
+
assertThat(message["jsonrpc"]?.jsonPrimitive?.content).isEqualTo("2.0")
+ }
+
+ server!!.stop()
+ serverThread.join(5000)
+ }
+
+ private fun mcpPost(
+ port: Int,
+ sessionId: String?,
+ body: String,
+ ): HttpURLConnection {
+ val connection = URL("http://localhost:$port/mcp").openConnection() as
HttpURLConnection
+ connection.requestMethod = "POST"
+ connection.doOutput = true
+ connection.setRequestProperty("Content-Type", "application/json")
+ connection.setRequestProperty("Accept", "application/json,
text/event-stream")
+ connection.setRequestProperty("MCP-Protocol-Version", "2025-06-18")
+ sessionId?.let { connection.setRequestProperty("Mcp-Session-Id", it) }
+ connection.connectTimeout = 1000
+ connection.readTimeout = 5000
+ connection.outputStream.use { it.write(body.toByteArray()) }
+ return connection
+ }
+
@Test
fun `list_workloads tool handler should return CallToolResult with JSON
content`() {
// This test directly tests the tool handler logic
@@ -177,8 +279,11 @@ class MCPServerTest {
// Create a mock request
val mockRequest =
CallToolRequest(
- name = "list_workloads",
- arguments = buildJsonObject {},
+ params =
+ CallToolRequestParams(
+ name = "list_workloads",
+ arguments = buildJsonObject {},
+ ),
)
// Call the handler
@@ -186,7 +291,7 @@ class MCPServerTest {
// Verify the result
assertThat(result).isNotNull()
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
// Verify the content is valid JSON
@@ -277,11 +382,14 @@ class MCPServerTest {
val firstWorkload = workloads.entries.first().key
val mockRequest =
CallToolRequest(
- name = "info",
- arguments =
- buildJsonObject {
- put("workload", firstWorkload)
- },
+ params =
+ CallToolRequestParams(
+ name = "info",
+ arguments =
+ buildJsonObject {
+ put("workload", firstWorkload)
+ },
+ ),
)
val result = toolHandler(mockRequest)
@@ -350,8 +458,11 @@ class MCPServerTest {
// Create a mock request
val mockRequest =
CallToolRequest(
- name = "fields",
- arguments = buildJsonObject {},
+ params =
+ CallToolRequestParams(
+ name = "fields",
+ arguments = buildJsonObject {},
+ ),
)
// Call the handler
@@ -359,7 +470,7 @@ class MCPServerTest {
// Verify the result
assertThat(result).isNotNull()
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
// Verify the content is valid JSON with generators
diff --git
a/src/test/kotlin/org/apache/cassandra/easystress/server/StressTestManagerTest.kt
b/src/test/kotlin/org/apache/cassandra/easystress/server/StressTestManagerTest.kt
index 7630f68..25f06b2 100644
---
a/src/test/kotlin/org/apache/cassandra/easystress/server/StressTestManagerTest.kt
+++
b/src/test/kotlin/org/apache/cassandra/easystress/server/StressTestManagerTest.kt
@@ -17,8 +17,10 @@
*/
package org.apache.cassandra.easystress.server
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
+import io.mockk.mockk
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
@@ -43,8 +45,8 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("duration", JsonPrimitive(10))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- manager.getRunTool().handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ manager.getRunTool().handler(mockk(relaxed = true), request)
// Should be running
assertThat(manager.isRunning()).isTrue()
@@ -69,8 +71,8 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("duration", JsonPrimitive(10))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- manager.getRunTool().handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ manager.getRunTool().handler(mockk(relaxed = true), request)
// Should have a thread
assertThat(manager.getCurrentThread()).isNotNull()
@@ -96,8 +98,8 @@ class StressTestManagerTest {
put("duration", JsonPrimitive(10))
put("threads", JsonPrimitive(2))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- manager.getRunTool().handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ manager.getRunTool().handler(mockk(relaxed = true), request)
// Should have a command
val command = manager.getCurrentCommand()
@@ -128,8 +130,8 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("duration", JsonPrimitive(10))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- manager.getRunTool().handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ manager.getRunTool().handler(mockk(relaxed = true), request)
val afterStart = System.currentTimeMillis()
@@ -158,14 +160,17 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("duration", JsonPrimitive(10))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- manager.getRunTool().handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ manager.getRunTool().handler(mockk(relaxed = true), request)
// Should be running
assertThat(manager.getStatus()).isEqualTo("running")
// Stop the test
- manager.getStopTool().handler(CallToolRequest(name = "stop",
arguments = EmptyJsonObject))
+ manager.getStopTool().handler(
+ mockk(relaxed = true),
+ CallToolRequest(CallToolRequestParams(name = "stop", arguments
= EmptyJsonObject)),
+ )
// Should be stopped
assertThat(manager.getStatus()).isEqualTo("stopped")
@@ -183,8 +188,8 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("duration", JsonPrimitive(10))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- manager.getRunTool().handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ manager.getRunTool().handler(mockk(relaxed = true), request)
// Verify state is populated
assertThat(manager.isRunning()).isTrue()
@@ -217,11 +222,11 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("iterations", JsonPrimitive(1))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- val result = runTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ val result = runTool.handler(mockk(relaxed = true), request)
// Should start successfully (background thread will fail when
connecting, but that's expected in unit test)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
// Clean up - stop the manager to release lock
@@ -241,15 +246,15 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("iterations", JsonPrimitive(1))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
// First call should succeed
- val result1 = runTool.handler(request)
- assertThat(result1.isError).isFalse()
+ val result1 = runTool.handler(mockk(relaxed = true), request)
+ assertThat(result1.isError ?: false).isFalse()
// Second call should fail with "already running" error
- val result2 = runTool.handler(request)
- assertThat(result2.isError).isTrue()
+ val result2 = runTool.handler(mockk(relaxed = true), request)
+ assertThat(result2.isError ?: false).isTrue()
assertThat(result2.content).isNotEmpty()
assertThat(result2.content.first().toString()).contains("already
running")
@@ -263,10 +268,10 @@ class StressTestManagerTest {
val manager = StressTestManager()
val stopTool = manager.getStopTool()
- val request = CallToolRequest(name = "stop", arguments =
EmptyJsonObject)
- val result = stopTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "stop",
arguments = EmptyJsonObject))
+ val result = stopTool.handler(mockk(relaxed = true), request)
- assertThat(result.isError).isTrue()
+ assertThat(result.isError ?: false).isTrue()
assertThat(result.content).isNotEmpty()
assertThat(result.content.first().toString()).contains("No stress
test is currently running")
}
@@ -285,17 +290,17 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("iterations", JsonPrimitive(1))
}
- val runRequest = CallToolRequest(name = "run", arguments = runArgs)
- runTool.handler(runRequest)
+ val runRequest = CallToolRequest(CallToolRequestParams(name =
"run", arguments = runArgs))
+ runTool.handler(mockk(relaxed = true), runRequest)
// Verify test is running
assertThat(manager.isRunning()).isTrue()
// Stop the test
- val stopRequest = CallToolRequest(name = "stop", arguments =
EmptyJsonObject)
- val result = stopTool.handler(stopRequest)
+ val stopRequest = CallToolRequest(CallToolRequestParams(name =
"stop", arguments = EmptyJsonObject))
+ val result = stopTool.handler(mockk(relaxed = true), stopRequest)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
assertThat(result.content.first().toString()).contains("stopped
successfully")
@@ -310,10 +315,10 @@ class StressTestManagerTest {
val manager = StressTestManager()
val statusTool = manager.getStatusTool()
- val request = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val result = statusTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val result = statusTool.handler(mockk(relaxed = true), request)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
}
@@ -332,18 +337,18 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("iterations", JsonPrimitive(1))
}
- val runRequest = CallToolRequest(name = "run", arguments = runArgs)
- runTool.handler(runRequest)
+ val runRequest = CallToolRequest(CallToolRequestParams(name =
"run", arguments = runArgs))
+ runTool.handler(mockk(relaxed = true), runRequest)
// Stop the test
- val stopRequest = CallToolRequest(name = "stop", arguments =
EmptyJsonObject)
- stopTool.handler(stopRequest)
+ val stopRequest = CallToolRequest(CallToolRequestParams(name =
"stop", arguments = EmptyJsonObject))
+ stopTool.handler(mockk(relaxed = true), stopRequest)
// Check status shows "stopped" message
- val statusRequest = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val result = statusTool.handler(statusRequest)
+ val statusRequest = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val result = statusTool.handler(mockk(relaxed = true),
statusRequest)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
val statusText = result.content.first().toString()
assertThat(statusText).contains("\"status\":\"stopped\"")
@@ -366,11 +371,11 @@ class StressTestManagerTest {
put("duration", JsonPrimitive(10))
put("threads", JsonPrimitive(1))
}
- val firstRunRequest = CallToolRequest(name = "run", arguments =
firstTestArgs)
- val startResult1 = runTool.handler(firstRunRequest)
+ val firstRunRequest = CallToolRequest(CallToolRequestParams(name =
"run", arguments = firstTestArgs))
+ val startResult1 = runTool.handler(mockk(relaxed = true),
firstRunRequest)
// Verify first test started successfully
- assertThat(startResult1.isError).isFalse()
+ assertThat(startResult1.isError ?: false).isFalse()
assertThat(startResult1.content.first().toString()).contains("started
successfully")
assertThat(manager.isRunning()).isTrue()
assertThat(manager.getStatus()).isEqualTo("running")
@@ -384,8 +389,8 @@ class StressTestManagerTest {
}
// 2. STOP FIRST TEST: Stop the running test (or verify it stopped
on its own)
- val stopRequest = CallToolRequest(name = "stop", arguments =
EmptyJsonObject)
- val stopResult = stopTool.handler(stopRequest)
+ val stopRequest = CallToolRequest(CallToolRequestParams(name =
"stop", arguments = EmptyJsonObject))
+ val stopResult = stopTool.handler(mockk(relaxed = true),
stopRequest)
// Verify stop was called and manager is no longer running
// Note: The test might have already failed and stopped on its
own, which is fine
@@ -400,11 +405,11 @@ class StressTestManagerTest {
put("duration", JsonPrimitive(2))
put("threads", JsonPrimitive(1))
}
- val secondRunRequest = CallToolRequest(name = "run", arguments =
secondTestArgs)
- val startResult2 = runTool.handler(secondRunRequest)
+ val secondRunRequest = CallToolRequest(CallToolRequestParams(name
= "run", arguments = secondTestArgs))
+ val startResult2 = runTool.handler(mockk(relaxed = true),
secondRunRequest)
// Verify second test started successfully
- assertThat(startResult2.isError).isFalse()
+ assertThat(startResult2.isError ?: false).isFalse()
assertThat(startResult2.content.first().toString()).contains("started
successfully")
assertThat(manager.isRunning()).isTrue()
assertThat(manager.getStatus()).isEqualTo("running")
@@ -413,19 +418,22 @@ class StressTestManagerTest {
assertThat(secondTestStartTime).isNotNull()
// 4. VERIFY STATUS DURING RUN: Check status while second test is
running
- val statusRequest = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val statusResult = statusTool.handler(statusRequest)
+ val statusRequest = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val statusResult = statusTool.handler(mockk(relaxed = true),
statusRequest)
- assertThat(statusResult.isError).isFalse()
+ assertThat(statusResult.isError ?: false).isFalse()
val statusText = statusResult.content.first().toString()
assertThat(statusText).contains("\"status\":\"running\"")
assertThat(statusText).contains("\"is_running\":true")
assertThat(statusText).contains("currently running")
// 5. LET SECOND TEST COMPLETE: Wait for test to finish naturally
- // The test will fail to connect but that's expected - it should
still complete the lifecycle
+ // The test will fail to connect or run for 2s duration - wait for
completion
withContext(Dispatchers.IO) {
- Thread.sleep(3000)
+ for (i in 1..50) {
+ if (!manager.isRunning()) break
+ Thread.sleep(100)
+ }
}
// Verify test completed or stopped (either is acceptable since
connection will fail)
@@ -435,10 +443,10 @@ class StressTestManagerTest {
assertThat(finalStatus).matches("^(completed|stopped|failed:.*)$")
// 6. VERIFY POST-COMPLETION STATUS
- val finalStatusRequest = CallToolRequest(name = "status",
arguments = EmptyJsonObject)
- val finalStatusResult = statusTool.handler(finalStatusRequest)
+ val finalStatusRequest =
CallToolRequest(CallToolRequestParams(name = "status", arguments =
EmptyJsonObject))
+ val finalStatusResult = statusTool.handler(mockk(relaxed = true),
finalStatusRequest)
- assertThat(finalStatusResult.isError).isFalse()
+ assertThat(finalStatusResult.isError ?: false).isFalse()
val finalStatusText = finalStatusResult.content.first().toString()
assertThat(finalStatusText).contains("\"is_running\":false")
assertThat(finalStatusText.contains("Last stress test") ||
finalStatusText.contains("Status:")).isTrue()
@@ -462,14 +470,14 @@ class StressTestManagerTest {
put("duration", JsonPrimitive(10))
put("threads", JsonPrimitive(3))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- runTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ runTool.handler(mockk(relaxed = true), request)
// Check status immediately while still running (before connection
fails)
- val statusRequest = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val result = statusTool.handler(statusRequest)
+ val statusRequest = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val result = statusTool.handler(mockk(relaxed = true),
statusRequest)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
val statusText = result.content.first().toString()
// Verify running state
@@ -502,10 +510,10 @@ class StressTestManagerTest {
val manager = StressTestManager()
val statusTool = manager.getStatusTool()
- val request = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val result = statusTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val result = statusTool.handler(mockk(relaxed = true), request)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
val statusText = result.content.first().toString()
assertThat(statusText).contains("\"status\":\"idle\"")
@@ -522,10 +530,10 @@ class StressTestManagerTest {
manager.setStatus("completed")
val statusTool = manager.getStatusTool()
- val request = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val result = statusTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val result = statusTool.handler(mockk(relaxed = true), request)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
val statusText = result.content.first().toString()
assertThat(statusText).contains("\"status\":\"completed\"")
@@ -542,10 +550,10 @@ class StressTestManagerTest {
manager.setStatus("failed: Connection refused")
val statusTool = manager.getStatusTool()
- val request = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val result = statusTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val result = statusTool.handler(mockk(relaxed = true), request)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
val statusText = result.content.first().toString()
assertThat(statusText).contains("\"status\":\"failed: Connection
refused\"")
@@ -568,8 +576,8 @@ class StressTestManagerTest {
put("host", JsonPrimitive("127.0.0.1"))
put("duration", JsonPrimitive(1))
}
- val request = CallToolRequest(name = "run", arguments = arguments)
- runTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "run",
arguments = arguments))
+ runTool.handler(mockk(relaxed = true), request)
val startTime = manager.getStartTime()
@@ -578,10 +586,10 @@ class StressTestManagerTest {
manager.setStatus("stopped")
// Get status after stopping
- val statusRequest = CallToolRequest(name = "status", arguments =
EmptyJsonObject)
- val result = statusTool.handler(statusRequest)
+ val statusRequest = CallToolRequest(CallToolRequestParams(name =
"status", arguments = EmptyJsonObject))
+ val result = statusTool.handler(mockk(relaxed = true),
statusRequest)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
val statusText = result.content.first().toString()
assertThat(statusText).contains("\"is_running\":false")
diff --git
a/src/test/kotlin/org/apache/cassandra/easystress/server/tools/FieldsToolTest.kt
b/src/test/kotlin/org/apache/cassandra/easystress/server/tools/FieldsToolTest.kt
index 9d8fb41..314a0a4 100644
---
a/src/test/kotlin/org/apache/cassandra/easystress/server/tools/FieldsToolTest.kt
+++
b/src/test/kotlin/org/apache/cassandra/easystress/server/tools/FieldsToolTest.kt
@@ -17,10 +17,12 @@
*/
package org.apache.cassandra.easystress.server.tools
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
-import io.modelcontextprotocol.kotlin.sdk.TextContent
+import io.mockk.mockk
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
@@ -35,10 +37,10 @@ class FieldsToolTest {
@Test
fun `should list available field generators`() =
runTest {
- val request = CallToolRequest(name = "fields", arguments =
EmptyJsonObject)
- val result = FieldsTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"fields", arguments = EmptyJsonObject))
+ val result = FieldsTool.handler(mockk(relaxed = true), request)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
@@ -59,8 +61,8 @@ class FieldsToolTest {
@Test
fun `should include name and description for each generator`() =
runTest {
- val request = CallToolRequest(name = "fields", arguments =
EmptyJsonObject)
- val result = FieldsTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"fields", arguments = EmptyJsonObject))
+ val result = FieldsTool.handler(mockk(relaxed = true), request)
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
val generators = jsonResponse["generators"]?.jsonArray
@@ -87,10 +89,10 @@ class FieldsToolTest {
@Test
fun `should return consistent results across multiple calls`() =
runTest {
- val request = CallToolRequest(name = "fields", arguments =
EmptyJsonObject)
+ val request = CallToolRequest(CallToolRequestParams(name =
"fields", arguments = EmptyJsonObject))
- val result1 = FieldsTool.handler(request)
- val result2 = FieldsTool.handler(request)
+ val result1 = FieldsTool.handler(mockk(relaxed = true), request)
+ val result2 = FieldsTool.handler(mockk(relaxed = true), request)
val response1 = result1.textContent()
val response2 = result2.textContent()
@@ -102,8 +104,8 @@ class FieldsToolTest {
@Test
fun `should include common field generators`() =
runTest {
- val request = CallToolRequest(name = "fields", arguments =
EmptyJsonObject)
- val result = FieldsTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"fields", arguments = EmptyJsonObject))
+ val result = FieldsTool.handler(mockk(relaxed = true), request)
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
val generators = jsonResponse["generators"]?.jsonArray
diff --git
a/src/test/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsToolTest.kt
b/src/test/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsToolTest.kt
index 969b3a4..c39c73c 100644
---
a/src/test/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsToolTest.kt
+++
b/src/test/kotlin/org/apache/cassandra/easystress/server/tools/ListWorkloadsToolTest.kt
@@ -17,10 +17,12 @@
*/
package org.apache.cassandra.easystress.server.tools
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
-import io.modelcontextprotocol.kotlin.sdk.TextContent
+import io.mockk.mockk
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
@@ -35,10 +37,10 @@ class ListWorkloadsToolTest {
@Test
fun `should list available workloads`() =
runTest {
- val request = CallToolRequest(name = "list_workloads", arguments =
EmptyJsonObject)
- val result = ListWorkloadsTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"list_workloads", arguments = EmptyJsonObject))
+ val result = ListWorkloadsTool.handler(mockk(relaxed = true),
request)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
@@ -63,8 +65,8 @@ class ListWorkloadsToolTest {
@Test
fun `should include known workloads`() =
runTest {
- val request = CallToolRequest(name = "list_workloads", arguments =
EmptyJsonObject)
- val result = ListWorkloadsTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name =
"list_workloads", arguments = EmptyJsonObject))
+ val result = ListWorkloadsTool.handler(mockk(relaxed = true),
request)
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
val workloads = jsonResponse["workloads"]?.jsonArray
@@ -82,10 +84,10 @@ class ListWorkloadsToolTest {
@Test
fun `should return consistent results across multiple calls`() =
runTest {
- val request = CallToolRequest(name = "list_workloads", arguments =
EmptyJsonObject)
+ val request = CallToolRequest(CallToolRequestParams(name =
"list_workloads", arguments = EmptyJsonObject))
- val result1 = ListWorkloadsTool.handler(request)
- val result2 = ListWorkloadsTool.handler(request)
+ val result1 = ListWorkloadsTool.handler(mockk(relaxed = true),
request)
+ val result2 = ListWorkloadsTool.handler(mockk(relaxed = true),
request)
val response1 = result1.textContent()
val response2 = result2.textContent()
diff --git
a/src/test/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoToolTest.kt
b/src/test/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoToolTest.kt
index 9162d50..0502b36 100644
---
a/src/test/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoToolTest.kt
+++
b/src/test/kotlin/org/apache/cassandra/easystress/server/tools/WorkloadInfoToolTest.kt
@@ -17,9 +17,11 @@
*/
package org.apache.cassandra.easystress.server.tools
-import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
-import io.modelcontextprotocol.kotlin.sdk.CallToolResult
-import io.modelcontextprotocol.kotlin.sdk.TextContent
+import io.mockk.mockk
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
@@ -32,7 +34,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class WorkloadInfoToolTest {
- private fun CallToolResult.textContent(): String = (this.content.first()
as TextContent).text!!
+ private fun CallToolResult.textContent(): String = (this.content.first()
as TextContent).text
@Test
fun `should require workload parameter`() {
@@ -47,10 +49,10 @@ class WorkloadInfoToolTest {
buildJsonObject {
put("workload", JsonPrimitive("nonexistent_workload_xyz"))
}
- val request = CallToolRequest(name = "info", arguments = arguments)
- val result = WorkloadInfoTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "info",
arguments = arguments))
+ val result = WorkloadInfoTool.handler(mockk(relaxed = true),
request)
- assertThat(result.isError).isTrue()
+ assertThat(result.isError ?: false).isTrue()
assertThat(result.textContent()).contains("not found")
}
@@ -65,10 +67,10 @@ class WorkloadInfoToolTest {
buildJsonObject {
put("workload", JsonPrimitive(firstWorkload))
}
- val request = CallToolRequest(name = "info", arguments = arguments)
- val result = WorkloadInfoTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "info",
arguments = arguments))
+ val result = WorkloadInfoTool.handler(mockk(relaxed = true),
request)
- assertThat(result.isError).isFalse()
+ assertThat(result.isError ?: false).isFalse()
assertThat(result.content).isNotEmpty()
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
@@ -102,8 +104,8 @@ class WorkloadInfoToolTest {
buildJsonObject {
put("workload", JsonPrimitive(firstWorkload))
}
- val request = CallToolRequest(name = "info", arguments = arguments)
- val result = WorkloadInfoTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "info",
arguments = arguments))
+ val result = WorkloadInfoTool.handler(mockk(relaxed = true),
request)
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
val schema = jsonResponse["schema"]?.jsonArray
@@ -127,8 +129,8 @@ class WorkloadInfoToolTest {
buildJsonObject {
put("workload", JsonPrimitive(firstWorkload))
}
- val request = CallToolRequest(name = "info", arguments = arguments)
- val result = WorkloadInfoTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "info",
arguments = arguments))
+ val result = WorkloadInfoTool.handler(mockk(relaxed = true),
request)
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
@@ -147,8 +149,8 @@ class WorkloadInfoToolTest {
buildJsonObject {
put("workload", JsonPrimitive(firstWorkload))
}
- val request = CallToolRequest(name = "info", arguments = arguments)
- val result = WorkloadInfoTool.handler(request)
+ val request = CallToolRequest(CallToolRequestParams(name = "info",
arguments = arguments))
+ val result = WorkloadInfoTool.handler(mockk(relaxed = true),
request)
val jsonResponse =
Json.parseToJsonElement(result.textContent()).jsonObject
val parameters = jsonResponse["parameters"]?.jsonArray
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]