Skip to content

Commit 6d359e5

Browse files
committed
fix search_node method, update IT, fix cli and env args
1 parent e156fc2 commit 6d359e5

File tree

10 files changed

+177
-146
lines changed

10 files changed

+177
-146
lines changed

servers/mcp-neo4j-memory/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
## Next
22

33
### Fixed
4+
* Fix bug in `search_nodes` method where query arg wasn't passed properly
45

56
### Changed
7+
* Implement FastMCP with function decorators to simplify server code
8+
* Add HTTP transport option
69

710
### Added
811

servers/mcp-neo4j-memory/Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
install-dev:
2-
python3 -m uv pip install -e .
2+
uv run python3 -m uv pip install -e .
33

44
test-unit:
5-
python3 -m pytest tests/unit/ -v
5+
uv run python3 -m pytest tests/unit/ -v
66

77
test-integration:
8-
python3 -m pytest tests/integration/ -v
8+
uv run python3 -m pytest tests/integration/ -v
99

1010
test-http:
11-
python3 -m pytest tests/integration/test_http_transport.py -v
11+
uv run python3 -m pytest tests/integration/test_http_transport.py -v
1212

1313
test-all:
14-
python3 -m pytest tests/ -v
14+
uv run python3 -m pytest tests/ -v
1515

1616
all: install-dev test-all

servers/mcp-neo4j-memory/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ Environment variables for HTTP configuration:
163163

164164
```bash
165165
export NEO4J_TRANSPORT=http
166-
export NEO4J_HTTP_HOST=0.0.0.0
167-
export NEO4J_HTTP_PORT=8080
168-
export NEO4J_HTTP_PATH=/api/mcp/
166+
export NEO4J_MCP_SERVER_HOST=0.0.0.0
167+
export NEO4J_MCP_SERVER_PORT=8080
168+
export NEO4J_MCP_SERVER_PATH=/api/mcp/
169169
mcp-neo4j-memory
170170
```
171171

servers/mcp-neo4j-memory/src/mcp_neo4j_memory/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ def main():
2020
default=os.getenv("NEO4J_DATABASE", "neo4j"),
2121
help="Neo4j database name")
2222
parser.add_argument("--transport", default="stdio", help="Transport type (stdio, sse, http)")
23-
parser.add_argument("--host", default=None, help="HTTP host (default: 127.0.0.1)")
24-
parser.add_argument("--port", type=int, default=None, help="HTTP port (default: 8000)")
25-
parser.add_argument("--path", default=None, help="HTTP path (default: /mcp/)")
23+
parser.add_argument("--server-host", default=None, help="HTTP host (default: 127.0.0.1)")
24+
parser.add_argument("--server-port", type=int, default=None, help="HTTP port (default: 8000)")
25+
parser.add_argument("--server-path", default=None, help="HTTP path (default: /mcp/)")
2626

2727
args = parser.parse_args()
2828
asyncio.run(server.main(
@@ -31,9 +31,9 @@ def main():
3131
args.password,
3232
args.database,
3333
args.transport or os.getenv("NEO4J_TRANSPORT", "stdio"),
34-
args.host or os.getenv("NEO4J_HTTP_HOST", "127.0.0.1"),
35-
args.port or int(os.getenv("NEO4J_HTTP_PORT", "8000")),
36-
args.path or os.getenv("NEO4J_HTTP_PATH", "/mcp/"),
34+
args.server_host or os.getenv("NEO4J_MCP_SERVER_HOST", "127.0.0.1"),
35+
args.server_port or int(os.getenv("NEO4J_MCP_SERVER_PORT", "8000")),
36+
args.server_path or os.getenv("NEO4J_MCP_SERVER_PATH", "/mcp/"),
3737
))
3838

3939

servers/mcp-neo4j-memory/src/mcp_neo4j_memory/server.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
import os
21
import logging
3-
import json
4-
from typing import Any, Dict, List, Optional, Literal
5-
from contextlib import asynccontextmanager
2+
from typing import Any, Dict, List, Literal
63

7-
import neo4j
84
from neo4j import AsyncGraphDatabase
95
from pydantic import BaseModel, Field
106

11-
from fastmcp.resources.types import TextResource
127
from fastmcp.server import FastMCP
138

