Skip to content

Commit 4bfa459

Browse files
authored
aura-manager - fix config parsing, remote deployments use stateful http to be consis… (#178)
* fix config parsing, remote deployments use stateful http to be consistent with previous config * update unit tests * update middleware IT to expect stateful behavior
1 parent c3a0a40 commit 4bfa459

File tree

7 files changed

+78
-46
lines changed

7 files changed

+78
-46
lines changed

servers/mcp-neo4j-cloud-aura-api/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* f-string bug in utils.py patched for earlier Python versions
55

66
### Changed
7+
* Use `stateless_http=False` when using `http` or `sse` transport to be consistent with previous configuration
78

89
### Added
910

servers/mcp-neo4j-cloud-aura-api/Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ COPY README.md /app/
2020
RUN pip install --no-cache-dir -e .
2121

2222
# Environment variables for Neo4j Aura API credentials
23-
ENV NEO4J_AURA_CLIENT_ID=""
24-
ENV NEO4J_AURA_CLIENT_SECRET=""
23+
ENV NEO4J_AURA_CLIENT_ID="test-client-id"
24+
ENV NEO4J_AURA_CLIENT_SECRET="test-client-secret"
2525
ENV NEO4J_TRANSPORT="stdio"
26-
ENV NEO4J_MCP_SERVER_HOST="127.0.0.1"
26+
ENV NEO4J_MCP_SERVER_HOST="0.0.0.0"
2727
ENV NEO4J_MCP_SERVER_PORT=8000
2828
ENV NEO4J_MCP_SERVER_PATH="/mcp/"
2929
ENV NEO4J_MCP_SERVER_ALLOW_ORIGINS=""

servers/mcp-neo4j-cloud-aura-api/src/mcp_neo4j_aura_manager/__init__.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import sys
77

8+
from .utils import process_config
89

910
# Configure logging
1011
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
@@ -35,28 +36,10 @@ def main():
3536

3637
args = parser.parse_args()
3738

38-
if not args.client_id or not args.client_secret:
39-
logger.error("Client ID and Client Secret are required. Provide them as arguments or environment variables.")
40-
sys.exit(1)
41-
42-
# Parse security arguments
43-
allow_origins_str = args.allow_origins or os.getenv("NEO4J_MCP_SERVER_ALLOW_ORIGINS", "")
44-
allow_origins = [origin.strip() for origin in allow_origins_str.split(",") if origin.strip()] if allow_origins_str else []
45-
46-
allowed_hosts_str = args.allowed_hosts or os.getenv("NEO4J_MCP_SERVER_ALLOWED_HOSTS", "localhost,127.0.0.1")
47-
allowed_hosts = [host.strip() for host in allowed_hosts_str.split(",") if host.strip()] if allowed_hosts_str else []
39+
config = process_config(args)
4840

4941
try:
50-
asyncio.run(server.main(
51-
args.client_id,
52-
args.client_secret,
53-
args.transport or os.getenv("NEO4J_TRANSPORT", "stdio"),
54-
args.server_host or os.getenv("NEO4J_MCP_SERVER_HOST", "127.0.0.1"),
55-
int(args.server_port or os.getenv("NEO4J_MCP_SERVER_PORT", 8000)),
56-
args.server_path or os.getenv("NEO4J_MCP_SERVER_PATH", "/mcp/"),
57-
allow_origins,
58-
allowed_hosts,
59-
))
42+
asyncio.run(server.main(**config))
6043
except KeyboardInterrupt:
6144
logger.info("Server stopped by user")
6245
except Exception as e:

servers/mcp-neo4j-cloud-aura-api/src/mcp_neo4j_aura_manager/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ async def main(
220220
f"Running Neo4j Aura Manager MCP Server with HTTP transport on {host}:{port}..."
221221
)
222222
await mcp.run_http_async(
223-
host=host, port=port, path=path, middleware=custom_middleware, stateless_http=True
223+
host=host, port=port, path=path, middleware=custom_middleware, stateless_http=False
224224
)
225225
case "stdio":
226226
logger.info("Running Neo4j Aura Manager MCP Server with stdio transport...")
@@ -229,7 +229,7 @@ async def main(
229229
logger.info(
230230
f"Running Neo4j Aura Manager MCP Server with SSE transport on {host}:{port}..."
231231
)
232-
await mcp.run_http_async(host=host, port=port, path=path, middleware=custom_middleware, transport="sse", stateless_http=True)
232+
await mcp.run_http_async(host=host, port=port, path=path, middleware=custom_middleware, transport="sse", stateless_http=False)
233233
case _:
234234
logger.error(
235235
f"Invalid transport: {transport} | Must be either 'stdio', 'sse', or 'http'"

servers/mcp-neo4j-cloud-aura-api/src/mcp_neo4j_aura_manager/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,9 @@ def process_config(args: argparse.Namespace) -> dict[str, Union[str, int, None]]
323323

324324
# server configuration
325325
config["transport"] = parse_transport(args)
326-
config["server_host"] = parse_server_host(args, config["transport"])
327-
config["server_port"] = parse_server_port(args, config["transport"])
328-
config["server_path"] = parse_server_path(args, config["transport"])
326+
config["host"] = parse_server_host(args, config["transport"])
327+
config["port"] = parse_server_port(args, config["transport"])
328+
config["path"] = parse_server_path(args, config["transport"])
329329

330330
# middleware configuration
331331
config["allow_origins"] = parse_allow_origins(args)

servers/mcp-neo4j-cloud-aura-api/tests/integration/test_http_transport_IT.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,13 +304,30 @@ async def test_cors_preflight_malicious_origin_blocked(self, middleware_test_ser
304304
async def test_cors_actual_request_no_cors_headers(self, middleware_test_server):
305305
"""Test actual request without Origin header (should work - not CORS)."""
306306
async with aiohttp.ClientSession() as session:
307+
# First, initiate session with the MCP server
307308
async with session.post(
308309
"http://127.0.0.1:8005/mcp/",
309-
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
310+
json={"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}}},
310311
headers={
311312
"Accept": "application/json, text/event-stream",
312313
"Content-Type": "application/json",
313314
},
315+
) as init_response:
316+
# Get session ID from response headers
317+
session_id = init_response.headers.get("mcp-session-id")
318+
319+
# Now make the actual test request with session ID
320+
headers = {
321+
"Accept": "application/json, text/event-stream",
322+
"Content-Type": "application/json",
323+
}
324+
if session_id:
325+
headers["mcp-session-id"] = session_id
326+
327+
async with session.post(
328+
"http://127.0.0.1:8005/mcp/",
329+
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
330+
headers=headers,
314331
) as response:
315332
# The server will fail to authenticate with dummy credentials, but that's ok
316333
# We just want to test that the middleware allows the request through
@@ -321,14 +338,30 @@ async def test_cors_actual_request_no_cors_headers(self, middleware_test_server)
321338
async def test_cors_actual_request_with_origin_blocked(self, middleware_test_server):
322339
"""Test actual CORS request with origin header (should work but no CORS headers)."""
323340
async with aiohttp.ClientSession() as session:
341+
# First, initiate session with the MCP server
324342
async with session.post(
325343
"http://127.0.0.1:8005/mcp/",
326-
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
344+
json={"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}}},
327345
headers={
328346
"Accept": "application/json, text/event-stream",
329347
"Content-Type": "application/json",
330-
"Origin": "http://localhost:3000",
331348
},
349+
) as init_response:
350+
session_id = init_response.headers.get("mcp-session-id")
351+
352+
# Now make the actual test request with session ID and Origin header
353+
headers = {
354+
"Accept": "application/json, text/event-stream",
355+
"Content-Type": "application/json",
356+
"Origin": "http://localhost:3000",
357+
}
358+
if session_id:
359+
headers["mcp-session-id"] = session_id
360+
361+
async with session.post(
362+
"http://127.0.0.1:8005/mcp/",
363+
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
364+
headers=headers,
332365
) as response:
333366
# Request should still work (server processes it) but no CORS headers
334367
assert response.status in [200, 401, 500] # Any non-CORS error is fine
@@ -340,15 +373,30 @@ async def test_cors_actual_request_with_origin_blocked(self, middleware_test_ser
340373
async def test_dns_rebinding_protection_trusted_hosts(self, middleware_test_server):
341374
"""Test DNS rebinding protection with TrustedHostMiddleware - allowed hosts."""
342375
async with aiohttp.ClientSession() as session:
343-
# Test with localhost - should be allowed (in default allowed_hosts)
376+
# First, initiate session with the MCP server
344377
async with session.post(
345378
"http://127.0.0.1:8005/mcp/",
346-
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
379+
json={"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}}},
347380
headers={
348381
"Accept": "application/json, text/event-stream",
349382
"Content-Type": "application/json",
350-
"Host": "localhost:8005",
351383
},
384+
) as init_response:
385+
session_id = init_response.headers.get("mcp-session-id")
386+
387+
# Test with localhost - should be allowed (in default allowed_hosts)
388+
headers = {
389+
"Accept": "application/json, text/event-stream",
390+
"Content-Type": "application/json",
391+
"Host": "localhost:8005",
392+
}
393+
if session_id:
394+
headers["mcp-session-id"] = session_id
395+
396+
async with session.post(
397+
"http://127.0.0.1:8005/mcp/",
398+
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
399+
headers=headers,
352400
) as response:
353401
print(f"Trusted host (localhost) response status: {response.status}")
354402
print(f"Trusted host (localhost) response headers: {dict(response.headers)}")

servers/mcp-neo4j-cloud-aura-api/tests/unit/test_utils.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -552,9 +552,9 @@ def test_process_config_all_provided(self, clean_env, args_factory):
552552
assert config["client_id"] == "test-client-id"
553553
assert config["client_secret"] == "test-client-secret"
554554
assert config["transport"] == "http"
555-
assert config["server_host"] == "test-host"
556-
assert config["server_port"] == 9000
557-
assert config["server_path"] == "/test/"
555+
assert config["host"] == "test-host"
556+
assert config["port"] == 9000
557+
assert config["path"] == "/test/"
558558
assert config["allow_origins"] == ["http://localhost:3000"]
559559
assert config["allowed_hosts"] == ["example.com", "www.example.com"]
560560

@@ -575,9 +575,9 @@ def test_process_config_env_vars(self, clean_env, args_factory):
575575
assert config["client_id"] == "env-client-id"
576576
assert config["client_secret"] == "env-client-secret"
577577
assert config["transport"] == "sse"
578-
assert config["server_host"] == "env-host"
579-
assert config["server_port"] == 8080
580-
assert config["server_path"] == "/env/"
578+
assert config["host"] == "env-host"
579+
assert config["port"] == 8080
580+
assert config["path"] == "/env/"
581581
assert config["allow_origins"] == ["http://env.com", "https://env.com"]
582582
assert config["allowed_hosts"] == ["env.com", "www.env.com"]
583583

@@ -593,9 +593,9 @@ def test_process_config_defaults(self, clean_env, args_factory, mock_logger):
593593
assert config["client_id"] == "test-client-id"
594594
assert config["client_secret"] == "test-client-secret"
595595
assert config["transport"] == "stdio" # default
596-
assert config["server_host"] is None # None for stdio
597-
assert config["server_port"] is None # None for stdio
598-
assert config["server_path"] is None # None for stdio
596+
assert config["host"] is None # None for stdio
597+
assert config["port"] is None # None for stdio
598+
assert config["path"] is None # None for stdio
599599
assert config["allow_origins"] == [] # default empty
600600
assert config["allowed_hosts"] == ["localhost", "127.0.0.1"] # default secure
601601

@@ -627,6 +627,6 @@ def test_process_config_transport_scenarios(
627627
config = process_config(args)
628628

629629
assert config["transport"] == transport
630-
assert config["server_host"] == expected_host
631-
assert config["server_port"] == expected_port
632-
assert config["server_path"] == expected_path
630+
assert config["host"] == expected_host
631+
assert config["port"] == expected_port
632+
assert config["path"] == expected_path

0 commit comments

Comments
 (0)