Skip to content

Commit 078830c

Browse files
committed
test with docker
1 parent bc60c1d commit 078830c

File tree

12 files changed

+170
-37
lines changed

12 files changed

+170
-37
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: MCP Neo4j Cypher Tests
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
paths:
7+
- 'servers/mcp-neo4j-data-modeling/**'
8+
pull_request:
9+
branches: [ main, master ]
10+
paths:
11+
- 'servers/mcp-neo4j-data-modeling/**'
12+
workflow_dispatch: # Allows manual triggering of the workflow
13+
14+
jobs:
15+
test:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@v3
20+
21+
- name: Set up Python 3.12
22+
uses: actions/setup-python@v4
23+
with:
24+
python-version: '3.12'
25+
26+
- name: Install UV
27+
run: |
28+
curl -LsSf https://astral.sh/uv/install.sh | sh
29+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
30+
31+
- name: Install dependencies
32+
run: |
33+
cd servers/mcp-neo4j-data-modeling
34+
uv venv
35+
uv pip install -e ".[dev]"
36+
37+
- name: Check format and linting
38+
run: |
39+
cd servers/mcp-neo4j-data-modeling
40+
make format
41+
42+
- name: Run tests
43+
run: |
44+
cd servers/mcp-neo4j-data-modeling
45+
make test
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# This workflow will upload a Python Package to PyPI when a release is created
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3+
4+
# This workflow uses actions that are not certified by GitHub.
5+
# They are provided by a third-party and are governed by
6+
# separate terms of service, privacy policy, and support
7+
# documentation.
8+
9+
name: Publish Neo4j MCP Data Modeling Package
10+
11+
on:
12+
push:
13+
tags:
14+
- mcp-neo4j-data-modeling-v*
15+
workflow_dispatch: # Allows manual triggering of the workflow
16+
17+
permissions:
18+
contents: read
19+
20+
jobs:
21+
release-build:
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- uses: actions/setup-python@v5
28+
with:
29+
python-version: "3.x"
30+
31+
- name: Build release distributions
32+
run: |
33+
cd servers/mcp-neo4j-data-modeling/
34+
python -m pip install build
35+
python -m build
36+
37+
- name: Upload distributions
38+
uses: actions/upload-artifact@v4
39+
with:
40+
name: release-dists
41+
path: servers/mcp-neo4j-data-modeling/dist/
42+
43+
pypi-publish:
44+
runs-on: ubuntu-latest
45+
needs:
46+
- release-build
47+
permissions:
48+
# IMPORTANT: this permission is mandatory for trusted publishing
49+
id-token: write
50+
51+
# Dedicated environments with protections for publishing are strongly recommended.
52+
# For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
53+
environment:
54+
name: pypi
55+
url: https://pypi.org/project/mcp-neo4j-data-modeling/
56+
57+
steps:
58+
59+
- uses: actions/checkout@v4
60+
61+
- name: Install uv
62+
uses: astral-sh/setup-uv@v5
63+
64+
- name: "Set up Python"
65+
uses: actions/setup-python@v5
66+
with:
67+
python-version-file: "servers/mcp-neo4j-data-modeling/pyproject.toml"
68+
69+
- name: Build release distributions
70+
run: |
71+
cd servers/mcp-neo4j-data-modeling/
72+
uv build
73+
74+
- name: Publish release
75+
env:
76+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
77+
run: |
78+
cd servers/mcp-neo4j-data-modeling/
79+
uv publish
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
FROM python:3.11-slim
1+
FROM python:3.12-slim
22

33
# Set working directory
44
WORKDIR /app
55

66
# Install build dependencies
7-
RUN pip install --no-cache-dir hatchling
7+
RUN pip install uv
88

9+
# Copy dependency files first
910
COPY pyproject.toml /app/
10-
11-
# Install dependencies
12-
RUN pip install --no-cache-dir neo4j>=5.26.0 mcp>=1.6.0
13-
11+
COPY uv.lock /app/
1412

1513
# Copy the source code
1614
COPY src/ /app/src/
1715
COPY README.md /app/
1816

19-
RUN pip install --no-cache-dir -e .
17+
# Install the package
18+
RUN uv sync
19+
2020

2121
# Command to run the server using the package entry point
22-
CMD ["sh", "-c", "mcp-neo4j-data-modeling"]
22+
CMD ["sh", "-c", "uv run mcp-neo4j-data-modeling --transport ${MCP_TRANSPORT}"]

servers/mcp-neo4j-data-modeling/Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
.PHONY: format test clean
44

55
format:
6-
uv run ruff format .
76
uv run ruff check --select I . --fix
7+
uv run ruff check --fix .
8+
uv run ruff format .
89

910
test:
10-
uv run pytest tests/ -vv
11+
uv run pytest tests/ -s
12+
13+
inspector:
14+
npx @modelcontextprotocol/inspector uv --directory src/mcp_neo4j_data_modeling run mcp-neo4j-data-modeling
1115

1216

1317
clean:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"mcpServers": {
3+
"neo4j-data-modeling": {
4+
"command": "docker",
5+
"args": [
6+
"run",
7+
"--rm",
8+
"-i",
9+
"--network=host",
10+
"-v", "${PWD}:/app",
11+
"-e", "MCP_TRANSPORT=stdio",
12+
"$(docker build -q -f ${PWD}/Dockerfile ${PWD})"
13+
]
14+
}
15+
}
16+
}

servers/mcp-neo4j-data-modeling/inspector.sh

Lines changed: 0 additions & 2 deletions
This file was deleted.

