Update mcp tool universe

This commit was merged in pull request #1.
This commit is contained in:
shahondin1624
2026-02-14 11:09:37 +01:00
committed by shahondin1624
parent 1624e74d4e
commit 115614916d
15 changed files with 1056 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

79
pom.xml Normal file
View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.shahondin1624</groupId>
<artifactId>knowledge-graph</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<neo4j.version>2026.01.4</neo4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>${neo4j.version}</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-bolt</artifactId>
<version>${neo4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.mcp-java</groupId>
<artifactId>mcp-server-lib</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>de.shahondin1624.knowledgegraph.Main</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,135 @@
package de.shahondin1624.knowledgegraph;
import org.neo4j.configuration.GraphDatabaseSettings;
import org.neo4j.configuration.connectors.BoltConnector;
import org.neo4j.configuration.helpers.SocketAddress;
import org.neo4j.io.ByteUnit;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.dbms.api.DatabaseManagementServiceBuilder;
import org.neo4j.graphdb.GraphDatabaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.time.Duration;
import java.util.Collections;
import java.util.stream.Stream;
import static org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME;
public class DatabaseLauncher {
private static final Logger logger = LoggerFactory.getLogger(DatabaseLauncher.class);
private final Path databaseDirectory;
private GraphDatabaseService graphDb;
public DatabaseLauncher() {
this(Paths.get(System.getProperty("user.home"), ".graphdb"));
}
public DatabaseLauncher(final Path databaseDirectory) {
this.databaseDirectory = databaseDirectory;
}
public void start() throws IOException, URISyntaxException {
logger.info("Starting database from directory: {}", databaseDirectory);
if (!Files.exists(databaseDirectory)) {
logger.info("Database directory does not exist, extracting template...");
extractDatabaseTemplate();
}
spinUpDatabase();
}
private void extractDatabaseTemplate() throws IOException, URISyntaxException {
final URL resourceUrl = getClass().getResource("/graphdb-template");
if (resourceUrl == null) {
logger.warn("No database template found at /graphdb-template, creating empty directory");
// If no template is provided, just create the directory
Files.createDirectories(databaseDirectory);
return;
}
URI resourceUri = resourceUrl.toURI();
logger.debug("Extracting database template from URI: {}", resourceUri);
if ("jar".equals(resourceUri.getScheme())) {
try (final FileSystem fileSystem = FileSystems.newFileSystem(resourceUri, Collections.emptyMap())) {
final Path source = fileSystem.getPath("/graphdb-template");
copyDirectory(source, databaseDirectory);
}
} else {
Path source = Paths.get(resourceUri);
copyDirectory(source, databaseDirectory);
}
logger.info("Database template extracted successfully to {}", databaseDirectory);
}
private void copyDirectory(final Path source, final Path target) throws IOException {
try (final Stream<Path> stream = Files.walk(source)) {
stream.forEach(path -> {
try {
String relativePathStr = source.relativize(path).toString();
Path destination = target.resolve(relativePathStr);
if (Files.isDirectory(path)) {
if (!Files.exists(destination)) {
Files.createDirectories(destination);
}
} else {
Files.copy(path, destination, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new RuntimeException("Failed to copy database template", e);
}
});
}
}
public void spinUpDatabase() {
logger.info("Spinning up Neo4j database...");
final Path configFile = databaseDirectory.resolve("neo4j.conf");
final DatabaseManagementServiceBuilder builder = new DatabaseManagementServiceBuilder(databaseDirectory);
if (Files.exists(configFile)) {
logger.info("Loading database configuration from {}", configFile);
builder.loadPropertiesFromFile(configFile);
} else {
logger.info("No configuration file found, using programmatic defaults");
configureProgrammatically(builder);
}
final DatabaseManagementService managementService = builder.build();
graphDb = managementService.database(DEFAULT_DATABASE_NAME);
GraphDatabaseProvider.setGraphDb(graphDb);
registerShutdownHook(managementService);
logger.info("Neo4j database '{}' is ready", DEFAULT_DATABASE_NAME);
}
private void configureProgrammatically(final DatabaseManagementServiceBuilder builder) {
builder.setConfig(GraphDatabaseSettings.pagecache_memory, ByteUnit.mebiBytes(512))
.setConfig(GraphDatabaseSettings.transaction_timeout, Duration.ofSeconds(60))
.setConfig(GraphDatabaseSettings.preallocate_logical_logs, true)
.setConfig(BoltConnector.enabled, true)
.setConfig(BoltConnector.listen_address, new SocketAddress("0.0.0.0", 7687))
.setConfig(BoltConnector.encryption_level, BoltConnector.EncryptionLevel.DISABLED)
.setConfig(GraphDatabaseSettings.auth_enabled, false);
}
private void registerShutdownHook(final DatabaseManagementService managementService) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.info("Shutting down Neo4j database...");
managementService.shutdown();
logger.info("Neo4j database shut down successfully");
}));
}
public GraphDatabaseService getGraphDb() {
return graphDb;
}
public Path getDatabaseDirectory() {
return databaseDirectory;
}
}

