hatch-xclam 0.7.0.dev12__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hatch/cli_hatch.py +120 -18
- hatch/mcp_host_config/__init__.py +4 -2
- hatch/mcp_host_config/backup.py +62 -31
- hatch/mcp_host_config/models.py +125 -1
- hatch/mcp_host_config/strategies.py +268 -1
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/METADATA +41 -32
- hatch_xclam-0.7.1.dist-info/RECORD +105 -0
- hatch_xclam-0.7.1.dist-info/top_level.txt +2 -0
- tests/integration/__init__.py +5 -0
- tests/integration/test_mcp_kiro_integration.py +153 -0
- tests/regression/__init__.py +5 -0
- tests/regression/test_mcp_codex_backup_integration.py +162 -0
- tests/regression/test_mcp_codex_host_strategy.py +163 -0
- tests/regression/test_mcp_codex_model_validation.py +117 -0
- tests/regression/test_mcp_kiro_backup_integration.py +241 -0
- tests/regression/test_mcp_kiro_cli_integration.py +141 -0
- tests/regression/test_mcp_kiro_decorator_registration.py +71 -0
- tests/regression/test_mcp_kiro_host_strategy.py +214 -0
- tests/regression/test_mcp_kiro_model_validation.py +116 -0
- tests/regression/test_mcp_kiro_omni_conversion.py +104 -0
- tests/test_data_utils.py +108 -0
- tests/test_mcp_cli_all_host_specific_args.py +194 -1
- tests/test_mcp_cli_direct_management.py +8 -5
- hatch_xclam-0.7.0.dev12.dist-info/RECORD +0 -152
- hatch_xclam-0.7.0.dev12.dist-info/top_level.txt +0 -3
- node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py +0 -45
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +0 -365
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +0 -206
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +0 -1272
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +0 -1547
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +0 -59
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +0 -152
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +0 -270
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +0 -574
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +0 -704
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/common.py +0 -709
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +0 -173
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +0 -169
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +0 -113
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +0 -55
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +0 -805
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +0 -1172
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +0 -1319
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +0 -128
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +0 -104
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +0 -462
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +0 -89
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +0 -56
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +0 -2745
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +0 -3976
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +0 -44
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +0 -2965
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +0 -67
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +0 -1391
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +0 -26
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/input.py +0 -3112
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +0 -99
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +0 -767
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +0 -1260
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +0 -174
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +0 -61
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +0 -373
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +0 -1939
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation_test.py +0 -54
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +0 -303
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +0 -3196
- node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +0 -65
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/__init__.py +0 -15
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_elffile.py +0 -108
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_manylinux.py +0 -252
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_musllinux.py +0 -83
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_parser.py +0 -359
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_structures.py +0 -61
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_tokenizer.py +0 -192
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/markers.py +0 -252
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/metadata.py +0 -825
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/py.typed +0 -0
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/requirements.py +0 -90
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/specifiers.py +0 -1030
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/tags.py +0 -553
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/utils.py +0 -172
- node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/version.py +0 -563
- node_modules/npm/node_modules/node-gyp/gyp/test_gyp.py +0 -261
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/WHEEL +0 -0
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/entry_points.txt +0 -0
- {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kiro MCP Model Validation Tests
|
|
3
|
+
|
|
4
|
+
Tests for MCPServerConfigKiro Pydantic model behavior, field validation,
|
|
5
|
+
and Kiro-specific field combinations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
|
|
11
|
+
from wobble.decorators import regression_test
|
|
12
|
+
|
|
13
|
+
from hatch.mcp_host_config.models import (
|
|
14
|
+
MCPServerConfigKiro,
|
|
15
|
+
MCPServerConfigOmni,
|
|
16
|
+
MCPHostType
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestMCPServerConfigKiro(unittest.TestCase):
|
|
21
|
+
"""Test suite for MCPServerConfigKiro model validation."""
|
|
22
|
+
|
|
23
|
+
@regression_test
|
|
24
|
+
def test_kiro_model_with_disabled_field(self):
|
|
25
|
+
"""Test Kiro model with disabled field."""
|
|
26
|
+
config = MCPServerConfigKiro(
|
|
27
|
+
name="kiro-server",
|
|
28
|
+
command="auggie",
|
|
29
|
+
args=["--mcp", "-m", "default"],
|
|
30
|
+
disabled=True
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self.assertEqual(config.command, "auggie")
|
|
34
|
+
self.assertTrue(config.disabled)
|
|
35
|
+
self.assertEqual(config.type, "stdio") # Inferred
|
|
36
|
+
|
|
37
|
+
@regression_test
|
|
38
|
+
def test_kiro_model_with_auto_approve_tools(self):
|
|
39
|
+
"""Test Kiro model with autoApprove field."""
|
|
40
|
+
config = MCPServerConfigKiro(
|
|
41
|
+
name="kiro-server",
|
|
42
|
+
command="auggie",
|
|
43
|
+
autoApprove=["codebase-retrieval", "fetch"]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.assertEqual(config.command, "auggie")
|
|
47
|
+
self.assertEqual(len(config.autoApprove), 2)
|
|
48
|
+
self.assertIn("codebase-retrieval", config.autoApprove)
|
|
49
|
+
self.assertIn("fetch", config.autoApprove)
|
|
50
|
+
|
|
51
|
+
@regression_test
|
|
52
|
+
def test_kiro_model_with_disabled_tools(self):
|
|
53
|
+
"""Test Kiro model with disabledTools field."""
|
|
54
|
+
config = MCPServerConfigKiro(
|
|
55
|
+
name="kiro-server",
|
|
56
|
+
command="python",
|
|
57
|
+
disabledTools=["dangerous-tool", "risky-tool"]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self.assertEqual(config.command, "python")
|
|
61
|
+
self.assertEqual(len(config.disabledTools), 2)
|
|
62
|
+
self.assertIn("dangerous-tool", config.disabledTools)
|
|
63
|
+
|
|
64
|
+
@regression_test
|
|
65
|
+
def test_kiro_model_all_fields_combined(self):
|
|
66
|
+
"""Test Kiro model with all Kiro-specific fields."""
|
|
67
|
+
config = MCPServerConfigKiro(
|
|
68
|
+
name="kiro-server",
|
|
69
|
+
command="auggie",
|
|
70
|
+
args=["--mcp"],
|
|
71
|
+
env={"DEBUG": "true"},
|
|
72
|
+
disabled=False,
|
|
73
|
+
autoApprove=["codebase-retrieval"],
|
|
74
|
+
disabledTools=["dangerous-tool"]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Verify all fields
|
|
78
|
+
self.assertEqual(config.command, "auggie")
|
|
79
|
+
self.assertFalse(config.disabled)
|
|
80
|
+
self.assertEqual(len(config.autoApprove), 1)
|
|
81
|
+
self.assertEqual(len(config.disabledTools), 1)
|
|
82
|
+
self.assertEqual(config.env["DEBUG"], "true")
|
|
83
|
+
|
|
84
|
+
@regression_test
|
|
85
|
+
def test_kiro_model_minimal_configuration(self):
|
|
86
|
+
"""Test Kiro model with minimal configuration."""
|
|
87
|
+
config = MCPServerConfigKiro(
|
|
88
|
+
name="kiro-server",
|
|
89
|
+
command="auggie"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self.assertEqual(config.command, "auggie")
|
|
93
|
+
self.assertEqual(config.type, "stdio") # Inferred
|
|
94
|
+
self.assertIsNone(config.disabled)
|
|
95
|
+
self.assertIsNone(config.autoApprove)
|
|
96
|
+
self.assertIsNone(config.disabledTools)
|
|
97
|
+
|
|
98
|
+
@regression_test
|
|
99
|
+
def test_kiro_model_remote_server_with_kiro_fields(self):
|
|
100
|
+
"""Test Kiro model with remote server and Kiro-specific fields."""
|
|
101
|
+
config = MCPServerConfigKiro(
|
|
102
|
+
name="kiro-remote",
|
|
103
|
+
url="https://api.example.com/mcp",
|
|
104
|
+
headers={"Authorization": "Bearer token"},
|
|
105
|
+
disabled=True,
|
|
106
|
+
autoApprove=["safe-tool"]
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self.assertEqual(config.url, "https://api.example.com/mcp")
|
|
110
|
+
self.assertTrue(config.disabled)
|
|
111
|
+
self.assertEqual(len(config.autoApprove), 1)
|
|
112
|
+
self.assertEqual(config.type, "sse") # Inferred for remote
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == '__main__':
|
|
116
|
+
unittest.main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kiro MCP Omni Conversion Tests
|
|
3
|
+
|
|
4
|
+
Tests for conversion from MCPServerConfigOmni to MCPServerConfigKiro
|
|
5
|
+
using the from_omni() method.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
|
|
10
|
+
from wobble.decorators import regression_test
|
|
11
|
+
|
|
12
|
+
from hatch.mcp_host_config.models import (
|
|
13
|
+
MCPServerConfigKiro,
|
|
14
|
+
MCPServerConfigOmni
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestKiroFromOmniConversion(unittest.TestCase):
|
|
19
|
+
"""Test suite for Kiro from_omni() conversion method."""
|
|
20
|
+
|
|
21
|
+
@regression_test
|
|
22
|
+
def test_kiro_from_omni_with_supported_fields(self):
|
|
23
|
+
"""Test Kiro from_omni with supported fields."""
|
|
24
|
+
omni = MCPServerConfigOmni(
|
|
25
|
+
name="kiro-server",
|
|
26
|
+
command="auggie",
|
|
27
|
+
args=["--mcp", "-m", "default"],
|
|
28
|
+
disabled=True,
|
|
29
|
+
autoApprove=["codebase-retrieval", "fetch"],
|
|
30
|
+
disabledTools=["dangerous-tool"]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Convert to Kiro model
|
|
34
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
35
|
+
|
|
36
|
+
# Verify all supported fields transferred
|
|
37
|
+
self.assertEqual(kiro.name, "kiro-server")
|
|
38
|
+
self.assertEqual(kiro.command, "auggie")
|
|
39
|
+
self.assertEqual(len(kiro.args), 3)
|
|
40
|
+
self.assertTrue(kiro.disabled)
|
|
41
|
+
self.assertEqual(len(kiro.autoApprove), 2)
|
|
42
|
+
self.assertEqual(len(kiro.disabledTools), 1)
|
|
43
|
+
|
|
44
|
+
@regression_test
|
|
45
|
+
def test_kiro_from_omni_with_unsupported_fields(self):
|
|
46
|
+
"""Test Kiro from_omni excludes unsupported fields."""
|
|
47
|
+
omni = MCPServerConfigOmni(
|
|
48
|
+
name="kiro-server",
|
|
49
|
+
command="python",
|
|
50
|
+
disabled=True, # Kiro field
|
|
51
|
+
envFile=".env", # VS Code field (unsupported by Kiro)
|
|
52
|
+
timeout=30000 # Gemini field (unsupported by Kiro)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Convert to Kiro model
|
|
56
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
57
|
+
|
|
58
|
+
# Verify Kiro fields transferred
|
|
59
|
+
self.assertEqual(kiro.command, "python")
|
|
60
|
+
self.assertTrue(kiro.disabled)
|
|
61
|
+
|
|
62
|
+
# Verify unsupported fields NOT transferred
|
|
63
|
+
self.assertFalse(hasattr(kiro, 'envFile') and kiro.envFile is not None)
|
|
64
|
+
self.assertFalse(hasattr(kiro, 'timeout') and kiro.timeout is not None)
|
|
65
|
+
|
|
66
|
+
@regression_test
|
|
67
|
+
def test_kiro_from_omni_exclude_unset_behavior(self):
|
|
68
|
+
"""Test that from_omni respects exclude_unset=True."""
|
|
69
|
+
omni = MCPServerConfigOmni(
|
|
70
|
+
name="kiro-server",
|
|
71
|
+
command="auggie"
|
|
72
|
+
# disabled, autoApprove, disabledTools not set
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
76
|
+
|
|
77
|
+
# Verify unset fields remain None
|
|
78
|
+
self.assertIsNone(kiro.disabled)
|
|
79
|
+
self.assertIsNone(kiro.autoApprove)
|
|
80
|
+
self.assertIsNone(kiro.disabledTools)
|
|
81
|
+
|
|
82
|
+
@regression_test
|
|
83
|
+
def test_kiro_from_omni_remote_server_conversion(self):
|
|
84
|
+
"""Test Kiro from_omni with remote server configuration."""
|
|
85
|
+
omni = MCPServerConfigOmni(
|
|
86
|
+
name="kiro-remote",
|
|
87
|
+
url="https://api.example.com/mcp",
|
|
88
|
+
headers={"Authorization": "Bearer token"},
|
|
89
|
+
disabled=False,
|
|
90
|
+
autoApprove=["safe-tool"]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
kiro = MCPServerConfigKiro.from_omni(omni)
|
|
94
|
+
|
|
95
|
+
# Verify remote server fields
|
|
96
|
+
self.assertEqual(kiro.url, "https://api.example.com/mcp")
|
|
97
|
+
self.assertEqual(kiro.headers["Authorization"], "Bearer token")
|
|
98
|
+
self.assertFalse(kiro.disabled)
|
|
99
|
+
self.assertEqual(len(kiro.autoApprove), 1)
|
|
100
|
+
self.assertEqual(kiro.type, "sse") # Inferred for remote
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == '__main__':
|
|
104
|
+
unittest.main()
|
tests/test_data_utils.py
CHANGED
|
@@ -292,6 +292,25 @@ class MCPHostConfigTestDataLoader(TestDataLoader):
|
|
|
292
292
|
with open(config_path, 'r') as f:
|
|
293
293
|
return json.load(f)
|
|
294
294
|
|
|
295
|
+
def load_kiro_mcp_config(self, config_type: str = "empty") -> Dict[str, Any]:
|
|
296
|
+
"""Load Kiro-specific MCP configuration templates.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
config_type: Type of Kiro configuration to load
|
|
300
|
+
- "empty": Empty mcpServers configuration
|
|
301
|
+
- "with_server": Single server with all Kiro fields
|
|
302
|
+
- "complex": Multi-server with mixed configurations
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Kiro MCP configuration dictionary
|
|
306
|
+
"""
|
|
307
|
+
config_path = self.mcp_host_configs_dir / f"kiro_mcp_{config_type}.json"
|
|
308
|
+
if not config_path.exists():
|
|
309
|
+
self._create_kiro_mcp_config(config_type)
|
|
310
|
+
|
|
311
|
+
with open(config_path, 'r') as f:
|
|
312
|
+
return json.load(f)
|
|
313
|
+
|
|
295
314
|
def _create_host_config_template(self, host_type: str, config_type: str):
|
|
296
315
|
"""Create host-specific configuration templates with inheritance patterns."""
|
|
297
316
|
templates = {
|
|
@@ -364,6 +383,50 @@ class MCPHostConfigTestDataLoader(TestDataLoader):
|
|
|
364
383
|
"args": ["server.py"]
|
|
365
384
|
}
|
|
366
385
|
}
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
# Kiro family templates
|
|
389
|
+
"kiro_simple": {
|
|
390
|
+
"mcpServers": {
|
|
391
|
+
"test_server": {
|
|
392
|
+
"command": "auggie",
|
|
393
|
+
"args": ["--mcp"],
|
|
394
|
+
"disabled": False,
|
|
395
|
+
"autoApprove": ["codebase-retrieval"]
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
"kiro_with_server": {
|
|
400
|
+
"mcpServers": {
|
|
401
|
+
"existing-server": {
|
|
402
|
+
"command": "auggie",
|
|
403
|
+
"args": ["--mcp", "-m", "default", "-w", "."],
|
|
404
|
+
"env": {"DEBUG": "true"},
|
|
405
|
+
"disabled": False,
|
|
406
|
+
"autoApprove": ["codebase-retrieval", "fetch"],
|
|
407
|
+
"disabledTools": ["dangerous-tool"]
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
"kiro_complex": {
|
|
412
|
+
"mcpServers": {
|
|
413
|
+
"local-server": {
|
|
414
|
+
"command": "auggie",
|
|
415
|
+
"args": ["--mcp"],
|
|
416
|
+
"disabled": False,
|
|
417
|
+
"autoApprove": ["codebase-retrieval"]
|
|
418
|
+
},
|
|
419
|
+
"remote-server": {
|
|
420
|
+
"url": "https://api.example.com/mcp",
|
|
421
|
+
"headers": {"Authorization": "Bearer token"},
|
|
422
|
+
"disabled": True,
|
|
423
|
+
"disabledTools": ["risky-tool"]
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
"otherSettings": {
|
|
427
|
+
"theme": "dark",
|
|
428
|
+
"fontSize": 14
|
|
429
|
+
}
|
|
367
430
|
}
|
|
368
431
|
}
|
|
369
432
|
|
|
@@ -470,3 +533,48 @@ class MCPHostConfigTestDataLoader(TestDataLoader):
|
|
|
470
533
|
config_path = self.mcp_host_configs_dir / f"mcp_server_{server_type}.json"
|
|
471
534
|
with open(config_path, 'w') as f:
|
|
472
535
|
json.dump(config, f, indent=2)
|
|
536
|
+
|
|
537
|
+
def _create_kiro_mcp_config(self, config_type: str):
|
|
538
|
+
"""Create Kiro-specific MCP configuration templates."""
|
|
539
|
+
templates = {
|
|
540
|
+
"empty": {
|
|
541
|
+
"mcpServers": {}
|
|
542
|
+
},
|
|
543
|
+
"with_server": {
|
|
544
|
+
"mcpServers": {
|
|
545
|
+
"existing-server": {
|
|
546
|
+
"command": "auggie",
|
|
547
|
+
"args": ["--mcp", "-m", "default", "-w", "."],
|
|
548
|
+
"env": {"DEBUG": "true"},
|
|
549
|
+
"disabled": False,
|
|
550
|
+
"autoApprove": ["codebase-retrieval", "fetch"],
|
|
551
|
+
"disabledTools": ["dangerous-tool"]
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
"complex": {
|
|
556
|
+
"mcpServers": {
|
|
557
|
+
"local-server": {
|
|
558
|
+
"command": "auggie",
|
|
559
|
+
"args": ["--mcp"],
|
|
560
|
+
"disabled": False,
|
|
561
|
+
"autoApprove": ["codebase-retrieval"]
|
|
562
|
+
},
|
|
563
|
+
"remote-server": {
|
|
564
|
+
"url": "https://api.example.com/mcp",
|
|
565
|
+
"headers": {"Authorization": "Bearer token"},
|
|
566
|
+
"disabled": True,
|
|
567
|
+
"disabledTools": ["risky-tool"]
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
"otherSettings": {
|
|
571
|
+
"theme": "dark",
|
|
572
|
+
"fontSize": 14
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
config = templates.get(config_type, {"mcpServers": {}})
|
|
578
|
+
config_path = self.mcp_host_configs_dir / f"kiro_mcp_{config_type}.json"
|
|
579
|
+
with open(config_path, 'w') as f:
|
|
580
|
+
json.dump(config, f, indent=2)
|
|
@@ -15,7 +15,7 @@ from hatch.cli_hatch import handle_mcp_configure, parse_input
|
|
|
15
15
|
from hatch.mcp_host_config import MCPHostType
|
|
16
16
|
from hatch.mcp_host_config.models import (
|
|
17
17
|
MCPServerConfigGemini, MCPServerConfigCursor, MCPServerConfigVSCode,
|
|
18
|
-
MCPServerConfigClaude
|
|
18
|
+
MCPServerConfigClaude, MCPServerConfigCodex
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
@@ -298,6 +298,199 @@ class TestToolFilteringArguments(unittest.TestCase):
|
|
|
298
298
|
self.assertEqual(server_config.excludeTools, ['dangerous_tool'])
|
|
299
299
|
|
|
300
300
|
|
|
301
|
+
class TestAllCodexArguments(unittest.TestCase):
|
|
302
|
+
"""Test ALL Codex-specific CLI arguments."""
|
|
303
|
+
|
|
304
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
305
|
+
@patch('sys.stdout', new_callable=StringIO)
|
|
306
|
+
def test_all_codex_arguments_accepted(self, mock_stdout, mock_manager_class):
|
|
307
|
+
"""Test that all Codex arguments are accepted and passed to model."""
|
|
308
|
+
mock_manager = MagicMock()
|
|
309
|
+
mock_manager_class.return_value = mock_manager
|
|
310
|
+
|
|
311
|
+
mock_result = MagicMock()
|
|
312
|
+
mock_result.success = True
|
|
313
|
+
mock_result.backup_path = None
|
|
314
|
+
mock_manager.configure_server.return_value = mock_result
|
|
315
|
+
|
|
316
|
+
# Test STDIO server with Codex-specific STDIO fields
|
|
317
|
+
result = handle_mcp_configure(
|
|
318
|
+
host='codex',
|
|
319
|
+
server_name='test-server',
|
|
320
|
+
command='npx',
|
|
321
|
+
args=['-y', '@upstash/context7-mcp'],
|
|
322
|
+
env_vars=['PATH', 'HOME'],
|
|
323
|
+
cwd='/workspace',
|
|
324
|
+
startup_timeout=15,
|
|
325
|
+
tool_timeout=120,
|
|
326
|
+
enabled=True,
|
|
327
|
+
include_tools=['read', 'write'],
|
|
328
|
+
exclude_tools=['delete'],
|
|
329
|
+
auto_approve=True
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Verify success
|
|
333
|
+
self.assertEqual(result, 0)
|
|
334
|
+
|
|
335
|
+
# Verify configure_server was called
|
|
336
|
+
mock_manager.configure_server.assert_called_once()
|
|
337
|
+
|
|
338
|
+
# Verify server_config is MCPServerConfigCodex
|
|
339
|
+
call_args = mock_manager.configure_server.call_args
|
|
340
|
+
server_config = call_args.kwargs['server_config']
|
|
341
|
+
self.assertIsInstance(server_config, MCPServerConfigCodex)
|
|
342
|
+
|
|
343
|
+
# Verify Codex-specific STDIO fields
|
|
344
|
+
self.assertEqual(server_config.env_vars, ['PATH', 'HOME'])
|
|
345
|
+
self.assertEqual(server_config.cwd, '/workspace')
|
|
346
|
+
self.assertEqual(server_config.startup_timeout_sec, 15)
|
|
347
|
+
self.assertEqual(server_config.tool_timeout_sec, 120)
|
|
348
|
+
self.assertTrue(server_config.enabled)
|
|
349
|
+
self.assertEqual(server_config.enabled_tools, ['read', 'write'])
|
|
350
|
+
self.assertEqual(server_config.disabled_tools, ['delete'])
|
|
351
|
+
|
|
352
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
353
|
+
@patch('sys.stdout', new_callable=StringIO)
|
|
354
|
+
def test_codex_env_vars_list(self, mock_stdout, mock_manager_class):
|
|
355
|
+
"""Test that env_vars accepts multiple values as a list."""
|
|
356
|
+
mock_manager = MagicMock()
|
|
357
|
+
mock_manager_class.return_value = mock_manager
|
|
358
|
+
|
|
359
|
+
mock_result = MagicMock()
|
|
360
|
+
mock_result.success = True
|
|
361
|
+
mock_result.backup_path = None
|
|
362
|
+
mock_manager.configure_server.return_value = mock_result
|
|
363
|
+
|
|
364
|
+
result = handle_mcp_configure(
|
|
365
|
+
host='codex',
|
|
366
|
+
server_name='test-server',
|
|
367
|
+
command='npx',
|
|
368
|
+
args=['-y', 'package'],
|
|
369
|
+
env_vars=['PATH', 'HOME', 'USER'],
|
|
370
|
+
auto_approve=True
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
self.assertEqual(result, 0)
|
|
374
|
+
call_args = mock_manager.configure_server.call_args
|
|
375
|
+
server_config = call_args.kwargs['server_config']
|
|
376
|
+
self.assertEqual(server_config.env_vars, ['PATH', 'HOME', 'USER'])
|
|
377
|
+
|
|
378
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
379
|
+
@patch('sys.stdout', new_callable=StringIO)
|
|
380
|
+
def test_codex_env_header_parsing(self, mock_stdout, mock_manager_class):
|
|
381
|
+
"""Test that env_header parses KEY=ENV_VAR format correctly."""
|
|
382
|
+
mock_manager = MagicMock()
|
|
383
|
+
mock_manager_class.return_value = mock_manager
|
|
384
|
+
|
|
385
|
+
mock_result = MagicMock()
|
|
386
|
+
mock_result.success = True
|
|
387
|
+
mock_result.backup_path = None
|
|
388
|
+
mock_manager.configure_server.return_value = mock_result
|
|
389
|
+
|
|
390
|
+
result = handle_mcp_configure(
|
|
391
|
+
host='codex',
|
|
392
|
+
server_name='test-server',
|
|
393
|
+
command='npx',
|
|
394
|
+
args=['-y', 'package'],
|
|
395
|
+
env_header=['X-API-Key=API_KEY', 'Authorization=AUTH_TOKEN'],
|
|
396
|
+
auto_approve=True
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
self.assertEqual(result, 0)
|
|
400
|
+
call_args = mock_manager.configure_server.call_args
|
|
401
|
+
server_config = call_args.kwargs['server_config']
|
|
402
|
+
self.assertEqual(server_config.env_http_headers, {
|
|
403
|
+
'X-API-Key': 'API_KEY',
|
|
404
|
+
'Authorization': 'AUTH_TOKEN'
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
408
|
+
@patch('sys.stdout', new_callable=StringIO)
|
|
409
|
+
def test_codex_timeout_fields(self, mock_stdout, mock_manager_class):
|
|
410
|
+
"""Test that timeout fields are passed as integers."""
|
|
411
|
+
mock_manager = MagicMock()
|
|
412
|
+
mock_manager_class.return_value = mock_manager
|
|
413
|
+
|
|
414
|
+
mock_result = MagicMock()
|
|
415
|
+
mock_result.success = True
|
|
416
|
+
mock_result.backup_path = None
|
|
417
|
+
mock_manager.configure_server.return_value = mock_result
|
|
418
|
+
|
|
419
|
+
result = handle_mcp_configure(
|
|
420
|
+
host='codex',
|
|
421
|
+
server_name='test-server',
|
|
422
|
+
command='npx',
|
|
423
|
+
args=['-y', 'package'],
|
|
424
|
+
startup_timeout=30,
|
|
425
|
+
tool_timeout=180,
|
|
426
|
+
auto_approve=True
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
self.assertEqual(result, 0)
|
|
430
|
+
call_args = mock_manager.configure_server.call_args
|
|
431
|
+
server_config = call_args.kwargs['server_config']
|
|
432
|
+
self.assertEqual(server_config.startup_timeout_sec, 30)
|
|
433
|
+
self.assertEqual(server_config.tool_timeout_sec, 180)
|
|
434
|
+
|
|
435
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
436
|
+
@patch('sys.stdout', new_callable=StringIO)
|
|
437
|
+
def test_codex_enabled_flag(self, mock_stdout, mock_manager_class):
|
|
438
|
+
"""Test that enabled flag works as boolean."""
|
|
439
|
+
mock_manager = MagicMock()
|
|
440
|
+
mock_manager_class.return_value = mock_manager
|
|
441
|
+
|
|
442
|
+
mock_result = MagicMock()
|
|
443
|
+
mock_result.success = True
|
|
444
|
+
mock_result.backup_path = None
|
|
445
|
+
mock_manager.configure_server.return_value = mock_result
|
|
446
|
+
|
|
447
|
+
result = handle_mcp_configure(
|
|
448
|
+
host='codex',
|
|
449
|
+
server_name='test-server',
|
|
450
|
+
command='npx',
|
|
451
|
+
args=['-y', 'package'],
|
|
452
|
+
enabled=True,
|
|
453
|
+
auto_approve=True
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
self.assertEqual(result, 0)
|
|
457
|
+
call_args = mock_manager.configure_server.call_args
|
|
458
|
+
server_config = call_args.kwargs['server_config']
|
|
459
|
+
self.assertTrue(server_config.enabled)
|
|
460
|
+
|
|
461
|
+
@patch('hatch.cli_hatch.MCPHostConfigurationManager')
|
|
462
|
+
@patch('sys.stdout', new_callable=StringIO)
|
|
463
|
+
def test_codex_reuses_shared_arguments(self, mock_stdout, mock_manager_class):
|
|
464
|
+
"""Test that Codex reuses shared arguments (cwd, include-tools, exclude-tools)."""
|
|
465
|
+
mock_manager = MagicMock()
|
|
466
|
+
mock_manager_class.return_value = mock_manager
|
|
467
|
+
|
|
468
|
+
mock_result = MagicMock()
|
|
469
|
+
mock_result.success = True
|
|
470
|
+
mock_result.backup_path = None
|
|
471
|
+
mock_manager.configure_server.return_value = mock_result
|
|
472
|
+
|
|
473
|
+
result = handle_mcp_configure(
|
|
474
|
+
host='codex',
|
|
475
|
+
server_name='test-server',
|
|
476
|
+
command='npx',
|
|
477
|
+
args=['-y', 'package'],
|
|
478
|
+
cwd='/workspace',
|
|
479
|
+
include_tools=['tool1', 'tool2'],
|
|
480
|
+
exclude_tools=['tool3'],
|
|
481
|
+
auto_approve=True
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
self.assertEqual(result, 0)
|
|
485
|
+
call_args = mock_manager.configure_server.call_args
|
|
486
|
+
server_config = call_args.kwargs['server_config']
|
|
487
|
+
|
|
488
|
+
# Verify shared arguments work for Codex STDIO servers
|
|
489
|
+
self.assertEqual(server_config.cwd, '/workspace')
|
|
490
|
+
self.assertEqual(server_config.enabled_tools, ['tool1', 'tool2'])
|
|
491
|
+
self.assertEqual(server_config.disabled_tools, ['tool3'])
|
|
492
|
+
|
|
493
|
+
|
|
301
494
|
if __name__ == '__main__':
|
|
302
495
|
unittest.main()
|
|
303
496
|
|
|
@@ -40,17 +40,19 @@ class TestMCPConfigureCommand(unittest.TestCase):
|
|
|
40
40
|
try:
|
|
41
41
|
result = main()
|
|
42
42
|
# If main() returns without SystemExit, check the handler was called
|
|
43
|
-
# Updated to include ALL host-specific parameters
|
|
43
|
+
# Updated to include ALL host-specific parameters (27 total)
|
|
44
44
|
mock_handler.assert_called_once_with(
|
|
45
45
|
'claude-desktop', 'weather-server', 'python', ['weather.py'],
|
|
46
|
-
None, None, None, None, False, None, None, None, None, None, None,
|
|
46
|
+
None, None, None, None, False, None, None, None, None, None, None,
|
|
47
|
+
False, None, None, None, None, None, False, None, None, False, False, False
|
|
47
48
|
)
|
|
48
49
|
except SystemExit as e:
|
|
49
50
|
# If SystemExit is raised, it should be 0 (success) and handler should have been called
|
|
50
51
|
if e.code == 0:
|
|
51
52
|
mock_handler.assert_called_once_with(
|
|
52
53
|
'claude-desktop', 'weather-server', 'python', ['weather.py'],
|
|
53
|
-
None, None, None, None, False, None, None, None, None, None, None,
|
|
54
|
+
None, None, None, None, False, None, None, None, None, None, None,
|
|
55
|
+
False, None, None, None, None, None, False, None, None, False, False, False
|
|
54
56
|
)
|
|
55
57
|
else:
|
|
56
58
|
self.fail(f"main() exited with code {e.code}, expected 0")
|
|
@@ -70,11 +72,12 @@ class TestMCPConfigureCommand(unittest.TestCase):
|
|
|
70
72
|
with patch('hatch.cli_hatch.handle_mcp_configure', return_value=0) as mock_handler:
|
|
71
73
|
try:
|
|
72
74
|
main()
|
|
73
|
-
# Updated to include ALL host-specific parameters
|
|
75
|
+
# Updated to include ALL host-specific parameters (27 total)
|
|
74
76
|
mock_handler.assert_called_once_with(
|
|
75
77
|
'cursor', 'file-server', None, None,
|
|
76
78
|
['API_KEY=secret', 'DEBUG=true'], 'http://localhost:8080',
|
|
77
|
-
['Authorization=Bearer token'], None, False, None, None, None, None, None, None,
|
|
79
|
+
['Authorization=Bearer token'], None, False, None, None, None, None, None, None,
|
|
80
|
+
False, None, None, None, None, None, False, None, None, True, True, True
|
|
78
81
|
)
|
|
79
82
|
except SystemExit as e:
|
|
80
83
|
self.assertEqual(e.code, 0)
|