mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-03 11:20:34 +00:00
Rework
This commit is contained in:
@@ -13,13 +13,13 @@ import io.modelcontextprotocol.server.McpTransportContextExtractor;
|
||||
import io.modelcontextprotocol.spec.*;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import io.modelcontextprotocol.util.KeepAliveScheduler;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
@@ -74,7 +74,7 @@ public class HttpStreamableServerTransportProvider implements McpStreamableServe
|
||||
*/
|
||||
private final ConcurrentHashMap<String, McpStreamableServerSession> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
private McpTransportContextExtractor<HttpExchange> contextExtractor;
|
||||
private final McpTransportContextExtractor<HttpExchange> contextExtractor;
|
||||
|
||||
/**
|
||||
* Flag indicating if the transport is shutting down.
|
||||
@@ -261,13 +261,15 @@ public class HttpStreamableServerTransportProvider implements McpStreamableServe
|
||||
}
|
||||
}
|
||||
|
||||
private void sendError(HttpExchange exchange, int code, String message) throws IOException {
|
||||
public void sendError(HttpExchange exchange, int code, String message) throws IOException {
|
||||
var b = message != null ? message.getBytes(StandardCharsets.UTF_8) : new byte[0];
|
||||
exchange.getResponseHeaders().add("Content-Encoding", UTF_8);
|
||||
exchange.sendResponseHeaders(code, b.length != 0 ? b.length : -1);
|
||||
try (OutputStream os = exchange.getResponseBody()) {
|
||||
os.write(b);
|
||||
}
|
||||
|
||||
TrackEvent.error("MCP server error: " + message);
|
||||
}
|
||||
|
||||
public void doPost(HttpExchange exchange)
|
||||
|
||||
@@ -5,11 +5,16 @@ import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import io.modelcontextprotocol.server.McpSyncServer;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.xpipe.app.core.AppOpenArguments;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreCategory;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.StorageListener;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.Value;
|
||||
|
||||
@@ -90,13 +95,64 @@ public class McpServer {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
try (exchange) {
|
||||
if (AppPrefs.get() == null) {
|
||||
transportProvider.sendError(exchange, 503, "Not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AppPrefs.get().enableMcpServer().get()) {
|
||||
transportProvider.sendError(exchange, 403, "MCP server is not enabled in the API settings menu");
|
||||
if (exchange.getRequestMethod().equals("POST")) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
ErrorEventFactory.fromMessage(
|
||||
"An external request was made to the XPipe MCP server, however the MCP server is not enabled in the API settings menu")
|
||||
.expected()
|
||||
.handle();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AppPrefs.get().disableApiAuthentication().get()) {
|
||||
var apiKey = exchange.getRequestHeaders().getFirst("Authorization");
|
||||
if (apiKey == null) {
|
||||
transportProvider.sendError(exchange, 403, "Header Authorization is not set");
|
||||
if (exchange.getRequestMethod().equals("POST")) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
ErrorEventFactory.fromMessage(
|
||||
"An external request was made to the XPipe MCP server without the header Authorization set. Please configure your MCP client with the Bearer API token you can find the API settings menu")
|
||||
.expected()
|
||||
.handle();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var correct = apiKey.replace("Bearer ", "").equals(AppPrefs.get().apiKey().get());
|
||||
if (!correct) {
|
||||
transportProvider.sendError(exchange, 403, "Invalid API key");
|
||||
if (exchange.getRequestMethod().equals("POST")) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
ErrorEventFactory.fromMessage("The Authorization header sent by the MCP client is not correct")
|
||||
.expected()
|
||||
.handle();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (exchange.getRequestMethod().equals("GET")) {
|
||||
transportProvider.doGet(exchange);
|
||||
} else if (exchange.getRequestMethod().equals("POST")) {
|
||||
transportProvider.doPost(exchange);
|
||||
} else if (exchange.getRequestMethod().equals("DELETE")) {
|
||||
transportProvider.doDelete(exchange);
|
||||
} else {
|
||||
transportProvider.doOther(exchange);
|
||||
}
|
||||
} finally {
|
||||
exchange.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,6 +60,8 @@ public class AppPrefs {
|
||||
.valueClass(Boolean.class)
|
||||
.requiresRestart(true)
|
||||
.build());
|
||||
final BooleanProperty enableMcpServer =
|
||||
mapVaultShared(new SimpleBooleanProperty(false), "enableMcpServer", Boolean.class, false);
|
||||
final BooleanProperty enableHttpApi =
|
||||
mapVaultShared(new SimpleBooleanProperty(false), "enableHttpApi", Boolean.class, false);
|
||||
final BooleanProperty dontAutomaticallyStartVmSshServer =
|
||||
@@ -305,6 +307,10 @@ public class AppPrefs {
|
||||
return enableHttpApi;
|
||||
}
|
||||
|
||||
public ObservableBooleanValue enableMcpServer() {
|
||||
return enableMcpServer;
|
||||
}
|
||||
|
||||
public ObservableBooleanValue pinLocalMachineOnStartup() {
|
||||
return pinLocalMachineOnStartup;
|
||||
}
|
||||
|
||||
@@ -32,10 +32,13 @@ public class HttpApiCategory extends AppPrefsCategory {
|
||||
.addComp(new ButtonComp(AppI18n.observable("openApiDocsButton"), () -> {
|
||||
DocumentationLink.API.open();
|
||||
}))
|
||||
.pref(prefs.enableMcpServer)
|
||||
.addToggle(prefs.enableMcpServer)
|
||||
.pref(prefs.apiKey)
|
||||
.addComp(new TextFieldComp(prefs.apiKey).maxWidth(getCompWidth()), prefs.apiKey)
|
||||
.pref(prefs.disableApiAuthentication)
|
||||
.addToggle(prefs.disableApiAuthentication))
|
||||
.addToggle(prefs.disableApiAuthentication)
|
||||
)
|
||||
.buildComp();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user