View File

@@ -0,0 +1,25 @@
package de.shahondin1624.knowledgegraph;
import org.neo4j.graphdb.GraphDatabaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Static provider for the GraphDatabaseService to be accessed by MCP tools.
*/
public class GraphDatabaseProvider {
private static final Logger logger = LoggerFactory.getLogger(GraphDatabaseProvider.class);
private static GraphDatabaseService graphDb;
public static synchronized void setGraphDb(final GraphDatabaseService db) {
logger.info("Registering GraphDatabaseService with provider");
graphDb = db;
}
public static synchronized GraphDatabaseService getGraphDb() {
if (graphDb == null) {
throw new IllegalStateException("GraphDatabaseService has not been initialized yet.");
}
return graphDb;
}
}

View File

@@ -0,0 +1,85 @@
package de.shahondin1624.knowledgegraph.tooling;
import de.shahondin1624.knowledgegraph.GraphDatabaseProvider;
import de.shahondin1624.knowledgegraph.util.DbHelper;
import io.modelcontextprotocol.spec.McpSchema;
import mcp.tools.DefaultMcpTool;
import mcp.tools.McpValidatedTool;
import mcp.tools.helper.SchemaBuilder;
import mcp.util.Result;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
/**
* MCP tool for creating a new node with optional labels.
*/
@DefaultMcpTool
public class CreateNodeTool extends McpValidatedTool {
private static final Logger logger = LoggerFactory.getLogger(CreateNodeTool.class);
@Override
public String name() {
return "create_node";
}
@Override
public String title() {
return "Create Node";
}
@Override
public String description() {
return "Creates a new node with the specified labels.";
}
@Override
public McpSchema.JsonSchema inputSchema() {
return new SchemaBuilder()
.addProperty("labels", "array", "Optional list of labels for the node")
.build();
}
@Override
protected boolean isIdempotent() {
return false;
}
@Override
@SuppressWarnings("unchecked")
public McpSchema.CallToolResult callValidated(McpSchema.CallToolRequest request, Map<String, Object> arguments) throws Exception {
List<String> labelNames = (List<String>) arguments.get("labels");
logger.debug("Creating node with labels: {}", labelNames);
final Result<String, Exception> result = getStringExceptionResult(labelNames);
if (result.isOk()) {
String elementId = result.unwrap();
logger.info("Successfully created node with ID: {}", elementId);
return success("Node created with Element ID: " + elementId);
} else {
logger.error("Failed to create node: {}", result.err().unwrap().getMessage());
return error("Failed to create node: " + result.err().unwrap().getMessage());
}
}
private static Result<String, Exception> getStringExceptionResult(List<String> labelNames) {
DbHelper dbHelper = new DbHelper(GraphDatabaseProvider.getGraphDb());
return dbHelper.wrapInTx(tx -> {
Node node;
if (labelNames == null || labelNames.isEmpty()) {
node = tx.createNode();
} else {
Label[] labels = labelNames.stream()
.map(Label::label)
.toArray(Label[]::new);
node = tx.createNode(labels);
}
return node.getElementId();
});
}
}

