Skip to content

Commit 2d91b91

Browse files
committed
feat(mcp): add McpFunctionProvider and enhance tool handling #330
- Introduced McpFunctionProvider to manage MCP server tools. - Refactored tool handling to support multiple tools and caching. - Updated AgentTool and related interfaces to handle lists of tools. - Added execute functionality for MCP tools.
1 parent 7c2a74c commit 2d91b91

File tree

9 files changed

+99
-23
lines changed

9 files changed

+99
-23
lines changed

core/src/223/main/resources/META-INF/autodev-core.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.assessment.SccFunctionProvider"/>
270270
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.knowledge.HistoryFunctionProvider"/>
271271
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.knowledge.KnowledgeFunctionProvider"/>
272+
<toolchainFunctionProvider implementation="cc.unitmesh.devti.mcp.provider.McpFunctionProvider"/>
272273
</extensions>
273274

274275
<actions>

core/src/233/main/resources/META-INF/autodev-core.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@
270270
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.assessment.SccFunctionProvider"/>
271271
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.knowledge.HistoryFunctionProvider"/>
272272
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.knowledge.KnowledgeFunctionProvider"/>
273+
<toolchainFunctionProvider implementation="cc.unitmesh.devti.mcp.provider.McpFunctionProvider"/>
273274
</extensions>
274275

275276
<actions>

