Skip to content

Commit d636f13

Browse files
authored
Merge pull request #585 from devoxx/feat-573
Support for SSE MCP events #573
2 parents b9b7008 + 3261930 commit d636f13

File tree

9 files changed

+875
-457
lines changed

9 files changed

+875
-457
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,25 @@
1818
@NoArgsConstructor
1919
@AllArgsConstructor
2020
public class MCPServer {
21+
/**
22+
* Transport type for MCP communication
23+
*/
24+
public enum TransportType {
25+
STDIO, // Standard I/O communication with a subprocess
26+
HTTP_SSE // HTTP Server-Sent Events for communication
27+
}
28+
29+
@Builder.Default
30+
private TransportType transportType = TransportType.STDIO;
2131
private String name;
32+
33+
// STDIO transport properties
2234
private String command;
2335
private List<String> args;
2436

37+
// HTTP SSE transport properties
38+
private String sseUrl;
39+
2540
@Builder.Default
2641
private Map<String, String> env = new HashMap<>();
2742

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

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import dev.langchain4j.mcp.McpToolProvider;
88
import dev.langchain4j.mcp.client.DefaultMcpClient;
99
import dev.langchain4j.mcp.client.McpClient;
10+
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
1011
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
1112
import dev.langchain4j.service.tool.ToolProvider;
1213
import lombok.extern.slf4j.Slf4j;
@@ -140,20 +141,28 @@ private McpClient createMcpClient(@NotNull MCPServer mcpServer) {
140141
try {
141142
MCPService.logDebug("Creating new MCP client for: " + serverName);
142143

143-
// Handle bash commands differently based on working implementation
144-
List<String> commandList;
145-
146-
// For other commands, use the standard format
147-
commandList = new ArrayList<>();
148-
commandList.add(mcpServer.getCommand());
149-
if (mcpServer.getArgs() != null) {
150-
commandList.addAll(mcpServer.getArgs());
151-
}
144+
// Create client based on transport type
145+
McpClient client;
152146

153-
MCPService.logDebug("Command list: " + commandList);
147+
if (mcpServer.getTransportType() == MCPServer.TransportType.HTTP_SSE) {
148+
// Create HTTP SSE client
149+
client = initHttpSseClient(mcpServer);
150+
} else {
151+
// Default to STDIO transport
152+
// Handle bash commands differently based on working implementation
153+
List<String> commandList = new ArrayList<>();
154+
commandList.add(mcpServer.getCommand());
155+
if (mcpServer.getArgs() != null) {
156+
commandList.addAll(mcpServer.getArgs());
157+
}
158+
159+
MCPService.logDebug("Command list: " + commandList);
160+
161+
// Create the client using the helper method
162+
client = initStdioClient(commandList, mcpServer.getEnv());
163+
}
154164

155-
// Create the client using the helper method and cache it
156-
McpClient client = initStdioClient(commandList, mcpServer.getEnv());
165+
// Cache the client if not null
157166
if (client != null) {
158167
clientCache.put(serverName, client);
159168
MCPService.logDebug("Added new MCP client to cache for: " + serverName);
@@ -165,6 +174,45 @@ private McpClient createMcpClient(@NotNull MCPServer mcpServer) {
165174
}
166175
}
167176

177+
/**
178+
* Helper method to initialize an HTTP SSE client with error handling
179+
*
180+
* @param mcpServer The MCP server configuration
181+
* @return An initialized MCP client or null if creation fails
182+
*/
183+
@Nullable
184+
private static McpClient initHttpSseClient(@NotNull MCPServer mcpServer) {
185+
try {
186+
String sseUrl = mcpServer.getSseUrl();
187+
if (sseUrl == null || sseUrl.trim().isEmpty()) {
188+
log.error("SSE URL cannot be empty for HTTP SSE transport");
189+
MCPService.logDebug("SSE URL cannot be empty for HTTP SSE transport");
190+
return null;
191+
}
192+
193+
MCPService.logDebug("Initializing HTTP SSE transport with URL: " + sseUrl);
194+
195+
// Create the transport
196+
HttpMcpTransport transport = new HttpMcpTransport.Builder()
197+
.sseUrl(sseUrl)
198+
.timeout(java.time.Duration.ofSeconds(60))
199+
.logRequests(MCPService.isDebugLogsEnabled())
200+
.logResponses(MCPService.isDebugLogsEnabled())
201+
.build();
202+
203+
// Create and return the client
204+
return new DefaultMcpClient.Builder()
205+
.clientName("DevoxxGenie")
206+
.protocolVersion("2024-11-05")
207+
.transport(transport)
208+
.build();
209+
} catch (Exception e) {
210+
log.error("Failed to initialize HTTP SSE client with URL: {}", mcpServer.getSseUrl(), e);
211+
MCPService.logDebug("Failed to initialize HTTP SSE client with URL: " + mcpServer.getSseUrl() + " - " + e.getMessage());
212+
return null;
213+
}
214+
}
215+
168216
/**
169217
* Helper method to initialize a stdio client with error handling
170218
*
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# MCP (Model Context Protocol) Support in DevoxxGenie
2+
3+
This package contains the implementation of MCP support in the DevoxxGenie plugin.
4+
5+
## Transport Types
6+
7+
The MCP client supports two transport types:
8+
9+
1. **STDIO** - Communicates with an MCP server via standard input/output, typically by starting a local process.
10+
2. **HTTP SSE** - Communicates with an MCP server via HTTP Server-Sent Events (SSE).
11+
12+
## Usage
13+
14+
1. Configure MCP servers in the plugin settings.
15+
2. Select the appropriate transport type for each server.
16+
3. For STDIO transport, specify the command and arguments.
17+
4. For HTTP SSE transport, specify the SSE URL.
18+
5. Test the connection to verify the server is working and fetch available tools.
19+
20+
## Transport-Specific Configuration
21+
22+
### STDIO Transport
23+
24+
#### NPX
25+
- **Command** - Full path to the command to execute (e.g., `/path/to/npx`).
26+
- **Arguments** - Arguments to pass to the command (e.g., `-y @modelcontextprotocol/server-filesystem /path/to/project`).
27+
28+
#### Java
29+
- **Command** - Full path to the command to execute (e.g., `/path/to/java`).
30+
- **Arguments** - Arguments to pass to the command (e.g., `-jar /path/to/jar-file.jar`).
31+
32+
### HTTP SSE Transport
33+
34+
- **SSE URL** - URL of the SSE endpoint for the MCP server (e.g., `http://localhost:3000/mcp/sse`).
35+
36+
## Implementation Details
37+
38+
- The `MCPExecutionService` creates the appropriate transport based on the server configuration.
39+
- Each transport type has its own panel in the server configuration dialog.
40+
- The MCPSettingsComponent displays server configurations in a table with transport-specific information.

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ public boolean isEnabled() {
135135
private void setupTable() {
136136
mcpTable.getColumnModel().getColumn(0).setPreferredWidth(60); // Enabled
137137
mcpTable.getColumnModel().getColumn(1).setPreferredWidth(100); // Name
138-
mcpTable.getColumnModel().getColumn(2).setPreferredWidth(100); // Command
139-
mcpTable.getColumnModel().getColumn(3).setPreferredWidth(250); // Arguments
138+
mcpTable.getColumnModel().getColumn(2).setPreferredWidth(100); // Transport Type
139+
mcpTable.getColumnModel().getColumn(3).setPreferredWidth(150); // Connection Info
140140
mcpTable.getColumnModel().getColumn(4).setPreferredWidth(80); // Env Count
141141
mcpTable.getColumnModel().getColumn(5).setPreferredWidth(230); // Tools
142142
mcpTable.getColumnModel().getColumn(6).setPreferredWidth(70); // View Button
@@ -441,7 +441,7 @@ public void reset() {
441441
* Table model for displaying MCP servers
442442
*/
443443
private static class MCPServerTableModel extends AbstractTableModel {
444-
private final String[] COLUMN_NAMES = {"Enabled", "Name", "Command", "Arguments", "Env Variables", "Tools", ""};
444+
private final String[] COLUMN_NAMES = {"Enabled", "Name", "Transport Type", "Connection Info", "Env Variables", "Tools", ""};
445445
@Getter
446446
private List<MCPServer> mcpServers = new ArrayList<>();
447447

@@ -497,14 +497,23 @@ public Object getValueAt(int rowIndex, int columnIndex) {
497497
return switch (columnIndex) {
498498
case 0 -> server.isEnabled();
499499
case 1 -> server.getName();
500-
case 2 -> server.getCommand();
501-
case 3 -> server.getArgs() != null ? String.join(" ", server.getArgs()) : "";
500+
case 2 -> server.getTransportType();
501+
case 3 -> getConnectionInfo(server);
502502
case 4 -> server.getEnv() != null ? server.getEnv().size() : 0;
503503
case 5 -> getToolsSummary(server);
504504
case 6 -> "View";
505505
default -> null;
506506
};
507507
}
508+
509+
private String getConnectionInfo(MCPServer server) {
510+
if (server.getTransportType() == MCPServer.TransportType.HTTP_SSE) {
511+
return server.getSseUrl();
512+
} else {
513+
// STDIO transport
514+
return server.getCommand();
515+
}
516+
}
508517

509518
private String getToolsSummary(MCPServer server) {
510519
if (server.getAvailableTools() == null || server.getAvailableTools().isEmpty()) {
@@ -517,7 +526,8 @@ private String getToolsSummary(MCPServer server) {
517526
public Class<?> getColumnClass(int columnIndex) {
518527
return switch (columnIndex) {
519528
case 0 -> Boolean.class;
520-
case 1, 2, 3, 5, 6 -> String.class;
529+
case 1, 3, 5, 6 -> String.class;
530+
case 2 -> MCPServer.TransportType.class;
521531
case 4 -> Integer.class;
522532
default -> Object.class;
523533
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.devoxx.genie.ui.settings.mcp.dialog;
2+
3+
import javax.swing.*;
4+
import java.awt.*;
5+
import java.io.PrintWriter;
6+
import java.io.StringWriter;
7+
8+
/**
9+
* Utility class for displaying error dialogs
10+
*/
11+
public class ErrorDialogUtil {
12+
13+
/**
14+
* Shows an error dialog with detailed information
15+
*
16+
* @param parent The parent component
17+
* @param title Dialog title
18+
* @param message Error message
19+
* @param exception The exception that occurred
20+
*/
21+
public static void showErrorDialog(Component parent, String title, String message, Exception exception) {
22+
// Create a text area for the stack trace
23+
JTextArea textArea = new JTextArea(15, 50);
24+
textArea.setEditable(false);
25+
26+
// Prepare detailed error message
27+
StringBuilder detailedMessage = new StringBuilder();
28+
detailedMessage.append(message).append("\n\n");
29+
30+
// Add stack trace if exception is provided
31+
if (exception != null) {
32+
// Root cause message
33+
Throwable cause = exception;
34+
while (cause.getCause() != null) {
35+
cause = cause.getCause();
36+
}
37+
detailedMessage.append("Root cause: ").append(cause.getMessage()).append("\n\n");
38+
39+
// Full stack trace for debugging
40+
StringWriter sw = new StringWriter();
41+
PrintWriter pw = new PrintWriter(sw);
42+
exception.printStackTrace(pw);
43+
detailedMessage.append("Stack trace:\n").append(sw);
44+
}
45+
46+
textArea.setText(detailedMessage.toString());
47+
JScrollPane scrollPane = new JScrollPane(textArea);
48+
49+
// Show the error dialog
50+
JOptionPane.showMessageDialog(
51+
parent,
52+
scrollPane,
53+
title,
54+
JOptionPane.ERROR_MESSAGE
55+
);
56+
}
57+
}

0 commit comments

Comments
 (0)