@@ -2757,6 +2757,260 @@ def test_configure_all_mcp_servers_separates_profile(
27572757 config = json .loads (profile_config .read_text ())
27582758 assert 'profile-server' in config ['mcpServers' ]
27592759
2760+ @patch ('platform.system' , return_value = 'Linux' )
2761+ @patch ('setup_environment.find_command_robust' , return_value = 'claude' )
2762+ @patch ('setup_environment.run_command' )
2763+ def test_configure_all_mcp_servers_profile_only_triggers_removal (
2764+ self ,
2765+ mock_run : MagicMock ,
2766+ mock_find : MagicMock ,
2767+ _mock_system : MagicMock ,
2768+ ) -> None :
2769+ """Test that profile-only servers trigger removal from all scopes via configure_all_mcp_servers."""
2770+ del mock_find , _mock_system
2771+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
2772+
2773+ servers = [
2774+ {'name' : 'profile-only-server' , 'command' : 'uvx profile-only' , 'scope' : 'profile' },
2775+ ]
2776+
2777+ with tempfile .TemporaryDirectory () as tmpdir :
2778+ profile_config = Path (tmpdir ) / 'test-mcp.json'
2779+
2780+ success , profile_servers , stats = setup_environment .configure_all_mcp_servers (
2781+ servers ,
2782+ profile_mcp_config_path = profile_config ,
2783+ )
2784+
2785+ assert success is True
2786+ assert len (profile_servers ) == 1
2787+ assert profile_servers [0 ]['name' ] == 'profile-only-server'
2788+
2789+ # Verify removal was attempted from all 3 scopes
2790+ assert mock_run .call_count == 3 , (
2791+ f'Expected 3 removal calls (user, local, project), got { mock_run .call_count } '
2792+ )
2793+
2794+ # Verify each call was a removal command
2795+ calls = mock_run .call_args_list
2796+ scopes_removed : set [str ] = set ()
2797+ for call in calls :
2798+ args = call [0 ][0 ]
2799+ assert 'mcp' in args
2800+ assert 'remove' in args
2801+ assert 'profile-only-server' in args
2802+ scope_idx = args .index ('--scope' ) + 1
2803+ scopes_removed .add (args [scope_idx ])
2804+
2805+ assert scopes_removed == {'user' , 'local' , 'project' }, (
2806+ f'Expected removal from all scopes, got: { scopes_removed } '
2807+ )
2808+
2809+ # Verify NO add command was executed (profile servers skip claude mcp add)
2810+ for call in calls :
2811+ args = call [0 ][0 ]
2812+ assert 'add' not in args , 'Profile-only server should not call claude mcp add'
2813+
2814+ # Verify stats are correct
2815+ assert stats ['profile_count' ] == 1
2816+ assert stats ['global_count' ] == 0
2817+ assert stats ['combined_count' ] == 0
2818+
2819+ @patch ('platform.system' , return_value = 'Linux' )
2820+ @patch ('setup_environment.find_command_robust' , return_value = 'claude' )
2821+ @patch ('setup_environment.run_command' )
2822+ def test_configure_all_mcp_servers_multi_scope_triggers_removal_and_add (
2823+ self ,
2824+ mock_run : MagicMock ,
2825+ mock_find : MagicMock ,
2826+ _mock_system : MagicMock ,
2827+ ) -> None :
2828+ """Test that [user, profile] scope triggers removal from all scopes AND adds to user scope."""
2829+ del mock_find , _mock_system
2830+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
2831+
2832+ servers = [
2833+ {
2834+ 'name' : 'multi-scope-server' ,
2835+ 'command' : 'uvx multi-scope' ,
2836+ 'scope' : ['user' , 'profile' ],
2837+ },
2838+ ]
2839+
2840+ with tempfile .TemporaryDirectory () as tmpdir :
2841+ profile_config = Path (tmpdir ) / 'test-mcp.json'
2842+
2843+ success , profile_servers , stats = setup_environment .configure_all_mcp_servers (
2844+ servers ,
2845+ profile_mcp_config_path = profile_config ,
2846+ )
2847+
2848+ assert success is True
2849+ assert len (profile_servers ) == 1
2850+ assert profile_servers [0 ]['name' ] == 'multi-scope-server'
2851+
2852+ # Verify removal was attempted from all 3 scopes (3 calls)
2853+ # PLUS the add command for user scope (1 call via run_command)
2854+ # Total should be at least 3 removal calls + potential add call
2855+ removal_calls = [
2856+ call for call in mock_run .call_args_list
2857+ if 'remove' in call [0 ][0 ]
2858+ ]
2859+ assert len (removal_calls ) == 3 , f'Expected 3 removal calls, got { len (removal_calls )} '
2860+
2861+ # Verify profile config created
2862+ assert profile_config .exists ()
2863+ config = json .loads (profile_config .read_text ())
2864+ assert 'multi-scope-server' in config ['mcpServers' ]
2865+
2866+ # Verify stats
2867+ assert stats ['profile_count' ] == 1
2868+ assert stats ['global_count' ] == 1
2869+ assert stats ['combined_count' ] == 1
2870+
2871+ @patch ('platform.system' , return_value = 'Linux' )
2872+ @patch ('setup_environment.find_command_robust' , return_value = 'claude' )
2873+ @patch ('setup_environment.run_command' )
2874+ def test_configure_all_mcp_servers_user_scope_unchanged (
2875+ self ,
2876+ mock_run : MagicMock ,
2877+ mock_find : MagicMock ,
2878+ _mock_system : MagicMock ,
2879+ ) -> None :
2880+ """Test that user-scoped servers work exactly as before (regression test)."""
2881+ del mock_find , _mock_system
2882+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
2883+
2884+ servers = [
2885+ {'name' : 'user-server' , 'command' : 'uvx user-server' , 'scope' : 'user' },
2886+ ]
2887+
2888+ with tempfile .TemporaryDirectory () as tmpdir :
2889+ profile_config = Path (tmpdir ) / 'test-mcp.json'
2890+
2891+ success , profile_servers , stats = setup_environment .configure_all_mcp_servers (
2892+ servers ,
2893+ profile_mcp_config_path = profile_config ,
2894+ )
2895+
2896+ assert success is True
2897+ # User-scope servers should NOT be in profile_servers
2898+ assert len (profile_servers ) == 0
2899+
2900+ # Should call removal (3 times) + add (1 time) = 4 calls
2901+ assert mock_run .call_count == 4
2902+
2903+ # Verify stats
2904+ assert stats ['profile_count' ] == 0
2905+ assert stats ['global_count' ] == 1
2906+ assert stats ['combined_count' ] == 0
2907+
2908+ # Profile config should NOT be created
2909+ assert not profile_config .exists ()
2910+
2911+ @patch ('platform.system' , return_value = 'Linux' )
2912+ @patch ('setup_environment.find_command_robust' , return_value = 'claude' )
2913+ @patch ('setup_environment.run_command' )
2914+ def test_configure_all_mcp_servers_multiple_profile_only_servers (
2915+ self ,
2916+ mock_run : MagicMock ,
2917+ mock_find : MagicMock ,
2918+ _mock_system : MagicMock ,
2919+ ) -> None :
2920+ """Test that multiple profile-only servers each trigger removal from all scopes."""
2921+ del mock_find , _mock_system
2922+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
2923+
2924+ servers = [
2925+ {'name' : 'profile-server-1' , 'command' : 'uvx profile1' , 'scope' : 'profile' },
2926+ {'name' : 'profile-server-2' , 'command' : 'uvx profile2' , 'scope' : 'profile' },
2927+ {'name' : 'profile-server-3' , 'transport' : 'http' , 'url' : 'http://localhost' , 'scope' : 'profile' },
2928+ ]
2929+
2930+ with tempfile .TemporaryDirectory () as tmpdir :
2931+ profile_config = Path (tmpdir ) / 'test-mcp.json'
2932+
2933+ success , profile_servers , stats = setup_environment .configure_all_mcp_servers (
2934+ servers ,
2935+ profile_mcp_config_path = profile_config ,
2936+ )
2937+
2938+ assert success is True
2939+ assert len (profile_servers ) == 3
2940+
2941+ # 3 servers * 3 removal scopes = 9 removal calls
2942+ assert mock_run .call_count == 9
2943+
2944+ # Verify all servers attempted removal from all scopes
2945+ server_scopes_removed : dict [str , set [str ]] = {
2946+ 'profile-server-1' : set (),
2947+ 'profile-server-2' : set (),
2948+ 'profile-server-3' : set (),
2949+ }
2950+ for call in mock_run .call_args_list :
2951+ args = call [0 ][0 ]
2952+ for server_name in server_scopes_removed :
2953+ if server_name in args :
2954+ scope_idx = args .index ('--scope' ) + 1
2955+ server_scopes_removed [server_name ].add (args [scope_idx ])
2956+
2957+ for server_name , scopes in server_scopes_removed .items ():
2958+ assert scopes == {'user' , 'local' , 'project' }, (
2959+ f'{ server_name } missing removal scopes: { scopes } '
2960+ )
2961+
2962+ # Verify stats
2963+ assert stats ['profile_count' ] == 3
2964+ assert stats ['global_count' ] == 0
2965+ assert stats ['combined_count' ] == 0
2966+
2967+ @patch ('platform.system' , return_value = 'Linux' )
2968+ @patch ('setup_environment.find_command_robust' , return_value = 'claude' )
2969+ @patch ('setup_environment.run_command' )
2970+ def test_configure_all_mcp_servers_comprehensive_scope_mix (
2971+ self ,
2972+ mock_run : MagicMock ,
2973+ mock_find : MagicMock ,
2974+ _mock_system : MagicMock ,
2975+ ) -> None :
2976+ """Test comprehensive mix: user-only, profile-only, and multi-scope servers."""
2977+ del mock_find , _mock_system
2978+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
2979+
2980+ servers = [
2981+ {'name' : 'user-only' , 'command' : 'uvx user-only' , 'scope' : 'user' },
2982+ {'name' : 'profile-only' , 'command' : 'uvx profile-only' , 'scope' : 'profile' },
2983+ {'name' : 'user-profile' , 'command' : 'uvx user-profile' , 'scope' : ['user' , 'profile' ]},
2984+ {'name' : 'local-profile' , 'command' : 'uvx local-profile' , 'scope' : ['local' , 'profile' ]},
2985+ ]
2986+
2987+ with tempfile .TemporaryDirectory () as tmpdir :
2988+ profile_config = Path (tmpdir ) / 'test-mcp.json'
2989+
2990+ success , profile_servers , stats = setup_environment .configure_all_mcp_servers (
2991+ servers ,
2992+ profile_mcp_config_path = profile_config ,
2993+ )
2994+
2995+ assert success is True
2996+
2997+ # 3 servers have profile scope: profile-only, user-profile, local-profile
2998+ assert len (profile_servers ) == 3
2999+ profile_names = {s ['name' ] for s in profile_servers }
3000+ assert profile_names == {'profile-only' , 'user-profile' , 'local-profile' }
3001+
3002+ # Verify stats
3003+ assert stats ['profile_count' ] == 3
3004+ assert stats ['global_count' ] == 3 # user-only, user-profile (user), local-profile (local)
3005+ assert stats ['combined_count' ] == 2 # user-profile, local-profile
3006+
3007+ # Verify profile config contains correct servers
3008+ config = json .loads (profile_config .read_text ())
3009+ assert 'profile-only' in config ['mcpServers' ]
3010+ assert 'user-profile' in config ['mcpServers' ]
3011+ assert 'local-profile' in config ['mcpServers' ]
3012+ assert 'user-only' not in config ['mcpServers' ]
3013+
27603014 @patch ('platform.system' , return_value = 'Linux' )
27613015 def test_launcher_script_includes_mcp_flags_when_profile_exists (self , _mock_system : MagicMock ) -> None :
27623016 """Test launcher script includes --strict-mcp-config when profile servers exist."""
0 commit comments