core/src/main/kotlin/cc/unitmesh/devti/agenttool/AgentTool.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package cc.unitmesh.devti.agenttool
33
data class AgentTool(val commandName: String, val description: String, val example: String) {
44
override fun toString(): String {
55
val string = if (description.isEmpty()) "" else """desc: $description"""
6-
return """<tool>name: ${commandName}, $string
7-
example:
6+
val example = if (example.isEmpty()) "" else """example:
87
<devin>
98
$example
10-
</devin>
9+
</devin>"""
10+
return """<tool>name: ${commandName}, $string, $example
1111
</tool>"""
1212
}
1313
}

core/src/main/kotlin/cc/unitmesh/devti/bridge/BridgeToolProvider.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ object BridgeToolProvider {
1818

1919
val functions = ToolchainFunctionProvider.all()
2020

21-
commonTools += functions.map {
22-
if (it.toolInfo() != null) {
23-
return@map listOf(it.toolInfo()!!)
21+
commonTools += functions.flatMap {
22+
if (it.toolInfos().isNotEmpty()) {
23+
return@flatMap it.toolInfos()
2424
}
2525

2626
val funcNames = it.funcNames()
2727
funcNames.map { name ->
2828
val example = BuiltinCommand.example(name)
2929
AgentTool(name, "", example)
3030
}
31-
}.flatten()
31+
}
3232

3333
return commonTools
3434
}

core/src/main/kotlin/cc/unitmesh/devti/devin/dataprovider/BuiltinCommand.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cc.unitmesh.devti.devin.dataprovider
22

33
import cc.unitmesh.devti.AutoDevIcons
44
import cc.unitmesh.devti.AutoDevNotifications
5+
import cc.unitmesh.devti.agenttool.AgentTool
56
import cc.unitmesh.devti.provider.toolchain.ToolchainFunctionProvider
67
import com.intellij.icons.AllIcons
78
import com.intellij.openapi.project.ProjectManager
@@ -164,10 +165,9 @@ enum class BuiltinCommand(
164165

165166
fun allToolchains(): List<String> {
166167
return ToolchainFunctionProvider.all().map {
167-
val toolInfo = it.toolInfo()
168-
if (toolInfo != null) {
169-
val base = toolInfo.commandName
170-
return@map it.funcNames().map { "$base:$it" }
168+
val toolInfo: List<AgentTool> = it.toolInfos()
169+
if (toolInfo.isNotEmpty()) {
170+
return@map toolInfo.map { it.commandName }
171171
}
172172

173173
it.funcNames()

core/src/main/kotlin/cc/unitmesh/devti/mcp/CustomMcpServerManager.kt

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package cc.unitmesh.devti.mcp
22

33
import cc.unitmesh.devti.settings.customize.customizeSetting
4-
import com.intellij.openapi.application.ApplicationManager
54
import com.intellij.openapi.components.Service
65
import com.intellij.openapi.diagnostic.logger
76
import com.intellij.openapi.project.Project
8-
import com.intellij.openapi.project.ProjectManager
97
import kotlinx.serialization.Serializable
108
import kotlinx.serialization.json.Json
119
import io.modelcontextprotocol.kotlin.sdk.client.Client
@@ -33,9 +31,8 @@ data class McpServer(
3331
) {
3432

3533
companion object {
36-
fun load(): McpConfig? {
37-
val project = ProjectManager.getInstance().openProjects.first()
38-
return tryParse(project.customizeSetting.mcpServerConfig)
34+
fun load(mcpServerConfig: String): McpConfig? {
35+
return tryParse(mcpServerConfig)
3936
}
4037

4138
fun tryParse(configs: String?): McpConfig? {
@@ -56,8 +53,20 @@ data class McpServer(
5653

5754
@Service(Service.Level.PROJECT)
5855
class CustomMcpServerManager(val project: Project) {
59-
fun collectServerInfos(): List<Any> {
60-
val mcpConfig = McpServer.load()
56+
val cached = mutableMapOf<String, List<Tool>>()
57+
val toolClientMap = mutableMapOf<Tool, Client>()
58+
59+
fun collectServerInfos(): List<Tool> {
60+
val mcpServerConfig = project.customizeSetting.mcpServerConfig
61+
if (mcpServerConfig.isEmpty()) {
62+
return emptyList()
63+
}
64+
65+
if (cached.containsKey(mcpServerConfig)) {
66+
return cached[mcpServerConfig]!!
67+
}
68+
69+
val mcpConfig = McpServer.load(mcpServerConfig)
6170
if (mcpConfig == null) {
6271
return emptyList()
6372
}
@@ -92,6 +101,10 @@ class CustomMcpServerManager(val project: Project) {
92101
future.complete(listTools.tools)
93102
}
94103

104+
listTools?.tools?.map {
105+
toolClientMap[it] = client
106+
}
107+
95108
listTools
96109
} catch (e: java.lang.Error) {
97110
logger<CustomMcpServerManager>().warn("Failed to list tools from ${it.key}: $e")
@@ -102,15 +115,35 @@ class CustomMcpServerManager(val project: Project) {
102115
future.get(30, java.util.concurrent.TimeUnit.SECONDS)
103116
}.flatten()
104117

105-
118+
cached[mcpServerConfig] = tools
106119
return tools
107120
}
108121

122+
fun execute(project: Project, tool: Tool, map: List<String>): Any {
123+
toolClientMap[tool]?.let {
124+
val future = CompletableFuture<Any>()
125+
kotlinx.coroutines.runBlocking {
126+
try {
127+
val result = it.callTool(tool.name, mapOf<String, Any?>(), true, null)
128+
future.complete(result)
129+
} catch (e: java.lang.Error) {
130+
logger<CustomMcpServerManager>().warn("Failed to execute tool ${tool.name}: $e")
131+
future.complete("Failed to execute tool ${tool.name}: $e")
132+
}
133+
}
134+
135+
return future.get(30, java.util.concurrent.TimeUnit.SECONDS)
136+
}
137+
138+
return "No such tool: ${tool.name} or failed to execute"
139+
}
140+
109141
companion object {
110142
private val logger = logger<CustomMcpServerManager>()
111143

112144
fun instance(project: Project): CustomMcpServerManager {
113145
return project.getService(CustomMcpServerManager::class.java)
114146
}
115147
}
116-
}
148+
}
149+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cc.unitmesh.devti.mcp.provider
2+
3+
import cc.unitmesh.devti.agenttool.AgentTool
4+
import cc.unitmesh.devti.mcp.CustomMcpServerManager
5+
import cc.unitmesh.devti.provider.toolchain.ToolchainFunctionProvider
6+
import com.intellij.openapi.project.Project
7+
import com.intellij.openapi.project.ProjectManager
8+
9+
class McpFunctionProvider : ToolchainFunctionProvider {
10+
override fun funcNames(): List<String> {
11+
val project = ProjectManager.getInstance().openProjects.firstOrNull() ?: return emptyList()
12+
return CustomMcpServerManager.instance(project).collectServerInfos().map { it.name }
13+
}
14+
15+
override fun toolInfos(): List<AgentTool> {
16+
val manager = CustomMcpServerManager.instance(
17+
ProjectManager.getInstance().openProjects.firstOrNull() ?: return emptyList()
18+
)
19+
return manager.collectServerInfos().map {
20+
AgentTool(it.name, it.description ?: "", "")
21+
}
22+
}
23+
24+
override fun isApplicable(project: Project, funcName: String): Boolean {
25+
return CustomMcpServerManager.instance(project).collectServerInfos().any { it.name == funcName }
26+
}
27+
28+
override fun execute(
29+
project: Project,
30+
prop: String,
31+
args: List<Any>,
32+
allVariables: Map<String, Any?>
33+
): Any {
34+
val tool = CustomMcpServerManager.instance(project).collectServerInfos().firstOrNull { it.name == prop }
35+
if (tool == null) {
36+
return "No such tool: $prop"
37+
}
38+
39+
return CustomMcpServerManager.instance(project).execute(project, tool, args.map { it.toString() })
40+
}
41+
}

core/src/main/kotlin/cc/unitmesh/devti/provider/toolchain/ToolchainFunctionProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import com.intellij.openapi.extensions.ExtensionPointName
55
import com.intellij.openapi.project.Project
66

77
interface ToolchainFunctionProvider {
8-
fun toolInfo(): AgentTool? = null
8+
fun toolInfos(): List<AgentTool> = emptyList()
99

1010
fun funcNames(): List<String>
1111

exts/ext-database/src/main/kotlin/cc/unitmesh/database/provider/DatabaseFunctionProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import com.intellij.openapi.diagnostic.logger
1212
import com.intellij.openapi.project.Project
1313

1414
class DatabaseFunctionProvider : ToolchainFunctionProvider {
15-
override fun toolInfo(): AgentTool? {
15+
override fun toolInfos(): List<AgentTool> {
1616
val example = BuiltinCommand.example("database")
17-
return AgentTool("database", "Database schema and query tool", example)
17+
return listOf(AgentTool("database", "Database schema and query tool", example))
1818
}
1919

2020
override fun isApplicable(project: Project, funcName: String): Boolean = DatabaseFunction.entries.any { it.funName == funcName }

0 commit comments

Comments
 (0)