@@ -4,14 +4,24 @@ import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
44import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification
55import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest
66import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse
7+ import io.modelcontextprotocol.kotlin.sdk.ListToolsResult
78import io.modelcontextprotocol.kotlin.sdk.Method
89import jakarta.inject.Inject
910import kotlinx.coroutines.channels.SendChannel
11+ import kotlinx.serialization.json.JsonObject
12+ import kotlinx.serialization.json.encodeToJsonElement
13+ import kotlinx.serialization.json.jsonObject
14+ import kotlinx.serialization.json.buildJsonObject
15+ import kotlinx.serialization.json.buildJsonArray
16+ import kotlinx.serialization.json.jsonArray
17+ import kotlinx.serialization.json.jsonPrimitive
18+ import misk.annotation.ExperimentalMiskApi
1019import misk.exceptions.BadRequestException
1120import misk.exceptions.NotFoundException
1221import misk.exceptions.WebActionException
1322import misk.logging.getLogger
1423import misk.mcp.McpSessionHandler
24+ import misk.mcp.McpTool
1525import misk.mcp.action.SESSION_ID_HEADER
1626import misk.web.HttpCall
1727import misk.web.sse.ServerSentEvent
@@ -31,11 +41,13 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi
3141 * @param mcpSessionHandler Optional session handler for managing client sessions
3242 * @param sendChannel Channel for sending SSE events to the client
3343 */
34- @OptIn(ExperimentalAtomicApi ::class )
35- internal class MiskStreamableHttpServerTransport @Inject constructor(
44+ @OptIn(ExperimentalAtomicApi ::class , ExperimentalMiskApi ::class )
45+ internal class MiskStreamableHttpServerTransport
46+ @Inject constructor (
3647 override val call: HttpCall ,
3748 private val mcpSessionHandler: McpSessionHandler ? ,
3849 private val sendChannel: SendChannel <ServerSentEvent >,
50+ private val mcpTools: Set <McpTool <* >>,
3951) : MiskServerTransport () {
4052
4153 private val initialized: AtomicBoolean = AtomicBoolean (false )
@@ -57,15 +69,79 @@ internal class MiskStreamableHttpServerTransport @Inject constructor(
5769 error(" Not connected" )
5870 }
5971
72+ val jsonObject: JsonObject = McpJson .encodeToJsonElement(message).jsonObject
73+
74+ val jsonObjectWithMetadata = if (message is JSONRPCResponse && message.result is ListToolsResult ) {
75+ transformJsonObjectWithToolMetadata(jsonObject)
76+ } else {
77+ jsonObject
78+ }
79+
6080 val event = ServerSentEvent (
6181 event = " message" ,
62- data = McpJson .encodeToString(message )
82+ data = McpJson .encodeToString(jsonObjectWithMetadata )
6383 )
6484
6585 logger.trace { " Sending SSE: $event " }
6686 sendChannel.send(event)
6787 }
6888
89+ private fun transformJsonObjectWithToolMetadata (jsonObject : JsonObject ): JsonObject {
90+ // Extract tools from the result.tools array
91+ val result = jsonObject[" result" ] as ? JsonObject ? : return jsonObject
92+ val toolsArray = result[" tools" ] ? : return jsonObject
93+
94+ if (! toolsArray.jsonArray.isEmpty()) {
95+ val transformedTools = toolsArray.jsonArray.map { toolElement ->
96+ val toolObject = toolElement.jsonObject
97+ val toolName = toolObject[" name" ]?.jsonPrimitive?.content
98+
99+ // Find the matching McpTool by name
100+ val matchingTool = mcpTools.find { it.name == toolName }
101+
102+ if (matchingTool != null ) {
103+ // Add metadata from the McpTool to the tool JSON
104+ buildJsonObject {
105+ // Copy all existing properties
106+ toolObject.forEach { (key, value) ->
107+ put(key, value)
108+ }
109+
110+ // Add metadata if available
111+ matchingTool.metadata?.let { metadata ->
112+ put(" _meta" , metadata)
113+ }
114+ }
115+ } else {
116+ toolElement
117+ }
118+ }
119+
120+ // Rebuild the JSON object with the transformed tools
121+ return buildJsonObject {
122+ jsonObject.forEach { (key, value) ->
123+ if (key == " result" ) {
124+ put(key, buildJsonObject {
125+ result.forEach { (resultKey, resultValue) ->
126+ if (resultKey == " tools" ) {
127+ put(resultKey, buildJsonArray {
128+ transformedTools.forEach { add(it) }
129+ })
130+ } else {
131+ put(resultKey, resultValue)
132+ }
133+ }
134+ })
135+ } else {
136+ put(key, value)
137+ }
138+ }
139+ }
140+ }
141+
142+ return jsonObject
143+ }
144+
69145 override suspend fun close () {
70146 if (initialized.compareAndSet(expectedValue = true , newValue = false )) {
71147 sendChannel.close()
0 commit comments