mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-29 07:20:35 +00:00
Add call API mcp endpoint
This commit is contained in:
@@ -73,6 +73,7 @@ public class AppMcpServer {
|
||||
mutationTools.add(McpTools.runCommand());
|
||||
mutationTools.add(McpTools.runScript());
|
||||
mutationTools.add(McpTools.toggleState());
|
||||
readOnlyTools.add(McpTools.callApi());
|
||||
|
||||
for (McpServerFeatures.SyncToolSpecification readOnlyTool : readOnlyTools) {
|
||||
syncServer.addTool(readOnlyTool);
|
||||
|
||||
@@ -33,13 +33,13 @@ public interface McpToolHandler
|
||||
} catch (BeaconClientException e) {
|
||||
ErrorEventFactory.fromThrowable(e).expected().omit().handle();
|
||||
return McpSchema.CallToolResult.builder()
|
||||
.addTextContent(e.getMessage())
|
||||
.addTextContent(e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName())
|
||||
.isError(true)
|
||||
.build();
|
||||
} catch (Throwable e) {
|
||||
ErrorEventFactory.fromThrowable(e).omit().handle();
|
||||
return McpSchema.CallToolResult.builder()
|
||||
.addTextContent(e.getMessage())
|
||||
.addTextContent(e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName())
|
||||
.isError(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.xpipe.app.core.AppExtensionManager;
|
||||
import io.xpipe.app.core.AppNames;
|
||||
import io.xpipe.app.ext.*;
|
||||
import io.xpipe.app.hub.comp.StoreViewState;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.process.ScriptHelper;
|
||||
import io.xpipe.app.process.ShellControl;
|
||||
import io.xpipe.app.process.TerminalInitScriptConfig;
|
||||
@@ -13,7 +14,9 @@ import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStorageQuery;
|
||||
import io.xpipe.app.terminal.TerminalLaunch;
|
||||
import io.xpipe.app.util.CommandDialog;
|
||||
import io.xpipe.app.util.HttpHelper;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconInterface;
|
||||
import io.xpipe.core.FilePath;
|
||||
import io.xpipe.core.JacksonMapper;
|
||||
|
||||
@@ -27,6 +30,9 @@ import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -74,6 +80,44 @@ public final class McpTools {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static McpServerFeatures.SyncToolSpecification callApi() throws IOException {
|
||||
var tool = McpSchemaFiles.loadTool("call_api.json");
|
||||
return McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
.callHandler(McpToolHandler.of((req) -> {
|
||||
var path = req.getStringArgument("path");
|
||||
var payload = req.getRawRequest().arguments().get("payload");
|
||||
var payloadJson = JacksonMapper.getDefault().valueToTree(payload);
|
||||
|
||||
if (!AppPrefs.get().enableHttpApi().get()) {
|
||||
throw new BeaconClientException("HTTP API is not enabled");
|
||||
}
|
||||
|
||||
var i = BeaconInterface.byPath(path);
|
||||
if (i.isEmpty()) {
|
||||
throw new BeaconClientException("No API endpoint found for path " + path);
|
||||
}
|
||||
|
||||
var httpReq = HttpRequest.newBuilder().uri(URI.create("http://localhost:" + AppBeaconServer.get().getPort() + path))
|
||||
.header("Authorization", "Bearer " + AppPrefs.get().apiKey().get())
|
||||
.POST(HttpRequest.BodyPublishers.ofString(payloadJson.toPrettyString())).build();
|
||||
var httpRes = HttpHelper.client().send(httpReq, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
var resJson = JacksonMapper.getDefault().readTree(httpRes.body());
|
||||
if (httpRes.statusCode() >= 400) {
|
||||
return McpSchema.CallToolResult.builder()
|
||||
.addTextContent(resJson.toPrettyString())
|
||||
.isError(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
return McpSchema.CallToolResult.builder()
|
||||
.addTextContent(resJson.toPrettyString())
|
||||
.build();
|
||||
}))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "call_api",
|
||||
"description": "Calls the XPipe HTTP API at a specified endpoint with a json payload. The OpenAPI schema file is available at https://docs.xpipe.io/openapi.yaml",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "The API endpoint to call, e.g. /connection/add"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"description": "The json payload for the API endpoint according to the schema"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"payload"
|
||||
]
|
||||
},
|
||||
"annotations": {
|
||||
"readOnlyHint": false,
|
||||
"destructiveHint": true,
|
||||
"openWorldHint": false
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,6 @@ public class CategoryInfoExchange extends BeaconInterface<CategoryInfoExchange.R
|
||||
@NonNull
|
||||
UUID category;
|
||||
|
||||
@NonNull
|
||||
UUID parentCategory;
|
||||
|
||||
@NonNull
|
||||
|
||||
Reference in New Issue
Block a user