149
# Set up logging
@@ -185,9 +180,9 @@ async def search_nodes(self, query: str) -> KnowledgeGraph:
185180
# Fallback to simple text search
186181
result = await session.run("""
187182
MATCH (e:Entity)
188-
WHERE e.name CONTAINS $query OR e.type CONTAINS $query OR ANY(obs IN e.observations WHERE obs CONTAINS $query)
183+
WHERE e.name CONTAINS $q OR e.type CONTAINS $q OR ANY(obs IN e.observations WHERE obs CONTAINS $q)
189184
RETURN e.name as name, e.type as type, e.observations as observations
190-
""", query=query)
185+
""", q=query)
191186
logger.debug("Using fallback text search")
192187

193188
entities = []
@@ -385,14 +380,15 @@ async def main(
385380

386381
# Run the server with the specified transport
387382
logger.info(f"Starting server with transport: {transport}")
388-
if transport == "http":
389-
logger.info(f"HTTP server starting on {host}:{port}{path}")
390-
await mcp.run_http_async(host=host, port=port, path=path)
391-
elif transport == "stdio":
392-
logger.info("STDIO server starting")
393-
await mcp.run_stdio_async()
394-
elif transport == "sse":
395-
logger.info(f"SSE server starting on {host}:{port}{path}")
396-
await mcp.run_sse_async(host=host, port=port, path=path)
397-
else:
398-
raise ValueError(f"Unsupported transport: {transport}")
383+
match transport:
384+
case "http":
385+
logger.info(f"HTTP server starting on {host}:{port}{path}")
386+
await mcp.run_http_async(host=host, port=port, path=path)
387+
case "stdio":
388+
logger.info("STDIO server starting")
389+
await mcp.run_stdio_async()
390+
case "sse":
391+
logger.info(f"SSE server starting on {host}:{port}{path}")
392+
await mcp.run_sse_async(host=host, port=port, path=path)
393+
case _:
394+
raise ValueError(f"Unsupported transport: {transport}")

servers/mcp-neo4j-memory/tests/integration/conftest.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import asyncio
12
import os
3+
import subprocess
24
from typing import Any
35

46
import pytest
57
import pytest_asyncio
68
from neo4j import GraphDatabase
79
from testcontainers.neo4j import Neo4jContainer
810

11+
from mcp_neo4j_memory.server import Neo4jMemory, create_mcp_server
12+
913
neo4j = (
1014
Neo4jContainer("neo4j:latest")
1115
.with_env("NEO4J_apoc_export_file_enabled", "true")
@@ -37,4 +41,49 @@ async def async_neo4j_driver(setup: Neo4jContainer):
3741
try:
3842
yield driver
3943
finally:
40-
await driver.close()
44+
await driver.close()
45+
46+
@pytest.fixture
47+
def memory(neo4j_driver):
48+
"""Create a memory instance."""
49+
return Neo4jMemory(neo4j_driver)
50+
51+
@pytest.fixture
52+
def mcp_server(neo4j_driver, memory):
53+
"""Create an MCP server instance."""
54+
return create_mcp_server(neo4j_driver, memory)
55+
56+
57+
@pytest_asyncio.fixture
58+
async def sse_server(setup: Neo4jContainer):
59+
"""Start the MCP server in SSE mode."""
60+
61+
62+
process = await asyncio.create_subprocess_exec(
63+
"uv", "run", "mcp-neo4j-memory",
64+
"--transport", "sse",
65+
"--server-host", "127.0.0.1",
66+
"--server-port", "8002",
67+
"--db-url", setup.get_connection_url(),
68+
"--username", setup.username,
69+
"--password", setup.password,
70+
"--database", "neo4j",
71+
stdout=subprocess.PIPE,
72+
stderr=subprocess.PIPE,
73+
cwd=os.getcwd()
74+
)
75+
76+
await asyncio.sleep(3)
77+
78+
if process.returncode is not None:
79+
stdout, stderr = await process.communicate()
80+
raise RuntimeError(f"Server failed to start. stdout: {stdout.decode()}, stderr: {stderr.decode()}")
81+
82+
yield process
83+
84+
try:
85+
process.terminate()
86+
await asyncio.wait_for(process.wait(), timeout=5.0)
87+
except asyncio.TimeoutError:
88+
process.kill()
89+
await process.wait()

0 commit comments

Comments
 (0)