mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +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 {
|
return when {
|
||||||
result.result == null -> McpToolCallResult.text("[null]")
|
result.result == null -> McpToolCallResult.text("[null]")
|
||||||
result.result is Unit -> McpToolCallResult.text("[success]")
|
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.javaClass.isPrimitive -> McpToolCallResult.text(result.result.toString())
|
||||||
result.result is McpToolCallResult -> result.result
|
result.result is McpToolCallResult -> result.result
|
||||||
result.result is McpToolCallResultContent -> McpToolCallResult(arrayOf(result.result), isError = false)
|
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.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.serializerOrNull
|
import kotlinx.serialization.serializerOrNull
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
import kotlin.reflect.KCallable
|
import kotlin.reflect.KCallable
|
||||||
import kotlin.reflect.KParameter
|
import kotlin.reflect.KParameter
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
@@ -44,7 +45,12 @@ class CallableBridge(private val callable: KCallable<*>, private val thisRef: An
|
|||||||
argMap[parameter] = decodedArg
|
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)
|
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.annotations.McpTool
|
||||||
import com.intellij.mcpserver.project
|
import com.intellij.mcpserver.project
|
||||||
import com.intellij.mcpserver.util.resolveRel
|
import com.intellij.mcpserver.util.resolveRel
|
||||||
|
import com.intellij.openapi.application.EDT
|
||||||
import com.intellij.openapi.application.invokeLater
|
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.FileEditorManager
|
||||||
import com.intellij.openapi.fileEditor.TextEditor
|
import com.intellij.openapi.fileEditor.TextEditor
|
||||||
import com.intellij.openapi.project.guessProjectDir
|
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.breakpoints.XLineBreakpoint
|
||||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil
|
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 {
|
class DebuggerToolset : McpToolset {
|
||||||
@McpTool
|
@McpTool
|
||||||
@@ -39,23 +42,23 @@ class DebuggerToolset : McpToolset {
|
|||||||
@McpDescription("Line number where to toggle the breakpoint (1-based)")
|
@McpDescription("Line number where to toggle the breakpoint (1-based)")
|
||||||
line: Int
|
line: Int
|
||||||
): String {
|
): String {
|
||||||
val project = coroutineContext.project
|
val project = currentCoroutineContext().project
|
||||||
val projectDir = project.guessProjectDir()?.toNioPathOrNull()
|
val projectDir = project.guessProjectDir()?.toNioPathOrNull()
|
||||||
?: return "can't find project dir"
|
?: return "can't find project dir"
|
||||||
val virtualFile = LocalFileSystem.getInstance().findFileByNioFile(projectDir.resolveRel(filePathInProject))
|
val virtualFile = LocalFileSystem.getInstance().findFileByNioFile(projectDir.resolveRel(filePathInProject))
|
||||||
?: return "can't find file '$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
|
val editor = editors.filterIsInstance<TextEditor>().firstOrNull()?.editor
|
||||||
?: return "can't find editor for file '$filePathInProject'"
|
?: return "can't find editor for file '$filePathInProject'"
|
||||||
runWriteAction {
|
writeAction {
|
||||||
val position = XSourcePositionImpl.create(virtualFile, line - 1)
|
val position = XSourcePositionImpl.create(virtualFile, line - 1)
|
||||||
XBreakpointUtil.toggleLineBreakpoint(project, position, false, editor, false, true, true).onSuccess {
|
XBreakpointUtil.toggleLineBreakpoint(project, position, false, editor, false, true, true).onSuccess {
|
||||||
invokeLater {
|
invokeLater {
|
||||||
position.createNavigatable(project).navigate(true)
|
position.createNavigatable(project).navigate(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "ok"
|
return "ok"
|
||||||
}
|
}
|
||||||
@@ -71,7 +74,7 @@ class DebuggerToolset : McpToolset {
|
|||||||
Note: Only includes line breakpoints, not other breakpoint types (e.g., method breakpoints)
|
Note: Only includes line breakpoints, not other breakpoint types (e.g., method breakpoints)
|
||||||
""")
|
""")
|
||||||
suspend fun get_debugger_breakpoints(): String {
|
suspend fun get_debugger_breakpoints(): String {
|
||||||
val project = coroutineContext.project
|
val project = currentCoroutineContext().project
|
||||||
val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
|
val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
|
||||||
return breakpointManager.allBreakpoints
|
return breakpointManager.allBreakpoints
|
||||||
.filterIsInstance<XLineBreakpoint<*>>() // Only consider line breakpoints
|
.filterIsInstance<XLineBreakpoint<*>>() // Only consider line breakpoints
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package com.intellij.mcpserver.toolsets.terminal
|
package com.intellij.mcpserver.toolsets.terminal
|
||||||
|
|
||||||
|
import com.intellij.mcpserver.McpExpectedError
|
||||||
import com.intellij.mcpserver.McpServerBundle
|
import com.intellij.mcpserver.McpServerBundle
|
||||||
import com.intellij.mcpserver.McpToolset
|
import com.intellij.mcpserver.McpToolset
|
||||||
import com.intellij.mcpserver.annotations.McpDescription
|
import com.intellij.mcpserver.annotations.McpDescription
|
||||||
@@ -37,7 +38,8 @@ class TerminalToolset : McpToolset {
|
|||||||
suspend fun get_terminal_text(): String {
|
suspend fun get_terminal_text(): String {
|
||||||
val project = coroutineContext.project
|
val project = coroutineContext.project
|
||||||
val text = runReadAction<String?> {
|
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 ?: ""
|
return text ?: ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,31 @@
|
|||||||
package com.intellij.mcpserver
|
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_PORT
|
||||||
import com.intellij.mcpserver.stdio.IJ_MCP_SERVER_PROJECT_PATH
|
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.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import kotlin.test.fail
|
|
||||||
|
|
||||||
@TestApplication
|
class McpJsonGenerationTest {
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_createStdioMcpServerJsonConfiguration() {
|
fun test_createStdioMcpServerJsonConfiguration() {
|
||||||
val port = 12345
|
val port = 12345
|
||||||
val projectPath = "/test/project/path"
|
val projectPath = "/test/project/path"
|
||||||
|
|
||||||
val jsonConfig = createStdioMcpServerJsonConfiguration(port, projectPath)
|
val jsonConfig = createStdioMcpServerJsonConfiguration(port, projectPath)
|
||||||
|
|
||||||
// Verify the JSON structure
|
// Verify the JSON structure
|
||||||
Assertions.assertNotNull(jsonConfig["command"])
|
Assertions.assertNotNull(jsonConfig["command"])
|
||||||
Assertions.assertNotNull(jsonConfig["args"])
|
Assertions.assertNotNull(jsonConfig["args"])
|
||||||
Assertions.assertNotNull(jsonConfig["env"])
|
Assertions.assertNotNull(jsonConfig["env"])
|
||||||
|
|
||||||
// Verify command is a java executable
|
// Verify command is a java executable
|
||||||
val command = jsonConfig["command"]?.jsonPrimitive?.content
|
val command = jsonConfig["command"]?.jsonPrimitive?.content
|
||||||
assertTrue(command?.endsWith("java") == true, "Command should be java executable")
|
assertTrue(command?.endsWith("java") == true, "Command should be java executable")
|
||||||
|
|
||||||
// Verify environment variables
|
// Verify environment variables
|
||||||
val env = jsonConfig["env"]?.jsonObject!!
|
val env = jsonConfig["env"]?.jsonObject!!
|
||||||
Assertions.assertNotNull(env)
|
Assertions.assertNotNull(env)
|
||||||
@@ -74,9 +36,9 @@ class StdioRunnerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun test_createStdioMcpServerJsonConfiguration_withoutProjectPath() {
|
fun test_createStdioMcpServerJsonConfiguration_withoutProjectPath() {
|
||||||
val port = 12345
|
val port = 12345
|
||||||
|
|
||||||
val jsonConfig = createStdioMcpServerJsonConfiguration(port, null)
|
val jsonConfig = createStdioMcpServerJsonConfiguration(port, null)
|
||||||
|
|
||||||
// Verify the JSON structure
|
// Verify the JSON structure
|
||||||
Assertions.assertNotNull(jsonConfig["command"])
|
Assertions.assertNotNull(jsonConfig["command"])
|
||||||
Assertions.assertNotNull(jsonConfig["args"])
|
Assertions.assertNotNull(jsonConfig["args"])
|
||||||
@@ -94,9 +56,9 @@ class StdioRunnerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun test_createSseServerJsonEntry() {
|
fun test_createSseServerJsonEntry() {
|
||||||
val port = 8080
|
val port = 8080
|
||||||
|
|
||||||
val jsonConfig = createSseServerJsonEntry(port)
|
val jsonConfig = createSseServerJsonEntry(port)
|
||||||
|
|
||||||
// Verify the JSON structure
|
// Verify the JSON structure
|
||||||
Assertions.assertEquals("sse", jsonConfig["type"]?.jsonPrimitive?.content)
|
Assertions.assertEquals("sse", jsonConfig["type"]?.jsonPrimitive?.content)
|
||||||
Assertions.assertEquals("http://localhost:$port/sse", jsonConfig["url"]?.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