Skip to content

Neo4j Cypher SSE #45

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 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
13 changes: 10 additions & 3 deletions servers/mcp-neo4j-cypher/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ RUN pip install --no-cache-dir -e .
# Environment variables for Neo4j connection
ENV NEO4J_URL="bolt://host.docker.internal:7687"
ENV NEO4J_USERNAME="neo4j"
ENV NEO4J_PASSWORD="password"
ENV NEO4J_PASSWORD="neo4j_password"
ENV NEO4J_DATABASE="neo4j"

# Command to run the server using the package entry point
CMD ["sh", "-c", "mcp-neo4j-cypher --db-url ${NEO4J_URL} --username ${NEO4J_USERNAME} --password ${NEO4J_PASSWORD}"]
# Set transport type (can be "sse" or "stdio")
ENV MCP_TRANSPORT="sse"

# Expose port for SSE transport
EXPOSE 8000

# Command to run the server using the entry point (will use transport type from MCP_TRANSPORT env var)
CMD ["python", "-m", "src.mcp_neo4j_memory.main"]
5 changes: 4 additions & 1 deletion servers/mcp-neo4j-cypher/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ dependencies = [
"mcp[cli]>=1.6.0",
"neo4j>=5.26.0",
"pydantic>=2.10.1",
"fastapi",
"uvicorn",
"pytz"
]

[build-system]
Expand All @@ -24,4 +27,4 @@ dev-dependencies = [
]

[project.scripts]
mcp-neo4j-cypher = "mcp_neo4j_cypher:main"
mcp-neo4j-cypher = "mcp_neo4j_cypher.main:main"
108 changes: 108 additions & 0 deletions servers/mcp-neo4j-cypher/src/mcp_neo4j_cypher/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import logging
import os
from datetime import datetime

import pytz
from fastapi import FastAPI

from src.server import create_mcp_server, create_neo4j_driver, healthcheck
from src.transport import TransportLayer

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp_neo4j_cypher")


async def start_server(transport_type="sse"):
"""Start the MCP server with the specified transport"""
# Get environment variables for Neo4j connection
neo4j_uri = os.environ.get(
"NEO4J_URI", os.environ.get("NEO4J_URL", "bolt://host.docker.internal:7687")
)
neo4j_user = os.environ.get("NEO4J_USERNAME", os.environ.get("NEO4J_USER", "neo4j"))
neo4j_password = os.environ.get("NEO4J_PASSWORD", "neo4j_password")
neo4j_database = os.environ.get("NEO4J_DATABASE", "neo4j")

try:
# Health check Neo4j connection
try:
healthcheck(neo4j_uri, neo4j_user, neo4j_password, neo4j_database)
except Exception as e:
logger.error(f"Failed to connect to Neo4j: {e}")
raise e

# Create Neo4j driver
neo4j_driver = await create_neo4j_driver(neo4j_uri, neo4j_user, neo4j_password)

# Create the MCP server
logger.info(f"Creating MCP server with transport type: {transport_type}")
mcp_server = create_mcp_server(neo4j_driver, neo4j_database)

# Create transport and run server
transport = TransportLayer.create_transport(transport_type)
return await transport.run_server(mcp_server)
except Exception as e:
logger.error(f"Failed to start server: {e}")
raise e


# For FastAPI app
app = FastAPI()


@app.on_event("startup")
async def startup_event():
global app
transport_type = os.environ.get("MCP_TRANSPORT", "sse")
if transport_type.lower() == "sse":
# If using SSE, mount the SSE app to this app
sse_app = await start_server("sse")

# Add health check endpoint
@app.get("/health")
def health_check():
neo4j_uri = os.environ.get(
"NEO4J_URI",
os.environ.get("NEO4J_URL", "bolt://host.docker.internal:7687"),
)
neo4j_user = os.environ.get(
"NEO4J_USERNAME", os.environ.get("NEO4J_USER", "neo4j")
)
neo4j_password = os.environ.get("NEO4J_PASSWORD", "neo4j_password")
neo4j_database = os.environ.get("NEO4J_DATABASE", "neo4j")
timestamp = datetime.now(pytz.UTC).isoformat()

try:
healthcheck(neo4j_uri, neo4j_user, neo4j_password, neo4j_database)
return {"status": "ok", "timestamp": timestamp}
except Exception as e:
logger.error(f"Failed to connect to Neo4j: {e}")
return {"status": "connection failed", "timestamp": timestamp}

# Add root endpoint
@app.get("/")
def read_root():
return {
"message": "Neo4j MCP Cypher Server",
"transport": "SSE",
"sse_endpoint": "/sse",
}

# Mount all routes from the SSE app
for route in sse_app.routes:
app.routes.append(route)
else:
# For stdio, just inform that this app should not be used
@app.get("/")
def read_root():
return {
"error": "This server is configured to use stdio transport, not HTTP/SSE",
"message": "Please run this server directly from the command line",
}

@app.get("/health")
def health_check():
return {
"status": "error",
"message": "Server is configured for stdio transport",
}
60 changes: 60 additions & 0 deletions servers/mcp-neo4j-cypher/src/mcp_neo4j_cypher/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import argparse
import asyncio
import os


async def start_stdio_server():
"""Start the server with stdio transport"""
from src.app import start_server

await start_server("stdio")


def main():
"""Command-line entry point for running the MCP server"""
parser = argparse.ArgumentParser(description="MCP Neo4j Cypher Server")
parser.add_argument(
"--transport",
choices=["sse", "stdio"],
default="sse",
help="Transport type (sse or stdio)",
)
parser.add_argument("--db-url", dest="neo4j_uri", help="Neo4j database URL")
parser.add_argument("--username", dest="neo4j_username", help="Neo4j username")
parser.add_argument("--password", dest="neo4j_password", help="Neo4j password")
parser.add_argument(
"--database", dest="neo4j_database", help="Neo4j database name", default="neo4j"
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="Port for HTTP server (when using SSE transport)",
)

args = parser.parse_args()

# Set environment variables from arguments
if args.neo4j_uri:
os.environ["NEO4J_URI"] = args.neo4j_uri
if args.neo4j_username:
os.environ["NEO4J_USERNAME"] = args.neo4j_username
if args.neo4j_password:
os.environ["NEO4J_PASSWORD"] = args.neo4j_password
if args.neo4j_database:
os.environ["NEO4J_DATABASE"] = args.neo4j_database

os.environ["MCP_TRANSPORT"] = args.transport

if args.transport == "stdio":
# Run with stdio transport
asyncio.run(start_stdio_server())
else:
# Run with SSE transport via FastAPI/Uvicorn
import uvicorn

uvicorn.run("src.app:app", host="0.0.0.0", port=args.port, reload=False)


if __name__ == "__main__":
main()
Loading