Skip to content

Commit ad3da19

Browse files
authored
Merge pull request #61 from neo4j-contrib/data-modeling-add-mermaid-viz-support
add mermaid viz support
2 parents fc98eb1 + dc660a0 commit ad3da19

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/data_model.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55
import neo4j_viz as nvl
66
from pydantic import BaseModel, Field, ValidationInfo, field_validator
77

8+
NODE_COLOR_PALETTE = [
9+
("#e3f2fd", "#1976d2"), # Light Blue / Blue
10+
("#f3e5f5", "#7b1fa2"), # Light Purple / Purple
11+
("#e8f5e8", "#388e3c"), # Light Green / Green
12+
("#fff3e0", "#f57c00"), # Light Orange / Orange
13+
("#fce4ec", "#c2185b"), # Light Pink / Pink
14+
("#e0f2f1", "#00695c"), # Light Teal / Teal
15+
("#f1f8e9", "#689f38"), # Light Lime / Lime
16+
("#fff8e1", "#ffa000"), # Light Amber / Amber
17+
("#e8eaf6", "#3f51b5"), # Light Indigo / Indigo
18+
("#efebe9", "#5d4037"), # Light Brown / Brown
19+
("#fafafa", "#424242"), # Light Grey / Dark Grey
20+
("#e1f5fe", "#0277bd"), # Light Cyan / Cyan
21+
("#f9fbe7", "#827717"), # Light Yellow-Green / Olive
22+
("#fff1f0", "#d32f2f"), # Light Red / Red
23+
("#f4e6ff", "#6a1b9a"), # Light Violet / Violet
24+
("#e6f7ff", "#1890ff"), # Very Light Blue / Bright Blue
25+
]
26+
827

928
def _generate_relationship_pattern(
1029
start_node_label: str, relationship_type: str, end_node_label: str
@@ -147,6 +166,12 @@ def to_nvl(self) -> nvl.Node:
147166
properties=self.all_properties_dict,
148167
)
149168

169+
def get_mermaid_config_str(self) -> str:
170+
"Get the Mermaid configuration string for the node."
171+
props = [f"<br/>{self.key_property.name}: {self.key_property.type} | KEY"]
172+
props.extend([f"<br/>{p.name}: {p.type}" for p in self.properties])
173+
return f'{self.label}["{self.label}{''.join(props)}"]'
174+
150175
@classmethod
151176
def from_arrows(cls, arrows_node_dict: dict[str, Any]) -> "Node":
152177
"Convert an Arrows Node to a Node."
@@ -268,6 +293,16 @@ def to_nvl(self) -> nvl.Relationship:
268293
properties=self.all_properties_dict,
269294
)
270295

296+
def get_mermaid_config_str(self) -> str:
297+
"Get the Mermaid configuration string for the relationship."
298+
props = (
299+
[f"<br/>{self.key_property.name}: {self.key_property.type} | KEY"]
300+
if self.key_property
301+
else []
302+
)
303+
props.extend([f"<br/>{p.name}: {p.type}" for p in self.properties])
304+
return f"{self.start_node_label} -->|{self.type}{''.join(props)}| {self.end_node_label}"
305+
271306
@classmethod
272307
def from_arrows(
273308
cls,
@@ -419,6 +454,33 @@ def to_nvl(self) -> nvl.VisualizationGraph:
419454
relationships=[r.to_nvl() for r in self.relationships],
420455
)
421456

457+
def _generate_mermaid_config_styling_str(self) -> str:
458+
"Generate the Mermaid configuration string for the data model."
459+
node_color_config = ""
460+
461+
for idx, node in enumerate(self.nodes):
462+
node_color_config += f"classDef node_{idx}_color fill:{NODE_COLOR_PALETTE[idx % len(NODE_COLOR_PALETTE)][0]},stroke:{NODE_COLOR_PALETTE[idx % len(NODE_COLOR_PALETTE)][1]},stroke-width:3px,color:#000,font-size:12px\nclass {node.label} node_{idx}_color\n\n"
463+
464+
return f"""
465+
%% Styling
466+
{node_color_config}
467+
"""
468+
469+
def get_mermaid_config_str(self) -> str:
470+
"Get the Mermaid configuration string for the data model."
471+
mermaid_nodes = [n.get_mermaid_config_str() for n in self.nodes]
472+
mermaid_relationships = [r.get_mermaid_config_str() for r in self.relationships]
473+
mermaid_styling = self._generate_mermaid_config_styling_str()
474+
return f"""graph TD
475+
%% Nodes
476+
{"\n".join(mermaid_nodes)}
477+
478+
%% Relationships
479+
{"\n".join(mermaid_relationships)}
480+
481+
{mermaid_styling}
482+
"""
483+
422484
@classmethod
423485
def from_arrows(cls, arrows_data_model_dict: dict[str, Any]) -> "DataModel":
424486
"Convert an Arrows Data Model to a Data Model."

servers/mcp-neo4j-data-modeling/src/mcp_neo4j_data_modeling/server.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ def validate_data_model(
101101
raise ValueError(f"Validation error: {e}")
102102

103103
@mcp.tool()
104-
def visualize_data_model(data_model: DataModel) -> None:
105-
"Open an interactive graph visualization in the default web browser. Validates the data model before opening the visualization. Warning: May not be useable in Docker environments."
104+
def visualize_data_model_in_browser(data_model: DataModel) -> None:
105+
"Open an interactive graph visualization in the default web browser. Validates the data model before opening the visualization. Warning: May not be useable in some environments such as Docker and Claude Desktop."
106106
logger.info("Validating the data model.")
107107
try:
108108
dm_validated = DataModel.model_validate(data_model, strict=True)
@@ -141,6 +141,17 @@ def export_to_arrows_json(data_model: DataModel) -> str:
141141
logger.info("Exporting the data model to the Arrows web application format.")
142142
return data_model.to_arrows_json_str()
143143

144+
@mcp.tool()
145+
def get_mermaid_config_str(data_model: DataModel) -> str:
146+
"Get the Mermaid configuration string for the data model. This may be visualized in Claude Desktop and other applications with Mermaid support."
147+
logger.info("Getting the Mermaid configuration string for the data model.")
148+
try:
149+
dm_validated = DataModel.model_validate(data_model, strict=True)
150+
except ValidationError as e:
151+
logger.error(f"Validation error: {e}")
152+
raise ValueError(f"Validation error: {e}")
153+
return dm_validated.get_mermaid_config_str()
154+
144155
return mcp
145156

146157

0 commit comments

Comments
 (0)