Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions src/main/kotlin/org/jetbrains/mcpserverplugin/MCPService.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package org.jetbrains.ide.mcp

import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level.APP
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
import io.ktor.client.HttpClient
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.http.ContentType.*
import io.ktor.http.contentType
import io.ktor.util.decodeBase64String
import io.ktor.util.encodeBase64
import io.ktor.util.*
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.FullHttpRequest
import io.netty.handler.codec.http.HttpMethod
Expand All @@ -23,7 +20,6 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
import okio.ByteString.Companion.decodeBase64
import org.jetbrains.ide.RestService
import org.jetbrains.mcpserverplugin.AbstractMcpTool
import org.jetbrains.mcpserverplugin.McpTool
Expand Down Expand Up @@ -66,12 +62,12 @@ class MCPService : RestService() {

override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
val path = urlDecoder.path().split(serviceName).last().trimStart('/')
val project = getLastFocusedOrOpenedProject() ?: return null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty line can be removed

val tools = McpToolManager.Companion.getAllTools()

when (path) {
"list_tools" -> handleListTools(tools, request, context)
else -> handleToolExecution(path, tools, request, context, project)
else -> handleToolExecution(path, tools, request, context)
}
return null
}
Expand All @@ -96,7 +92,6 @@ class MCPService : RestService() {
tools: List<AbstractMcpTool<*>>,
request: FullHttpRequest,
context: ChannelHandlerContext,
project: Project
) {
val tool = tools.find { it.name == path } ?: run {
sendJson(Response(error = "Unknown tool: $path"), request, context)
Expand All @@ -112,7 +107,7 @@ class MCPService : RestService() {
return
}
val result = try {
toolHandle(tool, project, args)
toolHandle(tool, args)
} catch (e: Throwable) {
logger<MCPService>().warn("Failed to execute tool $path", e)
Response(error = "Failed to execute tool $path, message ${e.message}")
Expand Down Expand Up @@ -146,9 +141,9 @@ class MCPService : RestService() {
}
}

private fun <Args : Any> toolHandle(tool: McpTool<Args>, project: Project, args: Any): Response {
private fun <Args : Any> toolHandle(tool: McpTool<Args>, args: Any): Response {
@Suppress("UNCHECKED_CAST")
return tool.handle(project, args as Args)
return tool.handle(args as Args)
}

override fun isMethodSupported(method: HttpMethod): Boolean =
Expand Down Expand Up @@ -187,6 +182,13 @@ class MCPService : RestService() {
@Serializable
object NoArgs

interface ProjectAware {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please provide a java doc for this interface? This is needed to better understand the meaning behind this interface

val projectName: String
}

@Serializable
data class ProjectOnly(override val projectName: String) : ProjectAware

@Serializable
data class ToolInfo(
val name: String,
Expand All @@ -210,5 +212,8 @@ data class JsonSchemaObject(

@Serializable
data class PropertySchema(
val type: String
val type: String,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this modification?

)


fun ProjectAware.getProject() = ProjectManager.getInstance().openProjects.find { it.name == projectName }
3 changes: 1 addition & 2 deletions src/main/kotlin/org/jetbrains/mcpserverplugin/McpTool.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package org.jetbrains.mcpserverplugin

import com.intellij.openapi.project.Project
import org.jetbrains.ide.mcp.Response
import kotlin.reflect.KClass

interface McpTool<Args : Any> {
val name: String
val description: String
fun handle(project: Project, args: Args): Response
fun handle(args: Args): Response
}

abstract class AbstractMcpTool<Args : Any> : McpTool<Args> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.jetbrains.mcpserverplugin.general.WaitTool
import org.jetbrains.mcpserverplugin.git.GetVcsStatusTool
import org.jetbrains.mcpserverplugin.general.ReformatCurrentFileTool
import org.jetbrains.mcpserverplugin.general.ReformatFileTool
import org.jetbrains.mcpserverplugin.general.ListProjectsTool

class McpToolManager {
companion object {
Expand Down Expand Up @@ -76,6 +77,7 @@ class McpToolManager {
ReformatCurrentFileTool(),
ReformatFileTool(),
GetProblemsTools(),
ListProjectsTool(),
)
}
}
21 changes: 13 additions & 8 deletions src/main/kotlin/org/jetbrains/mcpserverplugin/debuggerTools.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.jetbrains.mcpserverplugin

import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.toNioPathOrNull
Expand All @@ -11,30 +10,34 @@ import com.intellij.xdebugger.breakpoints.XLineBreakpoint
import com.intellij.xdebugger.impl.XSourcePositionImpl
import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil
import kotlinx.serialization.Serializable
import org.jetbrains.ide.mcp.NoArgs
import org.jetbrains.ide.mcp.ProjectAware
import org.jetbrains.ide.mcp.ProjectOnly
import org.jetbrains.ide.mcp.Response
import org.jetbrains.ide.mcp.getProject
import org.jetbrains.mcpserverplugin.general.resolveRel

@Serializable
data class ToggleBreakpointArgs(val filePathInProject: String, val line: Int)
data class ToggleBreakpointArgs(val filePathInProject: String, val line: Int, override val projectName: String) :
ProjectAware
class ToggleBreakpointTool : AbstractMcpTool<ToggleBreakpointArgs>() {
override val name: String = "toggle_debugger_breakpoint"
override val description: String = """
Toggles a debugger breakpoint at the specified line in a project file.
Use this tool to add or remove breakpoints programmatically.
Requires two parameters:
Requires three parameters:
- filePathInProject: The relative path to the file within the project
- line: The line number where to toggle the breakpoint. The line number is starts at 1 for the first line.
- projectName: The name of the project to work with. Use list_projects tool to get available project names.
Returns one of two possible responses:
- "ok" if the breakpoint was successfully toggled
- "can't find project dir" if the project directory cannot be determined
Note: Automatically navigates to the breakpoint location in the editor
"""

override fun handle(
project: Project,
args: ToggleBreakpointArgs
): Response {
val project = args.getProject() ?: return Response(error = "Project not found")
val projectDir = project.guessProjectDir()?.toNioPathOrNull()
?: return Response(error = "can't find project dir")
val virtualFile = LocalFileSystem.getInstance().findFileByNioFile(projectDir.resolveRel(args.filePathInProject))
Expand All @@ -52,11 +55,13 @@ class ToggleBreakpointTool : AbstractMcpTool<ToggleBreakpointArgs>() {
}
}

class GetBreakpointsTool : AbstractMcpTool<NoArgs>() {
class GetBreakpointsTool : AbstractMcpTool<ProjectOnly>() {
override val name: String = "get_debugger_breakpoints"
override val description: String = """
Retrieves a list of all line breakpoints currently set in the project.
Use this tool to get information about existing debugger breakpoints.
Requires parameter:
- projectName: The name of the project to get breakpoints from. Use list_projects tool to get available project names.
Returns a JSON-formatted list of breakpoints, where each entry contains:
- path: The absolute file path where the breakpoint is set
- line: The line number (1-based) where the breakpoint is located
Expand All @@ -65,9 +70,9 @@ class GetBreakpointsTool : AbstractMcpTool<NoArgs>() {
"""

override fun handle(
project: Project,
args: NoArgs
args: ProjectOnly
): Response {
val project = args.getProject() ?: return Response(error = "Project not found")
val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
return Response(breakpointManager.allBreakpoints
.filterIsInstance<XLineBreakpoint<*>>() // Only consider line breakpoints
Expand Down
Loading