Skip to content

feat: Load multiple sse servers on runtime #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.modelcontextprotocol.client;

public class McpServerInstance {

protected String name;

public McpServerInstance() {
}

public McpServerInstance(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.modelcontextprotocol.client;

public class SseServerInstance extends McpServerInstance {

private String url;

public SseServerInstance() {
super();
}

public SseServerInstance(String name, String url) {
super(name);
this.url = url;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.modelcontextprotocol.client;

import java.util.List;
import java.util.Map;

public class StdioServerInstance extends McpServerInstance {

private String command;

private List<String> args;

private Map<String, String> env;

public StdioServerInstance() {
super();
}

public StdioServerInstance(String name, String command, List<String> args, Map<String, String> env) {
super(name);
this.command = command;
this.args = args;
this.env = env;
}

public String getCommand() {
return command;
}

public void setCommand(String command) {
this.command = command;
}

public List<String> getArgs() {
return args;
}

public Map<String, String> getEnv() {
return env;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.modelcontextprotocol.client.transport;

import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import io.modelcontextprotocol.client.SseServerInstance;
import io.modelcontextprotocol.client.StdioServerInstance;
import io.modelcontextprotocol.util.Assert;


public class McpServerLoader {

public CompletableFuture<List<McpSyncClient>> initServersAsync(List<SseServerInstance> sseServerInstances, List<StdioServerInstance> stdioServerInstances) {
if (sseServerInstances.isEmpty() && stdioServerInstances.isEmpty()) {
throw new IllegalArgumentException("No servers found to initialize.");
}

List<CompletableFuture<McpSyncClient>> futures = new ArrayList<>();

// Initialize SSE servers asynchronously
for (SseServerInstance server : sseServerInstances) {
Assert.notNull(server.getUrl(), "URL is required");
CompletableFuture<McpSyncClient> future = CompletableFuture.supplyAsync(() -> {
String serverUrl = server.getUrl();
if (serverUrl == null || serverUrl.isEmpty()) {
System.out.println("Skipping " + server.getName() + ": URL not available");
return null;
}

try {
System.out.println("\nInitializing connection to: " + server.getName());
HttpClientSseClientTransport newTransport = new HttpClientSseClientTransport(serverUrl);
McpSyncClient newClient = McpClient.sync(newTransport).build();
newClient.initialize();
System.out.println("✅ Successfully connected to " + server.getName());
return newClient;
} catch (Exception e) {
System.out.println("✗ Failed to connect to SSE server " + server.getName() + ": " + e.getMessage());
return null;
}
});
futures.add(future);
}

// Initialize STDIO servers asynchronously
for (StdioServerInstance server : stdioServerInstances) {
Assert.notNull(server.getCommand(), "Command is required");
Assert.notNull(server.getArgs(), "Args is required");
Assert.notNull(server.getEnv(), "Env is required");
CompletableFuture<McpSyncClient> future = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("\nInitializing STDIO connection for: " + server.getName());
ServerParameters params = ServerParameters.builder(server.getCommand())
.args(server.getArgs())
.env(server.getEnv())
.build();
StdioClientTransport transport = new StdioClientTransport(params);
McpSyncClient newStdioClient = McpClient.sync(transport).build();
newStdioClient.initialize();
System.out.println("✅ Successfully connected to STDIO server " + server.getName());
return newStdioClient;
} catch (Exception e) {
System.out.println("✗ Failed to connect to STDIO server " + server.getName() + ": " + e.getMessage());
return null;
}
});
futures.add(future);
}

// Combine all futures and filter out failed connections (nulls)
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(CopyOnWriteArrayList::new)));
}

public List<McpSyncClient> initServers(List<SseServerInstance> sseServerInstances, List<StdioServerInstance> stdioServerInstances) {
return initServersAsync(sseServerInstances, stdioServerInstances).join();
}
}