fix conformance tests (#483)

# Fix conformance tests.

1. fix node version to workaround
https://github.com/nodejs/node/issues/56645
2. use `@argfile` to handle long classpaths
3. specify extension for npx for windows
4. Install `@modelcontextprotocol/conformance@0.1.8` GitHub Actions
workflow.
5. Centralize logging for process streams in conformance tests: Add
`startLogging` utility for InputStream logging with `KotlinLogging`.
Replace inline process stream handling with `startLogging` calls

Note: Currently tests pass using
`@modelcontextprotocol/conformance@0.1.8`/ But changing to 0.1.9 will
fail client scenarios (it is not related to this PR).

## Motivation and Context
Before fix conformance tests are broken on Windows

## How Has This Been Tested?
Run test locally and in github actions

## Breaking Changes
No

## Types of changes

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
May be we should update `CONTRIBUTING.md/Building/Requirements`. But
currently it does not mention at all: node, android, etc.

---------

Co-authored-by: f.bobin <f.bobin@vk.team>
Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com>
This commit is contained in:
Fedor Bobin 2026-01-19 16:45:54 +03:00 • committed by GitHub
commit c73507b96a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 119 additions and 52 deletions

View file

@ -1,7 +1,9 @@
package io.modelcontextprotocol.kotlin.sdk.conformance
import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.test.utils.NPX
import io.modelcontextprotocol.kotlin.test.utils.findFreePort
import io.modelcontextprotocol.kotlin.test.utils.startLogging
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.DynamicTest
@ -13,10 +15,14 @@ import java.lang.management.ManagementFactory
import java.net.HttpURLConnection
import java.net.URI
import java.util.concurrent.TimeUnit
import kotlin.io.path.createTempFile
import kotlin.properties.Delegates
private val logger = KotlinLogging.logger {}
val processStderrLogger = KotlinLogging.logger(name = "stderr")
val processStdoutLogger = KotlinLogging.logger(name = "stdout")
private const val CONFORMANCE_VERSION = "0.1.8"
enum class TransportType {
@ -210,79 +216,75 @@ class ConformanceTest {
}
private fun runServerConformanceTest(scenario: String, transportType: TransportType) {
val processBuilder = when (transportType) {
val serverUrl = when (transportType) {
TransportType.SSE -> {
val serverUrl = "http://127.0.0.1:$serverPort/mcp"
ProcessBuilder(
"npx",
"@modelcontextprotocol/conformance@$CONFORMANCE_VERSION",
"server",
"--url",
serverUrl,
"--scenario",
scenario,
).apply {
inheritIO()
}
"http://127.0.0.1:$serverPort/mcp"
}
TransportType.WEBSOCKET -> {
val serverUrl = "ws://127.0.0.1:$serverPort/ws"
ProcessBuilder(
"npx",
"@modelcontextprotocol/conformance@$CONFORMANCE_VERSION",
"server",
"--url",
serverUrl,
"--scenario",
scenario,
).apply {
inheritIO()
}
"ws://127.0.0.1:$serverPort/ws"
}
}
val processBuilder = ProcessBuilder(
NPX,
"@modelcontextprotocol/conformance@$CONFORMANCE_VERSION",
"server",
"--url",
serverUrl,
"--scenario",
scenario,
)
runConformanceTest("server", scenario, processBuilder, transportType)
}
private fun runClientConformanceTest(scenario: String, transportType: TransportType) {
val testClasspath = getTestClasspath()
val clientCommand = when (transportType) {
// Create an argfile to avoid Windows command line length limits
val argFile = createTempFile(suffix = ".args").toFile()
argFile.deleteOnExit()
val mainClass = when (transportType) {
TransportType.SSE -> {
val serverUrl = "http://127.0.0.1:$serverPort/mcp"
listOf(
"java",
"-cp",
testClasspath,
"io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceClientKt",
serverUrl,
argFile.writeText(
buildString {
appendLine("-cp")
appendLine(testClasspath)
appendLine("io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceClientKt")
},
)
"http://127.0.0.1:$serverPort/mcp"
}
TransportType.WEBSOCKET -> {
val serverUrl = "ws://127.0.0.1:$serverPort/ws"
listOf(
"java",
"-cp",
testClasspath,
"io.modelcontextprotocol.kotlin.sdk.conformance.WebSocketConformanceClientKt",
serverUrl,
argFile.writeText(
buildString {
appendLine("-cp")
appendLine(testClasspath)
appendLine("io.modelcontextprotocol.kotlin.sdk.conformance.WebSocketConformanceClientKt")
},
)
"ws://127.0.0.1:$serverPort/ws"
}
}
val clientCommand = listOf(
"java",
"@${argFile.absolutePath}",
mainClass,
)
val processBuilder = ProcessBuilder(
"npx",
NPX,
"@modelcontextprotocol/conformance@$CONFORMANCE_VERSION",
"client",
"--command",
clientCommand.joinToString(" "),
"--scenario",
scenario,
).apply {
inheritIO()
}
)
runConformanceTest("client", scenario, processBuilder, transportType)
}
@ -296,10 +298,20 @@ class ConformanceTest {
val capitalizedType = type.replaceFirstChar { it.uppercase() }
logger.info { "Running $type conformance test [$transportType]: $scenario" }
val timeoutSeconds = System.getenv("CONFORMANCE_TEST_TIMEOUT_SECONDS")?.toLongOrNull()
?: DEFAULT_TEST_TIMEOUT_SECONDS
val timeoutSeconds =
System.getenv("CONFORMANCE_TEST_TIMEOUT_SECONDS")?.toLongOrNull() ?: DEFAULT_TEST_TIMEOUT_SECONDS
val process = processBuilder.start()
process.errorStream.startLogging(
logger = processStderrLogger,
name = "test(PID=${process.pid()})",
)
process.inputStream.startLogging(
logger = processStdoutLogger,
name = "test(PID=${process.pid()})",
)
val completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS)
if (!completed) {

View file

@ -5,4 +5,6 @@ org.slf4j.simpleLogger.showDateTime=false
# Log level for specific packages or classes
org.slf4j.simpleLogger.log.io.ktor.server=DEBUG
org.slf4j.simpleLogger.log.stdout=TRACE
org.slf4j.simpleLogger.log.stderr=TRACE
org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG