Skip to content

Commit 22fbdc8

Browse files
Add support for MCP streamable HTTP
Signed-off-by: mydeveloperplanet <[email protected]>
1 parent 7955d40 commit 22fbdc8

File tree

7 files changed

+203
-16
lines changed

7 files changed

+203
-16
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ dependencies {
7070
implementation("dev.langchain4j:langchain4j-web-search-engine-tavily:$lg4j_beta_version")
7171
implementation("dev.langchain4j:langchain4j-azure-open-ai:$lg4j_version")
7272
implementation("dev.langchain4j:langchain4j-chroma:$lg4j_beta_version")
73-
// implementation("dev.langchain4j:langchain4j-mcp:$lg4j_version")
73+
implementation("dev.langchain4j:langchain4j-mcp:$lg4j_beta_version")
7474
implementation("dev.langchain4j:langchain4j-reactor:$lg4j_beta_version")
7575
implementation(platform("software.amazon.awssdk:bom:2.40.5"))
7676
implementation("software.amazon.awssdk:bedrock")

src/main/java/com/devoxx/genie/model/mcp/MCPServer.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public class MCPServer {
2323
*/
2424
public enum TransportType {
2525
STDIO, // Standard I/O communication with a subprocess
26-
HTTP_SSE // HTTP Server-Sent Events for communication
26+
HTTP_SSE, // HTTP Server-Sent Events for communication (deprecated)
27+
HTTP // Streamable HTTP
2728
}
2829

2930
@Builder.Default
@@ -34,8 +35,8 @@ public enum TransportType {
3435
private String command;
3536
private List<String> args;
3637

37-
// HTTP SSE transport properties
38-
private String sseUrl;
38+
// HTTP transport properties
39+
private String url;
3940

4041
@Builder.Default
4142
private Map<String, String> env = new HashMap<>();

src/main/java/com/devoxx/genie/service/mcp/MCPExecutionService.java

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import dev.langchain4j.mcp.McpToolProvider;
1010
import dev.langchain4j.mcp.client.DefaultMcpClient;
1111
import dev.langchain4j.mcp.client.McpClient;
12+
import dev.langchain4j.mcp.client.transport.McpTransport;
1213
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
14+
import dev.langchain4j.mcp.client.transport.http.StreamableHttpMcpTransport;
1315
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
1416
import dev.langchain4j.service.tool.ToolProvider;
1517
import lombok.extern.slf4j.Slf4j;
@@ -144,6 +146,9 @@ private McpClient createMcpClient(@NotNull MCPServer mcpServer) {
144146
if (mcpServer.getTransportType() == MCPServer.TransportType.HTTP_SSE) {
145147
// Create HTTP SSE client
146148
client = initHttpSseClient(mcpServer);
149+
} else if (mcpServer.getTransportType() == MCPServer.TransportType.HTTP) {
150+
// Create Streamable HTTP client
151+
client = initStreamableHttpClient(mcpServer);
147152
} else {
148153
// Default to STDIO transport
149154
// Handle bash commands differently based on working implementation
@@ -180,7 +185,7 @@ private McpClient createMcpClient(@NotNull MCPServer mcpServer) {
180185
@Nullable
181186
private static McpClient initHttpSseClient(@NotNull MCPServer mcpServer) {
182187
try {
183-
String sseUrl = mcpServer.getSseUrl();
188+
String sseUrl = mcpServer.getUrl();
184189
if (sseUrl == null || sseUrl.trim().isEmpty()) {
185190
log.error("SSE URL cannot be empty for HTTP SSE transport");
186191
MCPService.logDebug("SSE URL cannot be empty for HTTP SSE transport");
@@ -206,8 +211,49 @@ private static McpClient initHttpSseClient(@NotNull MCPServer mcpServer) {
206211
.build();
207212

208213
} catch (Exception e) {
209-
log.error("Failed to initialize HTTP SSE client with URL: {}", mcpServer.getSseUrl(), e);
210-
MCPService.logDebug("Failed to initialize HTTP SSE client with URL: " + mcpServer.getSseUrl() + " - " + e.getMessage());
214+
log.error("Failed to initialize HTTP SSE client with URL: {}", mcpServer.getUrl(), e);
215+
MCPService.logDebug("Failed to initialize HTTP SSE client with URL: " + mcpServer.getUrl() + " - " + e.getMessage());
216+
return null;
217+
}
218+
}
219+
220+
/**
221+
* Helper method to initialize a streamable HTTP client with error handling
222+
*
223+
* @param mcpServer The MCP server configuration
224+
* @return An initialized MCP client or null if creation fails
225+
*/
226+
@Nullable
227+
private static McpClient initStreamableHttpClient(@NotNull MCPServer mcpServer) {
228+
try {
229+
String url = mcpServer.getUrl();
230+
if (url == null || url.trim().isEmpty()) {
231+
log.error("HTTP URL cannot be empty for streamable HTTP transport");
232+
MCPService.logDebug("HTTP URL cannot be empty for streamable HTTP transport");
233+
return null;
234+
}
235+
236+
MCPService.logDebug("Initializing streamable HTTP transport with URL: " + url);
237+
238+
// Create the transport
239+
McpTransport transport = new StreamableHttpMcpTransport.Builder()
240+
.url(url)
241+
.timeout(java.time.Duration.ofSeconds(DevoxxGenieStateService.getInstance().getTimeout()))
242+
.logRequests(MCPService.isDebugLogsEnabled())
243+
.logResponses(MCPService.isDebugLogsEnabled())
244+
.build();
245+
246+
// Create and return the client
247+
return new DefaultMcpClient.Builder()
248+
.clientName("DevoxxGenie")
249+
.protocolVersion("2024-11-05")
250+
.transport(transport)
251+
.toolExecutionTimeout(java.time.Duration.ofSeconds(DevoxxGenieStateService.getInstance().getTimeout()))
252+
.build();
253+
254+
} catch (Exception e) {
255+
log.error("Failed to initialize streamable HTTP client with URL: {}", mcpServer.getUrl(), e);
256+
MCPService.logDebug("Failed to initialize streamable HTTP client with URL: " + mcpServer.getUrl() + " - " + e.getMessage());
211257
return null;
212258
}
213259
}

src/main/java/com/devoxx/genie/ui/settings/mcp/MCPSettingsComponent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,8 +566,8 @@ public String getColumnName(int column) {
566566
}
567567

568568
private String getConnectionInfo(@NotNull MCPServer server) {
569-
if (server.getTransportType() == MCPServer.TransportType.HTTP_SSE) {
570-
return server.getSseUrl();
569+
if (server.getTransportType() == MCPServer.TransportType.HTTP_SSE || server.getTransportType() == MCPServer.TransportType.HTTP) {
570+
return server.getUrl();
571571
} else {
572572
// STDIO transport
573573
return server.getCommand();

src/main/java/com/devoxx/genie/ui/settings/mcp/dialog/MCPServerDialog.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.devoxx.genie.model.mcp.MCPServer;
44
import com.devoxx.genie.ui.settings.mcp.dialog.transport.HttpSseTransportPanel;
55
import com.devoxx.genie.ui.settings.mcp.dialog.transport.StdioTransportPanel;
6+
import com.devoxx.genie.ui.settings.mcp.dialog.transport.StreamableHttpTransportPanel;
67
import com.devoxx.genie.ui.settings.mcp.dialog.transport.TransportPanel;
78
import com.intellij.openapi.ui.DialogWrapper;
89
import com.intellij.openapi.ui.Messages;
@@ -53,6 +54,7 @@ public MCPServerDialog(MCPServer existingServer) {
5354
// Initialize transport panels
5455
transportPanels.put(MCPServer.TransportType.STDIO, new StdioTransportPanel());
5556
transportPanels.put(MCPServer.TransportType.HTTP_SSE, new HttpSseTransportPanel());
57+
transportPanels.put(MCPServer.TransportType.HTTP, new StreamableHttpTransportPanel());
5658

5759
// Initialize UI components
5860
initUI();

src/main/java/com/devoxx/genie/ui/settings/mcp/dialog/transport/HttpSseTransportPanel.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package com.devoxx.genie.ui.settings.mcp.dialog.transport;
22

3+
import java.awt.GridBagConstraints;
4+
import java.awt.GridBagLayout;
5+
import java.awt.Insets;
6+
import java.time.Duration;
7+
8+
import javax.swing.JLabel;
9+
import javax.swing.JPanel;
10+
import javax.swing.JTextField;
11+
312
import com.devoxx.genie.model.mcp.MCPServer;
13+
14+
import lombok.extern.slf4j.Slf4j;
15+
416
import dev.langchain4j.mcp.client.DefaultMcpClient;
517
import dev.langchain4j.mcp.client.McpClient;
618
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
7-
import lombok.extern.slf4j.Slf4j;
8-
9-
import javax.swing.*;
10-
import java.awt.*;
11-
import java.time.Duration;
1219

1320
/**
1421
* Panel for configuring HTTP SSE MCP transport
@@ -63,7 +70,7 @@ public JPanel getPanel() {
6370

6471
@Override
6572
public void loadSettings(MCPServer server) {
66-
sseUrlField.setText(server.getSseUrl());
73+
sseUrlField.setText(server.getUrl());
6774
}
6875

6976
@Override
@@ -116,7 +123,7 @@ public McpClient createClient() throws Exception {
116123

117124
@Override
118125
public void applySettings(MCPServer.MCPServerBuilder builder) {
119-
builder.sseUrl(sseUrlField.getText().trim())
126+
builder.url(sseUrlField.getText().trim())
120127
.transportType(MCPServer.TransportType.HTTP_SSE);
121128
}
122129
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.devoxx.genie.ui.settings.mcp.dialog.transport;
2+
3+
import java.awt.GridBagConstraints;
4+
import java.awt.GridBagLayout;
5+
import java.awt.Insets;
6+
import java.time.Duration;
7+
8+
import javax.swing.JLabel;
9+
import javax.swing.JPanel;
10+
import javax.swing.JTextField;
11+
12+
import com.devoxx.genie.model.mcp.MCPServer;
13+
14+
import lombok.extern.slf4j.Slf4j;
15+
16+
import dev.langchain4j.mcp.client.DefaultMcpClient;
17+
import dev.langchain4j.mcp.client.McpClient;
18+
import dev.langchain4j.mcp.client.transport.McpTransport;
19+
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
20+
import dev.langchain4j.mcp.client.transport.http.StreamableHttpMcpTransport;
21+
22+
/**
23+
* Panel for configuring Streamable HTTP MCP transport
24+
*/
25+
@Slf4j
26+
public class StreamableHttpTransportPanel implements TransportPanel {
27+
private final JPanel panel;
28+
private final JTextField urlField = new JTextField();
29+
30+
public StreamableHttpTransportPanel() {
31+
panel = new JPanel(new GridBagLayout());
32+
GridBagConstraints gbc = new GridBagConstraints();
33+
gbc.fill = GridBagConstraints.HORIZONTAL;
34+
gbc.insets = new Insets(5, 5, 5, 5);
35+
gbc.gridwidth = 1;
36+
int row = 0;
37+
38+
// HTTP URL field
39+
gbc.gridx = 0;
40+
gbc.gridy = row++;
41+
gbc.weightx = 0.0;
42+
panel.add(new JLabel("HTTP URL:"), gbc);
43+
44+
gbc.gridx = 1;
45+
gbc.weightx = 1.0;
46+
panel.add(urlField, gbc);
47+
48+
// Help text
49+
gbc.gridx = 1;
50+
gbc.gridy = row++;
51+
gbc.weightx = 1.0;
52+
gbc.fill = GridBagConstraints.HORIZONTAL;
53+
panel.add(new JLabel("<html>Enter the HTTP endpoint URL for the MCP server.</html>"), gbc);
54+
55+
gbc.gridx = 1;
56+
gbc.gridy = row++;
57+
gbc.weightx = 0.0;
58+
panel.add(new JLabel("For example: http://localhost:3000/mcp"), gbc);
59+
60+
gbc.gridx = 1;
61+
gbc.gridy = row++;
62+
gbc.weighty = 0.0;
63+
gbc.fill = GridBagConstraints.BOTH;
64+
panel.add(getExampleLabel(), gbc);
65+
66+
}
67+
68+
@Override
69+
public JPanel getPanel() {
70+
return panel;
71+
}
72+
73+
@Override
74+
public void loadSettings(MCPServer server) {
75+
urlField.setText(server.getUrl());
76+
}
77+
78+
@Override
79+
public boolean isValid() {
80+
String url = urlField.getText().trim();
81+
return !url.isEmpty() && (url.startsWith("http://") || url.startsWith("https://"));
82+
}
83+
84+
@Override
85+
public String getErrorMessage() {
86+
String url = urlField.getText().trim();
87+
if (url.isEmpty()) {
88+
return "HTTP URL cannot be empty";
89+
} else if (!url.startsWith("http://") && !url.startsWith("https://")) {
90+
return "HTTP URL must start with http:// or https://";
91+
}
92+
return null;
93+
}
94+
95+
@Override
96+
public McpClient createClient() throws Exception {
97+
String url = urlField.getText().trim();
98+
99+
// Validate URL
100+
if (url.isEmpty()) {
101+
throw new IllegalArgumentException("HTTP URL cannot be empty");
102+
}
103+
104+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
105+
throw new IllegalArgumentException("HTTP URL must start with http:// or https://");
106+
}
107+
108+
log.debug("Creating Streamable HTTP transport with URL: {}", url);
109+
110+
// Create the transport
111+
McpTransport transport = new StreamableHttpMcpTransport.Builder()
112+
.url(url)
113+
.timeout(Duration.ofSeconds(60))
114+
.logRequests(true)
115+
.logResponses(true)
116+
.build();
117+
118+
// Create and return the client
119+
return new DefaultMcpClient.Builder()
120+
.clientName("DevoxxGenie")
121+
.protocolVersion("2024-11-05")
122+
.transport(transport)
123+
.build();
124+
}
125+
126+
@Override
127+
public void applySettings(MCPServer.MCPServerBuilder builder) {
128+
builder.url(urlField.getText().trim())
129+
.transportType(MCPServer.TransportType.HTTP);
130+
}
131+
}

0 commit comments

Comments
 (0)