[MCP Server] Add advanced tests for all toolsets. Improve transport tests

(cherry picked from commit bcb3c215ddcccfad3311ed5163503175e54254a4)

IJ-CR-166188

GitOrigin-RevId: 0a16d89676ee5f7d9f80688805bd4312d7f20ccd
This commit is contained in:
Artem.Bukhonov
2025-06-17 19:59:34 +02:00
committed by intellij-monorepo-bot
parent 23483ce45d
commit efe41f6d8d
15 changed files with 758 additions and 62 deletions

View File

@@ -14,6 +14,7 @@ class ReflectionCallableMcpTool(override val descriptor: McpToolDescriptor, priv
return when {
result.result == null -> McpToolCallResult.text("[null]")
result.result is Unit -> McpToolCallResult.text("[success]")
result.result is String -> McpToolCallResult.text(result.result) // special case for String to avoid extra quotes added by Any?.toString()
result.result.javaClass.isPrimitive -> McpToolCallResult.text(result.result.toString())
result.result is McpToolCallResult -> result.result
result.result is McpToolCallResultContent -> McpToolCallResult(arrayOf(result.result), isError = false)

View File

@@ -4,6 +4,7 @@ import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.serializerOrNull
import java.lang.reflect.InvocationTargetException
import kotlin.reflect.KCallable
import kotlin.reflect.KParameter
import kotlin.reflect.KType
@@ -44,7 +45,12 @@ class CallableBridge(private val callable: KCallable<*>, private val thisRef: An
argMap[parameter] = decodedArg
}
val result = callable.callSuspendBy(argMap)
val result = try {
callable.callSuspendBy(argMap)
}
catch (e: InvocationTargetException) {
throw e.cause ?: e
}
return Result(result, callable.returnType, json)
}
}

View File

