1
+ package cc.unitmesh.git.mcp
2
+
3
+ import cc.unitmesh.devti.mcp.host.AbstractMcpTool
4
+ import cc.unitmesh.devti.mcp.host.NoArgs
5
+ import cc.unitmesh.devti.mcp.host.Response
6
+ import com.intellij.compiler.cache.git.GitCommitsIterator
7
+ import com.intellij.openapi.project.Project
8
+ import com.intellij.openapi.project.guessProjectDir
9
+ import com.intellij.openapi.vcs.ProjectLevelVcsManager
10
+ import com.intellij.openapi.vcs.changes.ChangeListManager
11
+ import com.intellij.openapi.vfs.toNioPathOrNull
12
+ import git4idea.history.GitHistoryUtils
13
+ import git4idea.repo.GitRepositoryManager
14
+ import kotlinx.serialization.Serializable
15
+ import kotlin.io.path.Path
16
+
17
+ @Serializable
18
+ data class CommitQuery (val text : String )
19
+
20
+ class FindCommitByTextTool : AbstractMcpTool <CommitQuery >() {
21
+ override val name: String = " find_commit_by_message"
22
+ override val description: String = """
23
+ Searches for a commit based on the provided text or keywords in the project history.
24
+ Useful for finding specific change sets or code modifications by commit messages or diff content.
25
+ Takes a query parameter and returns the matching commit information.
26
+ Returns matched commit hashes as a JSON array.
27
+ """
28
+
29
+ override fun handle (project : Project , args : CommitQuery ): Response {
30
+ val queryText = args.text
31
+ val matchingCommits = mutableListOf<String >()
32
+
33
+ try {
34
+ val vcs = ProjectLevelVcsManager .getInstance(project).allVcsRoots
35
+ .mapNotNull { it.path }
36
+
37
+ if (vcs.isEmpty()) {
38
+ return Response (" Error: No VCS configured for this project" )
39
+ }
40
+
41
+ // Iterate over each VCS root to search for commits
42
+ vcs.forEach { vcsRoot ->
43
+ val repository = GitRepositoryManager .getInstance(project).getRepositoryForRoot(vcsRoot)
44
+ ? : return @forEach
45
+
46
+ val gitLog = GitHistoryUtils .history(project, repository.root)
47
+
48
+ gitLog.forEach { commit ->
49
+ if (commit.fullMessage.contains(queryText, ignoreCase = true )) {
50
+ matchingCommits.add(commit.id.toString())
51
+ }
52
+ }
53
+ }
54
+
55
+ // Check if any matches were found
56
+ return if (matchingCommits.isNotEmpty()) {
57
+ Response (matchingCommits.joinToString(prefix = " [" , postfix = " ]" , separator = " ," ) { " \" $it \" " })
58
+ } else {
59
+ Response (" No commits found matching the query: $queryText " )
60
+ }
61
+
62
+ } catch (e: Exception ) {
63
+ // Handle any errors that occur during the search
64
+ return Response (" Error while searching commits: ${e.message} " )
65
+ }
66
+ return Response (" Feature not yet implemented" )
67
+ }
68
+ }
69
+
70
+ class GetVcsStatusTool : AbstractMcpTool <NoArgs >() {
71
+ override val name: String = " get_project_vcs_status"
72
+ override val description: String = """
73
+ Retrieves the current version control status of files in the project.
74
+ Use this tool to get information about modified, added, deleted, and moved files in your VCS (e.g., Git).
75
+ Returns a JSON-formatted list of changed files, where each entry contains:
76
+ - path: The file path relative to project root
77
+ - type: The type of change (e.g., MODIFICATION, ADDITION, DELETION, MOVED)
78
+ Returns an empty list ([]) if no changes are detected or VCS is not configured.
79
+ Returns error "project dir not found" if project directory cannot be determined.
80
+ Note: Works with any VCS supported by the IDE, but is most commonly used with Git
81
+ """
82
+
83
+ override fun handle (project : Project , args : NoArgs ): Response {
84
+ val projectDir = project.guessProjectDir()?.toNioPathOrNull()
85
+ ? : return Response (error = " project dir not found" )
86
+
87
+ val changeListManager = ChangeListManager .getInstance(project)
88
+ val changes = changeListManager.allChanges
89
+
90
+ return Response (changes.mapNotNull { change ->
91
+ val absolutePath = change.virtualFile?.path ? : change.afterRevision?.file?.path
92
+ val changeType = change.type
93
+
94
+ if (absolutePath != null ) {
95
+ try {
96
+ val relativePath = projectDir.relativize(Path (absolutePath)).toString()
97
+ relativePath to changeType
98
+ } catch (e: IllegalArgumentException ) {
99
+ null // Skip files outside project directory
100
+ }
101
+ } else {
102
+ null
103
+ }
104
+ }.joinToString(" ,\n " , prefix = " [" , postfix = " ]" ) {
105
+ """ {"path": "${it.first} ", "type": "${it.second} "}"""
106
+ })
107
+ }
108
+ }
0 commit comments