Skip to content

Commit 2ad4178

Browse files
committed
Merge branch 'main' into feat/http-mode
2 parents 20b249e + 53ae31f commit 2ad4178

File tree

3 files changed

+80
-16
lines changed

3 files changed

+80
-16
lines changed

servers/mcp-neo4j-cypher/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## Next
22

33
### Fixed
4+
* Updated the `get_neo4j_schema` tool to include Relationship properties as well
45

56
### Changed
67
* Update error handling in read and write tools

servers/mcp-neo4j-cypher/src/mcp_neo4j_cypher/server.py

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,71 @@ async def get_neo4j_schema() -> list[TextResource]:
6262
"""
6363

6464
get_schema_query = """
65-
call apoc.meta.data() yield label, property, type, other, unique, index, elementType
66-
where elementType = 'node' and not label starts with '_'
67-
with label,
68-
collect(case when type <> 'RELATIONSHIP' then [property, type + case when unique then " unique" else "" end + case when index then " indexed" else "" end] end) as attributes,
69-
collect(case when type = 'RELATIONSHIP' then [property, head(other)] end) as relationships
70-
RETURN label, apoc.map.fromPairs(attributes) as attributes, apoc.map.fromPairs(relationships) as relationships
71-
"""
65+
CALL apoc.meta.schema();
66+
"""
67+
68+
def clean_schema(schema: dict) -> dict:
69+
cleaned = {}
70+
71+
for key, entry in schema.items():
72+
73+
new_entry = {
74+
"type": entry["type"]
75+
}
76+
if "count" in entry:
77+
new_entry["count"] = entry["count"]
78+
79+
labels = entry.get("labels", [])
80+
if labels:
81+
new_entry["labels"] = labels
82+
83+
props = entry.get("properties", {})
84+
clean_props = {}
85+
for pname, pinfo in props.items():
86+
cp = {}
87+
if "indexed" in pinfo:
88+
cp["indexed"] = pinfo["indexed"]
89+
if "type" in pinfo:
90+
cp["type"] = pinfo["type"]
91+
if cp:
92+
clean_props[pname] = cp
93+
if clean_props:
94+
new_entry["properties"] = clean_props
95+
96+
if entry.get("relationships"):
97+
rels_out = {}
98+
for rel_name, rel in entry["relationships"].items():
99+
cr = {}
100+
if "direction" in rel:
101+
cr["direction"] = rel["direction"]
102+
# nested labels
103+
rlabels = rel.get("labels", [])
104+
if rlabels:
105+
cr["labels"] = rlabels
106+
# nested properties
107+
rprops = rel.get("properties", {})
108+
clean_rprops = {}
109+
for rpname, rpinfo in rprops.items():
110+
crp = {}
111+
if "indexed" in rpinfo:
112+
crp["indexed"] = rpinfo["indexed"]
113+
if "type" in rpinfo:
114+
crp["type"] = rpinfo["type"]
115+
if crp:
116+
clean_rprops[rpname] = crp
117+
if clean_rprops:
118+
cr["properties"] = clean_rprops
119+
120+
if cr:
121+
rels_out[rel_name] = cr
122+
123+
if rels_out:
124+
new_entry["relationships"] = rels_out
125+
126+
cleaned[key] = new_entry
127+
128+
return cleaned
129+
72130

73131
try:
74132
async with neo4j_driver.session(database=database) as session:
@@ -78,7 +136,11 @@ async def get_neo4j_schema() -> list[TextResource]:
78136

79137
logger.debug(f"Read query returned {len(results_json_str)} rows")
80138

81-
return [TextResource(uri="neo4j://schema", text=results_json_str)]
139+
schema = json.loads(results_json_str)[0].get('value')
140+
schema_clean = clean_schema(schema)
141+
schema_clean_str = json.dumps(schema_clean)
142+
143+
return types.CallToolResult(content=[types.TextContent(type="text", text=schema_clean_str)])
82144

83145
except Exception as e:
84146
logger.error(f"Database error retrieving schema: {e}")

servers/mcp-neo4j-cypher/tests/integration/test_server_tools_IT.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ async def test_get_neo4j_schema(mcp_server: FastMCP, init_data: Any):
1010
tool = await mcp_server.get_tool("get_neo4j_schema")
1111
response = await tool.run(dict())
1212

13-
text_content = response.content[0].text
14-
outer = json.loads(text_content)
15-
inner = json.loads(outer["text"])
16-
schema = inner[0]
17-
18-
assert "label" in schema
19-
assert "attributes" in schema
20-
assert "relationships" in schema
13+
temp_parsed = json.loads(response[0].text)['content'][0]['text']
14+
schema = json.loads(temp_parsed)
15+
16+
# Verify the schema result
17+
assert "Person" in schema
18+
assert schema['Person']['count'] == 3
19+
assert len(schema['Person']['properties']) == 2
20+
assert "FRIEND" in schema['Person']['relationships']
21+
2122

2223

2324
@pytest.mark.asyncio(loop_scope="function")

0 commit comments

Comments
 (0)