View File

@@ -0,0 +1,84 @@
package de.shahondin1624.knowledgegraph.tooling;
import de.shahondin1624.knowledgegraph.GraphDatabaseProvider;
import de.shahondin1624.knowledgegraph.util.DbHelper;
import io.modelcontextprotocol.spec.McpSchema;
import mcp.tools.DefaultMcpTool;
import mcp.tools.McpValidatedTool;
import mcp.tools.helper.SchemaBuilder;
import org.neo4j.graphdb.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* MCP tool for executing Cypher queries.
*/
@DefaultMcpTool
public class ExecuteCypherTool extends McpValidatedTool {
private static final Logger logger = LoggerFactory.getLogger(ExecuteCypherTool.class);
@Override
public String name() {
return "execute_cypher";
}
@Override
public String title() {
return "Execute Cypher Query";
}
@Override
public String description() {
return "Executes a Cypher query on the Neo4j database and returns the result as a list of maps.";
}
@Override
public McpSchema.JsonSchema inputSchema() {
return new SchemaBuilder()
.addProperty("query", "string", "The Cypher query to execute")
.addProperty("parameters", "object", "Optional query parameters")
.required("query")
.build();
}
@Override
protected boolean isReadOnly() {
return false;
}
@Override
protected boolean isIdempotent() {
return false;
}
@Override
@SuppressWarnings("unchecked")
public McpSchema.CallToolResult callValidated(McpSchema.CallToolRequest request, Map<String, Object> arguments) throws Exception {
final String query = (String) arguments.get("query");
final Map<String, Object> parameters = (Map<String, Object>) arguments.get("parameters");
logger.debug("Executing Cypher query: {} with parameters: {}", query, parameters);
final DbHelper dbHelper = new DbHelper(GraphDatabaseProvider.getGraphDb());
mcp.util.Result<List<Map<String, Object>>, Exception> result = dbHelper.wrapInTx(tx -> {
Result neoResult = tx.execute(query, parameters != null ? parameters : Map.of());
List<Map<String, Object>> rows = new ArrayList<>();
while (neoResult.hasNext()) {
rows.add(neoResult.next());
}
return rows;
});
if (result.isOk()) {
List<Map<String, Object>> rows = result.unwrap();
logger.info("Successfully executed Cypher query, returned {} rows", rows.size());
return success(rows.toString());
} else {
logger.error("Cypher query execution failed: {}", result.err().unwrap().getMessage());
return error("Query execution failed: " + result.err().unwrap().getMessage());
}
}
}

View File

@@ -0,0 +1,91 @@
package de.shahondin1624.knowledgegraph.tooling;
import de.shahondin1624.knowledgegraph.GraphDatabaseProvider;
import de.shahondin1624.knowledgegraph.util.DbHelper;
import io.modelcontextprotocol.spec.McpSchema;
import mcp.tools.DefaultMcpTool;
import mcp.tools.McpValidatedTool;
import mcp.tools.helper.SchemaBuilder;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* MCP tool for finding nodes by label and property.
*/
@DefaultMcpTool
public class FindNodesTool extends McpValidatedTool {
private static final Logger logger = LoggerFactory.getLogger(FindNodesTool.class);
@Override
public String name() {
return "find_nodes";
}
@Override
public String title() {
return "Find Nodes";
}
@Override
public String description() {
return "Finds nodes by label and a property key-value pair.";
}
@Override
public McpSchema.JsonSchema inputSchema() {
return new SchemaBuilder()
.addProperty("label", "string", "The label of the nodes to find")
.addProperty("key", "string", "The property key to filter by")
.addProperty("value", "string", "The property value to filter by")
.required("label", "key", "value")
.build();
}
@Override
public McpSchema.CallToolResult callValidated(McpSchema.CallToolRequest request, Map<String, Object> arguments) throws Exception {
String labelName = (String) arguments.get("label");
String key = (String) arguments.get("key");
Object value = arguments.get("value");
logger.debug("Finding nodes with label: {}, key: {}, value: {}", labelName, key, value);
DbHelper dbHelper = new DbHelper(GraphDatabaseProvider.getGraphDb());
mcp.util.Result<List<Map<String, Object>>, Exception> result = dbHelper.wrapInTx(tx -> {
List<Map<String, Object>> nodes = new ArrayList<>();
try (ResourceIterator<Node> it = tx.findNodes(Label.label(labelName), key, value)) {
while (it.hasNext()) {
Node node = it.next();
nodes.add(Map.of(
"elementId", node.getElementId(),
"labels", StreamSupportLabels(node.getLabels()),
"properties", node.getAllProperties()
));
}
}
return nodes;
});
if (result.isOk()) {
List<Map<String, Object>> nodes = result.unwrap();
logger.info("Successfully found {} nodes", nodes.size());
return success(nodes.toString());
} else {
logger.error("Failed to find nodes: {}", result.err().unwrap().getMessage());
return error("Failed to find nodes: " + result.err().unwrap().getMessage());
}
}
private List<String> StreamSupportLabels(Iterable<Label> labels) {
List<String> labelNames = new ArrayList<>();
for (Label label : labels) {
labelNames.add(label.name());
}
return labelNames;
}
}

