Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
1. Add a new get_relationship_properties_keys tool.
2. Add targetNode filtering for longest_path.
3. Add support for similarity algorithms.
4. Add relationship directionality used by graph projection as an optional parameter to all appropriate algorithms. This include all algorithms that support both directed and undirected graphs and behave differently.

### Bug Fixes
1. Return node names in several path algorithms that only returned node ids.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@


class ArticleRankHandler(AlgorithmHandler):
def article_rank(self, **kwargs):
with projected_graph(self.gds) as G:
def article_rank(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
# If any optional parameter is not None, use that parameter
args = locals()
params = {
Expand Down Expand Up @@ -46,6 +46,7 @@ def article_rank(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.article_rank(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
sourceNodes=arguments.get("sourceNodes"),
Expand Down Expand Up @@ -75,8 +76,8 @@ def execute(self, arguments: Dict[str, Any]) -> Any:


class BetweennessCentralityHandler(AlgorithmHandler):
def betweenness_centrality(self, **kwargs):
with projected_graph(self.gds) as G:
def betweenness_centrality(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
params = {
k: v
for k, v in kwargs.items()
Expand All @@ -99,6 +100,7 @@ def betweenness_centrality(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.betweenness_centrality(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
samplingSize=arguments.get("samplingSize"),
Expand All @@ -124,13 +126,13 @@ def bridges(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.bridges(
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty")
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
)


class CELFHandler(AlgorithmHandler):
def celf(self, **kwargs):
with projected_graph(self.gds) as G:
def celf(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
params = {
k: v
for k, v in kwargs.items()
Expand All @@ -147,6 +149,7 @@ def celf(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.celf(
undirected=arguments.get("undirected", False),
seedSetSize=arguments.get("seedSetSize"),
monteCarloSimulations=arguments.get("monteCarloSimulations"),
propagationProbability=arguments.get("propagationProbability"),
Expand All @@ -155,8 +158,8 @@ def execute(self, arguments: Dict[str, Any]) -> Any:


class ClosenessCentralityHandler(AlgorithmHandler):
def closeness_centrality(self, **kwargs):
with projected_graph(self.gds) as G:
def closeness_centrality(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
params = {
k: v
for k, v in kwargs.items()
Expand All @@ -174,20 +177,20 @@ def closeness_centrality(self, **kwargs):
centrality = filter_identifiers(
self.gds, node_identifier_property, node_names, centrality
)

return centrality

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.closeness_centrality(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
useWassermanFaust=arguments.get("useWassermanFaust"),
)


class DegreeCentralityHandler(AlgorithmHandler):
def degree_centrality(self, **kwargs):
with projected_graph(self.gds) as G:
def degree_centrality(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
params = {
k: v
for k, v in kwargs.items()
Expand All @@ -210,15 +213,16 @@ def degree_centrality(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.degree_centrality(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
orientation=arguments.get("orientation"),
)


class EigenvectorCentralityHandler(AlgorithmHandler):
def eigenvector_centrality(self, **kwargs):
with projected_graph(self.gds) as G:
def eigenvector_centrality(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
params = {
k: v
for k, v in kwargs.items()
Expand Down Expand Up @@ -250,6 +254,7 @@ def eigenvector_centrality(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.eigenvector_centrality(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
maxIterations=arguments.get("maxIterations"),
Expand All @@ -261,8 +266,8 @@ def execute(self, arguments: Dict[str, Any]) -> Any:


class PageRankHandler(AlgorithmHandler):
def pagerank(self, **kwargs):
with projected_graph(self.gds) as G:
def pagerank(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
params = {
k: v
for k, v in kwargs.items()
Expand Down Expand Up @@ -293,6 +298,7 @@ def pagerank(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.pagerank(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
sourceNodes=arguments.get("sourceNodes"),
Expand All @@ -303,8 +309,8 @@ def execute(self, arguments: Dict[str, Any]) -> Any:


class HarmonicCentralityHandler(AlgorithmHandler):
def harmonic_centrality(self, **kwargs):
with projected_graph(self.gds) as G:
def harmonic_centrality(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
centrality = self.gds.closeness.harmonic.stream(G)

# Add node names to the results if nodeIdentifierProperty is provided
Expand All @@ -320,14 +326,15 @@ def harmonic_centrality(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.harmonic_centrality(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
)


class HITSHandler(AlgorithmHandler):
def hits(self, **kwargs):
with projected_graph(self.gds) as G:
def hits(self, undirected: bool = False, **kwargs):
with projected_graph(self.gds, undirected=undirected) as G:
params = {
k: v
for k, v in kwargs.items()
Expand All @@ -349,6 +356,7 @@ def hits(self, **kwargs):

def execute(self, arguments: Dict[str, Any]) -> Any:
return self.hits(
undirected=arguments.get("undirected", False),
nodes=arguments.get("nodes"),
nodeIdentifierProperty=arguments.get("nodeIdentifierProperty"),
hitsIterations=arguments.get("hitsIterations"),
Expand Down
36 changes: 36 additions & 0 deletions mcp_server/src/mcp_server_neo4j_gds/centrality_algorithm_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
"Supported values are None, MinMax, Max, Mean, Log, and StdScore. "
"To apply scaler-specific configuration, use the Map syntax: {scaler: 'name', ...}.",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": [],
},
Expand Down Expand Up @@ -101,6 +105,10 @@
"type": "string",
"description": "Property of the relationship to use for weighting. If not specified, all relationships are treated equally.",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": [],
},
Expand Down Expand Up @@ -142,6 +150,10 @@
"type": "string",
"description": "Property name to use for identifying nodes (e.g., 'name', 'Name', 'title'). Use get_node_properties_keys to find available properties.",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": ["seedSetSize"],
},
Expand All @@ -167,6 +179,10 @@
"type": "boolean",
"description": "If true, uses the Wasserman-Faust formula for closeness centrality. ",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": [],
},
Expand All @@ -190,6 +206,10 @@
"type": "string",
"description": "The orientation used to compute node degrees. Supported orientations are NATURAL (for out-degree), REVERSE (for in-degree) and UNDIRECTED (for both in-degree and out-degree) ",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": [],
},
Expand Down Expand Up @@ -256,6 +276,10 @@
"Supported values are None, MinMax, Max, Mean, Log, and StdScore. "
"To apply scaler-specific configuration, use the Map syntax: {scaler: 'name', ...}.",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
},
),
Expand Down Expand Up @@ -307,6 +331,10 @@
},
],
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": [],
},
Expand All @@ -327,6 +355,10 @@
"type": "string",
"description": "Property name to use for identifying nodes (e.g., 'name', 'Name', 'title'). Use get_node_properties_keys to find available properties.",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": [],
},
Expand Down Expand Up @@ -365,6 +397,10 @@
"enum": ["AUTO", "RANGE", "DEGREE"],
"description": "The partitioning scheme used to divide the work between threads. Available options are AUTO, RANGE, DEGREE.",
},
"undirected": {
"type": "boolean",
"description": "Whether to treat the graph as undirected or not. Default is false (directed).",
},
},
"required": [],
},
Expand Down
Loading