File size: 9,108 Bytes
dd7600a
 
ca11c0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3e3aed
ca11c0a
 
 
 
 
 
 
 
 
 
 
 
 
a3e3aed
ca11c0a
 
 
 
 
 
 
 
 
 
 
 
 
dd7600a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca11c0a
 
 
 
 
 
 
a3e3aed
ca11c0a
 
 
 
 
 
 
 
 
 
 
 
a3e3aed
ca11c0a
 
 
 
 
 
 
 
 
 
dd7600a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca11c0a
 
 
 
 
 
 
a3e3aed
ca11c0a
 
 
 
 
 
 
 
 
a3e3aed
ca11c0a
 
 
 
 
 
 
 
 
 
 
 
23b21a0
ca11c0a
 
 
 
 
 
 
 
 
23b21a0
ca11c0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd7600a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
from pathlib import Path

from fastmcp.cli.install import install_app


class TestInstallApp:
    """Test the install subapp."""

    def test_install_app_exists(self):
        """Test that the install app is properly configured."""
        # install_app.name is a tuple in cyclopts
        assert "install" in install_app.name
        assert "Install MCP servers" in install_app.help

    def test_install_commands_registered(self):
        """Test that all install commands are registered."""
        # Check that the app has the expected help text and structure
        # This is a simpler check that doesn't rely on internal methods
        assert hasattr(install_app, "help")
        assert "Install MCP servers" in install_app.help

        # We can test that the commands parse without errors
        try:
            install_app.parse_args(["claude-code", "--help"])
            install_app.parse_args(["claude-desktop", "--help"])
            install_app.parse_args(["cursor", "--help"])
            install_app.parse_args(["mcp-json", "--help"])
        except SystemExit:
            # Help commands exit with 0, that's expected
            pass


class TestClaudeCodeInstall:
    """Test claude-code install command."""

    def test_claude_code_basic(self):
        """Test basic claude-code install command parsing."""
        # Parse command with correct parameter names
        command, bound, _ = install_app.parse_args(
            ["claude-code", "server.py", "--name", "test-server"]
        )

        # Verify parsing was successful
        assert command is not None
        assert bound.arguments["server_spec"] == "server.py"
        assert bound.arguments["server_name"] == "test-server"

    def test_claude_code_with_options(self):
        """Test claude-code install with various options."""
        command, bound, _ = install_app.parse_args(
            [
                "claude-code",
                "server.py",
                "--name",
                "test-server",
                "--with",
                "package1",
                "--with",
                "package2",
                "--env",
                "VAR1=value1",
            ]
        )

        assert bound.arguments["with_packages"] == ["package1", "package2"]
        assert bound.arguments["env_vars"] == ["VAR1=value1"]

    def test_claude_code_with_new_options(self):
        """Test claude-code install with new uv options."""
        from pathlib import Path

        command, bound, _ = install_app.parse_args(
            [
                "claude-code",
                "server.py",
                "--python",
                "3.11",
                "--project",
                "/workspace",
                "--with-requirements",
                "requirements.txt",
            ]
        )

        assert bound.arguments["python"] == "3.11"
        assert bound.arguments["project"] == Path("/workspace")
        assert bound.arguments["with_requirements"] == Path("requirements.txt")


class TestClaudeDesktopInstall:
    """Test claude-desktop install command."""

    def test_claude_desktop_basic(self):
        """Test basic claude-desktop install command parsing."""
        command, bound, _ = install_app.parse_args(
            ["claude-desktop", "server.py", "--name", "test-server"]
        )

        assert command is not None
        assert bound.arguments["server_spec"] == "server.py"
        assert bound.arguments["server_name"] == "test-server"

    def test_claude_desktop_with_env_vars(self):
        """Test claude-desktop install with environment variables."""
        command, bound, _ = install_app.parse_args(
            [
                "claude-desktop",
                "server.py",
                "--name",
                "test-server",
                "--env",
                "VAR1=value1",
                "--env",
                "VAR2=value2",
            ]
        )

        assert bound.arguments["env_vars"] == ["VAR1=value1", "VAR2=value2"]

    def test_claude_desktop_with_new_options(self):
        """Test claude-desktop install with new uv options."""
        from pathlib import Path

        command, bound, _ = install_app.parse_args(
            [
                "claude-desktop",
                "server.py",
                "--python",
                "3.10",
                "--project",
                "/my/project",
                "--with-requirements",
                "reqs.txt",
            ]
        )

        assert bound.arguments["python"] == "3.10"
        assert bound.arguments["project"] == Path("/my/project")
        assert bound.arguments["with_requirements"] == Path("reqs.txt")