View File

@@ -0,0 +1,85 @@
package de.shahondin1624.knowledgegraph.tooling;
import de.shahondin1624.knowledgegraph.GraphDatabaseProvider;
import de.shahondin1624.knowledgegraph.util.DbHelper;
import io.modelcontextprotocol.spec.McpSchema;
import mcp.tools.DefaultMcpTool;
import mcp.tools.McpValidatedTool;
import mcp.tools.helper.SchemaBuilder;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ResourceIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* MCP tool for finding relationships by type and property.
*/
@DefaultMcpTool
public class FindRelationshipsTool extends McpValidatedTool {
private static final Logger logger = LoggerFactory.getLogger(FindRelationshipsTool.class);
@Override
public String name() {
return "find_relationships";
}
@Override
public String title() {
return "Find Relationships";
}
@Override
public String description() {
return "Finds relationships by type and a property key-value pair.";
}
@Override
public McpSchema.JsonSchema inputSchema() {
return new SchemaBuilder()
.addProperty("type", "string", "The type of the relationships to find")
.addProperty("key", "string", "The property key to filter by")
.addProperty("value", "string", "The property value to filter by")
.required("type", "key", "value")
.build();
}
@Override
public McpSchema.CallToolResult callValidated(McpSchema.CallToolRequest request, Map<String, Object> arguments) throws Exception {
String typeName = (String) arguments.get("type");
String key = (String) arguments.get("key");
Object value = arguments.get("value");
logger.debug("Finding relationships with type: {}, key: {}, value: {}", typeName, key, value);
DbHelper dbHelper = new DbHelper(GraphDatabaseProvider.getGraphDb());
mcp.util.Result<List<Map<String, Object>>, Exception> result = dbHelper.wrapInTx(tx -> {
List<Map<String, Object>> relationships = new ArrayList<>();
try (ResourceIterator<Relationship> it = tx.findRelationships(RelationshipType.withName(typeName), key, value)) {
while (it.hasNext()) {
Relationship rel = it.next();
relationships.add(Map.of(
"elementId", rel.getElementId(),
"type", rel.getType().name(),
"startNodeId", rel.getStartNode().getElementId(),
"endNodeId", rel.getEndNode().getElementId(),
"properties", rel.getAllProperties()
));
}
}
return relationships;
});
if (result.isOk()) {
List<Map<String, Object>> rels = result.unwrap();
logger.info("Successfully found {} relationships", rels.size());
return success(rels.toString());
} else {
logger.error("Failed to find relationships: {}", result.err().unwrap().getMessage());
return error("Failed to find relationships: " + result.err().unwrap().getMessage());
}
}
}

View File

