[MCP Server] Track changes related only to a particular call

(cherry picked from commit ae72e42dba48c6c6a656c4a9bb1a45c92546fec8)

GitOrigin-RevId: 894355da9e7928ad0723846154922a0f03fffa1f
This commit is contained in:
Artem.Bukhonov
2025-07-18 22:58:20 +02:00
committed by intellij-monorepo-bot
parent ef284060e5
commit 7c7a9b1ce4
2 changed files with 33 additions and 12 deletions

View File

@@ -7,12 +7,17 @@ import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext
class McpCallAdditionalData(
val callId: Int,
val clientInfo: ClientInfo,
val project: Project?,
val mcpToolDescriptor: McpToolDescriptor,
val rawArguments: JsonObject,
val meta: JsonObject
)
) {
override fun toString(): String {
return "McpCallAdditionalData(id=$callId, clientInfo=$clientInfo, toolName=${mcpToolDescriptor.name}"
}
}
class ClientInfo(val name: String, val version: String)
@@ -20,7 +25,8 @@ class McpCallAdditionalDataElement(val additionalData: McpCallAdditionalData) :
companion object Key : CoroutineContext.Key<McpCallAdditionalDataElement>
}
val CoroutineContext.mcpCallAdditionalData: McpCallAdditionalData get() = get(McpCallAdditionalDataElement)?.additionalData ?: error("mcpCallAdditionalData called outside of a MCP call")
val CoroutineContext.mcpCallAdditionalDataOrNull: McpCallAdditionalData? get() = get(McpCallAdditionalDataElement)?.additionalData
val CoroutineContext.mcpCallAdditionalData: McpCallAdditionalData get() = mcpCallAdditionalDataOrNull ?: error("mcpCallAdditionalData called outside of a MCP call")
/**
* Returns information about the MCP client that is calling a tool.

View File

@@ -1,5 +1,6 @@
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.settings.McpServerSettings
@@ -45,6 +46,8 @@ import kotlinx.coroutines.flow.update
import kotlinx.serialization.json.JsonPrimitive
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.coroutines.cancellation.CancellationException
@@ -58,6 +61,8 @@ class McpServerService(val cs: CoroutineScope) {
}
private val server = MutableStateFlow(startServerIfEnabled())
@OptIn(ExperimentalAtomicApi::class)
private val callId = AtomicInteger(0)
val isRunning: Boolean
get() = server.value != null
@@ -198,11 +203,25 @@ private fun McpTool.mcpToolToRegisteredTool(server: Server, projectPathFromIniti
val vfsEvent = CopyOnWriteArrayList<VFileEvent>()
val initialDocumentContents = ConcurrentHashMap<Document, String>()
val clientVersion = server.clientVersion ?: Implementation("Unknown MCP client", "Unknown version")
val additionalData = McpCallAdditionalData(
callId = callId.getAndAdd(1),
clientInfo = ClientInfo(clientVersion.name, clientVersion.version),
project = project,
mcpToolDescriptor = descriptor,
rawArguments = request.arguments,
meta = request._meta
)
val callResult = coroutineScope {
VirtualFileManager.getInstance().addAsyncFileListener(this, AsyncFileListener { events ->
vfsEvent.addAll(events)
val inHandlerData = currentThreadContext().mcpCallAdditionalDataOrNull
if (inHandlerData != null && inHandlerData.callId == additionalData.callId) {
logger.trace { "VFS changes detected for call: $inHandlerData" }
vfsEvent.addAll(events)
}
// probably we have to read initial contents here
// see comment below near `is VFileContentChangeEvent`
return@AsyncFileListener object : AsyncFileListener.ChangeApplier {}
@@ -211,20 +230,16 @@ private fun McpTool.mcpToolToRegisteredTool(server: Server, projectPathFromIniti
val documentListener = object : DocumentListener {
// record content before any change
override fun beforeDocumentChange(event: DocumentEvent) {
initialDocumentContents.computeIfAbsent(event.document) { event.document.text }
val inHandlerData = currentThreadContext().mcpCallAdditionalDataOrNull
if (inHandlerData != null && inHandlerData.callId == additionalData.callId) {
logger.trace { "Document changes detected for call: $inHandlerData" }
initialDocumentContents.computeIfAbsent(event.document) { event.document.text }
}
}
}
EditorFactory.getInstance().eventMulticaster.addDocumentListener(documentListener, this.asDisposable())
val clientVersion = server.clientVersion ?: Implementation("Unknown MCP client", "Unknown version")
val additionalData = McpCallAdditionalData(
clientInfo = ClientInfo(clientVersion.name, clientVersion.version),
project = project,
mcpToolDescriptor = descriptor,
rawArguments = request.arguments,
meta = request._meta
)
withContext(
McpCallAdditionalDataElement(additionalData)
) {