@@ -1600,6 +1600,302 @@ def test_configure_mcp_server_windows_prefers_shell_script_over_cmd(
16001600 assert '/claude"' in bash_cmd or "/claude'" in bash_cmd or 'claude" mcp' in bash_cmd
16011601
16021602
1603+ class TestMCPTildeExpansionWindows :
1604+ """Test tilde expansion in user-scope STDIO MCP commands on Windows.
1605+
1606+ Validates that tildes in commands are expanded using Python's os.path.expanduser()
1607+ before being passed to Git Bash, preventing bash from expanding ~ to Unix-style
1608+ /c/Users/... paths.
1609+ """
1610+
1611+ @patch ('platform.system' , return_value = 'Windows' )
1612+ @patch ('setup_environment.expand_tildes_in_command' )
1613+ @patch ('setup_environment.run_bash_command' )
1614+ @patch ('setup_environment.run_command' )
1615+ @patch ('setup_environment.find_command_robust' )
1616+ def test_configure_mcp_server_stdio_windows_expands_tilde_in_command (
1617+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_expand_tilde , mock_system ,
1618+ ):
1619+ """Verify tildes in user-scope STDIO commands are expanded to Windows paths.
1620+
1621+ Ensures that tildes are expanded using Python's os.path.expanduser() which
1622+ produces Windows paths (C:\\ Users\\ ...) rather than letting Git Bash expand
1623+ them to Unix-style paths (/c/Users/...).
1624+ """
1625+ del mock_system # Unused but required for patch
1626+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1627+ mock_run_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1628+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1629+ # Mock expand_tildes_in_command to return Windows-style expanded path
1630+ mock_expand_tilde .return_value = 'python C:\\ Users\\ test\\ .claude\\ mcp\\ mcp_wrapper.py'
1631+
1632+ server = {
1633+ 'name' : 'tilde-test-server' ,
1634+ 'scope' : 'user' ,
1635+ 'command' : 'python ~/.claude/mcp/mcp_wrapper.py' ,
1636+ }
1637+
1638+ result = setup_environment .configure_mcp_server (server )
1639+
1640+ assert result is True
1641+ # Verify expand_tildes_in_command was called with the original command
1642+ mock_expand_tilde .assert_called_once_with ('python ~/.claude/mcp/mcp_wrapper.py' )
1643+ # Verify bash command contains expanded path with forward slashes
1644+ bash_cmd = mock_bash_cmd .call_args .args [0 ]
1645+ assert 'C:/Users/test/.claude/mcp/mcp_wrapper.py' in bash_cmd
1646+ # Verify no unexpanded tilde in command portion (after '--')
1647+ command_part = bash_cmd .split ('-- ' )[1 ] if '-- ' in bash_cmd else bash_cmd
1648+ assert '~' not in command_part
1649+
1650+ @patch ('platform.system' , return_value = 'Windows' )
1651+ @patch ('setup_environment.expand_tildes_in_command' )
1652+ @patch ('setup_environment.run_bash_command' )
1653+ @patch ('setup_environment.run_command' )
1654+ @patch ('setup_environment.find_command_robust' )
1655+ def test_configure_mcp_server_stdio_windows_npx_with_tilde_expands (
1656+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_expand_tilde , mock_system ,
1657+ ):
1658+ """Verify npx commands with tildes are expanded AND wrapped with cmd /c.
1659+
1660+ npx commands on Windows require both tilde expansion AND cmd /c wrapper
1661+ for proper execution in Git Bash.
1662+ """
1663+ del mock_system # Unused but required for patch
1664+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1665+ mock_run_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1666+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1667+ mock_expand_tilde .return_value = 'npx C:\\ Users\\ test\\ .claude\\ mcp\\ server-script'
1668+
1669+ server = {
1670+ 'name' : 'npx-tilde-server' ,
1671+ 'scope' : 'user' ,
1672+ 'command' : 'npx ~/.claude/mcp/server-script' ,
1673+ }
1674+
1675+ result = setup_environment .configure_mcp_server (server )
1676+
1677+ assert result is True
1678+ bash_cmd = mock_bash_cmd .call_args .args [0 ]
1679+ # Verify cmd /c wrapper with expanded path
1680+ assert 'cmd /c npx C:/Users/test/.claude/mcp/server-script' in bash_cmd
1681+ # Verify no unexpanded tilde in the command part
1682+ command_part = bash_cmd .split ('-- ' )[1 ] if '-- ' in bash_cmd else bash_cmd
1683+ assert '~' not in command_part
1684+
1685+ @patch ('platform.system' , return_value = 'Windows' )
1686+ @patch ('setup_environment.expand_tildes_in_command' )
1687+ @patch ('setup_environment.run_bash_command' )
1688+ @patch ('setup_environment.run_command' )
1689+ @patch ('setup_environment.find_command_robust' )
1690+ def test_configure_mcp_server_stdio_windows_no_tilde_unchanged (
1691+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_expand_tilde , mock_system ,
1692+ ):
1693+ """Verify commands without tildes are not modified (regression test).
1694+
1695+ Commands that don't contain tildes should pass through unchanged to
1696+ ensure backward compatibility.
1697+ """
1698+ del mock_system # Unused but required for patch
1699+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1700+ mock_run_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1701+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1702+ # Command without tilde returns unchanged
1703+ mock_expand_tilde .return_value = 'uvx mcp-server-package'
1704+
1705+ server = {
1706+ 'name' : 'no-tilde-server' ,
1707+ 'scope' : 'user' ,
1708+ 'command' : 'uvx mcp-server-package' ,
1709+ }
1710+
1711+ result = setup_environment .configure_mcp_server (server )
1712+
1713+ assert result is True
1714+ mock_expand_tilde .assert_called_once_with ('uvx mcp-server-package' )
1715+ bash_cmd = mock_bash_cmd .call_args .args [0 ]
1716+ assert 'uvx mcp-server-package' in bash_cmd
1717+
1718+ @patch ('platform.system' , return_value = 'Windows' )
1719+ @patch ('setup_environment.run_bash_command' )
1720+ @patch ('setup_environment.run_command' )
1721+ @patch ('setup_environment.find_command_robust' )
1722+ def test_configure_mcp_server_profile_scope_unchanged (
1723+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_system ,
1724+ ):
1725+ """Verify profile-scope servers return early without STDIO add operation.
1726+
1727+ Profile-scope servers are configured via --strict-mcp-config, not claude mcp add.
1728+ On Windows, removals use run_bash_command, then the function returns early.
1729+ """
1730+ del mock_system , mock_run_cmd # Unused but required for patch
1731+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1732+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1733+
1734+ server = {
1735+ 'name' : 'profile-server' ,
1736+ 'scope' : 'profile' ,
1737+ 'command' : 'python ~/.claude/mcp/script.py' ,
1738+ }
1739+
1740+ result = setup_environment .configure_mcp_server (server )
1741+
1742+ assert result is True
1743+ # On Windows, removals use run_bash_command (3 calls: user, local, project)
1744+ # Then profile scope returns early - no add operation
1745+ assert mock_bash_cmd .call_count == 3
1746+ # All bash calls should be removal commands, not 'mcp add'
1747+ for call in mock_bash_cmd .call_args_list :
1748+ bash_cmd = call .args [0 ]
1749+ assert 'mcp remove' in bash_cmd
1750+ assert 'mcp add' not in bash_cmd
1751+
1752+ @patch ('platform.system' , return_value = 'Windows' )
1753+ @patch ('setup_environment.expand_tildes_in_command' )
1754+ @patch ('setup_environment.run_bash_command' )
1755+ @patch ('setup_environment.run_command' )
1756+ @patch ('setup_environment.find_command_robust' )
1757+ def test_configure_mcp_server_stdio_windows_multiple_tildes_expanded (
1758+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_expand_tilde , mock_system ,
1759+ ):
1760+ """Verify commands with multiple tilde paths all get expanded.
1761+
1762+ When a command contains multiple tilde references (e.g., script path
1763+ and config path), all should be expanded.
1764+ """
1765+ del mock_system # Unused but required for patch
1766+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1767+ mock_run_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1768+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1769+ # Both tildes expanded
1770+ mock_expand_tilde .return_value = (
1771+ 'python C:\\ Users\\ test\\ .claude\\ mcp\\ script.py '
1772+ '--config C:\\ Users\\ test\\ .config\\ mcp.yaml'
1773+ )
1774+
1775+ server = {
1776+ 'name' : 'multi-tilde-server' ,
1777+ 'scope' : 'user' ,
1778+ 'command' : 'python ~/.claude/mcp/script.py --config ~/.config/mcp.yaml' ,
1779+ }
1780+
1781+ result = setup_environment .configure_mcp_server (server )
1782+
1783+ assert result is True
1784+ bash_cmd = mock_bash_cmd .call_args .args [0 ]
1785+ # Verify both paths expanded with forward slashes
1786+ assert 'C:/Users/test/.claude/mcp/script.py' in bash_cmd
1787+ assert 'C:/Users/test/.config/mcp.yaml' in bash_cmd
1788+ # No unexpanded tildes in command portion
1789+ command_part = bash_cmd .split ('-- ' )[1 ] if '-- ' in bash_cmd else bash_cmd
1790+ assert '~' not in command_part
1791+
1792+ @patch ('platform.system' , return_value = 'Windows' )
1793+ @patch ('setup_environment.expand_tildes_in_command' )
1794+ @patch ('setup_environment.run_bash_command' )
1795+ @patch ('setup_environment.run_command' )
1796+ @patch ('setup_environment.find_command_robust' )
1797+ def test_configure_mcp_server_stdio_windows_backslash_to_forward_slash (
1798+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_expand_tilde , mock_system ,
1799+ ):
1800+ """Verify backslashes from os.path.expanduser() are converted to forward slashes.
1801+
1802+ Windows os.path.expanduser() returns backslashes (C:\\ Users\\ ...) which must
1803+ be converted to forward slashes for consistent cross-platform paths.
1804+ """
1805+ del mock_system # Unused but required for patch
1806+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1807+ mock_run_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1808+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1809+ # Simulate os.path.expanduser returning backslashes
1810+ mock_expand_tilde .return_value = 'python C:\\ Users\\ test\\ .claude\\ script.py'
1811+
1812+ server = {
1813+ 'name' : 'backslash-test-server' ,
1814+ 'scope' : 'user' ,
1815+ 'command' : 'python ~/.claude/script.py' ,
1816+ }
1817+
1818+ result = setup_environment .configure_mcp_server (server )
1819+
1820+ assert result is True
1821+ bash_cmd = mock_bash_cmd .call_args .args [0 ]
1822+ # Extract command portion after '--'
1823+ command_part = bash_cmd .split ('-- ' )[1 ] if '-- ' in bash_cmd else bash_cmd
1824+ # Verify no backslashes in command part
1825+ assert '\\ ' not in command_part
1826+ # Verify forward slashes are used
1827+ assert 'C:/Users/test/.claude/script.py' in bash_cmd
1828+
1829+ @patch ('platform.system' , return_value = 'Windows' )
1830+ @patch ('setup_environment.expand_tildes_in_command' )
1831+ @patch ('setup_environment.run_bash_command' )
1832+ @patch ('setup_environment.run_command' )
1833+ @patch ('setup_environment.find_command_robust' )
1834+ def test_configure_mcp_server_local_scope_stdio_with_tilde (
1835+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_expand_tilde , mock_system ,
1836+ ):
1837+ """Verify local-scope also gets tilde expansion on Windows.
1838+
1839+ Local-scope servers use the same STDIO code path as user-scope
1840+ and should receive the same tilde expansion treatment.
1841+ """
1842+ del mock_system # Unused but required for patch
1843+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1844+ mock_run_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1845+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1846+ mock_expand_tilde .return_value = 'python C:\\ Users\\ test\\ .claude\\ mcp\\ local_server.py'
1847+
1848+ server = {
1849+ 'name' : 'local-tilde-server' ,
1850+ 'scope' : 'local' ,
1851+ 'command' : 'python ~/.claude/mcp/local_server.py' ,
1852+ }
1853+
1854+ result = setup_environment .configure_mcp_server (server )
1855+
1856+ assert result is True
1857+ bash_cmd = mock_bash_cmd .call_args .args [0 ]
1858+ # Tilde expanded and backslashes converted
1859+ assert 'C:/Users/test/.claude/mcp/local_server.py' in bash_cmd
1860+ # No unexpanded tilde in command portion
1861+ command_part = bash_cmd .split ('-- ' )[1 ] if '-- ' in bash_cmd else bash_cmd
1862+ assert '~' not in command_part
1863+
1864+ @patch ('platform.system' , return_value = 'Windows' )
1865+ @patch ('setup_environment.expand_tildes_in_command' )
1866+ @patch ('setup_environment.run_bash_command' )
1867+ @patch ('setup_environment.run_command' )
1868+ @patch ('setup_environment.find_command_robust' )
1869+ def test_configure_mcp_server_combined_scope_with_tilde (
1870+ self , mock_find , mock_run_cmd , mock_bash_cmd , mock_expand_tilde , mock_system ,
1871+ ):
1872+ """Verify combined scope servers with tildes work correctly.
1873+
1874+ When scope is [user, profile], the user-scope add command should
1875+ have expanded tilde, while profile servers are returned for separate handling.
1876+ """
1877+ del mock_system # Unused but required for patch
1878+ mock_find .return_value = 'C:\\ Users\\ Test\\ AppData\\ Roaming\\ npm\\ claude.CMD'
1879+ mock_run_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1880+ mock_bash_cmd .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
1881+ mock_expand_tilde .return_value = 'python C:\\ Users\\ test\\ .claude\\ mcp\\ combined.py'
1882+
1883+ server = {
1884+ 'name' : 'combined-tilde-server' ,
1885+ 'scope' : ['user' , 'profile' ],
1886+ 'command' : 'python ~/.claude/mcp/combined.py' ,
1887+ }
1888+
1889+ result = setup_environment .configure_mcp_server (server )
1890+
1891+ assert result is True
1892+ # expand_tildes_in_command should be called for user-scope add
1893+ mock_expand_tilde .assert_called ()
1894+ # Verify user-scope bash command has expanded path
1895+ bash_cmd = mock_bash_cmd .call_args .args [0 ]
1896+ assert 'C:/Users/test/.claude/mcp/combined.py' in bash_cmd
1897+
1898+
16031899class TestCreateAdditionalSettings :
16041900 """Test additional settings creation."""
16051901
0 commit comments