11package com.justai.jaicf.activator.llm.mcp
22
3- import com.justai.jaicf.activator.llm.builder.JsonSchemaBuilder
3+ import com.justai.jaicf.activator.llm.builder.inputSchemaToBuilder
44import com.justai.jaicf.activator.llm.tool.LLMTool
55import com.justai.jaicf.activator.llm.tool.LLMToolCallContext
66import com.justai.jaicf.activator.llm.tool.llmTool
@@ -26,7 +26,7 @@ class McpService() : AutoCloseable {
2626 private val mcp: Client = Client (clientInfo = Implementation (name = " mcp-client-jaicf" , version = " 1.0.0" ))
2727 private var process: Process ? = null
2828
29- suspend fun connectStdio (command : List <String >) {
29+ private suspend fun connectStdio (command : List <String >) {
3030 process = runCatching { ProcessBuilder (command).start() }
3131 .getOrElse { throw IllegalStateException (" Failed to start MCP process with command: $command " , it) }
3232
@@ -39,7 +39,7 @@ class McpService() : AutoCloseable {
3939 }
4040 }
4141
42- suspend fun connectSse (
42+ private suspend fun connectSse (
4343 urlString : String ,
4444 client : HttpClient ,
4545 reconnectionTime : Duration ? ,
@@ -49,7 +49,7 @@ class McpService() : AutoCloseable {
4949 mcp.connect(transport)
5050 }
5151
52- suspend fun connectWebSocket (
52+ private suspend fun connectWebSocket (
5353 urlString : String ,
5454 client : HttpClient ,
5555 requestBuilder : HttpRequestBuilder .() -> Unit
@@ -58,7 +58,7 @@ class McpService() : AutoCloseable {
5858 mcp.connect(transport)
5959 }
6060
61- suspend fun connectStreamableHttp (
61+ private suspend fun connectStreamableHttp (
6262 urlString : String ,
6363 client : HttpClient ,
6464 reconnectionTime : Duration ? ,
@@ -68,7 +68,51 @@ class McpService() : AutoCloseable {
6868 mcp.connect(transport)
6969 }
7070
71- suspend fun getTools (): List <Tool > = mcp.listTools()?.tools ? : emptyList()
71+ private suspend fun getTools (): List <Tool > = mcp.listTools()?.tools ? : emptyList()
72+
73+ private fun asTool (
74+ tool : Tool ,
75+ description : String? ,
76+ responseBuilder : McpServiceResponseBuilder
77+ ): LLMTool <Map <String , Any >> {
78+ return llmTool<Map <String , Any >>(
79+ name = tool.name,
80+ description = description ? : tool.description ? : " " ,
81+ parameters = {
82+ val inputSchemaMap = mapOf (
83+ " type" to tool.inputSchema.type,
84+ " properties" to tool.inputSchema.properties,
85+ " required" to (tool.inputSchema.required ? : emptyList())
86+ )
87+ inputSchemaToBuilder(inputSchemaMap)
88+ }
89+ ) {
90+ responseBuilder(callTool(tool.name, call.arguments))
91+ }
92+ }
93+
94+ fun tools (
95+ toolNames : List <String > = emptyList(),
96+ responseBuilder : McpServiceResponseBuilder = { it }
97+ ): List <LLMTool <Map <String , Any >>> {
98+ val availableTools = runBlocking { getTools() }
99+ return availableTools
100+ .filter { toolNames.isEmpty() || it.name in toolNames }
101+ .map { asTool(it, null , responseBuilder) }
102+ }
103+
104+ fun tool (
105+ toolName : String ,
106+ description : String? = null,
107+ responseBuilder : McpServiceResponseBuilder = { it }
108+ ): LLMTool <Map <String , Any >> {
109+
110+ val availableTools = runBlocking { getTools() }
111+ val tool = availableTools.find { it.name == toolName }
112+ ? : throw IllegalArgumentException (" Unknown tool $toolName " )
113+
114+ return asTool(tool, description, responseBuilder)
115+ }
72116
73117 suspend fun callTool (name : String , arguments : Map <String , Any ?>): CallToolResultBase {
74118 return mcp.callTool(name, arguments)
@@ -86,11 +130,11 @@ class McpService() : AutoCloseable {
86130 ) = McpService ().apply { runBlocking { connectStdio(command) } }
87131
88132 fun sse (
89- urlString : String ,
133+ url : String ,
90134 client : HttpClient = HttpClient { install(SSE ) },
91135 reconnectionTime : Duration ? = null,
92136 requestBuilder : HttpRequestBuilder .() -> Unit = {}
93- ) = McpService ().apply { runBlocking { connectSse(urlString , client, reconnectionTime) { requestBuilder() } } }
137+ ) = McpService ().apply { runBlocking { connectSse(url , client, reconnectionTime) { requestBuilder() } } }
94138
95139 fun websocket (
96140 urlString : String ,
@@ -115,108 +159,3 @@ class McpService() : AutoCloseable {
115159 }
116160 }
117161}
118-
119- fun McpService.asTools (
120- tools : List <String > = emptyList(),
121- responseBuilder : McpServiceResponseBuilder = { it }
122- ): List <LLMTool <Map <String , Any >>> {
123- val availableTools = runBlocking { getTools() }
124- return availableTools
125- .filter { tools.isEmpty() || it.name in tools }
126- .map { getLlmTool(it, null , responseBuilder) }
127- }
128-
129- fun McpService.getTool (
130- toolName : String ,
131- description : String? = null,
132- responseBuilder : McpServiceResponseBuilder = { it }
133- ): LLMTool <Map <String , Any >> {
134-
135- val availableTools = runBlocking { getTools() }
136- val tool = availableTools.find { it.name == toolName }
137- ? : throw IllegalArgumentException (" Unknown tool $toolName " )
138-
139- return getLlmTool(tool, description, responseBuilder)
140- }
141-
142- private fun McpService.getLlmTool (
143- tool : Tool ,
144- description : String? ,
145- responseBuilder : McpServiceResponseBuilder
146- ): LLMTool <Map <String , Any >> {
147- return llmTool<Map <String , Any >>(
148- name = tool.name,
149- description = description ? : tool.description ? : " " ,
150- parameters = {
151- val inputSchemaMap = mapOf (
152- " type" to tool.inputSchema.type,
153- " properties" to tool.inputSchema.properties,
154- " required" to (tool.inputSchema.required ? : emptyList())
155- )
156- this .convertInputSchemaToBuilder(inputSchemaMap)
157- }
158- ) {
159- responseBuilder(callTool(tool.name, call.arguments))
160- }
161- }
162-
163- private fun JsonSchemaBuilder.convertInputSchemaToBuilder (inputSchema : Map <String , Any >? ) {
164- inputSchema ? : return
165-
166- val properties = inputSchema[" properties" ] as ? Map <String , Any > ? : return
167- val required = inputSchema[" required" ] as ? List <String > ? : emptyList()
168-
169- properties.forEach { (propName, propDef) ->
170- val propMap = when (propDef) {
171- is kotlinx.serialization.json.JsonObject -> {
172- propDef.entries.associate { (key, value) ->
173- key to when (value) {
174- is kotlinx.serialization.json.JsonPrimitive -> {
175- when {
176- value.isString -> value.content
177- value.content == " true" || value.content == " false" -> value.content.toBoolean()
178- value.content.toIntOrNull() != null -> value.content.toInt()
179- value.content.toDoubleOrNull() != null -> value.content.toDouble()
180- else -> value.content
181- }
182- }
183-
184- is kotlinx.serialization.json.JsonArray -> {
185- value.map {
186- if (it is kotlinx.serialization.json.JsonPrimitive ) it.content else it.toString()
187- }
188- }
189-
190- else -> value.toString()
191- }
192- }
193- }
194-
195- is Map <* , * > -> propDef as ? Map <String , Any >
196- else -> null
197- } ? : return @forEach
198-
199- val type = propMap[" type" ] as ? String ? : return @forEach
200- val description = propMap[" description" ] as ? String
201- val isRequired = propName in required
202-
203- when (type) {
204- " string" -> {
205- val enumValues = (propMap[" enum" ] as ? List <* >)?.mapNotNull { it as ? String }
206- str(propName, description, isRequired, enumValues)
207- }
208-
209- " integer" -> {
210- int(propName, description, isRequired)
211- }
212-
213- " number" -> {
214- num(propName, description, isRequired)
215- }
216-
217- " boolean" -> {
218- bool(propName, description, isRequired)
219- }
220- }
221- }
222- }
0 commit comments