class TestCursorInstall:
    """Test cursor install command."""

    def test_cursor_basic(self):
        """Test basic cursor install command parsing."""
        command, bound, _ = install_app.parse_args(
            ["cursor", "server.py", "--name", "test-server"]
        )

        assert command is not None
        assert bound.arguments["server_spec"] == "server.py"
        assert bound.arguments["server_name"] == "test-server"

    def test_cursor_with_options(self):
        """Test cursor install with options."""
        command, bound, _ = install_app.parse_args(
            ["cursor", "server.py", "--name", "test-server"]
        )

        assert bound.arguments["server_spec"] == "server.py"
        assert bound.arguments["server_name"] == "test-server"


class TestMcpJsonInstall:
    """Test mcp-json install command."""

    def test_mcp_json_basic(self):
        """Test basic mcp-json install command parsing."""
        command, bound, _ = install_app.parse_args(
            ["mcp-json", "server.py", "--name", "test-server"]
        )

        assert command is not None
        assert bound.arguments["server_spec"] == "server.py"
        assert bound.arguments["server_name"] == "test-server"

    def test_mcp_json_with_copy(self):
        """Test mcp-json install with copy to clipboard option."""
        command, bound, _ = install_app.parse_args(
            ["mcp-json", "server.py", "--name", "test-server", "--copy"]
        )

        assert bound.arguments["copy"] is True


class TestInstallCommandParsing:
    """Test command parsing and error handling."""

    def test_install_minimal_args(self):
        """Test install commands with minimal required arguments."""
        # Each command should work with just a server spec
        commands_to_test = [
            ["claude-code", "server.py"],
            ["claude-desktop", "server.py"],
            ["cursor", "server.py"],
        ]

        for cmd_args in commands_to_test:
            command, bound, _ = install_app.parse_args(cmd_args)
            assert command is not None
            assert bound.arguments["server_spec"] == "server.py"

    def test_mcp_json_minimal(self):
        """Test that mcp-json works with minimal arguments."""
        # Should work with just server spec
        command, bound, _ = install_app.parse_args(["mcp-json", "server.py"])
        assert command is not None
        assert bound.arguments["server_spec"] == "server.py"

    def test_python_option(self):
        """Test --python option for all install commands."""
        commands_to_test = [
            ["claude-code", "server.py", "--python", "3.11"],
            ["claude-desktop", "server.py", "--python", "3.11"],
            ["cursor", "server.py", "--python", "3.11"],
            ["mcp-json", "server.py", "--python", "3.11"],
        ]

        for cmd_args in commands_to_test:
            command, bound, _ = install_app.parse_args(cmd_args)
            assert command is not None
            assert bound.arguments["python"] == "3.11"

    def test_with_requirements_option(self):
        """Test --with-requirements option for all install commands."""
        commands_to_test = [
            ["claude-code", "server.py", "--with-requirements", "requirements.txt"],
            ["claude-desktop", "server.py", "--with-requirements", "requirements.txt"],
            ["cursor", "server.py", "--with-requirements", "requirements.txt"],
            ["mcp-json", "server.py", "--with-requirements", "requirements.txt"],
        ]

        for cmd_args in commands_to_test:
            command, bound, _ = install_app.parse_args(cmd_args)
            assert command is not None
            assert str(bound.arguments["with_requirements"]) == "requirements.txt"

    def test_project_option(self):
        """Test --project option for all install commands."""
        commands_to_test = [
            ["claude-code", "server.py", "--project", "/path/to/project"],
            ["claude-desktop", "server.py", "--project", "/path/to/project"],
            ["cursor", "server.py", "--project", "/path/to/project"],
            ["mcp-json", "server.py", "--project", "/path/to/project"],
        ]

        for cmd_args in commands_to_test:
            command, bound, _ = install_app.parse_args(cmd_args)
            assert command is not None
            assert str(bound.arguments["project"]) == str(Path("/path/to/project"))