@@ -0,0 +1,82 @@
package de.shahondin1624.knowledgegraph.tooling;
import de.shahondin1624.knowledgegraph.GraphDatabaseProvider;
import de.shahondin1624.knowledgegraph.util.DbHelper;
import io.modelcontextprotocol.spec.McpSchema;
import mcp.tools.DefaultMcpTool;
import mcp.tools.McpValidatedTool;
import mcp.tools.helper.SchemaBuilder;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* MCP tool for getting a node by its Element ID.
*/
@DefaultMcpTool
public class GetNodeTool extends McpValidatedTool {
private static final Logger logger = LoggerFactory.getLogger(GetNodeTool.class);
@Override
public String name() {
return "get_node";
}
@Override
public String title() {
return "Get Node";
}
@Override
public String description() {
return "Gets a node's labels and properties by its Element ID.";
}
@Override
public McpSchema.JsonSchema inputSchema() {
return new SchemaBuilder()
.addProperty("elementId", "string", "The Element ID of the node to retrieve")
.required("elementId")
.build();
}
@Override
public McpSchema.CallToolResult callValidated(McpSchema.CallToolRequest request, Map<String, Object> arguments) throws Exception {
String elementId = (String) arguments.get("elementId");
logger.debug("Retrieving node with ID: {}", elementId);
DbHelper dbHelper = new DbHelper(GraphDatabaseProvider.getGraphDb());
mcp.util.Result<Map<String, Object>, Exception> result = dbHelper.wrapInTx(tx -> {
Node node = tx.getNodeByElementId(elementId);
if (node == null) {
throw new RuntimeException("Node not found with ID: " + elementId);
}
return Map.of(
"elementId", node.getElementId(),
"labels", StreamSupportLabels(node.getLabels()),
"properties", node.getAllProperties()
);
});
if (result.isOk()) {
logger.info("Successfully retrieved node: {}", elementId);
return success(result.unwrap().toString());
} else {
logger.error("Failed to get node {}: {}", elementId, result.err().unwrap().getMessage());
return error("Failed to get node: " + result.err().unwrap().getMessage());
}
}
private List<String> StreamSupportLabels(Iterable<Label> labels) {
List<String> labelNames = new ArrayList<>();
for (Label label : labels) {
labelNames.add(label.name());
}
return labelNames;
}
}

View File

