diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/McpToolDescriptorElement.kt b/plugins/mcp-server/src/com/intellij/mcpserver/McpToolDescriptorElement.kt new file mode 100644 index 000000000000..944a7179facf --- /dev/null +++ b/plugins/mcp-server/src/com/intellij/mcpserver/McpToolDescriptorElement.kt @@ -0,0 +1,12 @@ +package com.intellij.mcpserver + +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +data class McpToolDescriptorElement(val mcpToolDescriptor: McpToolDescriptor?) : AbstractCoroutineContextElement(Key) { + companion object Key : CoroutineContext.Key +} + +val CoroutineContext.currentToolDescriptorOrNull: McpToolDescriptor? get() = get(McpToolDescriptorElement)?.mcpToolDescriptor +val CoroutineContext.currentToolDescriptor: McpToolDescriptor get() = get(McpToolDescriptorElement)?.mcpToolDescriptor + ?: error("currentToolDescriptor called outside of a MCP tool call") diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/ToolCallListener.kt b/plugins/mcp-server/src/com/intellij/mcpserver/ToolCallListener.kt index 729d15cb7df2..874a307b9466 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/ToolCallListener.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/ToolCallListener.kt @@ -3,6 +3,7 @@ package com.intellij.mcpserver import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.application import com.intellij.util.messages.Topic +import kotlin.coroutines.CoroutineContext interface ToolCallListener { companion object { @@ -12,9 +13,9 @@ interface ToolCallListener { fun beforeMcpToolCall(mcpToolDescriptor: McpToolDescriptor) {} - fun afterMcpToolCall(mcpToolDescriptor: McpToolDescriptor, events: List) {} + fun afterMcpToolCall(mcpToolDescriptor: McpToolDescriptor, events: List, error: Throwable?) {} - fun toolActivity(toolDescription: String) {} + fun toolActivity(mcpToolDescriptor: McpToolDescriptor, toolActivityDescription: String) {} } sealed interface McpToolSideEffectEvent @@ -26,6 +27,6 @@ class FileDeletedEvent(val file: VirtualFile, val content: String?) : FileEvent class FileMovedEvent(val file: VirtualFile, val oldParent: VirtualFile, val newParent: VirtualFile) : FileEvent class FileContentChangeEvent(val file: VirtualFile, val oldContent: String?, val newContent: String) : FileEvent -fun reportToolActivity(toolDescription: String) { - application.messageBus.syncPublisher(ToolCallListener.TOPIC).toolActivity(toolDescription) +fun CoroutineContext.reportToolActivity(toolDescription: String) { + application.messageBus.syncPublisher(ToolCallListener.TOPIC).toolActivity(this.currentToolDescriptor, toolDescription) } \ No newline at end of file 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 c76b0e7ab75d..9de5eaa2313b 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/impl/McpServerService.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/impl/McpServerService.kt @@ -217,47 +217,47 @@ private fun McpTool.mcpToolToRegisteredTool(): RegisteredTool { EditorFactory.getInstance().eventMulticaster.addDocumentListener(documentListener, this.asDisposable()) + val sideEffectEvents = mutableListOf() @Suppress("IncorrectCancellationExceptionHandling") try { application.messageBus.syncPublisher(ToolCallListener.TOPIC).beforeMcpToolCall(this@mcpToolToRegisteredTool.descriptor) logger.trace { "Start calling tool '${this@mcpToolToRegisteredTool.descriptor.name}'. Arguments: ${request.arguments}" } - val result = withContext(ProjectContextElement(project)) { + val result = withContext(ProjectContextElement(project) + McpToolDescriptorElement(this@mcpToolToRegisteredTool.descriptor)) { this@mcpToolToRegisteredTool.call(request.arguments) } logger.trace { "Tool call successful '${this@mcpToolToRegisteredTool.descriptor.name}'. Result: ${result.content.joinToString("\n") { it.toString() }}" } try { val processedChangedFiles = mutableSetOf() - val events = mutableListOf() for ((doc, oldContent) in initialDocumentContents) { val virtualFile = FileDocumentManager.getInstance().getFile(doc) ?: continue val newContent = readAction { doc.text } - events.add(FileContentChangeEvent(virtualFile, oldContent, newContent)) + sideEffectEvents.add(FileContentChangeEvent(virtualFile, oldContent, newContent)) processedChangedFiles.add(virtualFile) } for (event in vfsEvent) { when (event) { is VFileMoveEvent -> { - events.add(FileMovedEvent(event.file, event.oldParent, event.newParent)) + sideEffectEvents.add(FileMovedEvent(event.file, event.oldParent, event.newParent)) } is VFileCreateEvent -> { val virtualFile = event.file ?: continue val newContent = readAction { FileDocumentManager.getInstance().getDocument(virtualFile)?.text } ?: continue - events.add(FileCreatedEvent(virtualFile, newContent)) + sideEffectEvents.add(FileCreatedEvent(virtualFile, newContent)) } is VFileDeleteEvent -> { val virtualFile = event.file val document = readAction { FileDocumentManager.getInstance().getDocument(virtualFile) } ?: continue val oldContent = initialDocumentContents[document] - events.add(FileDeletedEvent(virtualFile, oldContent)) + sideEffectEvents.add(FileDeletedEvent(virtualFile, oldContent)) } is VFileCopyEvent -> { val createdFile = event.findCreatedFile() ?: continue val newContent = readAction { FileDocumentManager.getInstance().getDocument(createdFile)?.text } ?: continue - events.add(FileCreatedEvent(createdFile, newContent)) + sideEffectEvents.add(FileCreatedEvent(createdFile, newContent)) } is VFileContentChangeEvent -> { // reported in documents loop @@ -267,12 +267,11 @@ private fun McpTool.mcpToolToRegisteredTool(): RegisteredTool { // Important: there may be a case when file is changed via low level change (like File.replaceText). // in this case we don't track the old content, because it may be heavy, it requires loading the file in // AsyncFileListener above and decoding with encoding etc. The file can be binary etc. - events.add(FileContentChangeEvent(virtualFile, oldContent = null, newContent = newContent)) + sideEffectEvents.add(FileContentChangeEvent(virtualFile, oldContent = null, newContent = newContent)) } } } - application.messageBus.syncPublisher(ToolCallListener.TOPIC).afterMcpToolCall(this@mcpToolToRegisteredTool.descriptor, events) } catch (ce: CancellationException) { throw ce @@ -280,20 +279,24 @@ private fun McpTool.mcpToolToRegisteredTool(): RegisteredTool { catch (t: Throwable) { logger.error("Failed to process changed documents after calling MCP tool ${this@mcpToolToRegisteredTool.descriptor.name}", t) } + application.messageBus.syncPublisher(ToolCallListener.TOPIC).afterMcpToolCall(this@mcpToolToRegisteredTool.descriptor, sideEffectEvents, null) result } catch (ce: CancellationException) { val message = "MCP tool call has been cancelled: ${ce.message}" logger.traceThrowable { CancellationException(message, ce) } + application.messageBus.syncPublisher(ToolCallListener.TOPIC).afterMcpToolCall(this@mcpToolToRegisteredTool.descriptor, sideEffectEvents, ce) McpToolCallResult.error(message) } catch (mcpException: McpExpectedError) { logger.traceThrowable { mcpException } + application.messageBus.syncPublisher(ToolCallListener.TOPIC).afterMcpToolCall(this@mcpToolToRegisteredTool.descriptor, sideEffectEvents, mcpException) McpToolCallResult.error(mcpException.mcpErrorText) } catch (t: Throwable) { val errorMessage = "MCP tool call has been failed: ${t.message}" logger.error(t) + application.messageBus.syncPublisher(ToolCallListener.TOPIC).afterMcpToolCall(this@mcpToolToRegisteredTool.descriptor, sideEffectEvents, t) McpToolCallResult.error(errorMessage) } finally { diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/AnalysisToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/AnalysisToolset.kt index f489e3de1784..9d5ceaa8a87a 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/AnalysisToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/AnalysisToolset.kt @@ -51,7 +51,7 @@ class AnalysisToolset : McpToolset { @McpDescription(Constants.TIMEOUT_MILLISECONDS_DESCRIPTION) timeout: Int = Constants.MEDIUM_TIMEOUT_MILLISECONDS_VALUE, ): FileProblemsResult { - reportToolActivity("Collecting problems in file $filePath") + currentCoroutineContext().reportToolActivity("Collecting problems in file $filePath") val project = currentCoroutineContext().project val projectDir = project.projectDirectory @@ -107,6 +107,7 @@ class AnalysisToolset : McpToolset { @McpDescription(Constants.TIMEOUT_MILLISECONDS_DESCRIPTION) timeout: Int = Constants.LONG_TIMEOUT_MILLISECONDS_VALUE, ): ProjectProblemsResult { + currentCoroutineContext().reportToolActivity("Checking project issues") val project = currentCoroutineContext().project val problems = CopyOnWriteArrayList() @@ -144,6 +145,7 @@ class AnalysisToolset : McpToolset { |Returns structured information about each module including name and type. """) suspend fun get_project_modules(): ProjectModulesResult { + currentCoroutineContext().reportToolActivity("Listing modules") val project = currentCoroutineContext().project val modules = readAction { @@ -165,6 +167,7 @@ class AnalysisToolset : McpToolset { |Returns structured information about project library names. """) suspend fun get_project_dependencies(): ProjectDependenciesResult { + currentCoroutineContext().reportToolActivity("Checking dependencies") val project = currentCoroutineContext().project val dependencies = readAction { diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/CodeInsightToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/CodeInsightToolset.kt index 53cdd332797e..03e52d813cc7 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/CodeInsightToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/CodeInsightToolset.kt @@ -6,6 +6,7 @@ import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.mcpFail import com.intellij.mcpserver.project +import com.intellij.mcpserver.reportToolActivity import com.intellij.mcpserver.toolsets.Constants import com.intellij.mcpserver.util.SymbolInfo import com.intellij.mcpserver.util.convertHtmlToMarkdown @@ -45,6 +46,7 @@ class CodeInsightToolset : McpToolset { @McpDescription("1-based column number") column: Int, ): SymbolInfoResult { + currentCoroutineContext().reportToolActivity("Getting symbol info at '$filePath:$line:$column'") val project = currentCoroutineContext().project val resolvedPath = project.resolveInProject(filePath) diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/ExecutionToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/ExecutionToolset.kt index a35d40db2ddd..cc50b2ceb448 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/ExecutionToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/ExecutionToolset.kt @@ -18,6 +18,7 @@ import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.mcpFail import com.intellij.mcpserver.project +import com.intellij.mcpserver.reportToolActivity import com.intellij.mcpserver.toolsets.Constants import com.intellij.mcpserver.util.TruncateMode import com.intellij.mcpserver.util.checkUserConfirmationIfNeeded @@ -43,6 +44,7 @@ class ExecutionToolset : McpToolset { |Use this tool to query the list of available run configurations in the current project. """) suspend fun get_run_configurations(): RunConfigurationsList { + currentCoroutineContext().reportToolActivity("Getting run configurations") val project = currentCoroutineContext().project val runManager = RunManager.getInstance(project) @@ -78,6 +80,7 @@ class ExecutionToolset : McpToolset { @McpDescription(Constants.TRUNCATE_MODE_DESCRIPTION) truncateMode: TruncateMode = Constants.TRUCATE_MODE_VALUE, ): RunConfigurationResult { + currentCoroutineContext().reportToolActivity("Executing run configuration '$configurationName'") val project = currentCoroutineContext().project val runManager = RunManager.getInstance(project) diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FileToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FileToolset.kt index 16dba049463e..fb7f17e2fa84 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FileToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FileToolset.kt @@ -3,8 +3,7 @@ package com.intellij.mcpserver.toolsets.general -import com.intellij.mcpserver.McpServerBundle -import com.intellij.mcpserver.McpToolset +import com.intellij.mcpserver.* import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.mcpFail @@ -31,6 +30,7 @@ import com.intellij.platform.ide.progress.withBackgroundProgress import com.intellij.psi.search.FilenameIndex import com.intellij.psi.search.GlobalSearchScope import kotlinx.coroutines.* +import kotlinx.io.IOException import kotlinx.serialization.EncodeDefault import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable @@ -56,6 +56,7 @@ class FileToolset : McpToolset { @McpDescription("Maximum recursion depth") maxDepth: Int = 5, @McpDescription(Constants.TIMEOUT_MILLISECONDS_DESCRIPTION) timeout: Int = Constants.MEDIUM_TIMEOUT_MILLISECONDS_VALUE, ): DirectoryTreeInfo { + currentCoroutineContext().reportToolActivity("Traversing folder tree for '$directoryPath'") val project = currentCoroutineContext().project val resolvedPath = project.resolveInProject(directoryPath) if (!resolvedPath.exists()) mcpFail("No such directory: $resolvedPath") @@ -93,6 +94,7 @@ class FileToolset : McpToolset { @McpDescription("Timeout in milliseconds") timeout: Int = Constants.MEDIUM_TIMEOUT_MILLISECONDS_VALUE, ): FilesListResult { + currentCoroutineContext().reportToolActivity("Finding files with name containing '$nameKeyword'") val project = currentCoroutineContext().project val projectDir = project.projectDirectory @@ -143,6 +145,7 @@ class FileToolset : McpToolset { @McpDescription(Constants.TIMEOUT_MILLISECONDS_DESCRIPTION) timeout: Int = Constants.MEDIUM_TIMEOUT_MILLISECONDS_VALUE ) : FilesListResult { + currentCoroutineContext().reportToolActivity("Finding files by glob '$globPattern'") val project = currentCoroutineContext().project val projectDirPath = project.projectDirectory val fileIndex = ProjectRootManager.getInstance(project).getFileIndex() @@ -202,6 +205,7 @@ class FileToolset : McpToolset { @McpDescription(Constants.RELATIVE_PATH_IN_PROJECT_DESCRIPTION) filePath: String, ) { + currentCoroutineContext().reportToolActivity("Opening file '$filePath'") val project = currentCoroutineContext().project val resolvedPath = project.resolveInProject(filePath) @@ -222,6 +226,7 @@ class FileToolset : McpToolset { |Use this tool to explore current open editors. """) suspend fun get_all_open_file_paths(): OpenFilesInfo { + currentCoroutineContext().reportToolActivity("Getting open files") val project = currentCoroutineContext().project val projectDir = project.projectDirectory @@ -250,10 +255,21 @@ class FileToolset : McpToolset { @McpDescription("Content to write into the new file") text: String? = null, ) { + currentCoroutineContext().reportToolActivity("Creating file '$pathInProject'") val project = currentCoroutineContext().project val path = project.resolveInProject(pathInProject) - val newFile = LocalFileSystem.getInstance().createChildFile(null, VfsUtil.createDirectories(path.parent.pathString), path.name) + val newFile = try { + LocalFileSystem.getInstance().createChildFile(null, VfsUtil.createDirectories(path.parent.pathString), path.name) + } + catch (io: IOException) { + mcpFail("Can't create file: $path: ${io.message}") + } + val refreshed = CompletableDeferred() + LocalFileSystem.getInstance().refreshFiles(listOf(newFile), true, false) { + refreshed.complete(Unit) + } + refreshed.await() writeAction { val document = FileDocumentManager.getInstance().getDocument(newFile, project) ?: mcpFail("Can't get document for created file: $newFile") if (text != null) { diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FormattingToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FormattingToolset.kt index b0c36ee701c5..cad9d035e6a4 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FormattingToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/FormattingToolset.kt @@ -8,6 +8,7 @@ import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.mcpFail import com.intellij.mcpserver.project +import com.intellij.mcpserver.reportToolActivity import com.intellij.mcpserver.toolsets.Constants import com.intellij.mcpserver.util.resolveInProject import com.intellij.openapi.application.EDT @@ -29,6 +30,7 @@ class FormattingToolset : McpToolset { @McpDescription(Constants.RELATIVE_PATH_IN_PROJECT_DESCRIPTION) path: String, ): String { + currentCoroutineContext().reportToolActivity("Formatting file '$path'") val project = currentCoroutineContext().project val resolvedFilePath = project.resolveInProject(path) diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/RefactoringToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/RefactoringToolset.kt index 8d7cc46965ae..ecc761bb5232 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/RefactoringToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/RefactoringToolset.kt @@ -5,6 +5,7 @@ import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.mcpFail import com.intellij.mcpserver.project +import com.intellij.mcpserver.reportToolActivity import com.intellij.mcpserver.toolsets.Constants import com.intellij.mcpserver.util.resolveInProject import com.intellij.openapi.application.EDT @@ -44,6 +45,7 @@ class RefactoringToolset : McpToolset { @McpDescription("New name for the symbol") newName: String, ): String { + currentCoroutineContext().reportToolActivity("Renaming '$symbolName' to '$newName' in '$pathInProject'") val project = currentCoroutineContext().project val resolvedPath = project.resolveInProject(pathInProject) diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/TextToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/TextToolset.kt index fc57cc47689b..033784c520d8 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/TextToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/general/TextToolset.kt @@ -10,6 +10,7 @@ import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.mcpFail import com.intellij.mcpserver.project +import com.intellij.mcpserver.reportToolActivity import com.intellij.mcpserver.toolsets.Constants import com.intellij.mcpserver.util.* import com.intellij.openapi.application.readAction @@ -51,6 +52,7 @@ class TextToolset : McpToolset { @McpDescription("Max number of lines to return. Truncation will be performed depending on truncateMode.") maxLinesCount: Int = 1000, ): String { + currentCoroutineContext().reportToolActivity("Reading file '$pathInProject'") val project = currentCoroutineContext().project val resolvedPath = project.resolveInProject(pathInProject) @@ -102,6 +104,7 @@ class TextToolset : McpToolset { @McpDescription("Case-sensitive search") caseSensitive: Boolean = true, ) { + currentCoroutineContext().reportToolActivity("Replacing text in '$pathInProject': '$oldText' → '$newText'") val project = currentCoroutineContext().project val resolvedPath = project.resolveInProject(pathInProject) val (document, text) = readAction { @@ -153,7 +156,10 @@ class TextToolset : McpToolset { maxUsageCount: Int = 1000, @McpDescription(Constants.TIMEOUT_MILLISECONDS_DESCRIPTION) timeout: Int = Constants.MEDIUM_TIMEOUT_MILLISECONDS_VALUE, - ): UsageInfoResult = search_in_files(searchText, false, directoryToSearch, fileMask, caseSensitive, maxUsageCount, timeout) + ): UsageInfoResult { + currentCoroutineContext().reportToolActivity("Searching project files for '$searchText'") + return search_in_files(searchText, false, directoryToSearch, fileMask, caseSensitive, maxUsageCount, timeout) + } @McpTool @McpDescription(""" @@ -175,7 +181,10 @@ class TextToolset : McpToolset { maxUsageCount: Int = 1000, @McpDescription(Constants.TIMEOUT_MILLISECONDS_DESCRIPTION) timeout: Int = Constants.MEDIUM_TIMEOUT_MILLISECONDS_VALUE, - ): UsageInfoResult = search_in_files(regexPattern, true, directoryToSearch, fileMask, caseSensitive, maxUsageCount, timeout) + ): UsageInfoResult { + currentCoroutineContext().reportToolActivity("Searching content with regex '$regexPattern'") + return search_in_files(regexPattern, true, directoryToSearch, fileMask, caseSensitive, maxUsageCount, timeout) + } private suspend fun search_in_files( searchTextOrRegex: String, diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/terminal/TerminalToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/terminal/TerminalToolset.kt index d72f1dbf0fd1..5812d9413d7b 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/terminal/TerminalToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/terminal/TerminalToolset.kt @@ -7,6 +7,7 @@ import com.intellij.mcpserver.McpToolset import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.project +import com.intellij.mcpserver.reportToolActivity import com.intellij.mcpserver.toolsets.Constants import com.intellij.mcpserver.util.TruncateMode import com.intellij.mcpserver.util.checkUserConfirmationIfNeeded @@ -53,6 +54,7 @@ class TerminalToolset : McpToolset { @McpDescription(Constants.TRUNCATE_MODE_DESCRIPTION) truncateMode: TruncateMode = Constants.TRUCATE_MODE_VALUE, ): CommandExecutionResult { + currentCoroutineContext().reportToolActivity("Running command: '$command'") val project = currentCoroutineContext().project checkUserConfirmationIfNeeded(McpServerBundle.message("label.do.you.want.to.execute.command.in.terminal"), command, project) diff --git a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/vcs/VcsToolset.kt b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/vcs/VcsToolset.kt index cfb09b4f768f..9b1c416f42d2 100644 --- a/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/vcs/VcsToolset.kt +++ b/plugins/mcp-server/src/com/intellij/mcpserver/toolsets/vcs/VcsToolset.kt @@ -6,6 +6,7 @@ import com.intellij.mcpserver.McpToolset import com.intellij.mcpserver.annotations.McpDescription import com.intellij.mcpserver.annotations.McpTool import com.intellij.mcpserver.project +import com.intellij.mcpserver.reportToolActivity import com.intellij.mcpserver.util.projectDirectory import com.intellij.openapi.vcs.ProjectLevelVcsManager import com.intellij.openapi.vcs.changes.ChangeListManager @@ -26,6 +27,7 @@ class VcsToolset : McpToolset { @McpDescription("Text or keywords to search for in commit messages") text: String ): String { + currentCoroutineContext().reportToolActivity("Searching commits for '$text'") val project = currentCoroutineContext().project val queryText = text val matchingCommits = mutableListOf() @@ -77,6 +79,7 @@ class VcsToolset : McpToolset { Note: Works with any VCS supported by the IDE, but is most commonly used with Git """) suspend fun get_project_vcs_status(): String { + currentCoroutineContext().reportToolActivity("Checking VCS status") val project = currentCoroutineContext().project val projectDir = project.projectDirectory