Skip to content

Fix Cypher syntax error in relationship operations #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 60 additions & 20 deletions servers/mcp-neo4j-memory/src/mcp_neo4j_memory/server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import logging
import json
import re
from typing import Any, Dict, List, Optional
from contextlib import asynccontextmanager

Expand Down Expand Up @@ -120,20 +121,38 @@ async def create_entities(self, entities: List[Entity]) -> List[Entity]:
return entities

async def create_relations(self, relations: List[Relation]) -> List[Relation]:
# Group relations by type for batch processing
relations_by_type = {}

for relation in relations:
query = """
UNWIND $relations as relation
MATCH (from:Memory),(to:Memory)
WHERE from.name = relation.source
AND to.name = relation.target
MERGE (from)-[r:$(relation.relationType)]->(to)
"""

relation_type = relation.relationType

# Validate relation type to prevent Cypher injection
if not re.match(r"^[A-Z_][A-Z0-9_]*$", relation_type, re.IGNORECASE):
raise ValueError(f"Invalid relation type: {relation_type}")

if relation_type not in relations_by_type:
relations_by_type[relation_type] = []

relations_by_type[relation_type].append({
'from_name': relation.source,
'to_name': relation.target
})

# Process each relationship type in batch
for relation_type, relations_batch in relations_by_type.items():
query = f"""
UNWIND $relations_batch AS rel
MATCH (from:Memory), (to:Memory)
WHERE from.name = rel.from_name AND to.name = rel.to_name
MERGE (from)-[r:{relation_type}]->(to)
"""

self.neo4j_driver.execute_query(
query,
{"relations": [relation.model_dump() for relation in relations]}
query,
{"relations_batch": relations_batch}
)

return relations

async def add_observations(self, observations: List[ObservationAddition]) -> List[Dict[str, Any]]:
Expand Down Expand Up @@ -176,16 +195,37 @@ async def delete_observations(self, deletions: List[ObservationDeletion]) -> Non
)

async def delete_relations(self, relations: List[Relation]) -> None:
query = """
UNWIND $relations as relation
MATCH (source:Memory)-[r:$(relation.relationType)]->(target:Memory)
WHERE source.name = relation.source
AND target.name = relation.target
DELETE r
"""
# Group relations by type for batch processing
relations_by_type = {}

for relation in relations:
relation_type = relation.relationType

# Validate relation type to prevent Cypher injection
if not re.match(r"^[A-Z_][A-Z0-9_]*$", relation_type, re.IGNORECASE):
raise ValueError(f"Invalid relation type: {relation_type}")

if relation_type not in relations_by_type:
relations_by_type[relation_type] = []

relations_by_type[relation_type].append({
'source_name': relation.source,
'target_name': relation.target
})

# Delete each relationship type in batch
for relation_type, relations_batch in relations_by_type.items():
query = f"""
UNWIND $relations_batch AS rel
MATCH (source_node:Memory)-[r:{relation_type}]->(target_node:Memory)
WHERE source_node.name = rel.source_name
AND target_node.name = rel.target_name
DELETE r
"""

self.neo4j_driver.execute_query(
query,
{"relations": [relation.model_dump() for relation in relations]}
query,
{"relations_batch": relations_batch}
)

async def read_graph(self) -> KnowledgeGraph:
Expand Down