@@ -0,0 +1,65 @@
package de.shahondin1624.knowledgegraph.tooling;
import de.shahondin1624.knowledgegraph.GraphDatabaseProvider;
import de.shahondin1624.knowledgegraph.util.DbHelper;
import io.modelcontextprotocol.spec.McpSchema;
import mcp.tools.DefaultMcpTool;
import mcp.tools.McpValidatedTool;
import mcp.tools.helper.SchemaBuilder;
import org.neo4j.graphdb.Label;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* MCP tool for listing all labels in use in the database.
*/
@DefaultMcpTool
public class ListLabelsTool extends McpValidatedTool {
private static final Logger logger = LoggerFactory.getLogger(ListLabelsTool.class);
@Override
public String name() {
return "list_labels";
}
@Override
public String title() {
return "List Labels";
}
@Override
public String description() {
return "Returns a list of all labels currently in use in the database.";
}
@Override
public McpSchema.JsonSchema inputSchema() {
return new SchemaBuilder().build();
}
@Override
public McpSchema.CallToolResult callValidated(McpSchema.CallToolRequest request, Map<String, Object> arguments) throws Exception {
logger.debug("Listing all labels in use");
DbHelper dbHelper = new DbHelper(GraphDatabaseProvider.getGraphDb());
mcp.util.Result<List<String>, Exception> result = dbHelper.wrapInTx(tx -> {
List<String> labels = new ArrayList<>();
for (Label label : tx.getAllLabelsInUse()) {
labels.add(label.name());
}
return labels;
});
if (result.isOk()) {
List<String> labels = result.unwrap();
logger.info("Successfully listed {} labels", labels.size());
return success(labels.toString());
} else {
logger.error("Failed to list labels: {}", result.err().unwrap().getMessage());
return error("Failed to list labels: " + result.err().unwrap().getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
package de.shahondin1624.knowledgegraph.tooling;
import de.shahondin1624.knowledgegraph.GraphDatabaseProvider;
import de.shahondin1624.knowledgegraph.util.DbHelper;
import io.modelcontextprotocol.spec.McpSchema;
import mcp.tools.DefaultMcpTool;
import mcp.tools.McpValidatedTool;
import mcp.tools.helper.SchemaBuilder;
import org.neo4j.graphdb.RelationshipType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* MCP tool for listing all relationship types in use in the database.
*/
@DefaultMcpTool
public class ListRelationshipTypesTool extends McpValidatedTool {
private static final Logger logger = LoggerFactory.getLogger(ListRelationshipTypesTool.class);
@Override
public String name() {
return "list_relationship_types";
}
@Override
public String title() {
return "List Relationship Types";
}
@Override
public String description() {
return "Returns a list of all relationship types currently in use in the database.";
}
@Override
public McpSchema.JsonSchema inputSchema() {
return new SchemaBuilder().build();
}
@Override
public McpSchema.CallToolResult callValidated(McpSchema.CallToolRequest request, Map<String, Object> arguments) throws Exception {
logger.debug("Listing all relationship types in use");
DbHelper dbHelper = new DbHelper(GraphDatabaseProvider.getGraphDb());
mcp.util.Result<List<String>, Exception> result = dbHelper.wrapInTx(tx -> {
List<String> types = new ArrayList<>();
for (RelationshipType type : tx.getAllRelationshipTypesInUse()) {
types.add(type.name());
}
return types;
});
if (result.isOk()) {
List<String> types = result.unwrap();
logger.info("Successfully listed {} relationship types", types.size());
return success(types.toString());
} else {
logger.error("Failed to list relationship types: {}", result.err().unwrap().getMessage());
return error("Failed to list relationship types: " + result.err().unwrap().getMessage());
}
}
}

View File

@@ -0,0 +1,44 @@
package de.shahondin1624.knowledgegraph.util;
import mcp.util.Result;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DbHelper {
private static final Logger logger = LoggerFactory.getLogger(DbHelper.class);
private final GraphDatabaseService graphDb;
public DbHelper(GraphDatabaseService graphDb) {
this.graphDb = graphDb;
}
public Transaction beginTx() {
return graphDb.beginTx();
}
@SuppressWarnings("unchecked")
public <E, T extends Throwable> Result<E, T> wrapInTx(TransactionContextWithReturn<E> action) {
try (final TransactionWrapper tx = new TransactionWrapper(beginTx())) {
final E result = action.execute(tx);
tx.commit();
return Result.Ok(result);
} catch (final Exception e) {
logger.error("Transaction failed (with return): {}", e.getMessage(), e);
return Result.Err((T) e);
}
}
@SuppressWarnings("unchecked")
public <T extends Throwable> Result<Void, T> wrapInTx(TransactionContextWithoutReturn action) {
try (final TransactionWrapper tx = new TransactionWrapper(beginTx())) {
action.execute(tx);
tx.commit();
return Result.Ok(null);
} catch (final Exception e) {
logger.error("Transaction failed (without return): {}", e.getMessage(), e);
return Result.Err((T) e);
}
}
}

View File

@@ -0,0 +1,6 @@
package de.shahondin1624.knowledgegraph.util;
@FunctionalInterface
public interface TransactionContextWithReturn<E> {
E execute(final TransactionWrapper tx) throws Exception;
}

View File

@@ -0,0 +1,6 @@
package de.shahondin1624.knowledgegraph.util;
@FunctionalInterface
public interface TransactionContextWithoutReturn {
void execute(final TransactionWrapper tx) throws Exception;
}

View File

@@ -0,0 +1,161 @@
package de.shahondin1624.knowledgegraph.util;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.schema.Schema;
import org.neo4j.graphdb.traversal.BidirectionalTraversalDescription;
import org.neo4j.graphdb.traversal.TraversalDescription;
import java.util.Map;
public class TransactionWrapper implements AutoCloseable {
private final Transaction tx;
public TransactionWrapper(final Transaction tx) {
this.tx = tx;
}
public Node createNode() {
return tx.createNode();
}
public Node createNode(Label... labels) {
return tx.createNode(labels);
}
public Node getNodeByElementId(String s) {
return tx.getNodeByElementId(s);
}
public Relationship getRelationshipByElementId(String s) {
return tx.getRelationshipByElementId(s);
}
public BidirectionalTraversalDescription bidirectionalTraversalDescription() {
return tx.bidirectionalTraversalDescription();
}
public TraversalDescription traversalDescription() {
return tx.traversalDescription();
}
public Result execute(String s) throws QueryExecutionException {
return tx.execute(s);
}
public Result execute(String s, Map<String, Object> map) throws QueryExecutionException {
return tx.execute(s, map);
}
public Iterable<Label> getAllLabelsInUse() {
return tx.getAllLabelsInUse();
}
public Iterable<RelationshipType> getAllRelationshipTypesInUse() {
return tx.getAllRelationshipTypesInUse();
}
public Iterable<Label> getAllLabels() {
return tx.getAllLabels();
}
public Iterable<RelationshipType> getAllRelationshipTypes() {
return tx.getAllRelationshipTypes();
}
public Iterable<String> getAllPropertyKeys() {
return tx.getAllPropertyKeys();
}
public ResourceIterator<Node> findNodes(Label label, String s, String s1, StringSearchMode stringSearchMode) {
return tx.findNodes(label, s, s1, stringSearchMode);
}
public ResourceIterator<Node> findNodes(Label label, Map<String, Object> map) {
return tx.findNodes(label, map);
}
public ResourceIterator<Node> findNodes(Label label, String s, Object o, String s1, Object o1, String s2, Object o2) {
return tx.findNodes(label, s, o, s1, o1, s2, o2);
}
public ResourceIterator<Node> findNodes(Label label, String s, Object o, String s1, Object o1) {
return tx.findNodes(label, s, o, s1, o1);
}
public Node findNode(Label label, String s, Object o) {
return tx.findNode(label, s, o);
}
public ResourceIterator<Node> findNodes(Label label, String s, Object o) {
return tx.findNodes(label, s, o);
}
public ResourceIterator<Node> findNodes(Label label) {
return tx.findNodes(label);
}
public ResourceIterator<Relationship> findRelationships(RelationshipType relationshipType, String s, String s1, StringSearchMode stringSearchMode) {
return tx.findRelationships(relationshipType, s, s1, stringSearchMode);
}
public ResourceIterator<Relationship> findRelationships(RelationshipType relationshipType, Map<String, Object> map) {
return tx.findRelationships(relationshipType, map);
}
public ResourceIterator<Relationship> findRelationships(RelationshipType relationshipType, String s, Object o, String s1, Object o1, String s2, Object o2) {
return tx.findRelationships(relationshipType, s, o, s1, o1, s2, o2);
}
public ResourceIterator<Relationship> findRelationships(RelationshipType relationshipType, String s, Object o, String s1, Object o1) {
return tx.findRelationships(relationshipType, s, o, s1, o1);
}
public Relationship findRelationship(RelationshipType relationshipType, String s, Object o) {
return tx.findRelationship(relationshipType, s, o);
}
public ResourceIterator<Relationship> findRelationships(RelationshipType relationshipType, String s, Object o) {
return tx.findRelationships(relationshipType, s, o);
}
public ResourceIterator<Relationship> findRelationships(RelationshipType relationshipType) {
return tx.findRelationships(relationshipType);
}
public void terminate() {
tx.terminate();
}
public ResourceIterable<Node> getAllNodes() {
return tx.getAllNodes();
}
public ResourceIterable<Relationship> getAllRelationships() {
return tx.getAllRelationships();
}
public Lock acquireWriteLock(Entity entity) {
return tx.acquireWriteLock(entity);
}
public Lock acquireReadLock(Entity entity) {
return tx.acquireReadLock(entity);
}
public Schema schema() {
return tx.schema();
}
public void commit() {
tx.commit();
}
public void rollback() {
tx.rollback();
}
@Override
public void close() {
tx.close();
}
}