@@ -7,8 +7,9 @@ import com.intellij.mcpserver.annotations.McpDescription
import com.intellij.mcpserver.annotations.McpTool
import com.intellij.mcpserver.project
import com.intellij.mcpserver.util.resolveRel
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.application.writeAction
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.project.guessProjectDir
@@ -18,7 +19,9 @@ import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
import com.intellij.xdebugger.impl.XSourcePositionImpl
import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil
import kotlin.coroutines.coroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.withContext
class DebuggerToolset : McpToolset {
@McpTool
@@ -39,23 +42,23 @@ class DebuggerToolset : McpToolset {
@McpDescription("Line number where to toggle the breakpoint (1-based)")
line: Int
): String {
val project = coroutineContext.project
val project = currentCoroutineContext().project
val projectDir = project.guessProjectDir()?.toNioPathOrNull()
?: return "can't find project dir"
val virtualFile = LocalFileSystem.getInstance().findFileByNioFile(projectDir.resolveRel(filePathInProject))
?: return "can't find file '$filePathInProject'"
val editors = FileEditorManager.getInstance(project).openFile(virtualFile, true)
val editors = withContext(Dispatchers.EDT) { FileEditorManager.getInstance (project).openFile(virtualFile, true) }
val editor = editors.filterIsInstance<TextEditor>().firstOrNull()?.editor
?: return "can't find editor for file '$filePathInProject'"
runWriteAction {
val position = XSourcePositionImpl.create(virtualFile, line - 1)
XBreakpointUtil.toggleLineBreakpoint(project, position, false, editor, false, true, true).onSuccess {
invokeLater {
position.createNavigatable(project).navigate(true)
}
}
}
writeAction {
val position = XSourcePositionImpl.create(virtualFile, line - 1)
XBreakpointUtil.toggleLineBreakpoint(project, position, false, editor, false, true, true).onSuccess {
invokeLater {
position.createNavigatable(project).navigate(true)
}
}
}
return "ok"
}
@@ -71,7 +74,7 @@ class DebuggerToolset : McpToolset {
Note: Only includes line breakpoints, not other breakpoint types (e.g., method breakpoints)
""")
suspend fun get_debugger_breakpoints(): String {
val project = coroutineContext.project
val project = currentCoroutineContext().project
val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
return breakpointManager.allBreakpoints
.filterIsInstance<XLineBreakpoint<*>>() // Only consider line breakpoints

View File

@@ -2,6 +2,7 @@
package com.intellij.mcpserver.toolsets.terminal
import com.intellij.mcpserver.McpExpectedError
import com.intellij.mcpserver.McpServerBundle
import com.intellij.mcpserver.McpToolset
import com.intellij.mcpserver.annotations.McpDescription
@@ -37,7 +38,8 @@ class TerminalToolset : McpToolset {
suspend fun get_terminal_text(): String {
val project = coroutineContext.project
val text = runReadAction<String?> {
TerminalView.getInstance(project).getWidgets().firstOrNull()?.text
val terminalWidget = TerminalView.getInstance(project).getWidgets().firstOrNull() ?: throw McpExpectedError("No terminal available")
terminalWidget.text
}
return text ?: ""
}

View File

@@ -1,69 +1,31 @@
package com.intellij.mcpserver
import com.intellij.mcpserver.impl.McpServerService
import com.intellij.mcpserver.stdio.IJ_MCP_SERVER_PORT
import com.intellij.mcpserver.stdio.IJ_MCP_SERVER_PROJECT_PATH
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.TestDisposable
import io.ktor.utils.io.streams.asInput
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
import kotlinx.coroutines.test.runTest
import kotlinx.io.asSink
import kotlinx.io.buffered
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import kotlin.test.fail
@TestApplication
class StdioRunnerTest {
@TestDisposable
private lateinit var disposable: Disposable
@Test
fun list_tools_stdio_runner() = runTest {
McpServerService.getInstance().start()
Disposer.register(disposable, Disposable {
McpServerService.getInstance().stop()
})
val mcpServerCommandLine = createStdioMcpServerCommandLine(McpServerService.getInstance().port, null)
val processBuilder = mcpServerCommandLine.toProcessBuilder()
val process = processBuilder.start()
val stdioClientTransport = StdioClientTransport(process.inputStream.asInput(), process.outputStream.asSink().buffered())
val client = Client(Implementation(name = "test client", version = "1.0"))
client.connect(stdioClientTransport)
val listTools = client.listTools() ?: fail("No tools returned")
assert(listTools.tools.isNotEmpty()) { "No tools returned" }
stdioClientTransport.close()
if (!process.waitFor(10, java.util.concurrent.TimeUnit.SECONDS)) process.destroyForcibly()
if (!process.waitFor(10, java.util.concurrent.TimeUnit.SECONDS)) fail("Process is still alive")
}
class McpJsonGenerationTest {
@Test
fun test_createStdioMcpServerJsonConfiguration() {
val port = 12345
val projectPath = "/test/project/path"
val jsonConfig = createStdioMcpServerJsonConfiguration(port, projectPath)
// Verify the JSON structure
Assertions.assertNotNull(jsonConfig["command"])
Assertions.assertNotNull(jsonConfig["args"])
Assertions.assertNotNull(jsonConfig["env"])
// Verify command is a java executable
val command = jsonConfig["command"]?.jsonPrimitive?.content
assertTrue(command?.endsWith("java") == true, "Command should be java executable")
// Verify environment variables
val env = jsonConfig["env"]?.jsonObject!!
Assertions.assertNotNull(env)
@@ -74,9 +36,9 @@ class StdioRunnerTest {
@Test
fun test_createStdioMcpServerJsonConfiguration_withoutProjectPath() {
val port = 12345
val jsonConfig = createStdioMcpServerJsonConfiguration(port, null)
// Verify the JSON structure
Assertions.assertNotNull(jsonConfig["command"])
Assertions.assertNotNull(jsonConfig["args"])
@@ -94,9 +56,9 @@ class StdioRunnerTest {
@Test
fun test_createSseServerJsonEntry() {
val port = 8080
val jsonConfig = createSseServerJsonEntry(port)
// Verify the JSON structure
Assertions.assertEquals("sse", jsonConfig["type"]?.jsonPrimitive?.content)
Assertions.assertEquals("http://localhost:$port/sse", jsonConfig["url"]?.jsonPrimitive?.content)

View File

@@ -0,0 +1,95 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver
import com.intellij.mcpserver.impl.McpServerService
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.fixture.moduleFixture
import com.intellij.testFramework.junit5.fixture.projectFixture
import com.intellij.testFramework.junit5.fixture.sourceRootFixture
import com.intellij.testFramework.junit5.fixture.virtualFileFixture
import io.ktor.client.HttpClient
import io.ktor.client.plugins.sse.SSE
import io.modelcontextprotocol.kotlin.sdk.CallToolResultBase
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.platform.commons.annotation.Testable
import kotlin.test.fail
@Testable
@TestApplication
abstract class McpToolsetTestBase {
protected val projectFixture = projectFixture(openAfterCreation = true)
protected val project by projectFixture
protected val moduleFixture = projectFixture.moduleFixture("testModule")
protected val sourceRootFixture = moduleFixture.sourceRootFixture()
// TODO: no idea how to create a file in a subfolder
protected val mainJavaFileFixture = sourceRootFixture.virtualFileFixture("Main.java", "Main.java content")
protected val classJavaFileFixture = sourceRootFixture.virtualFileFixture("Class.java", "Class.java content")
protected val testJavaFileFixture = sourceRootFixture.virtualFileFixture("Test.java", "Test.java content")
protected val mainJavaFile by mainJavaFileFixture
protected val classJavaFile by classJavaFileFixture
protected val testJavaFile by testJavaFileFixture
protected suspend fun withConnection(action: suspend (Client) -> Unit) {
McpServerService.getInstance().start()
// Get the port from McpServerService
val port = McpServerService.getInstance().port
// Create HttpClient with SSE support
val httpClient = HttpClient {
install(SSE)
}
// Create SseClientTransport
val sseClientTransport = SseClientTransport(httpClient, "http://localhost:$port/")
// Create client
val client = Client(Implementation(name = "test client", version = "1.0"))
try { // Connect to the server
client.connect(sseClientTransport)
action(client)
}
finally { // Close the connection
sseClientTransport.close()
McpServerService.getInstance().stop()
}
}
protected val CallToolResultBase.textContent: TextContent get() = content.firstOrNull() as? TextContent
?: fail("Tool call result should be TextContent")
protected suspend fun testMcpTool(
toolName: String,
input: kotlinx.serialization.json.JsonObject,
output: String,
) {
testMcpTool(toolName, input) { result ->
val textContent = result.textContent
assertEquals(output, textContent.text, "Tool call result should match expected output")
}
}
protected suspend fun testMcpTool(
toolName: String,
input: kotlinx.serialization.json.JsonObject,
resultChecker: (CallToolResultBase) -> Unit,
) {
withConnection { client ->
// Call the tool with the provided input
val result = client.callTool(toolName, input) ?: fail("Tool call result should not be null")
resultChecker(result)
// Just verify that the call doesn't throw an exception
assertNotNull(result, "Tool call result should not be null")
// Log the result for debugging
println("[DEBUG_LOG] Tool $toolName result: $result")
}
}
}

View File

@@ -0,0 +1,134 @@
package com.intellij.mcpserver
import com.intellij.mcpserver.annotations.McpDescription
import com.intellij.mcpserver.impl.McpServerService
import com.intellij.mcpserver.impl.util.asTool
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.use
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.fixture.projectFixture
import com.intellij.util.application
import io.ktor.client.HttpClient
import io.ktor.client.plugins.sse.SSE
import io.ktor.utils.io.streams.asInput
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport
import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.io.asSink
import kotlinx.io.buffered
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.fail
@TestApplication
class TransportTest {
companion object {
val projectFixture = projectFixture(openAfterCreation = true)
val project by projectFixture
@JvmStatic
fun getTransports(): Array<TransportHolder> {
return arrayOf(
StdioTransportHolder(project),
SseTransportHolder(project),
)
}
}
@ParameterizedTest
@MethodSource("getTransports")
fun list_tools(transport: TransportHolder) = transportTest(transport) { client ->
val listTools = client.listTools() ?: fail("No tools returned")
assert(listTools.tools.isNotEmpty()) { "No tools returned" }
}
@ParameterizedTest
@MethodSource("getTransports")
fun tool_call_has_project(transport: TransportHolder) = transportTest(transport) { client ->
Disposer.newDisposable().use { disposable ->
application.extensionArea.getExtensionPoint(McpToolsProvider.EP).registerExtension(object : McpToolsProvider {
override fun getTools(): List<McpTool> {
return listOf(this@TransportTest::test_tool.asTool())
}
}, disposable)
client.callTool("test_tool", emptyMap())
val actual = withTimeout(2000) { projectFromTool.await() }
assertEquals(project, actual)
}
}
val projectFromTool = CompletableDeferred<Project?>()
@com.intellij.mcpserver.annotations.McpTool()
@McpDescription("Test description")
suspend fun test_tool() {
projectFromTool.complete(currentCoroutineContext().projectOrNull)
}
private fun transportTest(transportHolder: TransportHolder, action: suspend (Client) -> Unit) = runBlocking {
try {
McpServerService.getInstance().start()
val client = Client(Implementation(name = "test client", version = "1.0"))
client.connect(transportHolder.transport)
action(client)
}
finally {
transportHolder.close()
McpServerService.getInstance().stop()
}
}
}
abstract class TransportHolder {
abstract val transport: AbstractTransport
// do not make it AutoCloseable because Junit tries to close it automatically but we want to close it in test method manually
open fun close() {
runBlocking {
transport.close()
}
}
}
class StdioTransportHolder(project: Project) : TransportHolder() {
val process: Process by lazy {
createStdioMcpServerCommandLine(McpServerService.getInstance().port, project.basePath).toProcessBuilder().start()
}
override val transport: AbstractTransport by lazy {
StdioClientTransport(process.inputStream.asInput(), process.outputStream.asSink().buffered())
}
override fun close() {
super.close() //sseClientTransport.close()
if (!process.waitFor(10, TimeUnit.SECONDS)) process.destroyForcibly()
if (!process.waitFor(10, TimeUnit.SECONDS)) fail("Process is still alive")
}
override fun toString(): String {
return "Stdio"
}
}
class SseTransportHolder(project: Project) : TransportHolder() {
override val transport: AbstractTransport by lazy {
SseClientTransport(HttpClient {
install(SSE)
}, "http://localhost:${McpServerService.getInstance().port}/")
}
override fun toString(): String {
return "SSE"
}
}

View File

@@ -0,0 +1,108 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.general.BuiltinGeneralToolset
import io.kotest.common.runBlocking
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class BuiltinGeneralToolsetTest : McpToolsetTestBase() {
@Disabled("Depends on the previous runs that may create files via fixture")
@Test
fun search_in_files_content() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::search_in_files_content.name,
buildJsonObject {
put("searchText", JsonPrimitive("content"))
},
"""[{"path": "Main.java", "name": "Main.java"},
{"path": "Class.java", "name": "Class.java"},
{"path": "Test.java", "name": "Test.java"}]"""
)
}
@Test
fun get_run_configurations() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::get_run_configurations.name,
buildJsonObject {},
"[]"
)
}
@Test
fun run_configuration() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::run_configuration.name,
buildJsonObject {
put("configName", JsonPrimitive("test-config"))
},
"Run configuration with name 'test-config' not found."
)
}
@Test
fun get_project_modules() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::get_project_modules.name,
buildJsonObject {},
"[testModule]"
)
}
@Test
fun get_project_dependencies() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::get_project_dependencies.name,
buildJsonObject {},
"[]"
)
}
@Test
fun list_available_actions() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::list_available_actions.name,
buildJsonObject {}
) {
assert(it.textContent.text?.contains(""""id": "About"""") == true) { "'About' action not found"}
}
}
@Test
fun execute_action_by_id() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::execute_action_by_id.name,
buildJsonObject {
put("actionId", JsonPrimitive("SaveAll"))
},
"ok"
)
}
@Disabled("IMHO Useless tool. To remove")
@Test
fun get_progress_indicators() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::get_progress_indicators.name,
buildJsonObject {},
"[]"
)
}
@Test
@Disabled("Imho useless tool. To remove")
fun wait_tool() = runBlocking {
testMcpTool(
BuiltinGeneralToolset::wait.name,
buildJsonObject {
put("milliseconds", JsonPrimitive(100)) // Short wait for testing
},
"ok"
)
}
}

View File

@@ -0,0 +1,36 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.debugger.DebuggerToolset
import io.kotest.common.runBlocking
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class DebuggerToolsetTest : McpToolsetTestBase() {
@Test
fun get_debugger_breakpoints() = runBlocking {
testMcpTool(
DebuggerToolset::get_debugger_breakpoints.name,
buildJsonObject {},
"[]"
)
}
@Disabled("Tool doesn't work")
@Test
fun toggle_debugger_breakpoint() = runBlocking {
testMcpTool(
DebuggerToolset::toggle_debugger_breakpoint.name,
buildJsonObject {
put("filePathInProject", JsonPrimitive("Main.java"))
put("line", JsonPrimitive(0))
},
"[]"
)
}
}

View File

@@ -0,0 +1,36 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.general.ErrorToolset
import com.intellij.openapi.application.EDT
import com.intellij.openapi.fileEditor.FileEditorManager
import io.kotest.common.runBlocking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Test
class ErrorToolsetTest : McpToolsetTestBase() {
@Test
fun get_current_file_errors() = runBlocking {
withContext(Dispatchers.EDT) {
FileEditorManager.getInstance(project).openFile(mainJavaFile, true)
}
testMcpTool(
ErrorToolset::get_current_file_errors.name,
buildJsonObject {},
"[]"
)
}
@Test
fun get_project_problems() = runBlocking {
testMcpTool(
ErrorToolset::get_project_problems.name,
buildJsonObject {},
"[]"
)
}
}

View File

@@ -0,0 +1,102 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.general.FileToolset
import com.intellij.openapi.application.EDT
import com.intellij.openapi.fileEditor.FileEditorManager
import io.kotest.common.runBlocking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class FileToolsetTest : McpToolsetTestBase() {
@Disabled("Output contains the project directory name that is not predictable because of generated")
@Test
fun list_directory_tree_in_folder() = runBlocking {
testMcpTool(
FileToolset::list_directory_tree_in_folder.name,
buildJsonObject {
put("pathInProject", JsonPrimitive("/"))
put("maxDepth", JsonPrimitive(2))
},
"""{"name":"IJ4720168971772072169","type":"directory","path":"","children":[{"name":"Class.java","type":"file","path":"Class.java"},{"name":"Test.java","type":"file","path":"Test.java"},{"name":"Main.java","type":"file","path":"Main.java"}]}"""
)
}
@Test
fun list_files_in_folder() = runBlocking {
testMcpTool(
FileToolset::list_files_in_folder.name,
buildJsonObject {
put("pathInProject", JsonPrimitive("/"))
},
"""[{"name": "Class.java", "type": "file", "path": "Class.java"},
{"name": "Test.java", "type": "file", "path": "Test.java"},
{"name": "Main.java", "type": "file", "path": "Main.java"}]"""
)
}
@Test
fun find_files_by_name_substring() = runBlocking {
testMcpTool(
FileToolset::find_files_by_name_substring.name,
buildJsonObject {
put("nameSubstring", JsonPrimitive("test"))
},
"""[{"path": "Test.java", "name": "Test.java"}]"""
)
}
@Test
fun create_new_file_with_text() = runBlocking {
testMcpTool(
FileToolset::create_new_file_with_text.name,
buildJsonObject {
put("pathInProject", JsonPrimitive("test/test_file.txt"))
put("text", JsonPrimitive("This is a test file"))
},
"ok"
)
}
@Test
fun open_file_in_editor() = runBlocking {
testMcpTool(
FileToolset::open_file_in_editor.name,
buildJsonObject {
put("filePath", JsonPrimitive("Test.java"))
},
"file is opened"
)
}
@Test
fun get_all_open_file_paths() = runBlocking {
withContext(Dispatchers.EDT) {
FileEditorManager.getInstance(project).openFile(mainJavaFile, true)
}
testMcpTool(
FileToolset::get_all_open_file_paths.name,
buildJsonObject {},
"Main.java"
)
}
@Test
fun get_open_in_editor_file_path() = runBlocking {
withContext(Dispatchers.EDT) {
FileEditorManager.getInstance(project).openFile(mainJavaFile, true)
}
testMcpTool(
FileToolset::get_open_in_editor_file_path.name,
buildJsonObject {}
) { result ->
assert(result.textContent.text?.endsWith("Main.java") == true)
}
}
}

View File

@@ -0,0 +1,39 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.general.FormattingToolset
import com.intellij.openapi.application.EDT
import com.intellij.openapi.fileEditor.FileEditorManager
import io.kotest.common.runBlocking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Test
class FormattingToolsetTest : McpToolsetTestBase() {
@Test
fun reformat_current_file() = runBlocking {
withContext(Dispatchers.EDT) {
FileEditorManager.getInstance(project).openFile(mainJavaFile, true)
}
testMcpTool(
FormattingToolset::reformat_current_file.name,
buildJsonObject {},
"ok"
)
}
@Test
fun reformat_file() = runBlocking {
testMcpTool(
FormattingToolset::reformat_file.name,
buildJsonObject {
put("pathInProject", JsonPrimitive(mainJavaFile.name))
},
"ok"
)
}
}

View File

@@ -0,0 +1,32 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.terminal.TerminalToolset
import io.kotest.common.runBlocking
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Test
class TerminalToolsetTest : McpToolsetTestBase() {
@Test
fun get_terminal_text() = runBlocking {
testMcpTool(
TerminalToolset::get_terminal_text.name,
buildJsonObject {},
"No terminal available"
)
}
@Test
fun execute_terminal_command() = runBlocking {
testMcpTool(
TerminalToolset::execute_terminal_command.name,
buildJsonObject {
put("command", JsonPrimitive("echo 'Hello, World!'"))
},
"No terminal available"
)
}
}

View File

@@ -0,0 +1,105 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.general.TextToolset
import com.intellij.openapi.application.EDT
import com.intellij.openapi.fileEditor.FileEditorManager
import io.kotest.common.runBlocking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Test
class TextToolsetTest : McpToolsetTestBase() {
@Test
fun get_open_in_editor_file_text() = runBlocking {
testMcpTool(
TextToolset::get_open_in_editor_file_text.name,
buildJsonObject {},
""
)
}
// TODO: IMHO badly designed tool. Can junk LLM input. To remove in favor of list_editor_files and get_text_by_path
@Test
fun get_all_open_file_texts() = runBlocking {
testMcpTool(
TextToolset::get_all_open_file_texts.name,
buildJsonObject {},
"[]"
)
}
@Test
fun get_selected_in_editor_text() = runBlocking {
testMcpTool(
TextToolset::get_selected_in_editor_text.name,
buildJsonObject {},
""
)
}
@Test
fun replace_selected_text() = runBlocking {
testMcpTool(
TextToolset::replace_selected_text.name,
buildJsonObject {
put("text", JsonPrimitive("replacement text"))
},
"no text selected"
)
}
@Test
fun replace_current_file_text() = runBlocking {
withContext(Dispatchers.EDT) {
FileEditorManager.getInstance(project).openFile(mainJavaFile, true)
}
testMcpTool(
TextToolset::replace_current_file_text.name,
buildJsonObject {
put("text", JsonPrimitive("new file content"))
},
"ok"
)
}
@Test
fun get_file_text_by_path() = runBlocking {
testMcpTool(
TextToolset::get_file_text_by_path.name,
buildJsonObject {
put("pathInProject", JsonPrimitive(testJavaFile.name))
},
"Test.java content"
)
}
@Test
fun replace_specific_text() = runBlocking {
testMcpTool(
TextToolset::replace_specific_text.name,
buildJsonObject {
put("pathInProject", JsonPrimitive(testJavaFile.name))
put("oldText", JsonPrimitive("content"))
put("newText", JsonPrimitive("updated content"))
},
"ok"
)
}
@Test
fun replace_file_text_by_path() = runBlocking {
testMcpTool(
TextToolset::replace_file_text_by_path.name,
buildJsonObject {
put("pathInProject", JsonPrimitive(mainJavaFile.name))
put("text", JsonPrimitive("updated content"))
},
"ok"
)
}
}

View File

@@ -0,0 +1,35 @@
@file:Suppress("TestFunctionName")
package com.intellij.mcpserver.toolsets
import com.intellij.mcpserver.McpToolsetTestBase
import com.intellij.mcpserver.toolsets.vcs.VcsToolset
import io.kotest.common.runBlocking
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class VcsToolsetTest : McpToolsetTestBase() {
@Disabled("Configure VCS in test project")
@Test
fun get_project_vcs_status() = runBlocking {
testMcpTool(
VcsToolset::get_project_vcs_status.name,
buildJsonObject {},
"[]"
)
}
@Disabled("Configure VCS in test project")
@Test
fun find_commit_by_message() = runBlocking {
testMcpTool(
VcsToolset::find_commit_by_message.name,
buildJsonObject {
put("text", JsonPrimitive("test commit"))
},
"Error: No VCS configured for this project"
)
}
}