1313 parse_server_path ,
1414 parse_allow_origins ,
1515 parse_allowed_hosts ,
16+ parse_stateless ,
1617 process_config ,
1718 parse_namespace ,
1819)
@@ -30,6 +31,7 @@ def clean_env():
3031 "NEO4J_MCP_SERVER_PATH" ,
3132 "NEO4J_MCP_SERVER_ALLOW_ORIGINS" ,
3233 "NEO4J_MCP_SERVER_ALLOWED_HOSTS" ,
34+ "NEO4J_MCP_SERVER_STATELESS" ,
3335 "NEO4J_NAMESPACE" ,
3436 ]
3537 # Store original values
@@ -60,6 +62,7 @@ def _create_args(**kwargs):
6062 "server_path" : None ,
6163 "allow_origins" : None ,
6264 "allowed_hosts" : None ,
65+ "stateless" : False ,
6366 "namespace" : None ,
6467 }
6568 defaults .update (kwargs )
@@ -536,6 +539,115 @@ def test_parse_allowed_hosts_with_spaces(self, clean_env, args_factory):
536539 assert result == expected_hosts
537540
538541
542+ class TestParseStateless :
543+ """Test stateless mode parsing functionality."""
544+
545+ def test_parse_stateless_from_cli_args_true (self , clean_env , args_factory ):
546+ """Test parsing stateless from CLI arguments when set to True."""
547+ args = args_factory (stateless = True )
548+ result = parse_stateless (args , "http" )
549+ assert result is True
550+
551+ def test_parse_stateless_from_cli_args_false (self , clean_env , args_factory ):
552+ """Test parsing stateless from CLI arguments when set to False."""
553+ args = args_factory (stateless = False )
554+ result = parse_stateless (args , "http" )
555+ assert result is False
556+
557+ def test_parse_stateless_from_env_var_true (self , clean_env , args_factory ):
558+ """Test parsing stateless from environment variable when set to 'true'."""
559+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "true"
560+ args = args_factory ()
561+ result = parse_stateless (args , "http" )
562+ assert result is True
563+
564+ def test_parse_stateless_from_env_var_false (self , clean_env , args_factory ):
565+ """Test parsing stateless from environment variable when set to 'false'."""
566+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "false"
567+ args = args_factory ()
568+ result = parse_stateless (args , "http" )
569+ assert result is False
570+
571+ def test_parse_stateless_from_env_var_one (self , clean_env , args_factory ):
572+ """Test parsing stateless from environment variable when set to '1'."""
573+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "1"
574+ args = args_factory ()
575+ result = parse_stateless (args , "http" )
576+ assert result is True
577+
578+ def test_parse_stateless_from_env_var_yes (self , clean_env , args_factory ):
579+ """Test parsing stateless from environment variable when set to 'yes'."""
580+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "yes"
581+ args = args_factory ()
582+ result = parse_stateless (args , "http" )
583+ assert result is True
584+
585+ def test_parse_stateless_cli_overrides_env (self , clean_env , args_factory ):
586+ """Test that CLI argument takes precedence over environment variable."""
587+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "false"
588+ args = args_factory (stateless = True )
589+ result = parse_stateless (args , "http" )
590+ assert result is True
591+
592+ def test_parse_stateless_defaults_false (self , clean_env , args_factory , mock_logger ):
593+ """Test that stateless defaults to False when not provided."""
594+ args = args_factory ()
595+ result = parse_stateless (args , "http" )
596+ assert result is False
597+
598+ # Check that info message was logged
599+ mock_logger .info .assert_called_once_with ("Info: No stateless mode provided. Defaulting to stateful mode (False)." )
600+
601+ def test_parse_stateless_stdio_warning_cli (self , clean_env , args_factory , mock_logger ):
602+ """Test warning when stateless provided with stdio transport via CLI."""
603+ args = args_factory (stateless = True )
604+ result = parse_stateless (args , "stdio" )
605+ assert result is True
606+
607+ # Check that warning was logged
608+ mock_logger .warning .assert_called_once ()
609+ assert "stateless` argument will be set, but ignored" in mock_logger .warning .call_args [0 ][0 ]
610+
611+ def test_parse_stateless_stdio_warning_env (self , clean_env , args_factory , mock_logger ):
612+ """Test warning when stateless provided with stdio transport via env var."""
613+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "true"
614+ args = args_factory ()
615+ result = parse_stateless (args , "stdio" )
616+ assert result is True
617+
618+ # Check that warning was logged
619+ mock_logger .warning .assert_called_once ()
620+ assert "NEO4J_MCP_SERVER_STATELESS` environment variable will be set, but ignored" in mock_logger .warning .call_args [0 ][0 ]
621+
622+ def test_parse_stateless_http_transport (self , clean_env , args_factory , mock_logger ):
623+ """Test stateless with http transport logs info message."""
624+ args = args_factory (stateless = True )
625+ result = parse_stateless (args , "http" )
626+ assert result is True
627+
628+ # Check that info message was logged
629+ mock_logger .info .assert_called_once_with ("Info: Stateless mode enabled via CLI argument." )
630+
631+ def test_parse_stateless_sse_transport (self , clean_env , args_factory , mock_logger ):
632+ """Test stateless with sse transport logs info message."""
633+ args = args_factory (stateless = True )
634+ result = parse_stateless (args , "sse" )
635+ assert result is True
636+
637+ # Check that info message was logged
638+ mock_logger .info .assert_called_once_with ("Info: Stateless mode enabled via CLI argument." )
639+
640+ def test_parse_stateless_env_var_case_insensitive (self , clean_env , args_factory ):
641+ """Test that environment variable is case insensitive."""
642+ test_cases = ["TRUE" , "True" , "TrUe" , "YES" , "Yes" , "YeS" ]
643+ for value in test_cases :
644+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = value
645+ args = args_factory ()
646+ result = parse_stateless (args , "http" )
647+ assert result is True , f"Failed for value: { value } "
648+ del os .environ ["NEO4J_MCP_SERVER_STATELESS" ]
649+
650+
539651class TestProcessConfig :
540652 def test_process_config_all_provided (self , clean_env , args_factory ):
541653 """Test process_config when all arguments are provided."""
@@ -547,7 +659,8 @@ def test_process_config_all_provided(self, clean_env, args_factory):
547659 server_port = 9000 ,
548660 server_path = "/test/" ,
549661 allow_origins = "http://localhost:3000" ,
550- allowed_hosts = "example.com,www.example.com"
662+ allowed_hosts = "example.com,www.example.com" ,
663+ stateless = True
551664 )
552665
553666 config = process_config (args )
@@ -560,6 +673,7 @@ def test_process_config_all_provided(self, clean_env, args_factory):
560673 assert config ["path" ] == "/test/"
561674 assert config ["allow_origins" ] == ["http://localhost:3000" ]
562675 assert config ["allowed_hosts" ] == ["example.com" , "www.example.com" ]
676+ assert config ["stateless" ] is True
563677
564678 def test_process_config_env_vars (self , clean_env , args_factory ):
565679 """Test process_config when using environment variables."""
@@ -571,6 +685,7 @@ def test_process_config_env_vars(self, clean_env, args_factory):
571685 os .environ ["NEO4J_MCP_SERVER_PATH" ] = "/env/"
572686 os .environ ["NEO4J_MCP_SERVER_ALLOW_ORIGINS" ] = "http://env.com,https://env.com"
573687 os .environ ["NEO4J_MCP_SERVER_ALLOWED_HOSTS" ] = "env.com,www.env.com"
688+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "true"
574689
575690 args = args_factory ()
576691 config = process_config (args )
@@ -583,6 +698,7 @@ def test_process_config_env_vars(self, clean_env, args_factory):
583698 assert config ["path" ] == "/env/"
584699 assert config ["allow_origins" ] == ["http://env.com" , "https://env.com" ]
585700 assert config ["allowed_hosts" ] == ["env.com" , "www.env.com" ]
701+ assert config ["stateless" ] is True
586702
587703 def test_process_config_defaults (self , clean_env , args_factory , mock_logger ):
588704 """Test process_config with minimal arguments (defaults applied)."""
@@ -601,6 +717,7 @@ def test_process_config_defaults(self, clean_env, args_factory, mock_logger):
601717 assert config ["path" ] is None # None for stdio
602718 assert config ["allow_origins" ] == [] # default empty
603719 assert config ["allowed_hosts" ] == ["localhost" , "127.0.0.1" ] # default secure
720+ assert config ["stateless" ] is False # default
604721
605722 def test_process_config_missing_credentials_raises_error (self , clean_env , args_factory ):
606723 """Test process_config raises error when credentials are missing."""
@@ -731,9 +848,70 @@ def test_process_config_namespace_default(self, clean_env, args_factory, mock_lo
731848 def test_process_config_namespace_empty_string (self , clean_env , args_factory ):
732849 """Test process_config when namespace is explicitly set to empty string."""
733850 args = args_factory (
734- client_id = "test-id" ,
851+ client_id = "test-id" ,
735852 client_secret = "test-secret" ,
736853 namespace = ""
737854 )
738855 config = process_config (args )
739- assert config ["namespace" ] == ""
856+ assert config ["namespace" ] == ""
857+
858+
859+ class TestStatelessConfigProcessing :
860+ """Test stateless configuration processing in process_config."""
861+
862+ def test_process_config_stateless_cli (self , clean_env , args_factory ):
863+ """Test process_config when stateless is provided via CLI argument."""
864+ args = args_factory (
865+ client_id = "test-id" ,
866+ client_secret = "test-secret" ,
867+ transport = "http" ,
868+ stateless = True
869+ )
870+ config = process_config (args )
871+ assert config ["stateless" ] is True
872+
873+ def test_process_config_stateless_env_var (self , clean_env , args_factory ):
874+ """Test process_config when stateless is provided via environment variable."""
875+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "true"
876+ args = args_factory (
877+ client_id = "test-id" ,
878+ client_secret = "test-secret" ,
879+ transport = "http"
880+ )
881+ config = process_config (args )
882+ assert config ["stateless" ] is True
883+
884+ def test_process_config_stateless_precedence (self , clean_env , args_factory ):
885+ """Test that CLI stateless argument takes precedence over environment variable."""
886+ os .environ ["NEO4J_MCP_SERVER_STATELESS" ] = "false"
887+ args = args_factory (
888+ client_id = "test-id" ,
889+ client_secret = "test-secret" ,
890+ transport = "http" ,
891+ stateless = True
892+ )
893+ config = process_config (args )
894+ assert config ["stateless" ] is True
895+
896+ def test_process_config_stateless_default (self , clean_env , args_factory , mock_logger ):
897+ """Test process_config when no stateless is provided (defaults to False)."""
898+ args = args_factory (
899+ client_id = "test-id" ,
900+ client_secret = "test-secret" ,
901+ transport = "http"
902+ )
903+ config = process_config (args )
904+ assert config ["stateless" ] is False
905+ mock_logger .info .assert_any_call ("Info: No stateless mode provided. Defaulting to stateful mode (False)." )
906+
907+ def test_process_config_stateless_stdio_ignored (self , clean_env , args_factory , mock_logger ):
908+ """Test that stateless mode is ignored for stdio transport."""
909+ args = args_factory (
910+ client_id = "test-id" ,
911+ client_secret = "test-secret" ,
912+ transport = "stdio" ,
913+ stateless = True
914+ )
915+ config = process_config (args )
916+ assert config ["stateless" ] is True # Value is set but logged as ignored
917+ mock_logger .warning .assert_any_call ("Warning: Stateless mode provided, but transport is `stdio`. The `stateless` argument will be set, but ignored." )
0 commit comments