servers/mcp-neo4j-data-modeling/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ readme = "README.md"
66
requires-python = ">=3.10"
77
dependencies = [
88
"mcp[cli]>=1.6.0",
9-
"neo4j>=5.26.0",
109
"neo4j-viz>=0.3.1",
1110
"pydantic>=2.10.1",
1211
]

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
11
import argparse
22
import asyncio
3-
import os
43

54
from . import server
65

76

87
def main():
98
"""Main entry point for the package."""
10-
parser = argparse.ArgumentParser(description="Neo4j data Modeling MCP Server")
11-
# parser.add_argument("--db-url", default=None, help="Neo4j connection URL")
12-
# parser.add_argument("--username", default=None, help="Neo4j username")
13-
# parser.add_argument("--password", default=None, help="Neo4j password")
14-
# parser.add_argument("--database", default=None, help="Neo4j database name")
9+
parser = argparse.ArgumentParser(description="Neo4j Data Modeling MCP Server")
1510
parser.add_argument("--transport", default="stdio", help="Transport type")
1611

1712
args = parser.parse_args()
1813
asyncio.run(
1914
server.main(
20-
# args.db_url or os.getenv("NEO4J_URL") or os.getenv("NEO4J_URI", "bolt://localhost:7687"),
21-
# args.username or os.getenv("NEO4J_USERNAME", "neo4j"),
22-
# args.password or os.getenv("NEO4J_PASSWORD", "password"),
23-
# args.database or os.getenv("NEO4J_DATABASE", "neo4j"),
2415
args.transport,
2516
)
2617
)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class Property(BaseModel):
3333
"A Neo4j Property."
3434

3535
name: str = Field(description="The name of the property. Should be in camelCase.")
36-
type: str = Field(default="STRING", description="The Neo4j type of the property")
36+
type: str = Field(default="STRING", description="The Neo4j type of the property. Should be all caps.")
3737
source: PropertySource | None = Field(
3838
default=None, description="The source of the property, if known."
3939
)
@@ -84,7 +84,7 @@ def to_arrows(self, is_key: bool = False) -> dict[str, Any]:
8484
class Node(BaseModel):
8585
"A Neo4j Node."
8686

87-
label: str = Field(description="The label of the node. Should be in PascalCase.")
87+
label: str = Field(description="The label of the node. Should be in PascalCase.", min_length=1)
8888
key_property: Property = Field(description="The key property of the node")
8989
properties: list[Property] = Field(
9090
default_factory=list, description="The properties of the node"
@@ -189,7 +189,7 @@ class Relationship(BaseModel):
189189
"A Neo4j Relationship."
190190

191191
type: str = Field(
192-
description="The type of the relationship. Should be in SCREAMING_SNAKE_CASE."
192+
description="The type of the relationship. Should be in SCREAMING_SNAKE_CASE.", min_length=1
193193
)
194194
start_node_label: str = Field(description="The label of the start node")
195195
end_node_label: str = Field(description="The label of the end node")

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any, Literal
55

66
from mcp.server.fastmcp import FastMCP
7+
from mcp import types as mcp_types
78
from pydantic import ValidationError
89

910
from .data_model import (
@@ -55,44 +56,47 @@ def data_model_schema() -> dict[str, Any]:
5556
return DataModel.model_json_schema()
5657

5758
@mcp.tool()
58-
def validate_node(node: Node) -> bool:
59+
def validate_node(node: Node) -> list[mcp_types.TextContent]:
5960
"Validate a single node. Returns True if the node is valid, otherwise raises a ValueError."
6061
logger.info("Validating a single node.")
6162
try:
6263
Node.model_validate(node, strict=True)
64+
logger.info(f"Node validated successfully")
65+
return True
6366
except ValidationError as e:
6467
logger.error(f"Validation error: {e}")
6568
raise ValueError(f"Validation error: {e}")
66-
logger.info("Node validated successfully.")
67-
return True
69+
6870

6971
@mcp.tool()
7072
def validate_relationship(relationship: Relationship) -> bool:
7173
"Validate a single relationship. Returns True if the relationship is valid, otherwise raises a ValueError."
7274
logger.info("Validating a single relationship.")
7375
try:
7476
Relationship.model_validate(relationship, strict=True)
77+
logger.info(f"Relationship validated successfully")
78+
return True
7579
except ValidationError as e:
7680
logger.error(f"Validation error: {e}")
7781
raise ValueError(f"Validation error: {e}")
78-
logger.info("Relationship validated successfully.")
79-
return True
82+
8083

8184
@mcp.tool()
8285
def validate_data_model(data_model: DataModel) -> bool:
8386
"Validate the entire data model. Returns True if the data model is valid, otherwise raises a ValueError."
8487
logger.info("Validating the entire data model.")
8588
try:
8689
DataModel.model_validate(data_model, strict=True)
90+
logger.info(f"Data model validated successfully")
91+
return True
8792
except ValidationError as e:
8893
logger.error(f"Validation error: {e}")
8994
raise ValueError(f"Validation error: {e}")
90-
logger.info("Data model validated successfully.")
91-
return True
95+
9296

9397
@mcp.tool()
9498
def visualize_data_model(data_model: DataModel) -> None:
95-
"Open an interactive graph visualization in the default web browser."
99+
"Open an interactive graph visualization in the default web browser. Warning: May not be useable in Docker environments."
96100
logger.info("Validating the data model.")
97101
try:
98102
dm_validated = DataModel.model_validate(data_model, strict=True)

0 commit comments

Comments
 (0)