11import logging
2+ import argparse
3+ import os
4+ from typing import Union , Literal
25
36logging .basicConfig (level = logging .INFO , format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
47
8+ ALLOWED_TRANSPORTS = ["stdio" , "http" , "sse" ]
9+
510def get_logger (name : str ) -> logging .Logger :
611 """Get a logger instance with consistent configuration."""
712 return logging .getLogger (name )
813
14+ logger = get_logger (__name__ )
15+
916def _validate_region (cloud_provider : str , region : str ) -> None :
1017 """
1118 Validate the region exists for the given cloud provider.
@@ -26,4 +33,302 @@ def _validate_region(cloud_provider: str, region: str) -> None:
2633 elif cloud_provider == "aws" and region .count ("-" ) != 2 :
2734 raise ValueError (f"Invalid region for AWS: { region } . Must follow the format 'region-zone-number'. Refer to https://neo4j.com/docs/aura/managing-instances/regions/ for valid regions." )
2835 elif cloud_provider == "azure" and region .count ("-" ) != 0 :
29- raise ValueError (f"Invalid region for Azure: { region } . Must follow the format 'regionzone'. Refer to https://neo4j.com/docs/aura/managing-instances/regions/ for valid regions." )
36+ raise ValueError (f"Invalid region for Azure: { region } . Must follow the format 'regionzone'. Refer to https://neo4j.com/docs/aura/managing-instances/regions/ for valid regions." )
37+
38+
39+ def parse_client_id (args : argparse .Namespace ) -> str :
40+ """
41+ Parse the client id from the command line arguments or environment variables.
42+
43+ Parameters
44+ ----------
45+ args : argparse.Namespace
46+ The command line arguments.
47+
48+ Returns
49+ -------
50+ client_id : str
51+ The client id.
52+
53+ Raises
54+ ------
55+ ValueError: If no client id is provided.
56+ """
57+ if args .client_id is not None :
58+ return args .client_id
59+ else :
60+ if os .getenv ("NEO4J_AURA_CLIENT_ID" ) is not None :
61+ return os .getenv ("NEO4J_AURA_CLIENT_ID" )
62+ else :
63+ logger .error ("Error: No Neo4j Aura Client ID provided. Please provide it as an argument or environment variable." )
64+ raise ValueError ("No Neo4j Aura Client ID provided. Please provide it as an argument or environment variable." )
65+
66+ def parse_client_secret (args : argparse .Namespace ) -> str :
67+ """
68+ Parse the client secret from the command line arguments or environment variables.
69+
70+ Parameters
71+ ----------
72+ args : argparse.Namespace
73+ The command line arguments.
74+
75+ Returns
76+ -------
77+ client_secret : str
78+ The client secret.
79+
80+ Raises
81+ ------
82+ ValueError: If no client secret is provided.
83+ """
84+ if args .client_secret is not None :
85+ return args .client_secret
86+ else :
87+ if os .getenv ("NEO4J_AURA_CLIENT_SECRET" ) is not None :
88+ return os .getenv ("NEO4J_AURA_CLIENT_SECRET" )
89+ else :
90+ logger .error ("Error: No Neo4j Aura Client Secret provided. Please provide it as an argument or environment variable." )
91+ raise ValueError ("No Neo4j Aura Client Secret provided. Please provide it as an argument or environment variable." )
92+
93+ def parse_transport (args : argparse .Namespace ) -> Literal ["stdio" , "http" , "sse" ]:
94+ """
95+ Parse the transport from the command line arguments or environment variables.
96+
97+ Parameters
98+ ----------
99+ args : argparse.Namespace
100+ The command line arguments.
101+
102+ Returns
103+ -------
104+ transport : str
105+ The transport.
106+
107+ Raises
108+ ------
109+ ValueError: If no transport is provided or is invalid.
110+ """
111+
112+ # parse transport
113+ if args .transport is not None :
114+ if args .transport not in ALLOWED_TRANSPORTS :
115+ logger .error (f"Invalid transport: { args .transport } . Allowed transports are: { ALLOWED_TRANSPORTS } " )
116+ raise ValueError (f"Invalid transport: { args .transport } . Allowed transports are: { ALLOWED_TRANSPORTS } " )
117+ return args .transport
118+ else :
119+ if os .getenv ("NEO4J_TRANSPORT" ) is not None :
120+ if os .getenv ("NEO4J_TRANSPORT" ) not in ALLOWED_TRANSPORTS :
121+ logger .error (f"Invalid transport: { os .getenv ("NEO4J_TRANSPORT" )} . Allowed transports are: { ALLOWED_TRANSPORTS } " )
122+ raise ValueError (f"Invalid transport: { os .getenv ("NEO4J_TRANSPORT" )} . Allowed transports are: { ALLOWED_TRANSPORTS } " )
123+ return os .getenv ("NEO4J_TRANSPORT" )
124+ else :
125+ logger .info ("Info: No transport type provided. Using default: stdio" )
126+ return "stdio"
127+
128+ def parse_server_host (args : argparse .Namespace , transport : Literal ["stdio" , "http" , "sse" ]) -> str :
129+ """
130+ Parse the server host from the command line arguments or environment variables.
131+
132+ Parameters
133+ ----------
134+ args : argparse.Namespace
135+ The command line arguments.
136+ transport : Literal["stdio", "http", "sse"]
137+ The transport.
138+
139+ Returns
140+ -------
141+ server_host : str
142+ The server host.
143+ """
144+ # check cli argument
145+ if args .server_host is not None :
146+ if transport == "stdio" :
147+ logger .warning ("Warning: Server host provided, but transport is `stdio`. The `server_host` argument will be set, but ignored." )
148+ return args .server_host
149+ # check environment variable
150+ else :
151+ # if environment variable exists
152+ if os .getenv ("NEO4J_MCP_SERVER_HOST" ) is not None :
153+ if transport == "stdio" :
154+ logger .warning ("Warning: Server host provided, but transport is `stdio`. The `NEO4J_MCP_SERVER_HOST` environment variable will be set, but ignored." )
155+ return os .getenv ("NEO4J_MCP_SERVER_HOST" )
156+ # if environment variable does not exist and not using stdio transport
157+ elif transport != "stdio" :
158+ logger .warning ("Warning: No server host provided and transport is not `stdio`. Using default server host: 127.0.0.1" )
159+ return "127.0.0.1"
160+ # if environment variable does not exist and using stdio transport
161+ else :
162+ logger .info ("Info: No server host provided and transport is `stdio`. `server_host` will be None." )
163+ return None
164+
165+ def parse_server_port (args : argparse .Namespace , transport : Literal ["stdio" , "http" , "sse" ]) -> int :
166+ """
167+ Parse the server port from the command line arguments or environment variables.
168+
169+ Parameters
170+ ----------
171+ args : argparse.Namespace
172+ The command line arguments.
173+ transport : Literal["stdio", "http", "sse"]
174+ The transport.
175+
176+ Returns
177+ -------
178+ server_port : int
179+ The server port.
180+ """
181+ # check cli argument
182+ if args .server_port is not None :
183+ if transport == "stdio" :
184+ logger .warning ("Warning: Server port provided, but transport is `stdio`. The `server_port` argument will be set, but ignored." )
185+ return args .server_port
186+ # check environment variable
187+ else :
188+ # if environment variable exists
189+ if os .getenv ("NEO4J_MCP_SERVER_PORT" ) is not None :
190+ if transport == "stdio" :
191+ logger .warning ("Warning: Server port provided, but transport is `stdio`. The `NEO4J_MCP_SERVER_PORT` environment variable will be set, but ignored." )
192+ return int (os .getenv ("NEO4J_MCP_SERVER_PORT" ))
193+ # if environment variable does not exist and not using stdio transport
194+ elif transport != "stdio" :
195+ logger .warning ("Warning: No server port provided and transport is not `stdio`. Using default server port: 8000" )
196+ return 8000
197+ # if environment variable does not exist and using stdio transport
198+ else :
199+ logger .info ("Info: No server port provided and transport is `stdio`. `server_port` will be None." )
200+ return None
201+
202+ def parse_server_path (args : argparse .Namespace , transport : Literal ["stdio" , "http" , "sse" ]) -> str :
203+ """
204+ Parse the server path from the command line arguments or environment variables.
205+
206+ Parameters
207+ ----------
208+ args : argparse.Namespace
209+ The command line arguments.
210+ transport : Literal["stdio", "http", "sse"]
211+ The transport.
212+
213+ Returns
214+ -------
215+ server_path : str
216+ The server path.
217+ """
218+ # check cli argument
219+ if args .server_path is not None :
220+ if transport == "stdio" :
221+ logger .warning ("Warning: Server path provided, but transport is `stdio`. The `server_path` argument will be set, but ignored." )
222+ return args .server_path
223+ # check environment variable
224+ else :
225+ # if environment variable exists
226+ if os .getenv ("NEO4J_MCP_SERVER_PATH" ) is not None :
227+ if transport == "stdio" :
228+ logger .warning ("Warning: Server path provided, but transport is `stdio`. The `NEO4J_MCP_SERVER_PATH` environment variable will be set, but ignored." )
229+ return os .getenv ("NEO4J_MCP_SERVER_PATH" )
230+ # if environment variable does not exist and not using stdio transport
231+ elif transport != "stdio" :
232+ logger .warning ("Warning: No server path provided and transport is not `stdio`. Using default server path: /mcp/" )
233+ return "/mcp/"
234+ # if environment variable does not exist and using stdio transport
235+ else :
236+ logger .info ("Info: No server path provided and transport is `stdio`. `server_path` will be None." )
237+ return None
238+
239+ def parse_allow_origins (args : argparse .Namespace ) -> list [str ]:
240+ """
241+ Parse the allow origins from the command line arguments or environment variables.
242+
243+ Parameters
244+ ----------
245+ args : argparse.Namespace
246+ The command line arguments.
247+
248+ Returns
249+ -------
250+ allow_origins : list[str]
251+ The allow origins.
252+ """
253+ # check cli argument
254+ if args .allow_origins is not None :
255+ # Handle comma-separated string from CLI
256+ return [origin .strip () for origin in args .allow_origins .split ("," ) if origin .strip ()]
257+ # check environment variable.
258+ else :
259+ if os .getenv ("NEO4J_MCP_SERVER_ALLOW_ORIGINS" ) is not None :
260+ # split comma-separated string into list.
261+ return [
262+ origin .strip () for origin in os .getenv ("NEO4J_MCP_SERVER_ALLOW_ORIGINS" , "" ).split ("," )
263+ if origin .strip ()
264+ ]
265+ else :
266+ logger .info ("Info: No allow origins provided. Defaulting to no allowed origins." )
267+ return list ()
268+
269+ def parse_allowed_hosts (args : argparse .Namespace ) -> list [str ]:
270+ """
271+ Parse the allowed hosts from the command line arguments or environment variables.
272+
273+ Parameters
274+ ----------
275+ args : argparse.Namespace
276+ The command line arguments.
277+
278+ Returns
279+ -------
280+ allowed_hosts : list[str]
281+ The allowed hosts.
282+ """
283+ # check cli argument
284+ if args .allowed_hosts is not None :
285+ # Handle comma-separated string from CLI
286+ return [host .strip () for host in args .allowed_hosts .split ("," ) if host .strip ()]
287+
288+ else :
289+ if os .getenv ("NEO4J_MCP_SERVER_ALLOWED_HOSTS" ) is not None :
290+ # split comma-separated string into list
291+ return [
292+ host .strip () for host in os .getenv ("NEO4J_MCP_SERVER_ALLOWED_HOSTS" , "" ).split ("," )
293+ if host .strip ()
294+ ]
295+ else :
296+ logger .info (
297+ "Info: No allowed hosts provided. Defaulting to secure mode - only localhost and 127.0.0.1 allowed."
298+ )
299+ return ["localhost" , "127.0.0.1" ]
300+
301+ def process_config (args : argparse .Namespace ) -> dict [str , Union [str , int , None ]]:
302+ """
303+ Process the command line arguments and environment variables to create a config dictionary.
304+ This may then be used as input to the main server function.
305+ If any value is not provided, then a warning is logged and a default value is used, if appropriate.
306+
307+ Parameters
308+ ----------
309+ args : argparse.Namespace
310+ The command line arguments.
311+
312+ Returns
313+ -------
314+ config : dict[str, str]
315+ The configuration dictionary.
316+ """
317+
318+ config = dict ()
319+
320+ # aura credentials
321+ config ["client_id" ] = parse_client_id (args )
322+ config ["client_secret" ] = parse_client_secret (args )
323+
324+ # server configuration
325+ 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" ])
329+
330+ # middleware configuration
331+ config ["allow_origins" ] = parse_allow_origins (args )
332+ config ["allowed_hosts" ] = parse_allowed_hosts (args )
333+
334+ return config
0 commit comments