This is an automated email from the ASF dual-hosted git repository.
yaozhq pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/geaflow.git
The following commit(s) were added to refs/heads/master by this push:
new 017893ce [MCP] Implement MCP local tools for demo case (#610)
017893ce is described below
commit 017893ce838caac25c368fb905d9137bbd80bb90
Author: Leomrlin <[email protected]>
AuthorDate: Wed Sep 10 19:27:50 2025 +0800
[MCP] Implement MCP local tools for demo case (#610)
* add local graph mcp server
* add type query tool
* update mcp version
* fix license
* fix pom
* fix pom
* refine code
* minor fix
* fix comments
* fix codestyle
* fix tools prompt
* minor fix
* refine code
* fix comment
---
geaflow-mcp/pom.xml | 71 ++++++-
.../geaflow/mcp/server/GeaFlowMcpActions.java | 33 +++
.../mcp/server/GeaFlowMcpActionsLocalImpl.java | 221 +++++++++++++++++++++
.../geaflow/mcp/server/GeaFlowMcpServer.java | 2 +
.../geaflow/mcp/server/GeaFlowMcpServerTools.java | 98 ++++++++-
.../org/apache/geaflow/mcp/server/ToolDesc.java | 64 ++++++
.../geaflow/mcp/server/util/McpConstants.java | 34 ++++
.../geaflow/mcp/server/util/McpLocalFileUtil.java | 66 ++++++
.../geaflow/mcp/server/util/QueryFormatUtil.java | 98 +++++++++
.../geaflow/mcp/server/util/QueryLocalRunner.java | 142 +++++++++++++
.../geaflow/mcp/server/GeaFlowMcpClientTest.java | 127 +++++++++++-
.../geaflow/mcp/server/util/YamlParserTest.java | 9 +-
geaflow-mcp/src/test/resources/gql/insert1 | 22 ++
geaflow-mcp/src/test/resources/gql/insert1_error | 22 ++
geaflow-mcp/src/test/resources/gql/licenseHead | 19 ++
geaflow-mcp/src/test/resources/gql/modern | 43 ++++
geaflow-mcp/src/test/resources/gql/modern_error | 43 ++++
geaflow-mcp/src/test/resources/gql/query1 | 28 +++
.../apache/geaflow/dsl/runtime/QueryContext.java | 4 +
.../dsl/runtime/command/CreateGraphCommand.java | 1 +
20 files changed, 1133 insertions(+), 14 deletions(-)
diff --git a/geaflow-mcp/pom.xml b/geaflow-mcp/pom.xml
index 45bc101d..95020ac3 100644
--- a/geaflow-mcp/pom.xml
+++ b/geaflow-mcp/pom.xml
@@ -31,7 +31,7 @@
<artifactId>geaflow-mcp</artifactId>
<properties>
- <solon-mcp.version>3.3.3-M1</solon-mcp.version>
+ <solon-mcp.version>3.5.1</solon-mcp.version>
<junit.version>5.10.1</junit.version>
<slf4j.version>1.7.15</slf4j.version>
<log4j.version>1.2.17</log4j.version>
@@ -120,6 +120,75 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-cluster</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-cluster</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-on-local</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-assembly</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-pipeline</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-dsl-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-dsl-plan</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-dsl-connector-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-dsl-connector-console</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-dsl-connector-random</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geaflow</groupId>
+ <artifactId>geaflow-dsl-connector-file</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
</dependencies>
<repositories>
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpActions.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpActions.java
new file mode 100644
index 00000000..4c66fedb
--- /dev/null
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpActions.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.mcp.server;
+
+public interface GeaFlowMcpActions {
+
+ String createGraph(String graphName, String ddl);
+
+ String queryGraph(String graphName, String gql);
+
+ String queryType(String graphName, String type);
+
+ String getGraphSchema(String graphName);
+
+ void withUser(String user);
+}
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpActionsLocalImpl.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpActionsLocalImpl.java
new file mode 100644
index 00000000..eefb2e3c
--- /dev/null
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpActionsLocalImpl.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.mcp.server;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.geaflow.cluster.local.client.LocalEnvironment;
+import org.apache.geaflow.common.exception.GeaflowRuntimeException;
+import org.apache.geaflow.dsl.common.types.TableField;
+import org.apache.geaflow.dsl.schema.GeaFlowGraph;
+import org.apache.geaflow.dsl.schema.GeaFlowTable;
+import org.apache.geaflow.env.IEnvironment;
+import org.apache.geaflow.mcp.server.util.McpLocalFileUtil;
+import org.apache.geaflow.mcp.server.util.QueryFormatUtil;
+import org.apache.geaflow.mcp.server.util.QueryLocalRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GeaFlowMcpActionsLocalImpl implements GeaFlowMcpActions {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(GeaFlowMcpActionsLocalImpl.class);
+
+ private Map<String, Object> configs;
+ private String user;
+ private IEnvironment localEnv = new LocalEnvironment();
+
+ public GeaFlowMcpActionsLocalImpl(Map<String, Object> configs) {
+ this.configs = configs;
+ }
+
+ @Override
+ public String createGraph(String graphName, String ddl) {
+ QueryLocalRunner runner = new QueryLocalRunner();
+ runner.withGraphName(graphName).withGraphDefine(ddl);
+ GeaFlowGraph graph;
+ try {
+ graph = runner.compileGraph();
+ } catch (Throwable e) {
+ LOGGER.error("Compile error: " + e.getCause().getMessage());
+ throw new GeaflowRuntimeException("Compile error: " +
e.getCause().getMessage());
+ }
+ if (graph == null) {
+ LOGGER.error("Cannot create graph: " + graphName);
+ throw new GeaflowRuntimeException("Cannot create graph: " +
graphName);
+ }
+ //Store graph ddl to schema
+ try {
+ McpLocalFileUtil.createAndWriteFile(
+ QueryLocalRunner.DSL_STATE_REMOTE_SCHEM_PATH, ddl,
graphName);
+ } catch (Throwable e) {
+ return runner.getErrorMsg();
+ }
+ return "Create graph " + graphName + " success.";
+ }
+
+ @Override
+ public String queryGraph(String graphName, String dml) {
+ QueryLocalRunner compileRunner = new QueryLocalRunner();
+ String ddl = null;
+ try {
+ ddl =
McpLocalFileUtil.readFile(QueryLocalRunner.DSL_STATE_REMOTE_SCHEM_PATH,
graphName);
+ } catch (Throwable e) {
+ LOGGER.error("Cannot get graph schema for: " + graphName);
+ throw new GeaflowRuntimeException("Cannot get graph schema for: "
+ graphName);
+ }
+ compileRunner.withGraphName(graphName).withGraphDefine(ddl);
+ GeaFlowGraph graph;
+ try {
+ graph = compileRunner.compileGraph();
+ } catch (Throwable e) {
+ LOGGER.error("Compile error: " + compileRunner.getErrorMsg());
+ throw new GeaflowRuntimeException("Compile error: " +
compileRunner.getErrorMsg());
+ }
+ if (graph == null) {
+ LOGGER.error("Cannot create graph: " + graphName);
+ throw new GeaflowRuntimeException("Cannot create graph: " +
graphName);
+ }
+
+ QueryLocalRunner runner = new QueryLocalRunner();
+ runner.withGraphDefine(ddl);
+ runner.withQuery(ddl + "\n" + QueryFormatUtil.makeUseGraph(graphName)
+ dml);
+ try {
+ runner.execute();
+ } catch (Throwable e) {
+ LOGGER.error("Run query error: " + e.getCause().getMessage());
+ throw new GeaflowRuntimeException("Run query error: " +
e.getCause().getMessage());
+ }
+ return "run query success: " + dml;
+ }
+
+ @Override
+ public String queryType(String graphName, String type) {
+ QueryLocalRunner compileRunner = new QueryLocalRunner();
+ String ddl = null;
+ try {
+ ddl =
McpLocalFileUtil.readFile(QueryLocalRunner.DSL_STATE_REMOTE_SCHEM_PATH,
graphName);
+ } catch (Throwable e) {
+ LOGGER.error("Cannot get graph schema for: " + graphName);
+ return "Cannot get graph schema for: " + graphName;
+ }
+ compileRunner.withGraphName(graphName).withGraphDefine(ddl);
+ GeaFlowGraph graph;
+ try {
+ graph = compileRunner.compileGraph();
+ } catch (Throwable e) {
+ return compileRunner.getErrorMsg();
+ }
+ if (graph == null) {
+ LOGGER.error("Cannot create graph: " + graphName);
+ throw new GeaflowRuntimeException("Cannot create graph: " +
graphName);
+ }
+
+ QueryLocalRunner runner = new QueryLocalRunner();
+ runner.withGraphDefine(ddl);
+ String dql = null;
+ String dirName = "query_result_" + Instant.now().toEpochMilli();
+ String resultPath = QueryLocalRunner.DSL_STATE_REMOTE_PATH + "/" +
dirName;
+ GeaFlowTable resultTable = null;
+ for (GeaFlowGraph.VertexTable vertexTable : graph.getVertexTables()) {
+ if (vertexTable.getTypeName().equals(type)) {
+ dql = QueryFormatUtil.makeResultTable(vertexTable, resultPath)
+ + "\n" +
QueryFormatUtil.makeEntityTableQuery(vertexTable);
+ resultTable = vertexTable;
+ }
+ }
+ for (GeaFlowGraph.EdgeTable edgeTable : graph.getEdgeTables()) {
+ if (edgeTable.getTypeName().equals(type)) {
+ dql = QueryFormatUtil.makeResultTable(edgeTable, resultPath)
+ + "\n" +
QueryFormatUtil.makeEntityTableQuery(edgeTable);
+ resultTable = edgeTable;
+ }
+ }
+ if (resultTable == null) {
+ LOGGER.error("Cannot find type: " + type + " in graph: " +
graphName);
+ throw new GeaflowRuntimeException("Cannot find type: " + type + "
in graph: " + graphName);
+ }
+ runner.withQuery(ddl + "\n" + QueryFormatUtil.makeUseGraph(graphName)
+ dql);
+ String resultContent = "null";
+ try {
+ runner.execute();
+ resultContent = readFile(resultPath);
+ } catch (Throwable e) {
+ return runner.getErrorMsg();
+ }
+ String schemaContent = "type: " + type + "\nschema: " +
resultTable.getFields().stream()
+ .map(TableField::getName).collect(Collectors.joining("|"));
+ return schemaContent + "\n" + resultContent;
+ }
+
+ @Override
+ public String getGraphSchema(String graphName) {
+ String ddl = null;
+ try {
+ ddl =
McpLocalFileUtil.readFile(QueryLocalRunner.DSL_STATE_REMOTE_SCHEM_PATH,
graphName);
+ } catch (Throwable e) {
+ LOGGER.error("Cannot get graph schema for: " + graphName);
+ return "Cannot get graph schema for: " + graphName;
+ }
+ return ddl;
+ }
+
+ @Override
+ public void withUser(String user) {
+ this.user = user;
+ }
+
+ private String readFile(String path) throws IOException {
+ File file = new File(path);
+ if (file.isHidden()) {
+ return "";
+ }
+ if (file.isFile()) {
+ return IOUtils.toString(new File(path).toURI(),
Charset.defaultCharset()).trim();
+ }
+ File[] files = file.listFiles();
+ StringBuilder content = new StringBuilder();
+ List<String> readTextList = new ArrayList<>();
+ if (files != null) {
+ for (File subFile : files) {
+ String readText = readFile(subFile.getAbsolutePath());
+ if (StringUtils.isBlank(readText)) {
+ continue;
+ }
+ readTextList.add(readText);
+ }
+ }
+ readTextList =
readTextList.stream().sorted().collect(Collectors.toList());
+ for (String readText : readTextList) {
+ if (content.length() > 0) {
+ content.append("\n");
+ }
+ content.append(readText);
+ }
+ return content.toString().trim();
+ }
+}
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServer.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServer.java
index 8377ec3b..d580c9f0 100644
---
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServer.java
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServer.java
@@ -31,6 +31,7 @@ import org.noear.solon.annotation.Controller;
@Controller
public class GeaFlowMcpServer {
private static final String SERVER_NAME = "geaflow-mcp-server";
+ private static final String SSE_CHANNEL = "sse";
private static final String SSE_ENDPOINT = "/geaflow/sse";
public static void main(String[] args) {
@@ -38,6 +39,7 @@ public class GeaFlowMcpServer {
// Manually build the mcp service endpoint.
McpServerEndpointProvider endpointProvider =
McpServerEndpointProvider.builder()
.name(SERVER_NAME)
+ .channel(SSE_CHANNEL)
.sseEndpoint(SSE_ENDPOINT)
.build();
endpointProvider.addTool(new MethodToolProvider(new
GeaFlowMcpServerTools()));
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServerTools.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServerTools.java
index 155ea9b4..e19625d9 100644
---
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServerTools.java
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/GeaFlowMcpServerTools.java
@@ -26,6 +26,7 @@ import
org.apache.geaflow.analytics.service.client.AnalyticsClient;
import org.apache.geaflow.analytics.service.client.AnalyticsClientBuilder;
import org.apache.geaflow.analytics.service.query.QueryResults;
import org.apache.geaflow.common.config.Configuration;
+import org.apache.geaflow.mcp.server.util.McpConstants;
import org.apache.geaflow.mcp.util.YamlParser;
import org.noear.solon.ai.annotation.ResourceMapping;
import org.noear.solon.ai.annotation.ToolMapping;
@@ -34,7 +35,7 @@ import org.noear.solon.annotation.Param;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@McpServerEndpoint(name = "geaflow-mcp-server", sseEndpoint = "/geaflow/sse")
+@McpServerEndpoint(name = "geaflow-mcp-server", channel = "sse", sseEndpoint =
"/geaflow/sse")
public class GeaFlowMcpServerTools {
private static final Logger LOGGER =
LoggerFactory.getLogger(GeaFlowMcpServerTools.class);
@@ -65,7 +66,6 @@ public class GeaFlowMcpServerTools {
* @param query GQL query.
* @return query result or error code.
*/
- @ToolMapping(description = "execute query")
public String executeQuery(@Param(name = "query", description = "query")
String query) {
AnalyticsClient analyticsClient = null;
@@ -114,4 +114,98 @@ public class GeaFlowMcpServerTools {
}
}
}
+
+
+ /**
+ * A tool that provides create graph capabilities.
+ *
+ * @param graphName graph name to create.
+ * @param ddl Create graph ddl.
+ * @return execute result or error message.
+ */
+ @ToolMapping(description = ToolDesc.createGraph)
+ public String createGraph(@Param(name = McpConstants.GRAPH_NAME,
description = "create graph name") String graphName,
+ @Param(name = McpConstants.DDL, description =
"create graph ddl") String ddl) {
+ try {
+ Map<String, Object> config = YamlParser.loadConfig();
+ GeaFlowMcpActions mcpActions = new
GeaFlowMcpActionsLocalImpl(config);
+ if (config.containsKey(SERVER_USER)) {
+ mcpActions.withUser(config.get(SERVER_USER).toString());
+ }
+ return mcpActions.createGraph(graphName, ddl);
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ return e.getMessage();
+ }
+ }
+
+ /**
+ * A tool that get graph schema.
+ *
+ * @param graphName graphName to get.
+ * @return execute result or error message.
+ */
+ @ToolMapping(description = ToolDesc.getGraphSchema)
+ public String getGraphSchema(@Param(name = McpConstants.GRAPH_NAME,
description = "get graph schema name") String graphName) {
+ try {
+ Map<String, Object> config = YamlParser.loadConfig();
+ GeaFlowMcpActions mcpActions = new
GeaFlowMcpActionsLocalImpl(config);
+ if (config.containsKey(SERVER_USER)) {
+ mcpActions.withUser(config.get(SERVER_USER).toString());
+ }
+ return mcpActions.getGraphSchema(graphName);
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ return e.getMessage();
+ }
+ }
+
+
+ /**
+ * A tool that provides insert data into graph capabilities.
+ *
+ * @param graphName graph name to operate.
+ * @param dml dml to run with graph.
+ * @return execute result or error message.
+ */
+ @ToolMapping(description = ToolDesc.insertGraph)
+ public String insertGraph(@Param(name = McpConstants.GRAPH_NAME,
description = "graph name") String graphName,
+ @Param(name = McpConstants.DML, description =
"dml insert values into graph") String dml) {
+ try {
+ Map<String, Object> config = YamlParser.loadConfig();
+ GeaFlowMcpActions mcpActions = new
GeaFlowMcpActionsLocalImpl(config);
+ if (config.containsKey(SERVER_USER)) {
+ mcpActions.withUser(config.get(SERVER_USER).toString());
+ }
+ return mcpActions.queryGraph(graphName, dml);
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ return e.getMessage();
+ }
+ }
+
+
+ /**
+ * A tool that provides graph query capabilities.
+ *
+ * @param graphName graph name to query.
+ * @param type query graph entity type.
+ * @return execute result or error message.
+ */
+ @ToolMapping(description = ToolDesc.queryType)
+ public String queryType(@Param(name = McpConstants.GRAPH_NAME, description
= "query graph name") String graphName,
+ @Param(name = McpConstants.TYPE, description =
"query graph vertex or edge type name") String type) {
+ try {
+ Map<String, Object> config = YamlParser.loadConfig();
+ GeaFlowMcpActions mcpActions = new
GeaFlowMcpActionsLocalImpl(config);
+ if (config.containsKey(SERVER_USER)) {
+ mcpActions.withUser(config.get(SERVER_USER).toString());
+ }
+ return mcpActions.queryType(graphName, type);
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ return e.getMessage();
+ }
+ }
+
}
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/ToolDesc.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/ToolDesc.java
new file mode 100644
index 00000000..41bcd896
--- /dev/null
+++ b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/ToolDesc.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.mcp.server;
+
+public class ToolDesc {
+
+ public static final String createGraph = "create graph with ddl, Set the
storeType to rocksdb, "
+ + "ensuring the syntax is correct and do not use any syntax not
present in the examples. "
+ + "DDL statements must end with a semicolon. "
+ + "example: CREATE GRAPH `modern` (\n"
+ + "\tVertex `person` (\n"
+ + "\t `id` bigint ID,\n"
+ + "\t `name` varchar,\n"
+ + "\t `age` int\n"
+ + "\t),\n"
+ + "\tVertex `software` (\n"
+ + "\t `id` bigint ID,\n"
+ + "\t `name` varchar,\n"
+ + "\t `lang` varchar\n"
+ + "\t),\n"
+ + "\tEdge `knows` (\n"
+ + "\t `srcId` bigint SOURCE ID,\n"
+ + "\t `targetId` bigint DESTINATION ID,\n"
+ + "\t `weight` double\n"
+ + "\t),\n"
+ + "\tEdge `created` (\n"
+ + "\t `srcId` bigint SOURCE ID,\n"
+ + " \t`targetId` bigint DESTINATION ID,\n"
+ + " \t`weight` double\n"
+ + "\t)\n"
+ + ") WITH (\n"
+ + "\tstoreType='rocksdb'\n"
+ + ");";
+
+ public static final String insertGraph = "Insert into graph with dml. "
+ + "A single call can only insert data into one vertex or edge
type, and can only use the VALUES syntax. "
+ + "Do not use any syntax not present in the examples. "
+ + "example: INSERT INTO `modern`.`person`(`id`, `name`, `age`)\n"
+ + "VALUES (1, 'jim', 20), (2, 'kate', 22)\n"
+ + ";";
+
+ public static final String queryType = "You need to provide the graph name
and the type name of the vertex or edge "
+ + "type to be queried. The query tool will return all data of this
type in the graph. A single call can only "
+ + "query data from one vertex or edge type, and only one type name
needs to be provided.";
+
+ public static final String getGraphSchema = "query graph schema.";
+}
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/McpConstants.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/McpConstants.java
new file mode 100644
index 00000000..1b00fd6c
--- /dev/null
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/McpConstants.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.mcp.server.util;
+
+public class McpConstants {
+
+ public static final String CREATE_GRAPH = "create_graph";
+ public static final String GRAPH_NAME = "graph_name";
+ public static final String DDL = "create_graph_ddl";
+ public static final String DQL = "query_graph_dql";
+ public static final String DML = "insert_graph_dml";
+ public static final String TYPE = "query_type_name";
+ public static final String CREATE_GRAPH_TOOL_NAME = "createGraph";
+ public static final String INSERT_GRAPH_TOOL_NAME = "insertGraph";
+ public static final String QUERY_GRAPH_TOOL_NAME = "queryGraph";
+ public static final String QUERY_TYPE_TOOL_NAME = "queryType";
+}
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/McpLocalFileUtil.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/McpLocalFileUtil.java
new file mode 100644
index 00000000..2852ea8b
--- /dev/null
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/McpLocalFileUtil.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.mcp.server.util;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+
+public class McpLocalFileUtil {
+
+
+ public static String createAndWriteFile(String root, String text,
String... fileNames) throws IOException {
+ Files.createDirectories(Paths.get(root));
+ String fileName = "execute_query_" + Instant.now().toEpochMilli();
+ if (fileNames != null && fileNames.length > 0) {
+ fileName = fileNames[0];
+ }
+
+ String fullPath = Paths.get(root, fileName).toString();
+
+ try (FileWriter writer = new FileWriter(fullPath)) {
+ if (text != null) {
+ writer.write(text);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return fileName;
+ }
+
+ public static String readFile(String root, String fileName) throws
IOException {
+ Path filePath = Paths.get(root, fileName);
+
+ if (!Files.exists(filePath)) {
+ throw new IOException("File not exist: " + filePath);
+ }
+
+ if (!Files.isRegularFile(filePath)) {
+ throw new IOException("Path is not file: " + filePath);
+ }
+
+ return new String(Files.readAllBytes(filePath),
StandardCharsets.UTF_8);
+ }
+}
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/QueryFormatUtil.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/QueryFormatUtil.java
new file mode 100644
index 00000000..08ce8217
--- /dev/null
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/QueryFormatUtil.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.mcp.server.util;
+
+import java.util.Locale;
+import org.apache.geaflow.dsl.common.types.TableField;
+import org.apache.geaflow.dsl.schema.GeaFlowGraph;
+import org.apache.geaflow.dsl.schema.GeaFlowTable;
+
+public class QueryFormatUtil {
+
+ public static String makeResultTable(GeaFlowTable table, String
resultPath) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CREATE TABLE output_table(\n");
+ int index = 0;
+ for (TableField field : table.getFields()) {
+ if (index > 0) {
+ builder.append(",\n");
+ }
+ builder.append("`").append(field.getName()).append("` ")
+ .append(tableTypeMapper(field.getType().getName()));
+ index++;
+ }
+ builder.append("\n) WITH ( \n");
+ builder.append("type='file',\n");
+
builder.append("geaflow.dsl.file.path='").append(resultPath).append("'\n");
+ builder.append(");\n");
+ return builder.toString();
+ }
+
+ public static String makeEntityTableQuery(GeaFlowTable table) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("INSERT INTO output_table\n");
+ if (table instanceof GeaFlowGraph.VertexTable) {
+ builder.append("Match(a:`")
+ .append(((GeaFlowGraph.VertexTable)table).getTypeName())
+ .append("`)\n");
+ } else {
+ builder.append("Match()-[a:`")
+ .append(((GeaFlowGraph.EdgeTable)table).getTypeName())
+ .append("`]-()\n");
+ }
+ builder.append("Return ");
+ int index = 0;
+ for (TableField field : table.getFields()) {
+ if (index > 0) {
+ builder.append(",\n");
+ }
+ builder.append("a.`").append(field.getName()).append("` ");
+ index++;
+ }
+ builder.append("\n;\n");
+ return builder.toString();
+ }
+
+ public static String makeUseGraph(String graphName) {
+ return "USE GRAPH " + graphName + ";\n";
+ }
+
+ private static String tableTypeMapper(String iType) {
+ String upper = iType.toUpperCase(Locale.ROOT);
+ switch (upper) {
+ case "STRING":
+ case "BINARY_STRING":
+ case "VARCHAR":
+ return "VARCHAR";
+ case "LONG":
+ case "INTEGER":
+ case "SHORT":
+ return "BIGINT";
+ case "FLOAT":
+ case "DOUBLE":
+ return "DOUBLE";
+ case "BOOL":
+ case "BOOLEAN":
+ return "BOOL";
+ default:
+ throw new RuntimeException("Cannt convert type name: " +
iType);
+ }
+ }
+}
diff --git
a/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/QueryLocalRunner.java
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/QueryLocalRunner.java
new file mode 100644
index 00000000..3f662a48
--- /dev/null
+++
b/geaflow-mcp/src/main/java/org/apache/geaflow/mcp/server/util/QueryLocalRunner.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.mcp.server.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.geaflow.cluster.system.ClusterMetaStore;
+import org.apache.geaflow.common.config.Configuration;
+import org.apache.geaflow.common.config.keys.DSLConfigKeys;
+import org.apache.geaflow.common.config.keys.FrameworkConfigKeys;
+import org.apache.geaflow.dsl.common.compile.CompileContext;
+import org.apache.geaflow.dsl.runtime.QueryClient;
+import org.apache.geaflow.dsl.runtime.QueryContext;
+import org.apache.geaflow.dsl.runtime.QueryEngine;
+import org.apache.geaflow.dsl.runtime.engine.GQLPipeLine;
+import org.apache.geaflow.dsl.runtime.engine.GeaFlowQueryEngine;
+import org.apache.geaflow.dsl.schema.GeaFlowGraph;
+import org.apache.geaflow.env.Environment;
+import org.apache.geaflow.env.EnvironmentFactory;
+import org.apache.geaflow.file.FileConfigKeys;
+import org.apache.geaflow.runtime.pipeline.PipelineContext;
+import org.apache.geaflow.runtime.pipeline.PipelineTaskType;
+import org.apache.geaflow.runtime.pipeline.task.PipelineTaskContext;
+
+public class QueryLocalRunner {
+
+
+ public static final String DSL_STATE_REMOTE_PATH = "/tmp/dsl/mcp";
+ public static final String DSL_STATE_REMOTE_SCHEM_PATH =
"/tmp/dsl/mcp/schema";
+ private String graphDefine;
+ private String graphName;
+ private String errorMsg;
+ private String query;
+ private final Map<String, String> config = new HashMap<>();
+
+ public QueryLocalRunner withConfig(Map<String, String> config) {
+ this.config.putAll(config);
+ return this;
+ }
+
+ public QueryLocalRunner withConfig(String key, Object value) {
+ this.config.put(key, String.valueOf(value));
+ return this;
+ }
+
+ public QueryLocalRunner withGraphDefine(String graphDefine) {
+ this.graphDefine = Objects.requireNonNull(graphDefine);
+ return this;
+ }
+
+ public QueryLocalRunner withGraphName(String graphName) {
+ this.graphName = Objects.requireNonNull(graphName);
+ return this;
+ }
+
+ public QueryLocalRunner withQuery(String query) {
+ this.query = Objects.requireNonNull(query);
+ return this;
+ }
+
+ public String getErrorMsg() {
+ return errorMsg;
+ }
+
+ public GeaFlowGraph compileGraph() throws Exception {
+ Map<String, String> config = new HashMap<>();
+ config.put(DSLConfigKeys.GEAFLOW_DSL_WINDOW_SIZE.getKey(),
String.valueOf(-1L));
+ if (this.graphDefine == null) {
+ throw new RuntimeException("Create graph ddl is empty");
+ }
+ config.put(FrameworkConfigKeys.SYSTEM_STATE_BACKEND_TYPE.getKey(),
"memory");
+ config.put(FileConfigKeys.ROOT.getKey(), DSL_STATE_REMOTE_PATH);
+ String fileName =
McpLocalFileUtil.createAndWriteFile(DSL_STATE_REMOTE_PATH, this.graphDefine);
+ config.put(DSLConfigKeys.GEAFLOW_DSL_QUERY_PATH.getKey(),
DSL_STATE_REMOTE_PATH + "/" + fileName);
+ config.put(DSLConfigKeys.GEAFLOW_DSL_QUERY_PATH_TYPE.getKey(), "file");
+ config.putAll(this.config);
+ Environment environment = EnvironmentFactory.onLocalEnvironment();
+ environment.getEnvironmentContext().withConfig(config);
+ // Compile graph name
+ CompileContext compileContext = new CompileContext();
+
config.put(DSLConfigKeys.GEAFLOW_DSL_COMPILE_PHYSICAL_PLAN_ENABLE.getKey(),
"false");
+ compileContext.setConfig(config);
+ PipelineContext pipelineContext = new
PipelineContext(PipelineTaskType.CompileTask.name(),
+ new Configuration(compileContext.getConfig()));
+ PipelineTaskContext pipelineTaskCxt = new PipelineTaskContext(0L,
pipelineContext);
+ QueryEngine engineContext = new GeaFlowQueryEngine(pipelineTaskCxt);
+ QueryContext queryContext = QueryContext.builder()
+ .setEngineContext(engineContext)
+ .setCompile(true)
+ .setTraversalParallelism(-1)
+ .build();
+ QueryClient queryClient = new QueryClient();
+ queryClient.executeQuery(this.graphDefine, queryContext);
+ return queryContext.getGraph(graphName);
+ }
+
+ public QueryLocalRunner execute() throws Exception {
+ Map<String, String> config = new HashMap<>();
+ config.put(DSLConfigKeys.GEAFLOW_DSL_WINDOW_SIZE.getKey(),
String.valueOf(-1L));
+ if (this.graphDefine == null) {
+ throw new RuntimeException("Create graph ddl is empty");
+ }
+ config.put(FrameworkConfigKeys.SYSTEM_STATE_BACKEND_TYPE.getKey(),
"memory");
+ config.put(FileConfigKeys.ROOT.getKey(), DSL_STATE_REMOTE_PATH);
+ String fileName =
McpLocalFileUtil.createAndWriteFile(DSL_STATE_REMOTE_PATH, this.query);
+ config.put(DSLConfigKeys.GEAFLOW_DSL_QUERY_PATH.getKey(),
DSL_STATE_REMOTE_PATH + "/" + fileName);
+ config.put(DSLConfigKeys.GEAFLOW_DSL_QUERY_PATH_TYPE.getKey(), "file");
+ config.put(FrameworkConfigKeys.BATCH_NUMBER_PER_CHECKPOINT.getKey(),
"1");
+ config.putAll(this.config);
+
+ Environment environment = EnvironmentFactory.onLocalEnvironment();
+ environment.getEnvironmentContext().withConfig(config);
+
+ GQLPipeLine gqlPipeLine = new GQLPipeLine(environment, 0);
+
+ try {
+ gqlPipeLine.execute();
+ } finally {
+ environment.shutdown();
+ ClusterMetaStore.close();
+ }
+ return this;
+ }
+}
diff --git
a/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/GeaFlowMcpClientTest.java
b/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/GeaFlowMcpClientTest.java
index 1a2e66e7..a564d638 100644
---
a/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/GeaFlowMcpClientTest.java
+++
b/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/GeaFlowMcpClientTest.java
@@ -19,10 +19,8 @@
package org.apache.geaflow.mcp.server;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import org.apache.commons.io.IOUtils;
+import org.apache.geaflow.mcp.server.util.McpConstants;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.noear.solon.ai.mcp.client.McpClientProvider;
@@ -30,10 +28,15 @@ import org.noear.solon.test.SolonTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.*;
+
@SolonTest(GeaFlowMcpServer.class)
public class GeaFlowMcpClientTest {
private static final Logger LOGGER =
LoggerFactory.getLogger(GeaFlowMcpClientTest.class);
+ private static final String SSE_CHANNEL = "sse";
private static final String SSE_ENDPOINT =
"http://localhost:8088/geaflow/sse";
private static final String QUERY = "query";
private static final String EXECUTE_QUERY_TOOL_NAME = "executeQuery";
@@ -60,7 +63,6 @@ public class GeaFlowMcpClientTest {
/**
* Call execute query tool.
*/
- @Test
public void testExecuteQuery() {
McpClientProvider toolProvider = McpClientProvider.builder()
.apiUrl(SSE_ENDPOINT)
@@ -77,6 +79,7 @@ public class GeaFlowMcpClientTest {
@Test
public void testGetServerVersion() {
McpClientProvider toolProvider = McpClientProvider.builder()
+ .channel(SSE_CHANNEL)
.apiUrl(SSE_ENDPOINT)
.build();
@@ -87,6 +90,7 @@ public class GeaFlowMcpClientTest {
@Test
public void testListTools() {
McpClientProvider toolProvider = McpClientProvider.builder()
+ .channel(SSE_CHANNEL)
.apiUrl(SSE_ENDPOINT)
.build();
@@ -95,7 +99,118 @@ public class GeaFlowMcpClientTest {
LOGGER.info("Tool: {}, desc: {}, input schema: {}", tool.name(),
tool.description(), tool.inputSchema());
toolNames.add(tool.name());
});
- Assertions.assertTrue(toolNames.contains(EXECUTE_QUERY_TOOL_NAME));
+
Assertions.assertTrue(toolNames.contains(McpConstants.CREATE_GRAPH_TOOL_NAME));
+ }
+
+ /**
+ * Call create graph tool.
+ */
+ @Test
+ public void testCreateGraph() throws IOException {
+ McpClientProvider toolProvider = McpClientProvider.builder()
+ .channel(SSE_CHANNEL)
+ .apiUrl(SSE_ENDPOINT)
+ .build();
+
+ String gql = IOUtils.resourceToString("/gql/modern",
Charset.defaultCharset()).trim();
+ Map<String, Object> map = new HashMap<>();
+ map.put(McpConstants.DDL, gql);
+ map.put(McpConstants.GRAPH_NAME, "modern");
+
+ String queryResults =
toolProvider.callToolAsText(McpConstants.CREATE_GRAPH_TOOL_NAME,
map).getContent();
+ LOGGER.info("queryResults: {}", queryResults);
+ Assertions.assertEquals("Create graph modern success.",
+ queryResults);
+
+ }
+
+ /**
+ * Call insert graph tool.
+ */
+ @Test
+ public void testInsertGraph() throws IOException {
+ McpClientProvider toolProvider = McpClientProvider.builder()
+ .channel(SSE_CHANNEL)
+ .apiUrl(SSE_ENDPOINT)
+ .build();
+
+ String gql = IOUtils.resourceToString("/gql/insert1",
Charset.defaultCharset()).trim();
+ Map<String, Object> map = new HashMap<>();
+ map.put(McpConstants.DML, gql);
+ map.put(McpConstants.GRAPH_NAME, "modern");
+
+ String queryResults =
toolProvider.callToolAsText(McpConstants.INSERT_GRAPH_TOOL_NAME,
map).getContent();
+ LOGGER.info("queryResults: {}", queryResults);
+ String licenseHead = IOUtils.resourceToString("/gql/licenseHead",
Charset.defaultCharset());
+ Assertions.assertEquals("run query success: " + licenseHead + "INSERT
INTO modern.person(id, name, age)\n" +
+ "VALUES (1, 'jim', 20), (2, 'kate', 22)\n" +
+ ";",
+ queryResults);
+
}
+ /**
+ * Call query graph tool.
+ */
+ @Test
+ public void testQueryGraphType() throws IOException {
+ McpClientProvider toolProvider = McpClientProvider.builder()
+ .channel(SSE_CHANNEL)
+ .apiUrl(SSE_ENDPOINT)
+ .build();
+
+ Map<String, Object> map = new HashMap<>();
+ map.put(McpConstants.TYPE, "person");
+ map.put(McpConstants.GRAPH_NAME, "modern");
+
+ String queryResults =
toolProvider.callToolAsText(McpConstants.QUERY_TYPE_TOOL_NAME,
map).getContent();
+ LOGGER.info("queryResults: {}", queryResults);
+ Assertions.assertTrue(queryResults.startsWith("type: person"));
+ Assertions.assertTrue(queryResults.contains("schema: id|name|age"));
+ Assertions.assertTrue(queryResults.contains("1,jim,20"));
+ Assertions.assertTrue(queryResults.contains("2,kate,22"));
+
+ }
+
+ /**
+ * Call create graph tool with error.
+ */
+ @Test
+ public void testCreateGraphFailed() throws IOException {
+ McpClientProvider toolProvider = McpClientProvider.builder()
+ .channel(SSE_CHANNEL)
+ .apiUrl(SSE_ENDPOINT)
+ .build();
+
+ String gql = IOUtils.resourceToString("/gql/modern_error",
Charset.defaultCharset()).trim();
+ Map<String, Object> map = new HashMap<>();
+ map.put(McpConstants.DDL, gql);
+ map.put(McpConstants.GRAPH_NAME, "modern");
+
+ String queryResults =
toolProvider.callToolAsText(McpConstants.CREATE_GRAPH_TOOL_NAME,
map).getContent();
+ LOGGER.info("queryResults: {}", queryResults);
+ Assertions.assertTrue(queryResults.startsWith("Compile error:
Encountered \"character\""));
+
+ }
+
+ /**
+ * Call insert graph tool.
+ */
+ @Test
+ public void testInsertGraphWithError() throws IOException {
+ McpClientProvider toolProvider = McpClientProvider.builder()
+ .channel(SSE_CHANNEL)
+ .apiUrl(SSE_ENDPOINT)
+ .build();
+
+ String gql = IOUtils.resourceToString("/gql/insert1_error",
Charset.defaultCharset()).trim();
+ Map<String, Object> map = new HashMap<>();
+ map.put(McpConstants.DML, gql);
+ map.put(McpConstants.GRAPH_NAME, "modern");
+
+ String queryResults =
toolProvider.callToolAsText(McpConstants.INSERT_GRAPH_TOOL_NAME,
map).getContent();
+ LOGGER.info("queryResults: {}", queryResults);
+ Assertions.assertTrue(queryResults.contains("Field:er is not found"));
+
+ }
}
diff --git
a/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/util/YamlParserTest.java
b/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/util/YamlParserTest.java
index 615f8b9c..b5af4f02 100644
---
a/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/util/YamlParserTest.java
+++
b/geaflow-mcp/src/test/java/org/apache/geaflow/mcp/server/util/YamlParserTest.java
@@ -19,17 +19,16 @@
package org.apache.geaflow.mcp.server.util;
-import static org.apache.geaflow.mcp.server.GeaFlowMcpServerTools.CONFIG;
-import static org.apache.geaflow.mcp.server.GeaFlowMcpServerTools.SERVER_HOST;
-import static org.apache.geaflow.mcp.server.GeaFlowMcpServerTools.SERVER_PORT;
-
import com.alibaba.fastjson.JSON;
-import java.util.Map;
import org.apache.geaflow.analytics.service.config.AnalyticsClientConfigKeys;
import org.apache.geaflow.mcp.util.YamlParser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.util.Map;
+
+import static org.apache.geaflow.mcp.server.GeaFlowMcpServerTools.*;
+
public class YamlParserTest {
private static final String LOCAL_HOST = "localhost";
diff --git a/geaflow-mcp/src/test/resources/gql/insert1
b/geaflow-mcp/src/test/resources/gql/insert1
new file mode 100644
index 00000000..a5b6c42e
--- /dev/null
+++ b/geaflow-mcp/src/test/resources/gql/insert1
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+INSERT INTO modern.person(id, name, age)
+VALUES (1, 'jim', 20), (2, 'kate', 22)
+;
\ No newline at end of file
diff --git a/geaflow-mcp/src/test/resources/gql/insert1_error
b/geaflow-mcp/src/test/resources/gql/insert1_error
new file mode 100644
index 00000000..f3708ff3
--- /dev/null
+++ b/geaflow-mcp/src/test/resources/gql/insert1_error
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+INSERT INTO modern.er(id, name, age)
+VALUES (1, 'jim', 20), (2, 'kate', 22)
+;
\ No newline at end of file
diff --git a/geaflow-mcp/src/test/resources/gql/licenseHead
b/geaflow-mcp/src/test/resources/gql/licenseHead
new file mode 100644
index 00000000..bd244d07
--- /dev/null
+++ b/geaflow-mcp/src/test/resources/gql/licenseHead
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/geaflow-mcp/src/test/resources/gql/modern
b/geaflow-mcp/src/test/resources/gql/modern
new file mode 100644
index 00000000..d65e4694
--- /dev/null
+++ b/geaflow-mcp/src/test/resources/gql/modern
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+CREATE GRAPH modern (
+ Vertex person (
+ id bigint ID,
+ name varchar,
+ age int
+ ),
+ Vertex software (
+ id bigint ID,
+ name varchar,
+ lang varchar
+ ),
+ Edge knows (
+ srcId bigint SOURCE ID,
+ targetId bigint DESTINATION ID,
+ weight double
+ ),
+ Edge created (
+ srcId bigint SOURCE ID,
+ targetId bigint DESTINATION ID,
+ weight double
+ )
+) WITH (
+ storeType='rocksdb'
+);
\ No newline at end of file
diff --git a/geaflow-mcp/src/test/resources/gql/modern_error
b/geaflow-mcp/src/test/resources/gql/modern_error
new file mode 100644
index 00000000..705948e2
--- /dev/null
+++ b/geaflow-mcp/src/test/resources/gql/modern_error
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+CREATE GRAPH modern (
+ Vertex character (
+ id bigint ID,
+ name varchar,
+ age int
+ ),
+ Vertex software (
+ id bigint ID,
+ name varchar,
+ lang varchar
+ ),
+ Edge knows (
+ srcId bigint SOURCE ID,
+ targetId bigint DESTINATION ID,
+ weight double
+ ),
+ Edge created (
+ srcId bigint SOURCE ID,
+ targetId bigint DESTINATION ID,
+ weight double
+ )
+) WITH (
+ storeType='rocksdb'
+);
\ No newline at end of file
diff --git a/geaflow-mcp/src/test/resources/gql/query1
b/geaflow-mcp/src/test/resources/gql/query1
new file mode 100644
index 00000000..dfeab063
--- /dev/null
+++ b/geaflow-mcp/src/test/resources/gql/query1
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+CREATE TABLE output_console(
+ res BIGINT
+) WITH (
+ type='file',
+ geaflow.dsl.file.path='/tmp/dsl/mcp/result'
+);
+
+INSERT INTO output_console
+Match (a) return a.id;
\ No newline at end of file
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/QueryContext.java
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/QueryContext.java
index 6f012610..c7d8c62e 100644
---
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/QueryContext.java
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/QueryContext.java
@@ -291,6 +291,10 @@ public class QueryContext {
return graphs.get(graphName);
}
+ public void addGraph(String graphName, GeaFlowGraph graph) {
+ graphs.put(graphName, graph);
+ }
+
public void addMaterializedGraph(String graphName) {
this.materializedGraphs.add(graphName);
}
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/command/CreateGraphCommand.java
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/command/CreateGraphCommand.java
index 9202823b..9efc49d0 100644
---
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/command/CreateGraphCommand.java
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/main/java/org/apache/geaflow/dsl/runtime/command/CreateGraphCommand.java
@@ -62,6 +62,7 @@ public class CreateGraphCommand implements IQueryCommand {
GeaFlowGraph graph = gContext.convertToGraph(createGraph);
gContext.registerGraph(graph);
processUsing(graph, context);
+ context.addGraph(graph.getName(), graph);
LOGGER.info("Succeed to create graph: {}.", graph);
return new QueryResult(true);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]