Skip to content

Commit 03cd25f

Browse files
committed
feat(mcp): add tools for finding commits by message and retrieving VCS status
1 parent b8c2b8d commit 03cd25f

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
}

exts/ext-git/src/main/resources/cc.unitmesh.git.xml

+3
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,8 @@
4646

4747
<extensions defaultExtensionNs="cc.unitmesh">
4848
<revisionProvider implementation="cc.unitmesh.git.actions.vcs.GitRevisionProvider"/>
49+
50+
<mcpTool implementation="cc.unitmesh.git.mcp.FindCommitByTextTool"/>
51+
<mcpTool implementation="cc.unitmesh.git.mcp.GetVcsStatusTool"/>
4952
</extensions>
5053
</idea-plugin>

0 commit comments

Comments
 (0)