mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-14 18:05:27 +07:00
[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:
committed by
intellij-monorepo-bot
parent
23483ce45d
commit
efe41f6d8d
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 ?: ""
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
134
plugins/mcp-server/test/com/intellij/mcpserver/TransportTest.kt
Normal file
134
plugins/mcp-server/test/com/intellij/mcpserver/TransportTest.kt
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
},
|
||||
"[]"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 {},
|
||||
"[]"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user