diff --git a/.idea/libraries/io_modelcontextprotocol_kotlin_sdk.xml b/.idea/libraries/io_modelcontextprotocol_kotlin_sdk.xml index 371aa6f53b37..f1e3398ce691 100644 --- a/.idea/libraries/io_modelcontextprotocol_kotlin_sdk.xml +++ b/.idea/libraries/io_modelcontextprotocol_kotlin_sdk.xml @@ -1,18 +1,45 @@ - + - - b1d152f70209b9fd42165afc7b98beeae87cabe688f8e82429928958dc61c411 + + 095e5d11868ae359bd640c922c2d1ed7a20ba519496599ee697e81c468761339 + + + 33d9417ab21ac60413d64f2b1d0075df42c6212ac1016a4817d67f8ee41c7c43 + + + 9e46aa3b51db4f8f6df635ba52318a8a28dec3c9ab226912267e8aaf7acdfbc7 + + + d4fcb967ca13cae00c6f76b41a974fb76d864e1806d6721d2dfc666480f66f22 + + + + + + + + + + + + - + + + + - + + + + \ No newline at end of file diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index 4d440354a364..d1ff90c4408b 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -2794,13 +2794,41 @@ jvm_import( source_jar = "@io_mockk-mockk-core-jvm-1_14_5-sources_http//file" ) -jvm_import( +java_library( name = "io-modelcontextprotocol-kotlin-sdk", - jar = "@io_modelcontextprotocol-kotlin-sdk-jvm-0_6_1_http//file", - source_jar = "@io_modelcontextprotocol-kotlin-sdk-jvm-0_6_1-sources_http//file", + exports = [ + ":io_modelcontextprotocol-kotlin-sdk-jvm-0_7_7_http_import", + ":io_modelcontextprotocol-kotlin-sdk-core-jvm-0_7_7_http_import", + ":io_modelcontextprotocol-kotlin-sdk-client-jvm-0_7_7_http_import", + ":io_modelcontextprotocol-kotlin-sdk-server-jvm-0_7_7_http_import", + ], visibility = ["//visibility:public"] ) +jvm_import( + name = "io_modelcontextprotocol-kotlin-sdk-jvm-0_7_7_http_import", + jar = "@io_modelcontextprotocol-kotlin-sdk-jvm-0_7_7_http//file", + source_jar = "@io_modelcontextprotocol-kotlin-sdk-jvm-0_7_7-sources_http//file" +) + +jvm_import( + name = "io_modelcontextprotocol-kotlin-sdk-core-jvm-0_7_7_http_import", + jar = "@io_modelcontextprotocol-kotlin-sdk-core-jvm-0_7_7_http//file", + source_jar = "@io_modelcontextprotocol-kotlin-sdk-core-jvm-0_7_7-sources_http//file" +) + +jvm_import( + name = "io_modelcontextprotocol-kotlin-sdk-client-jvm-0_7_7_http_import", + jar = "@io_modelcontextprotocol-kotlin-sdk-client-jvm-0_7_7_http//file", + source_jar = "@io_modelcontextprotocol-kotlin-sdk-client-jvm-0_7_7-sources_http//file" +) + +jvm_import( + name = "io_modelcontextprotocol-kotlin-sdk-server-jvm-0_7_7_http_import", + jar = "@io_modelcontextprotocol-kotlin-sdk-server-jvm-0_7_7_http//file", + source_jar = "@io_modelcontextprotocol-kotlin-sdk-server-jvm-0_7_7-sources_http//file" +) + jvm_import( name = "io-opentelemetry-sdk-extension-autoconfigure-spi", jar = "@io_opentelemetry-opentelemetry-sdk-extension-autoconfigure-spi-1_45_0_http//file", diff --git a/lib/MODULE.bazel b/lib/MODULE.bazel index e46b3230919e..ba54ea98f202 100644 --- a/lib/MODULE.bazel +++ b/lib/MODULE.bazel @@ -4312,17 +4312,59 @@ http_file( ) http_file( - name = "io_modelcontextprotocol-kotlin-sdk-jvm-0_6_1_http", - url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-jvm/0.6.1/kotlin-sdk-jvm-0.6.1.jar", - sha256 = "b1d152f70209b9fd42165afc7b98beeae87cabe688f8e82429928958dc61c411", - downloaded_file_path = "kotlin-sdk-jvm-0.6.1.jar" + name = "io_modelcontextprotocol-kotlin-sdk-jvm-0_7_7_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-jvm/0.7.7/kotlin-sdk-jvm-0.7.7.jar", + sha256 = "095e5d11868ae359bd640c922c2d1ed7a20ba519496599ee697e81c468761339", + downloaded_file_path = "kotlin-sdk-jvm-0.7.7.jar" ) http_file( - name = "io_modelcontextprotocol-kotlin-sdk-jvm-0_6_1-sources_http", - url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-jvm/0.6.1/kotlin-sdk-jvm-0.6.1-sources.jar", - sha256 = "8a681b8948a231946d47deaa38100351ad154cea8f15cbfc0361e10d5fa95eed", - downloaded_file_path = "kotlin-sdk-jvm-0.6.1-sources.jar" + name = "io_modelcontextprotocol-kotlin-sdk-core-jvm-0_7_7_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-core-jvm/0.7.7/kotlin-sdk-core-jvm-0.7.7.jar", + sha256 = "33d9417ab21ac60413d64f2b1d0075df42c6212ac1016a4817d67f8ee41c7c43", + downloaded_file_path = "kotlin-sdk-core-jvm-0.7.7.jar" +) + +http_file( + name = "io_modelcontextprotocol-kotlin-sdk-client-jvm-0_7_7_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-client-jvm/0.7.7/kotlin-sdk-client-jvm-0.7.7.jar", + sha256 = "9e46aa3b51db4f8f6df635ba52318a8a28dec3c9ab226912267e8aaf7acdfbc7", + downloaded_file_path = "kotlin-sdk-client-jvm-0.7.7.jar" +) + +http_file( + name = "io_modelcontextprotocol-kotlin-sdk-server-jvm-0_7_7_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-server-jvm/0.7.7/kotlin-sdk-server-jvm-0.7.7.jar", + sha256 = "d4fcb967ca13cae00c6f76b41a974fb76d864e1806d6721d2dfc666480f66f22", + downloaded_file_path = "kotlin-sdk-server-jvm-0.7.7.jar" +) + +http_file( + name = "io_modelcontextprotocol-kotlin-sdk-jvm-0_7_7-sources_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-jvm/0.7.7/kotlin-sdk-jvm-0.7.7-sources.jar", + sha256 = "dd2f46d3cdc1f5d6c4b1fa990fccf78b3f93cb2cb1ad7dadf693b184aadc837e", + downloaded_file_path = "kotlin-sdk-jvm-0.7.7-sources.jar" +) + +http_file( + name = "io_modelcontextprotocol-kotlin-sdk-core-jvm-0_7_7-sources_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-core-jvm/0.7.7/kotlin-sdk-core-jvm-0.7.7-sources.jar", + sha256 = "9a9dc9bfd6b099c6fd2cbc12a401e2f44dd4fc3cbbce19f13cc46cb6e3a6e424", + downloaded_file_path = "kotlin-sdk-core-jvm-0.7.7-sources.jar" +) + +http_file( + name = "io_modelcontextprotocol-kotlin-sdk-client-jvm-0_7_7-sources_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-client-jvm/0.7.7/kotlin-sdk-client-jvm-0.7.7-sources.jar", + sha256 = "37f7715675946f59a96a94252bf07ca1bf9ccc7e9b65045ebe8bb840f7c241d4", + downloaded_file_path = "kotlin-sdk-client-jvm-0.7.7-sources.jar" +) + +http_file( + name = "io_modelcontextprotocol-kotlin-sdk-server-jvm-0_7_7-sources_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/modelcontextprotocol/kotlin-sdk-server-jvm/0.7.7/kotlin-sdk-server-jvm-0.7.7-sources.jar", + sha256 = "3636f760f382a1f6fd424c431cd98d1d1cac5892601068839780cc32ef004b89", + downloaded_file_path = "kotlin-sdk-server-jvm-0.7.7-sources.jar" ) http_file( diff --git a/plugins/mcp-server/plugin-content.yaml b/plugins/mcp-server/plugin-content.yaml index 790a00fa58ec..ebc31827f78a 100644 --- a/plugins/mcp-server/plugin-content.yaml +++ b/plugins/mcp-server/plugin-content.yaml @@ -29,6 +29,9 @@ - name: io.modelcontextprotocol.kotlin.sdk files: - name: $MAVEN_REPOSITORY$/io/modelcontextprotocol/kotlin-sdk-jvm/0/kotlin-sdk-jvm-0.jar + - name: $MAVEN_REPOSITORY$/io/modelcontextprotocol/kotlin-sdk-core-jvm/0/kotlin-sdk-core-jvm-0.jar + - name: $MAVEN_REPOSITORY$/io/modelcontextprotocol/kotlin-sdk-client-jvm/0/kotlin-sdk-client-jvm-0.jar + - name: $MAVEN_REPOSITORY$/io/modelcontextprotocol/kotlin-sdk-server-jvm/0/kotlin-sdk-server-jvm-0.jar reason: <- intellij.mcpserver.stdio - name: ktor-server-sse-jvm files: diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/impl/McpServerService.kt b/plugins/mcp-server/src/com/intellij/mcpserver/impl/McpServerService.kt index 85776045954e..5e2dcebe548f 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/impl/McpServerService.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/impl/McpServerService.kt @@ -3,17 +3,12 @@ package com.intellij.mcpserver.impl import com.intellij.concurrency.currentThreadContext import com.intellij.mcpserver.* import com.intellij.mcpserver.impl.util.network.* -import com.intellij.mcpserver.impl.util.network.McpServerConnectionAddressProvider import com.intellij.mcpserver.impl.util.projectPathParameterName import com.intellij.mcpserver.settings.McpServerSettings import com.intellij.mcpserver.statistics.McpServerCounterUsagesCollector import com.intellij.mcpserver.stdio.IJ_MCP_SERVER_PROJECT_PATH import com.intellij.mcpserver.util.findMostRelevantProject -import com.intellij.openapi.application.ApplicationInfo -import com.intellij.openapi.application.ApplicationNamesInfo -import com.intellij.openapi.application.EDT -import com.intellij.openapi.application.readAction -import com.intellij.openapi.application.writeIntentReadAction +import com.intellij.openapi.application.* import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.components.serviceAsync @@ -48,6 +43,7 @@ import io.modelcontextprotocol.kotlin.sdk.* import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest @@ -158,7 +154,8 @@ class McpServerService(val cs: CoroutineScope) { private fun isKnownToken(token: String): Boolean = activeAuthorizedSessions.containsKey(token) private fun getSessionOptions(token: String?): McpSessionOptions { - return token?.let { activeAuthorizedSessions[it] } ?: McpSessionOptions(commandExecutionMode = AskCommandExecutionMode.RESPECT_GLOBAL_SETTINGS) + return token?.let { activeAuthorizedSessions[it] } + ?: McpSessionOptions(commandExecutionMode = AskCommandExecutionMode.RESPECT_GLOBAL_SETTINGS) } val port: Int @@ -237,7 +234,7 @@ class McpServerService(val cs: CoroutineScope) { finish() } } - }) { applicationCall -> + }) { applicationCall, transport -> // this is added because now a Kotlin MCP client doesn't support header adjusting for each request, only for initial one, see McpStdioRunner val projectPath = applicationCall.request.headers[IJ_MCP_SERVER_PROJECT_PATH] val mcpServer = Server( @@ -250,24 +247,30 @@ class McpServerService(val cs: CoroutineScope) { //prompts = ServerCapabilities.Prompts(listChanged = true), //resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), tools = ServerCapabilities.Tools(listChanged = true), + logging = null, + experimental = null, + sampling = null, + prompts = null, + resources = null, ) ) ) - mcpServer.setRequestHandler(Method.Defined.LoggingSetLevel) { _, _ -> - // Workaround inspector failure - return@setRequestHandler EmptyRequestResult() - } + val session = mcpServer.createSession(transport) + //session.setRequestHandler(Method.Defined.LoggingSetLevel) { _, _ -> + // // Workaround inspector failure + // return@setRequestHandler EmptyRequestResult() + //} launch { var previousTools: List? = null mcpTools.collectLatest { updatedTools -> previousTools?.forEach { previousTool -> mcpServer.removeTool(previousTool.descriptor.name) } - mcpServer.addTools(updatedTools.map { it.mcpToolToRegisteredTool(mcpServer, projectPath) }) + mcpServer.addTools(updatedTools.map { it.mcpToolToRegisteredTool(mcpServer, session, projectPath) }) previousTools = updatedTools } } - return@mcpPatched mcpServer + return@mcpPatched session } }.start(wait = false) } @@ -282,7 +285,7 @@ class McpServerService(val cs: CoroutineScope) { } } - private fun McpTool.mcpToolToRegisteredTool(server: Server, projectPathFromInitialRequest: String?): RegisteredTool { + private fun McpTool.mcpToolToRegisteredTool(server: Server, session: ServerSession, projectPathFromInitialRequest: String?): RegisteredTool { val tool = toSdkTool() return RegisteredTool(tool) { request -> val httpRequest = currentCoroutineContext().httpRequestOrNull @@ -318,7 +321,7 @@ class McpServerService(val cs: CoroutineScope) { val vfsEvent = CopyOnWriteArrayList() val initialDocumentContents = ConcurrentHashMap() - val clientVersion = server.clientVersion ?: Implementation("Unknown MCP client", "Unknown version") + val clientVersion = session.clientVersion ?: Implementation("Unknown MCP client", "Unknown version") val additionalData = McpCallInfo( callId = callId.getAndAdd(1), @@ -491,6 +494,7 @@ private fun McpTool.toSdkTool(): Tool { } else null val tool = Tool(name = descriptor.name, + title = null, description = descriptor.description, inputSchema = Tool.Input( properties = descriptor.inputSchema.propertiesSchema, diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/impl/util/network/mcp.sdk.util.kt b/plugins/mcp-server/src/com/intellij/mcpserver/impl/util/network/mcp.sdk.util.kt index ca3e0f2c2595..2c7bfe98bd71 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/impl/util/network/mcp.sdk.util.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/impl/util/network/mcp.sdk.util.kt @@ -4,11 +4,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.trace import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.ApplicationCallPipeline -import io.ktor.server.application.PipelineCall -import io.ktor.server.application.install +import io.ktor.server.application.* import io.ktor.server.request.ApplicationRequest import io.ktor.server.request.receiveText import io.ktor.server.response.respond @@ -28,17 +24,14 @@ import io.ktor.utils.io.KtorDsl import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport +import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.shared.Transport import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.withContext -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.* import kotlin.coroutines.CoroutineContext private val logger = logger() @@ -46,7 +39,7 @@ private val logger = logger() @KtorDsl fun Application.mcpPatched( prePhase: suspend PipelineContext<*, PipelineCall>.() -> Unit, - block: suspend (ApplicationCall) -> Server, + block: suspend (ApplicationCall, Transport) -> ServerSession, ) { val transports = ConcurrentMap() val streamableSessions = ConcurrentMap() @@ -83,19 +76,19 @@ fun Application.mcpPatched( private suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, transports: ConcurrentMap, - block: suspend (ApplicationCall) -> Server, + block: suspend (ApplicationCall, Transport) -> ServerSession, ) { val transport = mcpSseTransport(postEndpoint, transports) - val server = block(call) + val serverSession = block(call, transport) - server.onClose { + serverSession.onClose { logger.trace { "Server connection closed for sessionId: ${transport.sessionId}" } transports.remove(transport.sessionId) } - server.connect(transport) logger.trace { "Server connected to transport for sessionId: ${transport.sessionId}" } + awaitCancellation() } internal fun ServerSSESession.mcpSseTransport( @@ -137,7 +130,7 @@ internal suspend fun RoutingContext.mcpPostEndpoint( private suspend fun RoutingContext.handleStreamablePost( sessions: ConcurrentMap, - block: suspend (ApplicationCall) -> Server, + block: suspend (ApplicationCall, Transport) -> ServerSession, ) { val rawBody = try { call.receiveText() @@ -225,10 +218,10 @@ private suspend fun RoutingContext.findStreamableSession( private suspend fun createStreamableSession( applicationCall: ApplicationCall, sessions: ConcurrentMap, - block: suspend (ApplicationCall) -> Server, + block: suspend (ApplicationCall, Transport) -> ServerSession, ): StreamableSession { val transport = StreamableHttpServerTransport() - val server = block(applicationCall) + val serverSession = block(applicationCall, transport) transport.onError { logger.error("Error in Streamable HTTP transport", it) @@ -237,14 +230,14 @@ private suspend fun createStreamableSession( logger.trace { "Streamable HTTP transport closed for session ${transport.sessionId}" } sessions.remove(transport.sessionId) } - server.onClose { + serverSession.onClose { logger.trace { "Server closed for Streamable HTTP session ${transport.sessionId}" } sessions.remove(transport.sessionId) } - server.connect(transport) + serverSession.connect(transport) - val session = StreamableSession(transport, server) + val session = StreamableSession(transport, serverSession) sessions[transport.sessionId] = session logger.trace { "Streamable HTTP session started with id ${transport.sessionId}" } return session @@ -280,7 +273,7 @@ private suspend fun RoutingContext.respondJsonError(status: HttpStatusCode, code } private data class ParsedMessages(val messages: List, val isBatch: Boolean) -private data class StreamableSession(val transport: StreamableHttpServerTransport, val server: Server) +private data class StreamableSession(val transport: StreamableHttpServerTransport, val server: ServerSession) //–– your custom context element class HttpRequestElement(val request: ApplicationRequest) : CoroutineContext.Element {