File tree Expand file tree Collapse file tree
conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance
commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types
commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types
commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server
jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -179,7 +179,13 @@ fun Server.registerConformanceTools() {
179179 ),
180180 ),
181181 )
182- CallToolResult (listOf (TextContent (result.content.joinToString(" \n " ) { it.toString() })))
182+ val sampledText = result.content.joinToString(" \n " ) { content ->
183+ when (content) {
184+ is TextContent -> content.text
185+ else -> " Non-text sampling content: ${content::class .simpleName} "
186+ }
187+ }
188+ CallToolResult (listOf (TextContent (sampledText)))
183189 }
184190
185191 // 9. Elicitation
Original file line number Diff line number Diff line change @@ -91,6 +91,10 @@ public data class SamplingMessage(
9191 @SerialName(" _meta" )
9292 val meta : JsonObject ? = null ,
9393) {
94+ init {
95+ require(content.isNotEmpty()) { " content must contain at least one block" }
96+ }
97+
9498 /* *
9599 * Convenience constructor for a single-block message. Wraps [content] in a
96100 * singleton list so call sites can write `SamplingMessage(Role.User, TextContent("hi"))`
@@ -273,6 +277,10 @@ public data class CreateMessageResult(
273277 @SerialName(" _meta" )
274278 override val meta : JsonObject ? = null ,
275279) : ClientResult {
280+ init {
281+ require(content.isNotEmpty()) { " content must contain at least one block" }
282+ }
283+
276284 /* *
277285 * Convenience constructor for a single-block response. Wraps [content] in a
278286 * singleton list so call sites can write
Original file line number Diff line number Diff line change @@ -302,6 +302,13 @@ class SamplingTest {
302302 (m.content[0 ] as TextContent ).text shouldBe " hi"
303303 }
304304
305+ @Test
306+ fun `SamplingMessage rejects empty content` () {
307+ assertFailsWith<IllegalArgumentException > {
308+ SamplingMessage (role = Role .User , content = emptyList())
309+ }
310+ }
311+
305312 @Test
306313 fun `SamplingMessage single-element content serialises as single object` () {
307314 val m = SamplingMessage (role = Role .User , content = listOf (TextContent (" hi" )))
@@ -408,6 +415,17 @@ class SamplingTest {
408415 (decoded.content[0 ] as TextContent ).text shouldBe " hi"
409416 }
410417
418+ @Test
419+ fun `CreateMessageResult rejects empty content` () {
420+ assertFailsWith<IllegalArgumentException > {
421+ CreateMessageResult (
422+ role = Role .Assistant ,
423+ content = emptyList(),
424+ model = " test-model" ,
425+ )
426+ }
427+ }
428+
411429 // ============================================================================
412430 // SamplingContentSerializer (single-or-array wire heuristic)
413431 // ============================================================================
Original file line number Diff line number Diff line change @@ -20,8 +20,8 @@ import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent
2020 * 3. If the previous message contains `tool_use` blocks, the last message's
2121 * `tool_result` ids MUST form exactly the same set.
2222 *
23- * On the first violation throws [IllegalArgumentException]. No-op when there are fewer
24- * than two messages or no tool_use / tool_result blocks are involved.
23+ * On the first violation throws [IllegalArgumentException]. No-op when no
24+ * tool_use / tool_result blocks are involved.
2525 */
2626internal fun validateSamplingMessages (messages : List <SamplingMessage >) {
2727 if (messages.isEmpty()) return
@@ -44,6 +44,9 @@ internal fun validateSamplingMessages(messages: List<SamplingMessage>) {
4444 if (hasPreviousToolUse) {
4545 val toolUseIds = previous.filterIsInstance<ToolUseContent >().map { it.id }.toSet()
4646 val toolResultIds = last.filterIsInstance<ToolResultContent >().map { it.toolUseId }.toSet()
47+ require(toolResultIds.isNotEmpty()) {
48+ " tool_use blocks from previous message must be followed by matching tool_result blocks"
49+ }
4750 require(toolUseIds == toolResultIds) {
4851 " ids of tool_result blocks and tool_use blocks from previous message do not match"
4952 }
Original file line number Diff line number Diff line change @@ -8,6 +8,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent
88import kotlinx.serialization.json.JsonObject
99import org.junit.jupiter.api.assertDoesNotThrow
1010import kotlin.test.Test
11+ import kotlin.test.assertEquals
1112import kotlin.test.assertFailsWith
1213
1314/* *
@@ -83,4 +84,21 @@ class SamplingTest {
8384 )
8485 }
8586 }
87+
88+ @Test
89+ fun `validate tool_use requires explicit tool_result in last message` () {
90+ val error = assertFailsWith<IllegalArgumentException > {
91+ validateSamplingMessages(
92+ listOf (
93+ SamplingMessage (Role .Assistant , toolUse(" c1" )),
94+ SamplingMessage (Role .User , TextContent (" missing result" )),
95+ ),
96+ )
97+ }
98+
99+ assertEquals(
100+ " tool_use blocks from previous message must be followed by matching tool_result blocks" ,
101+ error.message,
102+ )
103+ }
86104}
You can’t perform that action at this time.
0 commit comments