Skip to content

Commit 23f981d

Browse files
authored
support geaflow mcp server and provide basic query service (#545)
1 parent fae74b3 commit 23f981d

11 files changed

Lines changed: 567 additions & 2 deletions

File tree

geaflow-mcp/pom.xml

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Licensed to the Apache Software Foundation (ASF) under one
4+
~ or more contributor license agreements. See the NOTICE file
5+
~ distributed with this work for additional information
6+
~ regarding copyright ownership. The ASF licenses this file
7+
~ to you under the Apache License, Version 2.0 (the
8+
~ "License"); you may not use this file except in compliance
9+
~ with the License. You may obtain a copy of the License at
10+
~
11+
~ http://www.apache.org/licenses/LICENSE-2.0
12+
~
13+
~ Unless required by applicable law or agreed to in writing,
14+
~ software distributed under the License is distributed on an
15+
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
~ KIND, either express or implied. See the License for the
17+
~ specific language governing permissions and limitations
18+
~ under the License.
19+
-->
20+
21+
<project xmlns="http://maven.apache.org/POM/4.0.0"
22+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
24+
<modelVersion>4.0.0</modelVersion>
25+
<parent>
26+
<groupId>com.antgroup.tugraph</groupId>
27+
<artifactId>geaflow-parent</artifactId>
28+
<version>0.6.4-SNAPSHOT</version>
29+
</parent>
30+
31+
<artifactId>geaflow-mcp</artifactId>
32+
33+
<properties>
34+
<solon-mcp.version>3.3.3-M1</solon-mcp.version>
35+
<junit.version>5.10.1</junit.version>
36+
<slf4j.version>1.7.15</slf4j.version>
37+
<log4j.version>1.2.17</log4j.version>
38+
<log4j.slf4j.version>2.17.1</log4j.slf4j.version>
39+
<lmax.disrupter.veresion>3.4.4</lmax.disrupter.veresion>
40+
</properties>
41+
42+
<dependencies>
43+
<dependency>
44+
<groupId>com.antgroup.tugraph</groupId>
45+
<artifactId>geaflow-analytics-service-client</artifactId>
46+
<version>${project.version}</version>
47+
<exclusions>
48+
<exclusion>
49+
<artifactId>jackson-databind</artifactId>
50+
<groupId>com.fasterxml.jackson.core</groupId>
51+
</exclusion>
52+
</exclusions>
53+
</dependency>
54+
55+
<dependency>
56+
<groupId>org.noear</groupId>
57+
<artifactId>solon-ai-mcp</artifactId>
58+
<version>${solon-mcp.version}</version>
59+
</dependency>
60+
61+
<dependency>
62+
<groupId>org.noear</groupId>
63+
<artifactId>solon-web</artifactId>
64+
<version>${solon-mcp.version}</version>
65+
</dependency>
66+
67+
<dependency>
68+
<groupId>org.noear</groupId>
69+
<artifactId>solon-test</artifactId>
70+
<version>${solon-mcp.version}</version>
71+
<scope>test</scope>
72+
</dependency>
73+
74+
<dependency>
75+
<groupId>org.slf4j</groupId>
76+
<artifactId>slf4j-api</artifactId>
77+
<version>${slf4j.version}</version>
78+
</dependency>
79+
80+
<!-- SLF4J to Log4j 1.2 Binding -->
81+
<dependency>
82+
<groupId>org.slf4j</groupId>
83+
<artifactId>slf4j-log4j12</artifactId>
84+
<version>${slf4j.version}</version>
85+
</dependency>
86+
<dependency>
87+
<groupId>log4j</groupId>
88+
<artifactId>log4j</artifactId>
89+
<version>${log4j.version}</version>
90+
</dependency>
91+
92+
<!-- SLF4J to Log4j 2 Binding -->
93+
<dependency>
94+
<groupId>org.apache.logging.log4j</groupId>
95+
<artifactId>log4j-slf4j-impl</artifactId>
96+
<version>${log4j.slf4j.version}</version>
97+
</dependency>
98+
<dependency>
99+
<groupId>org.apache.logging.log4j</groupId>
100+
<artifactId>log4j-core</artifactId>
101+
<version>${log4j.slf4j.version}</version>
102+
</dependency>
103+
<dependency>
104+
<groupId>org.apache.logging.log4j</groupId>
105+
<artifactId>log4j-api</artifactId>
106+
<version>${log4j.slf4j.version}</version>
107+
</dependency>
108+
109+
<!-- 异步日志依赖 -->
110+
<dependency>
111+
<groupId>com.lmax</groupId>
112+
<artifactId>disruptor</artifactId>
113+
<version>${lmax.disrupter.veresion}</version>
114+
</dependency>
115+
116+
<!-- test -->
117+
<dependency>
118+
<groupId>org.junit.jupiter</groupId>
119+
<artifactId>junit-jupiter</artifactId>
120+
<version>${junit.version}</version>
121+
<scope>test</scope>
122+
</dependency>
123+
</dependencies>
124+
125+
<repositories>
126+
<repository>
127+
<id>sonatype</id>
128+
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
129+
<releases>
130+
<enabled>false</enabled>
131+
</releases>
132+
<snapshots>
133+
<enabled>true</enabled>
134+
</snapshots>
135+
</repository>
136+
</repositories>
137+
138+
</project>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package com.antgroup.geaflow.mcp.server;
21+
22+
import org.noear.solon.Solon;
23+
import org.noear.solon.ai.chat.tool.MethodToolProvider;
24+
import org.noear.solon.ai.mcp.server.McpServerEndpointProvider;
25+
import org.noear.solon.ai.mcp.server.resource.MethodResourceProvider;
26+
import org.noear.solon.annotation.Controller;
27+
28+
/**
29+
* Main entrance, support jdk8+ environment.
30+
*/
31+
@Controller
32+
public class GeaFlowMcpServer {
33+
private static final String SERVER_NAME = "geaflow-mcp-server";
34+
private static final String SSE_ENDPOINT = "/geaflow/sse";
35+
36+
public static void main(String[] args) {
37+
Solon.start(GeaFlowMcpServer.class, args, app -> {
38+
// Manually build the mcp service endpoint.
39+
McpServerEndpointProvider endpointProvider = McpServerEndpointProvider.builder()
40+
.name(SERVER_NAME)
41+
.sseEndpoint(SSE_ENDPOINT)
42+
.build();
43+
endpointProvider.addTool(new MethodToolProvider(new GeaFlowMcpServerTools()));
44+
endpointProvider.addResource(new MethodResourceProvider(new GeaFlowMcpServerTools()));
45+
endpointProvider.postStart();
46+
});
47+
}
48+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package com.antgroup.geaflow.mcp.server;
21+
22+
import com.alibaba.fastjson.JSON;
23+
import com.alibaba.fastjson.JSONObject;
24+
import com.antgroup.geaflow.analytics.service.client.AnalyticsClient;
25+
import com.antgroup.geaflow.analytics.service.client.AnalyticsClientBuilder;
26+
import com.antgroup.geaflow.analytics.service.query.QueryResults;
27+
import com.antgroup.geaflow.common.config.Configuration;
28+
import com.antgroup.geaflow.mcp.util.YamlParser;
29+
import java.util.Map;
30+
import org.noear.solon.ai.annotation.ResourceMapping;
31+
import org.noear.solon.ai.annotation.ToolMapping;
32+
import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
33+
import org.noear.solon.annotation.Param;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
@McpServerEndpoint(name = "geaflow-mcp-server", sseEndpoint = "/geaflow/sse")
38+
public class GeaFlowMcpServerTools {
39+
private static final Logger LOGGER = LoggerFactory.getLogger(GeaFlowMcpServerTools.class);
40+
41+
private static final String RETRY_TIMES = "analytics.retry.times";
42+
private static final int DEFAULT_RETRY_TIMES = 3;
43+
private static final String ERROR = "error";
44+
public static final String SERVER_HOST = "analytics.server.host";
45+
public static final String SERVER_PORT = "analytics.server.port";
46+
public static final String SERVER_USER = "analytics.query.user";
47+
public static final String QUERY_TIMEOUT_MS = "analytics.query.timeout.ms";
48+
public static final String INIT_CHANNEL_POOLS = "analytics.init.channel.pools";
49+
public static final String CONFIG = "analytics.client.config";
50+
public static final String CURRENT_VERSION = "v1.0.0";
51+
52+
/**
53+
* Resource that provides getting geaflow mcp server version.
54+
* @return version id.
55+
*/
56+
@ResourceMapping(uri = "config://mcp-server-version", description = "Get mcp server version")
57+
public String getServerVersion() {
58+
return CURRENT_VERSION;
59+
}
60+
61+
/**
62+
* A tool that provides graph query capabilities.
63+
* @param query GQL query.
64+
* @return query result or error code.
65+
*/
66+
@ToolMapping(description = "execute query")
67+
public String executeQuery(@Param(name = "query", description = "query") String query) {
68+
AnalyticsClient analyticsClient = null;
69+
70+
try {
71+
Map<String, Object> config = YamlParser.loadConfig();
72+
int retryTimes = DEFAULT_RETRY_TIMES;
73+
if (config.containsKey(RETRY_TIMES)) {
74+
retryTimes = Integer.parseInt(config.get(RETRY_TIMES).toString());
75+
}
76+
77+
AnalyticsClientBuilder builder = AnalyticsClient
78+
.builder()
79+
.withHost(config.get(SERVER_HOST).toString())
80+
.withPort((Integer) config.get(SERVER_PORT))
81+
.withRetryNum(retryTimes);
82+
if (config.containsKey(CONFIG)) {
83+
Map<String, String> clientConfig = JSON.parseObject(config.get(CONFIG).toString(), Map.class);
84+
Configuration configuration = new Configuration(clientConfig);
85+
builder.withConfiguration(configuration);
86+
LOGGER.info("client config: {}", configuration);
87+
}
88+
if (config.containsKey(SERVER_USER)) {
89+
builder.withUser(config.get(SERVER_USER).toString());
90+
}
91+
if (config.containsKey(QUERY_TIMEOUT_MS)) {
92+
builder.withTimeoutMs((Integer) config.get(QUERY_TIMEOUT_MS));
93+
}
94+
if (config.containsKey(INIT_CHANNEL_POOLS)) {
95+
builder.withInitChannelPools((Boolean) config.get(INIT_CHANNEL_POOLS));
96+
}
97+
analyticsClient = builder.build();
98+
99+
QueryResults queryResults = analyticsClient.executeQuery(query);
100+
if (queryResults.getError() != null) {
101+
final JSONObject error = new JSONObject();
102+
error.put(ERROR, queryResults.getError());
103+
return error.toJSONString();
104+
}
105+
return queryResults.getFormattedData();
106+
} catch (Exception e) {
107+
LOGGER.error(e.getMessage(), e);
108+
throw new RuntimeException(e);
109+
} finally {
110+
if (analyticsClient != null) {
111+
analyticsClient.shutdown();
112+
}
113+
}
114+
}
115+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package com.antgroup.geaflow.mcp.util;
21+
22+
import java.io.InputStream;
23+
import java.util.Map;
24+
import org.yaml.snakeyaml.Yaml;
25+
26+
public class YamlParser {
27+
28+
private static final String CONFIG_FILE = "/app.yml";
29+
30+
public static Map<String, Object> loadConfig() {
31+
try (InputStream inputStream = YamlParser.class
32+
.getResourceAsStream(CONFIG_FILE)) {
33+
Yaml yaml = new Yaml();
34+
return yaml.load(inputStream);
35+
} catch (Exception e) {
36+
throw new RuntimeException(e);
37+
}
38+
}
39+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
################################################################################
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
################################################################################
18+
19+
server.port: 8088
20+
analytics.server.host: localhost
21+
analytics.server.port: 8090
22+
# Just for test, unnecessary configuration.
23+
analytics.client.config: "{\"geaflow.analytics.client.max.inbound.message.size\": 4194304}"

0 commit comments

Comments
 (0)