refactor: Harden detekt setup, address some issues (#507)
# Detekt setup hardeing; address some issues - Address some detekt findings: missing KDocs, formatting, use `check*` & `orEmpty()`` - Remove unnecessary KDoc comments from deprecated classes in `types.kt` - Add Detekt baseline XML files for all modules - Enforce stricter Detekt rules by setting `failOnSeverity` from `None` to `Error` ## Motivation and Context Detekt is not actually failing the build on violations. This PR is enabling Detekt to fail on errors. Baseline fines added for historical issues. Follow-up for #501 ## How Has This Been Tested? Regressions ## Breaking Changes No breaking changes. ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Refactoring - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed
This commit is contained in:
parent
4501f14b56
commit
ed9ad83290
41 changed files with 478 additions and 122 deletions
|
|
@ -26,7 +26,7 @@ subprojects {
|
|||
detekt {
|
||||
config = files("$rootDir/config/detekt/detekt.yml")
|
||||
buildUponDefaultConfig = true
|
||||
failOnSeverity.set(FailOnSeverity.Never)
|
||||
failOnSeverity.set(FailOnSeverity.Error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
9
conformance-test/detekt-baseline.xml
Normal file
9
conformance-test/detekt-baseline.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BracesOnWhenStatements:ConformanceServer.kt:HttpServerTransport$when</ID>
|
||||
<ID>BracesOnWhenStatements:ConformanceTest.kt:ConformanceTest$when</ID>
|
||||
<ID>ForbiddenComment:ConformanceTest.kt:ConformanceTest.Companion$// TODO: Fix</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -73,6 +73,7 @@ private const val MESSAGE_QUEUE_CAPACITY = 256
|
|||
private fun isInitializeRequest(json: JsonElement): Boolean =
|
||||
json is JsonObject && json["method"]?.jsonPrimitive?.contentOrNull == "initialize"
|
||||
|
||||
@Suppress("CyclomaticComplexMethod", "LongMethod")
|
||||
fun main(args: Array<String>) {
|
||||
val port = args.getOrNull(0)?.toIntOrNull() ?: 3000
|
||||
|
||||
|
|
@ -236,6 +237,7 @@ fun main(args: Array<String>) {
|
|||
}.start(wait = true)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun createConformanceServer(): Server {
|
||||
val server = Server(
|
||||
Implementation(
|
||||
|
|
@ -371,7 +373,9 @@ private class HttpServerTransport(private val sessionId: String) : AbstractTrans
|
|||
}
|
||||
}
|
||||
|
||||
else -> call.respond(HttpStatusCode.Accepted)
|
||||
else -> {
|
||||
call.respond(HttpStatusCode.Accepted)
|
||||
}
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import java.net.URI
|
|||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.io.path.createTempFile
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.test.fail
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
|
|
@ -320,7 +321,8 @@ class ConformanceTest {
|
|||
}
|
||||
process.destroyForcibly()
|
||||
throw AssertionError(
|
||||
"❌ $capitalizedType conformance test [$transportType] '$scenario' timed out after $timeoutSeconds seconds",
|
||||
"❌ $capitalizedType conformance test [$transportType] '$scenario' " +
|
||||
"timed out after $timeoutSeconds seconds",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -331,8 +333,9 @@ class ConformanceTest {
|
|||
logger.error {
|
||||
"$capitalizedType conformance test [$transportType] '$scenario' failed with exit code: $exitCode"
|
||||
}
|
||||
throw AssertionError(
|
||||
"❌ $capitalizedType conformance test [$transportType] '$scenario' failed (exit code: $exitCode). Check test output above for details.",
|
||||
fail(
|
||||
"❌ $capitalizedType conformance test [$transportType] '$scenario' " +
|
||||
"failed (exit code: $exitCode). Check test output above for details.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class WebSocketClientTransport(override val session: WebSocketSession) : WebSock
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
fun main(args: Array<String>) {
|
||||
require(args.isNotEmpty()) {
|
||||
"Server WebSocket URL must be provided as an argument"
|
||||
|
|
|
|||
11
integration-test/detekt-baseline-commonTestSourceSet.xml
Normal file
11
integration-test/detekt-baseline-commonTestSourceSet.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${ }</ID>
|
||||
<ID>EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${}</ID>
|
||||
<ID>LargeClass:ClientTest.kt:ClientTest</ID>
|
||||
<ID>LongMethod:ClientTest.kt:ClientTest$@Test fun `JSONRPCRequest with ToolsList method and default params returns list of tools`</ID>
|
||||
<ID>LongMethod:ClientTest.kt:ClientTest$@Test fun `should handle logging setLevel request`</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
47
integration-test/detekt-baseline-test.xml
Normal file
47
integration-test/detekt-baseline-test.xml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>AbstractClassCanBeConcreteClass:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$AbstractPromptIntegrationTest</ID>
|
||||
<ID>AbstractClassCanBeConcreteClass:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$AbstractResourceIntegrationTest</ID>
|
||||
<ID>AbstractClassCanBeConcreteClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$AbstractToolIntegrationTest</ID>
|
||||
<ID>AbstractClassCanBeConcreteClass:BaseTransportTest.kt:BaseTransportTest$BaseTransportTest</ID>
|
||||
<ID>CyclomaticComplexMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool</ID>
|
||||
<ID>EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${ }</ID>
|
||||
<ID>EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${}</ID>
|
||||
<ID>ForbiddenComment:StdioClientTransportTest.kt:StdioClientTransportTest$// TODO: fix running on windows</ID>
|
||||
<ID>InjectDispatcher:AbstractKotlinClientTsServerTest.kt:AbstractKotlinClientTsServerTest$IO</ID>
|
||||
<ID>InjectDispatcher:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$IO</ID>
|
||||
<ID>InjectDispatcher:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$IO</ID>
|
||||
<ID>InjectDispatcher:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$IO</ID>
|
||||
<ID>InjectDispatcher:KotlinClientTsServerEdgeCasesTestSse.kt:KotlinClientTsServerEdgeCasesTestSse$IO</ID>
|
||||
<ID>InjectDispatcher:KotlinClientTsServerEdgeCasesTestStdio.kt:KotlinClientTsServerEdgeCasesTestStdio$IO</ID>
|
||||
<ID>InjectDispatcher:SseIntegrationTest.kt:SseIntegrationTest$IO</ID>
|
||||
<ID>InjectDispatcher:StdioClientTransportTest.kt:StdioClientTransportTest$IO</ID>
|
||||
<ID>InjectDispatcher:StreamableHttpIntegrationTest.kt:StreamableHttpIntegrationTest$IO</ID>
|
||||
<ID>InjectDispatcher:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$IO</ID>
|
||||
<ID>InjectDispatcher:WebSocketIntegrationTest.kt:WebSocketIntegrationTest$IO</ID>
|
||||
<ID>LargeClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest : KotlinTestBase</ID>
|
||||
<ID>LargeClass:ClientTest.kt:ClientTest</ID>
|
||||
<ID>LongMethod:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$@Test fun testMissingRequiredArguments</ID>
|
||||
<ID>LongMethod:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer</ID>
|
||||
<ID>LongMethod:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$override fun configureServer</ID>
|
||||
<ID>LongMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool</ID>
|
||||
<ID>LongMethod:ClientTest.kt:ClientTest$@Test fun `JSONRPCRequest with ToolsList method and default params returns list of tools`</ID>
|
||||
<ID>LongMethod:ClientTest.kt:ClientTest$@Test fun `should handle logging setLevel request`</ID>
|
||||
<ID>LongMethod:KotlinServerForTsClientSse.kt:HttpServerTransport$suspend fun handleRequest</ID>
|
||||
<ID>LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun createMcpServer: Server</ID>
|
||||
<ID>LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun start</ID>
|
||||
<ID>LongMethod:KotlinTestBase.kt:KotlinTestBase$protected fun setupServer</ID>
|
||||
<ID>LongMethod:ServerResourcesNotificationSubscribeTest.kt:ServerResourcesNotificationSubscribeTest$@Test fun `should send resource notifications`</ID>
|
||||
<ID>LongMethod:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$@Test @Timeout(30, unit = TimeUnit.SECONDS) fun testComplexConcurrentRequests: Unit</ID>
|
||||
<ID>MatchingDeclarationName:PromptIntegrationTestSse.kt:SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest</ID>
|
||||
<ID>SleepInsteadOfDelay:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$sleep(500)</ID>
|
||||
<ID>ThrowsCount:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer</ID>
|
||||
<ID>UnusedPrivateProperty:KotlinClientTsServerTestSse.kt:KotlinClientTsServerTestSse$private val host = "localhost"</ID>
|
||||
<ID>UseCheckOrError:ClientTest.kt:ClientTest.<no name provided>$throw IllegalStateException("Test error")</ID>
|
||||
<ID>UseOrEmpty:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$(request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.content } ?: emptyList()</ID>
|
||||
<ID>UseRequire:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$throw IllegalArgumentException("Missing required argument: $argName")</ID>
|
||||
<ID>VarCouldBeVal:KotlinClientTsServerEdgeCasesTestSse.kt:KotlinClientTsServerEdgeCasesTestSse$private lateinit var client: Client</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -40,7 +40,7 @@ class InMemoryTransport : AbstractTransport() {
|
|||
}
|
||||
|
||||
override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {
|
||||
val other = otherTransport ?: throw IllegalStateException("Not connected")
|
||||
val other = checkNotNull(otherTransport) { "Not connected" }
|
||||
|
||||
other._onMessage.invoke(message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() {
|
|||
),
|
||||
),
|
||||
) { request ->
|
||||
val args = request.params.arguments ?: emptyMap()
|
||||
val args = request.params.arguments.orEmpty()
|
||||
val arg1 = args["requiredArg1"] ?: throw IllegalArgumentException(
|
||||
"Missing required argument: requiredArg1",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() {
|
|||
fun `addPrompt should send notification`() = runTest {
|
||||
// Configure notification handler
|
||||
val notifications = mutableListOf<PromptListChangedNotification>()
|
||||
client.setNotificationHandler<PromptListChangedNotification>(Method.Defined.NotificationsPromptsListChanged) {
|
||||
notifications.add(it)
|
||||
client.setNotificationHandler<PromptListChangedNotification>(
|
||||
Method.Defined.NotificationsPromptsListChanged,
|
||||
) { notification ->
|
||||
notifications.add(notification)
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
|
||||
|
|
@ -96,8 +98,10 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() {
|
|||
fun `notification should not be send when removed prompt does not exists`() = runTest {
|
||||
// Track notifications
|
||||
val notifications = mutableListOf<PromptListChangedNotification>()
|
||||
client.setNotificationHandler<PromptListChangedNotification>(Method.Defined.NotificationsPromptsListChanged) {
|
||||
notifications.add(it)
|
||||
client.setNotificationHandler<PromptListChangedNotification>(
|
||||
Method.Defined.NotificationsPromptsListChanged,
|
||||
) { notification ->
|
||||
notifications.add(notification)
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class ServerPromptsTest : AbstractServerFeaturesTest() {
|
|||
server.addPrompt(testPrompt) {
|
||||
GetPromptResult(
|
||||
description = "Test prompt description",
|
||||
messages = listOf(),
|
||||
messages = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -70,13 +70,13 @@ class ServerPromptsTest : AbstractServerFeaturesTest() {
|
|||
server.addPrompt(testPrompt1) {
|
||||
GetPromptResult(
|
||||
description = "Test prompt description 1",
|
||||
messages = listOf(),
|
||||
messages = emptyList(),
|
||||
)
|
||||
}
|
||||
server.addPrompt(testPrompt2) {
|
||||
GetPromptResult(
|
||||
description = "Test prompt description 2",
|
||||
messages = listOf(),
|
||||
messages = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,12 +50,12 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() {
|
|||
name = "Test Resource 1",
|
||||
description = "A test resource 1",
|
||||
mimeType = "text/plain",
|
||||
) {
|
||||
) { request ->
|
||||
ReadResourceResult(
|
||||
contents = listOf(
|
||||
TextResourceContents(
|
||||
text = "Test resource content 1",
|
||||
uri = it.uri,
|
||||
uri = request.uri,
|
||||
mimeType = "text/plain",
|
||||
),
|
||||
),
|
||||
|
|
@ -67,12 +67,12 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() {
|
|||
name = "Test Resource 2",
|
||||
description = "A test resource 2",
|
||||
mimeType = "text/plain",
|
||||
) {
|
||||
) { request ->
|
||||
ReadResourceResult(
|
||||
contents = listOf(
|
||||
TextResourceContents(
|
||||
text = "Test resource content 2",
|
||||
uri = it.uri,
|
||||
uri = request.uri,
|
||||
mimeType = "text/plain",
|
||||
),
|
||||
),
|
||||
|
|
@ -121,8 +121,8 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() {
|
|||
println("Thread ${Thread.currentThread().name} test 1")
|
||||
// Track notifications
|
||||
val notifications = ConcurrentLinkedQueue<ResourceUpdatedNotification>()
|
||||
client.setNotificationHandler<ResourceUpdatedNotification>(NotificationsResourcesUpdated) {
|
||||
notifications.add(it)
|
||||
client.setNotificationHandler<ResourceUpdatedNotification>(NotificationsResourcesUpdated) { notification ->
|
||||
notifications.add(notification)
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ class ServerResourcesNotificationTest : AbstractServerFeaturesTest() {
|
|||
val notifications = mutableListOf<ResourceListChangedNotification>()
|
||||
client.setNotificationHandler<ResourceListChangedNotification>(
|
||||
Method.Defined.NotificationsResourcesListChanged,
|
||||
) {
|
||||
notifications.add(it)
|
||||
) { notification ->
|
||||
notifications.add(notification)
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() {
|
|||
fun `removeTools should remove multiple tools and send two notifications`() = runTest {
|
||||
// Configure notification handler
|
||||
val notifications = mutableListOf<ToolListChangedNotification>()
|
||||
client.setNotificationHandler<ToolListChangedNotification>(Method.Defined.NotificationsToolsListChanged) {
|
||||
notifications.add(it)
|
||||
client.setNotificationHandler<ToolListChangedNotification>(
|
||||
Method.Defined.NotificationsToolsListChanged,
|
||||
) { notification ->
|
||||
notifications.add(notification)
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
|
||||
|
|
@ -83,8 +85,10 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() {
|
|||
fun `notification should not be send when removed tool does not exists`() = runTest {
|
||||
// Track notifications
|
||||
val notifications = mutableListOf<ToolListChangedNotification>()
|
||||
client.setNotificationHandler<ToolListChangedNotification>(Method.Defined.NotificationsToolsListChanged) {
|
||||
notifications.add(it)
|
||||
client.setNotificationHandler<ToolListChangedNotification>(
|
||||
Method.Defined.NotificationsToolsListChanged,
|
||||
) { notification ->
|
||||
notifications.add(notification)
|
||||
CompletableDeferred(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
14
kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml
Normal file
14
kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize</ID>
|
||||
<ID>LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize</ID>
|
||||
<ID>MaxLineLength:StdioClientTransport.kt:StdioClientTransport$*</ID>
|
||||
<ID>ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages</ID>
|
||||
<ID>ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend</ID>
|
||||
<ID>TooGenericExceptionCaught:Client.kt:Client$error: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>LongMethod:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$@Ignore @Test fun testNotificationSchemaE2E</ID>
|
||||
<ID>MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}"""</ID>
|
||||
<ID>MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}"""</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
19
kotlin-sdk-client/detekt-baseline-main.xml
Normal file
19
kotlin-sdk-client/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize</ID>
|
||||
<ID>InjectDispatcher:StdioClientTransport.kt:StdioClientTransport$Default</ID>
|
||||
<ID>InjectDispatcher:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$Default</ID>
|
||||
<ID>LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize</ID>
|
||||
<ID>MaxLineLength:StdioClientTransport.kt:StdioClientTransport$*</ID>
|
||||
<ID>NoNameShadowing:StdioClientTransport.kt:StdioClientTransport$source</ID>
|
||||
<ID>ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages</ID>
|
||||
<ID>ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend</ID>
|
||||
<ID>TooGenericExceptionCaught:Client.kt:Client$error: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable</ID>
|
||||
<ID>UseCheckOrError:Client.kt:Client$throw IllegalStateException( "Server does not support resource subscriptions (required for $method)", )</ID>
|
||||
<ID>UseCheckOrError:Client.kt:Client$throw IllegalStateException("Client does not support roots capability.")</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
18
kotlin-sdk-client/detekt-baseline-test.xml
Normal file
18
kotlin-sdk-client/detekt-baseline-test.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>AbstractClassCanBeConcreteClass:AbstractStreamableHttpClientTest.kt:AbstractStreamableHttpClientTest$AbstractStreamableHttpClientTest</ID>
|
||||
<ID>ForbiddenComment:StreamableHttpClientTest.kt:StreamableHttpClientTest$// TODO: how to get notifications via Client API?</ID>
|
||||
<ID>InjectDispatcher:StdioClientTransportErrorHandlingTest.kt:StdioClientTransportErrorHandlingTest$IO</ID>
|
||||
<ID>InjectDispatcher:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$Default</ID>
|
||||
<ID>LongMethod:StreamableHttpClientTest.kt:StreamableHttpClientTest$@Test fun `test streamableHttpClient`</ID>
|
||||
<ID>LongMethod:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$@Ignore @Test fun testNotificationSchemaE2E</ID>
|
||||
<ID>LongParameterList:MockMcp.kt:MockMcp$fun handleJSONRPCRequest</ID>
|
||||
<ID>LongParameterList:MockMcp.kt:MockMcp$fun handleWithResult</ID>
|
||||
<ID>LongParameterList:MockMcp.kt:MockMcp$fun onInitialize</ID>
|
||||
<ID>MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}"""</ID>
|
||||
<ID>MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}"""</ID>
|
||||
<ID>UseOrEmpty:ClientMetaParameterTest.kt:ClientMetaParameterTest$exception.message ?: ""</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -107,6 +107,7 @@ public suspend fun mcpClient(
|
|||
* @param clientInfo Information about the client implementation (name, version).
|
||||
* @param options Configuration options for this client.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
public open class Client(private val clientInfo: Implementation, options: ClientOptions = ClientOptions()) :
|
||||
Protocol(options) {
|
||||
|
||||
|
|
@ -159,8 +160,8 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
else -> true
|
||||
}
|
||||
|
||||
if (!hasCapability) {
|
||||
throw IllegalStateException("Server does not support $capability (required for $method)")
|
||||
check(hasCapability) {
|
||||
"Server does not support $capability (required for $method)"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +185,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
val result = request<InitializeResult>(message)
|
||||
|
||||
if (!SUPPORTED_PROTOCOL_VERSIONS.contains(result.protocolVersion)) {
|
||||
throw IllegalStateException(
|
||||
error(
|
||||
"Server's protocol version is not supported: ${result.protocolVersion}",
|
||||
)
|
||||
}
|
||||
|
|
@ -213,8 +214,8 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
override fun assertCapabilityForMethod(method: Method) {
|
||||
when (method) {
|
||||
Method.Defined.LoggingSetLevel -> {
|
||||
if (serverCapabilities?.logging == null) {
|
||||
throw IllegalStateException("Server does not support logging (required for $method)")
|
||||
checkNotNull(serverCapabilities?.logging) {
|
||||
"Server does not support logging (required for $method)"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +223,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
Method.Defined.PromptsList,
|
||||
Method.Defined.CompletionComplete,
|
||||
-> {
|
||||
if (serverCapabilities?.prompts == null) {
|
||||
checkNotNull(serverCapabilities?.prompts) {
|
||||
throw IllegalStateException("Server does not support prompts (required for $method)")
|
||||
}
|
||||
}
|
||||
|
|
@ -244,8 +245,8 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
}
|
||||
|
||||
Method.Defined.ToolsCall, Method.Defined.ToolsList -> {
|
||||
if (serverCapabilities?.tools == null) {
|
||||
throw IllegalStateException("Server does not support tools (required for $method)")
|
||||
checkNotNull(serverCapabilities?.tools) {
|
||||
"Server does not support tools (required for $method)"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,10 +263,8 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
override fun assertNotificationCapability(method: Method) {
|
||||
when (method) {
|
||||
Method.Defined.NotificationsRootsListChanged -> {
|
||||
if (capabilities.roots?.listChanged != true) {
|
||||
throw IllegalStateException(
|
||||
"Client does not support roots list changed notifications (required for $method)",
|
||||
)
|
||||
check(capabilities.roots?.listChanged == true) {
|
||||
"Client does not support roots list changed notifications (required for $method)"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,26 +284,20 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
override fun assertRequestHandlerCapability(method: Method) {
|
||||
when (method) {
|
||||
Method.Defined.SamplingCreateMessage -> {
|
||||
if (capabilities.sampling == null) {
|
||||
throw IllegalStateException(
|
||||
"Client does not support sampling capability (required for $method)",
|
||||
)
|
||||
checkNotNull(capabilities.sampling) {
|
||||
"Client does not support sampling capability (required for $method)"
|
||||
}
|
||||
}
|
||||
|
||||
Method.Defined.RootsList -> {
|
||||
if (capabilities.roots == null) {
|
||||
throw IllegalStateException(
|
||||
"Client does not support roots capability (required for $method)",
|
||||
)
|
||||
checkNotNull(capabilities.roots) {
|
||||
"Client does not support roots capability (required for $method)"
|
||||
}
|
||||
}
|
||||
|
||||
Method.Defined.ElicitationCreate -> {
|
||||
if (capabilities.elicitation == null) {
|
||||
throw IllegalStateException(
|
||||
"Client does not support elicitation capability (required for $method)",
|
||||
)
|
||||
checkNotNull(capabilities.elicitation) {
|
||||
"Client does not support elicitation capability (required for $method)"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -494,9 +487,9 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
* @throws IllegalStateException If the client does not support roots.
|
||||
*/
|
||||
public fun addRoot(uri: String, name: String) {
|
||||
if (capabilities.roots == null) {
|
||||
checkNotNull(capabilities.roots) {
|
||||
logger.error { "Failed to add root '$name': Client does not support roots capability" }
|
||||
throw IllegalStateException("Client does not support roots capability.")
|
||||
"Client does not support roots capability."
|
||||
}
|
||||
logger.info { "Adding root: $name ($uri)" }
|
||||
roots.update { current -> current.put(uri, Root(uri, name)) }
|
||||
|
|
@ -509,9 +502,9 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
* @throws IllegalStateException If the client does not support roots.
|
||||
*/
|
||||
public fun addRoots(rootsToAdd: List<Root>) {
|
||||
if (capabilities.roots == null) {
|
||||
checkNotNull(capabilities.roots) {
|
||||
logger.error { "Failed to add roots: Client does not support roots capability" }
|
||||
throw IllegalStateException("Client does not support roots capability.")
|
||||
"Client does not support roots capability."
|
||||
}
|
||||
logger.info { "Adding ${rootsToAdd.size} roots" }
|
||||
roots.update { current -> current.putAll(rootsToAdd.associateBy { it.uri }) }
|
||||
|
|
@ -550,9 +543,9 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
* @throws IllegalStateException If the client does not support roots.
|
||||
*/
|
||||
public fun removeRoots(uris: List<String>): Int {
|
||||
if (capabilities.roots == null) {
|
||||
checkNotNull(capabilities.roots) {
|
||||
logger.error { "Failed to remove roots: Client does not support roots capability" }
|
||||
throw IllegalStateException("Client does not support roots capability.")
|
||||
"Client does not support roots capability."
|
||||
}
|
||||
logger.info { "Removing ${uris.size} roots" }
|
||||
|
||||
|
|
@ -587,9 +580,9 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
* @throws IllegalStateException if the client does not support elicitation.
|
||||
*/
|
||||
public fun setElicitationHandler(handler: (ElicitRequest) -> ElicitResult) {
|
||||
if (capabilities.elicitation == null) {
|
||||
checkNotNull(capabilities.elicitation) {
|
||||
logger.error { "Failed to set elicitation handler: Client does not support elicitation" }
|
||||
throw IllegalStateException("Client does not support elicitation.")
|
||||
"Client does not support elicitation."
|
||||
}
|
||||
logger.info { "Setting the elicitation handler" }
|
||||
|
||||
|
|
@ -600,7 +593,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
|
||||
// --- Internal Handlers ---
|
||||
|
||||
private suspend fun handleListRoots(): ListRootsResult {
|
||||
private fun handleListRoots(): ListRootsResult {
|
||||
val rootList = roots.value.values.toList()
|
||||
return ListRootsResult(rootList)
|
||||
}
|
||||
|
|
@ -634,7 +627,8 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
|
||||
val labels = it.split('.')
|
||||
require(labels.all { label -> label.matches(labelPattern) }) {
|
||||
"Invalid _meta key '$key': prefix labels must start with a letter, end with letter/digit, and contain only letters, digits, or hyphens"
|
||||
"Invalid _meta key '$key': prefix labels must start with a letter, end with letter/digit, " +
|
||||
"and contain only letters, digits, or hyphens"
|
||||
}
|
||||
|
||||
require(
|
||||
|
|
@ -649,7 +643,8 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
|
||||
// Validate name (empty allowed)
|
||||
require(name.isEmpty() || name.matches(namePattern)) {
|
||||
"Invalid _meta key '$key': name must start and end with alphanumeric characters, and contain only alphanumerics, hyphens, underscores, or dots"
|
||||
"Invalid _meta key '$key': name must start and end with alphanumeric characters, " +
|
||||
"and contain only alphanumerics, hyphens, underscores, or dots"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ public class StreamableHttpClientTransport(
|
|||
/**
|
||||
* Sends a single message with optional resumption support
|
||||
*/
|
||||
@Suppress("ReturnCount", "CyclomaticComplexMethod")
|
||||
override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) {
|
||||
logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" }
|
||||
|
||||
|
|
@ -259,6 +260,7 @@ public class StreamableHttpClientTransport(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private suspend fun collectSse(
|
||||
session: ClientSSESession,
|
||||
replayMessageId: RequestId?,
|
||||
|
|
@ -295,6 +297,7 @@ public class StreamableHttpClientTransport(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private suspend fun handleInlineSse(
|
||||
response: HttpResponse,
|
||||
replayMessageId: RequestId?,
|
||||
|
|
@ -331,6 +334,7 @@ public class StreamableHttpClientTransport(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LoopWithTooManyJumpStatements")
|
||||
while (!channel.isClosedForRead) {
|
||||
val line = channel.readUTF8Line() ?: break
|
||||
if (line.isEmpty()) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue