Skip to content

Commit becf1d7

Browse files
author
e.kopp
committed
add example for sse mcp transport protocol;
add streamableHttp transport protocol
1 parent 521aeee commit becf1d7

File tree

5 files changed

+117
-23
lines changed

5 files changed

+117
-23
lines changed

activators/llm/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
implementation(libs.kotlin.stdlib)
3030

3131
api(libs.ktor.client)
32+
implementation(libs.ktor.client.cio)
3233
implementation(libs.ktor.client.content.negotiation)
3334
implementation(libs.ktor.serialization.jackson)
3435

activators/llm/src/main/kotlin/com/justai/jaicf/activator/llm/mcp/McpService.kt

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.justai.jaicf.activator.llm.tool.LLMTool
55
import com.justai.jaicf.activator.llm.tool.LLMToolCallContext
66
import com.justai.jaicf.activator.llm.tool.llmTool
77
import io.ktor.client.*
8+
import io.ktor.client.engine.cio.*
89
import io.ktor.client.plugins.sse.*
910
import io.ktor.client.plugins.websocket.*
1011
import io.ktor.client.request.*
@@ -14,6 +15,7 @@ import io.modelcontextprotocol.kotlin.sdk.Tool
1415
import io.modelcontextprotocol.kotlin.sdk.client.Client
1516
import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport
1617
import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
18+
import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport
1719
import io.modelcontextprotocol.kotlin.sdk.client.WebSocketClientTransport
1820
import kotlinx.coroutines.runBlocking
1921
import kotlinx.io.asSink
@@ -26,16 +28,19 @@ typealias McpServiceResponseBuilder = suspend LLMToolCallContext<Map<String, Any
2628
class McpService() : AutoCloseable {
2729

2830
private val mcp: Client = Client(clientInfo = Implementation(name = "mcp-client-jaicf", version = "1.0.0"))
31+
private var process: Process? = null
2932

3033
suspend fun connectStdio(command: List<String>) {
31-
val process = ProcessBuilder(command).start()
34+
process = runCatching { ProcessBuilder(command).start() }
35+
.getOrElse { throw IllegalStateException("Failed to start MCP process with command: $command", it) }
3236

33-
val transport = StdioClientTransport(
34-
input = process.inputStream.asSource().buffered(),
35-
output = process.outputStream.asSink().buffered()
36-
)
37-
38-
mcp.connect(transport)
37+
process?.let { proc ->
38+
val transport = StdioClientTransport(
39+
input = proc.inputStream.asSource().buffered(),
40+
output = proc.outputStream.asSink().buffered()
41+
)
42+
mcp.connect(transport)
43+
}
3944
}
4045

4146
suspend fun connectSse(
@@ -57,6 +62,15 @@ class McpService() : AutoCloseable {
5762
mcp.connect(transport)
5863
}
5964

65+
suspend fun connectStreamableHttp(
66+
urlString: String,
67+
client: HttpClient,
68+
requestBuilder: HttpRequestBuilder.() -> Unit
69+
) {
70+
val transport = StreamableHttpClientTransport(client, urlString, null, requestBuilder)
71+
mcp.connect(transport)
72+
}
73+
6074
suspend fun getTools(): List<Tool> = mcp.listTools()?.tools ?: emptyList()
6175

6276
suspend fun callTool(name: String, arguments: Map<String, Any?>): CallToolResultBase {
@@ -66,6 +80,7 @@ class McpService() : AutoCloseable {
6680

6781
override fun close() {
6882
runBlocking { mcp.close() }
83+
process?.destroy()
6984
}
7085

7186
companion object {
@@ -85,6 +100,12 @@ class McpService() : AutoCloseable {
85100
client: HttpClient = HttpClient { install(WebSockets) },
86101
requestBuilder: HttpRequestBuilder.() -> Unit = {}
87102
) = McpService().apply { runBlocking { connectWebSocket(urlString, client, requestBuilder) } }
103+
104+
fun streamableHttp(
105+
urlString: String,
106+
client: HttpClient = HttpClient(CIO) { install(SSE) },
107+
requestBuilder: HttpRequestBuilder.() -> Unit = {}
108+
) = McpService().apply { runBlocking { connectStreamableHttp(urlString, client, requestBuilder) } }
88109
}
89110
}
90111

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
version: '3'
2+
services:
3+
gateway-duckduckgo-sse:
4+
image: docker/mcp-gateway
5+
command:
6+
- --port=8081
7+
- --transport=sse
8+
- --verbose
9+
- --servers=duckduckgo
10+
ports:
11+
- "8081:8081"
12+
volumes:
13+
- /var/run/docker.sock:/var/run/docker.sock
14+
15+
gateway-duckduckgo-streaming:
16+
image: docker/mcp-gateway
17+
command:
18+
- --port=8083
19+
- --transport=streaming
20+
- --verbose
21+
- --servers=duckduckgo
22+
ports:
23+
- "8083:8083"
24+
volumes:
25+
- /var/run/docker.sock:/var/run/docker.sock

examples/llm-example/src/main/kotlin/com/justai/jaicf/examples/llm/ScenarioWithMcpService.kt

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,24 @@ import com.openai.core.JsonValue
1313
*
1414
* You can expose either a single tool from an MCP service or all tools at once.
1515
*
16-
* Supported transport protocols: STDIO, SSE, and WebSocket.
16+
* ## Supported Transport Protocols:
17+
* - **STDIO** - Direct process communication (use `McpService.stdio()`)
18+
* - **SSE** - Server-Sent Events (use `McpService.sse()`)
19+
* - **StreamableHttp** - HTTP with streaming support (use `McpService.streamableHttp()`)
20+
* - **WebSocket** - WebSocket protocol (use `McpService.websocket()`)
21+
*
22+
* ### Docker MCP Gateway Transports:
23+
* Docker MCP Gateway supports: `stdio`, `sse`, `streaming` (maps to StreamableHttp)
24+
* - SSE: `--transport=sse` → use with `McpService.sse()`
25+
* - Streaming: `--transport=streaming` → use with `McpService.streamableHttp()`
26+
*
27+
* ## Example 1: STDIO Transport (Direct Process Communication)
1728
*
1829
* This example uses an STDIO transport connection to the [Amazon Product Search](https://smithery.ai/server/@SiliconValleyInsight/amazon-product-search) service.
1930
*
2031
* IMPORTANT! If you use the STDIO transport type, consider calling the McpService#close() method to free resources.
2132
*/
22-
val mcpService = McpService.stdio(
33+
val mcpServiceStdio = McpService.stdio(
2334
listOf(
2435
"npx",
2536
"-y",
@@ -31,36 +42,72 @@ val mcpService = McpService.stdio(
3142
)
3243
)
3344

45+
/**
46+
* ## Example 2: SSE Transport via Docker MCP Gateway
47+
*
48+
* This example demonstrates connecting to MCP services through Docker MCP Gateway.
49+
* Docker MCP Gateway allows you to run MCP servers in containers and access them via HTTP.
50+
*
51+
* ### Setup:
52+
* 1. See docker-compose.yml example at: activators/llm/src/main/kotlin/com/justai/jaicf/activator/llm/mcp/gateway/docker-compose.yml
53+
* 2. Start the gateway: `docker-compose up`
54+
* 3. Connect to the gateway via SSE transport as shown below
55+
*
56+
* ### Using MCP Services with API Keys:
57+
* Some services require API keys. Pass them via environment variables in docker-compose.yml:
58+
* ```
59+
* environment:
60+
* EXA_API_KEY: your-api-key-here
61+
* ```
62+
*
63+
* ### StreamableHttp Transport:
64+
* For streaming HTTP support, use `McpService.streamableHttp()` which corresponds to
65+
* Docker MCP Gateway's `--transport=streaming` option. This provides HTTP-based streaming
66+
* for better performance with long-running operations.
67+
* ```
68+
* val mcpStreamable = McpService.streamableHttp("http://localhost:8083/mcp")
69+
* ```
70+
*
71+
* This example connects to DuckDuckGo search service running on port 8081 (SSE).
72+
*/
73+
val mcpServiceSse = McpService.sse(
74+
urlString = "http://localhost:8081/sse"
75+
)
76+
3477
/**
3578
* IMPORTANT! Set up your OPENAI_API_KEY and OPENAI_BASE_URL env before running
3679
*/
3780
private val scenario = Scenario {
3881
llmState("main", {
3982
model = "gpt-4.1-nano"
4083

84+
// Example using DuckDuckGo via Docker MCP Gateway
85+
// Available tools: "search" and "fetch_content"
86+
4187
// Option 1: The most simple way to add a tool. Provide a tool name via `toolName` parameter.
42-
tool(mcpService.getTool("find_products_to_buy"))
88+
tool(mcpServiceSse.getTool("search"))
4389

4490
// Option 2: expose a subset of tools from the MCP service via the `tools` parameter.
45-
// In this example, only the `find_products_to_buy` and `get_product_details` tools are available.
46-
mcp(service = mcpService, tools = listOf("find_products_to_buy", "get_product_details"))
91+
mcp(service = mcpServiceSse, tools = listOf("search", "fetch_content"))
4792

48-
// Option 2.1: Omit the `tools` parameter to expose all tools from the MCP service.
49-
mcp(service = mcpService)
93+
// Option 3: Omit the `tools` parameter to expose all tools from the MCP service.
94+
mcp(service = mcpServiceSse)
5095

51-
// Option 3: expose a single specific tool from the MCP service.
52-
tool<JsonValue>(mcpService, "shop_for_items")
96+
// Option 4: expose a single specific tool from the MCP service.
97+
tool<JsonValue>(mcpServiceSse, "search")
5398

54-
// Option 4: expose a single tool and override its default description to fit your scenario.
99+
// Option 5: expose a single tool and override its default description to fit your scenario.
55100
tool<JsonValue>(
56-
mcp = mcpService,
57-
toolName = "get_search_options",
58-
description = "Use this tool only if user directly asks about search options"
101+
mcp = mcpServiceSse,
102+
toolName = "search",
103+
description = "Search the web using DuckDuckGo when user asks to find information online"
59104
)
60105

61-
// You can also handle tool call result before send it back to llm
62-
mcp(service = mcpService, tools = listOf("get_cache_stats")) {
106+
// You can also handle tool call result before sending it back to LLM
107+
mcp(service = mcpServiceSse, tools = listOf("search")) {
63108
// Handle the tool results via the `it` parameter.
109+
// For example, you can format or filter the response here
110+
it
64111
}
65112
}) {
66113
llm.withToolCalls {

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ googleActions = "1.8.0"
2525
alexa = "2.86.0"
2626
messenger4j = "1.1.0"
2727
openai = "2.17.0"
28-
modelcontextprotocol = "0.5.0"
28+
modelcontextprotocol = "0.7.2"
2929
lexruntimev2 = "2.32.4"
3030
grpc-okhttp = "1.73.0"
3131
google-dialogflow = "4.74.0"

0 commit comments

Comments
 (0)