hatch-xclam 0.7.1__py3-none-any.whl → 0.7.1.dev1__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.
@@ -8,10 +8,8 @@ strategies with decorator registration following Hatchling patterns.
8
8
 
9
9
  import platform
10
10
  import json
11
- import tomllib # Python 3.11+ built-in
12
- import tomli_w # TOML writing
13
11
  from pathlib import Path
14
- from typing import Optional, Dict, Any, TextIO
12
+ from typing import Optional, Dict, Any
15
13
  import logging
16
14
 
17
15
  from .host_management import MCPHostStrategy, register_host_strategy
@@ -609,172 +607,3 @@ class GeminiHostStrategy(MCPHostStrategy):
609
607
  except Exception as e:
610
608
  logger.error(f"Failed to write Gemini configuration: {e}")
611
609
  return False
612
-
613
-
614
- @register_host_strategy(MCPHostType.CODEX)
615
- class CodexHostStrategy(MCPHostStrategy):
616
- """Configuration strategy for Codex IDE with TOML support.
617
-
618
- Codex uses TOML configuration at ~/.codex/config.toml with a unique
619
- structure using [mcp_servers.<server-name>] tables.
620
- """
621
-
622
- def __init__(self):
623
- self.config_format = "toml"
624
- self._preserved_features = {} # Preserve [features] section
625
-
626
- def get_config_path(self) -> Optional[Path]:
627
- """Get Codex configuration path."""
628
- return Path.home() / ".codex" / "config.toml"
629
-
630
- def get_config_key(self) -> str:
631
- """Codex uses 'mcp_servers' key (note: underscore, not camelCase)."""
632
- return "mcp_servers"
633
-
634
- def is_host_available(self) -> bool:
635
- """Check if Codex is available by checking for config directory."""
636
- codex_dir = Path.home() / ".codex"
637
- return codex_dir.exists()
638
-
639
- def validate_server_config(self, server_config: MCPServerConfig) -> bool:
640
- """Codex validation - supports both STDIO and HTTP servers."""
641
- return server_config.command is not None or server_config.url is not None
642
-
643
- def read_configuration(self) -> HostConfiguration:
644
- """Read Codex TOML configuration file."""
645
- config_path = self.get_config_path()
646
- if not config_path or not config_path.exists():
647
- return HostConfiguration(servers={})
648
-
649
- try:
650
- with open(config_path, 'rb') as f:
651
- toml_data = tomllib.load(f)
652
-
653
- # Preserve [features] section for later write
654
- self._preserved_features = toml_data.get('features', {})
655
-
656
- # Extract MCP servers from [mcp_servers.*] tables
657
- mcp_servers = toml_data.get(self.get_config_key(), {})
658
-
659
- servers = {}
660
- for name, server_data in mcp_servers.items():
661
- try:
662
- # Flatten nested env section if present
663
- flat_data = self._flatten_toml_server(server_data)
664
- servers[name] = MCPServerConfig(**flat_data)
665
- except Exception as e:
666
- logger.warning(f"Invalid server config for {name}: {e}")
667
- continue
668
-
669
- return HostConfiguration(servers=servers)
670
-
671
- except Exception as e:
672
- logger.error(f"Failed to read Codex configuration: {e}")
673
- return HostConfiguration(servers={})
674
-
675
- def write_configuration(self, config: HostConfiguration, no_backup: bool = False) -> bool:
676
- """Write Codex TOML configuration file with backup support."""
677
- config_path = self.get_config_path()
678
- if not config_path:
679
- return False
680
-
681
- try:
682
- config_path.parent.mkdir(parents=True, exist_ok=True)
683
-
684
- # Read existing configuration to preserve non-MCP settings
685
- existing_data = {}
686
- if config_path.exists():
687
- try:
688
- with open(config_path, 'rb') as f:
689
- existing_data = tomllib.load(f)
690
- except Exception:
691
- pass
692
-
693
- # Preserve [features] section
694
- if 'features' in existing_data:
695
- self._preserved_features = existing_data['features']
696
-
697
- # Convert servers to TOML structure
698
- servers_data = {}
699
- for name, server_config in config.servers.items():
700
- servers_data[name] = self._to_toml_server(server_config)
701
-
702
- # Build final TOML structure
703
- final_data = {}
704
-
705
- # Preserve [features] at top
706
- if self._preserved_features:
707
- final_data['features'] = self._preserved_features
708
-
709
- # Add MCP servers
710
- final_data[self.get_config_key()] = servers_data
711
-
712
- # Preserve other top-level keys
713
- for key, value in existing_data.items():
714
- if key not in ('features', self.get_config_key()):
715
- final_data[key] = value
716
-
717
- # Use atomic write with TOML serializer
718
- backup_manager = MCPHostConfigBackupManager()
719
- atomic_ops = AtomicFileOperations()
720
-
721
- def toml_serializer(data: Any, f: TextIO) -> None:
722
- # tomli_w.dumps returns a string, write it to the file
723
- toml_str = tomli_w.dumps(data)
724
- f.write(toml_str)
725
-
726
- atomic_ops.atomic_write_with_serializer(
727
- file_path=config_path,
728
- data=final_data,
729
- serializer=toml_serializer,
730
- backup_manager=backup_manager,
731
- hostname="codex",
732
- skip_backup=no_backup
733
- )
734
-
735
- return True
736
-
737
- except Exception as e:
738
- logger.error(f"Failed to write Codex configuration: {e}")
739
- return False
740
-
741
- def _flatten_toml_server(self, server_data: Dict[str, Any]) -> Dict[str, Any]:
742
- """Flatten nested TOML server structure to flat dict.
743
-
744
- TOML structure:
745
- [mcp_servers.name]
746
- command = "npx"
747
- args = ["-y", "package"]
748
- [mcp_servers.name.env]
749
- VAR = "value"
750
-
751
- Becomes:
752
- {"command": "npx", "args": [...], "env": {"VAR": "value"}}
753
-
754
- Also maps Codex-specific 'http_headers' to universal 'headers' field.
755
- """
756
- # TOML already parses nested tables into nested dicts
757
- # So [mcp_servers.name.env] becomes {"env": {...}}
758
- data = dict(server_data)
759
-
760
- # Map Codex 'http_headers' to universal 'headers' for MCPServerConfig
761
- if 'http_headers' in data:
762
- data['headers'] = data.pop('http_headers')
763
-
764
- return data
765
-
766
- def _to_toml_server(self, server_config: MCPServerConfig) -> Dict[str, Any]:
767
- """Convert MCPServerConfig to TOML-compatible dict structure.
768
-
769
- Maps universal 'headers' field back to Codex-specific 'http_headers'.
770
- """
771
- data = server_config.model_dump(exclude_unset=True)
772
-
773
- # Remove 'name' field as it's the table key in TOML
774
- data.pop('name', None)
775
-
776
- # Map universal 'headers' to Codex 'http_headers' for TOML
777
- if 'headers' in data:
778
- data['http_headers'] = data.pop('headers')
779
-
780
- return data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hatch-xclam
3
- Version: 0.7.1
3
+ Version: 0.7.1.dev1
4
4
  Summary: Package manager for the Cracking Shells ecosystem
5
5
  Author: Cracking Shells Team
6
6
  Project-URL: Homepage, https://github.com/CrackingShells/Hatch
@@ -17,7 +17,6 @@ Requires-Dist: packaging>=20.0
17
17
  Requires-Dist: docker>=7.1.0
18
18
  Requires-Dist: pydantic>=2.0.0
19
19
  Requires-Dist: hatch-validator>=0.8.0
20
- Requires-Dist: tomli-w>=1.0.0
21
20
  Provides-Extra: docs
22
21
  Requires-Dist: mkdocs>=1.4.0; extra == "docs"
23
22
  Requires-Dist: mkdocstrings[python]>=0.20.0; extra == "docs"
@@ -31,7 +30,7 @@ Dynamic: license-file
31
30
 
32
31
  ## Introduction
33
32
 
34
- Hatch is the package manager for managing Model Context Protocol (MCP) servers with environment isolation, multi-type dependency resolution, and multi-host deployment. Deploy MCP servers to Claude Desktop, VS Code, Cursor, Kiro, Codex, and other platforms with automatic dependency management.
33
+ Hatch is the package manager for managing Model Context Protocol (MCP) servers with environment isolation, multi-type dependency resolution, and multi-host deployment. Deploy MCP servers to Claude Desktop, VS Code, Cursor, Kiro, and other platforms with automatic dependency management.
35
34
 
36
35
  The canonical documentation is at `docs/index.md` and published at <https://hatch.readthedocs.io/en/latest/>.
37
36
 
@@ -39,7 +38,7 @@ The canonical documentation is at `docs/index.md` and published at <https://hatc
39
38
 
40
39
  - **Environment Isolation** — Create separate, isolated workspaces for different projects without conflicts
41
40
  - **Multi-Type Dependency Resolution** — Automatically resolve and install system packages, Python packages, Docker containers, and Hatch packages
42
- - **Multi-Host Deployment** — Configure MCP servers on multiple host platforms
41
+ - **Multi-Host Deployment** — Deploy MCP servers to Claude Desktop, Claude Code, VS Code, Cursor, Kiro, LM Studio, and Google Gemini CLI
43
42
  - **Package Validation** — Ensure packages meet schema requirements before distribution
44
43
  - **Development-Focused** — Optimized for rapid development and testing of MCP server ecosystems
45
44
 
@@ -52,7 +51,6 @@ Hatch supports deployment to the following MCP host platforms:
52
51
  - **VS Code** — Visual Studio Code with the MCP extension for tool integration
53
52
  - **Cursor** — AI-first code editor with built-in MCP server support
54
53
  - **Kiro** — Kiro IDE with MCP support
55
- - **Codex** — OpenAI Codex with MCP server configuration support
56
54
  - **LM Studio** — Local LLM inference platform with MCP server integration
57
55
  - **Google Gemini CLI** — Command-line interface for Google's Gemini model with MCP support
58
56
 
@@ -1,5 +1,5 @@
1
1
  hatch/__init__.py,sha256=5JFQZiaZQewEWg8WktQKEdT8IeH0KstndZf27VH7sq4,594
2
- hatch/cli_hatch.py,sha256=Z7-N0DdWT1xK23EL0Gr5mx2wjqhDFzFROt1nUxPAnZM,112093
2
+ hatch/cli_hatch.py,sha256=mDWKVA8Cg8lpXElkJbj8-7xyzI1AiJk3nlp2kOtpwPk,109150
3
3
  hatch/environment_manager.py,sha256=9R9PJYPKQLmWeGXBrOzXxty20la33LgCCYY8o2aMFBQ,60757
4
4
  hatch/package_loader.py,sha256=Sa2JIoio1QlMT2tOGwZhC6pFJIs419cYyoodzyaTDl4,11269
5
5
  hatch/python_environment_manager.py,sha256=guU3zz4_WG3ptuX_ATGCRIi_fDxNHlaQtMv3kiRSo8k,28894
@@ -15,13 +15,13 @@ hatch/installers/installer_base.py,sha256=mId6Q_DLOQPZriq3wu3BCU-ckouom3EZgbWJQq
15
15
  hatch/installers/python_installer.py,sha256=MS9Q8wKjMAy7MEWk7zcAAiFgN0KzOVJFmMzXt1MSH8g,13632
16
16
  hatch/installers/registry.py,sha256=ZOEEMJy_kL5LVj5Mf7s1_CIovDnUVag6nB01dEU9Xeg,6831
17
17
  hatch/installers/system_installer.py,sha256=bdrmw3I9g2EU2E94-4vtJj01RhmekX9GxylU1RPT3Lk,22869
18
- hatch/mcp_host_config/__init__.py,sha256=STHzYwcyO6blKSwcMRibcD_4VKHgpEjru-uY1OQM9yA,1781
19
- hatch/mcp_host_config/backup.py,sha256=X6wnLkPFYv4e5ObrWQSgQ_a_6rcmsFgysQwBZF_osWM,17831
18
+ hatch/mcp_host_config/__init__.py,sha256=YZ9LEbR5E5p6padhPt114wvcsLCz16c2RPduhvvsj9I,1727
19
+ hatch/mcp_host_config/backup.py,sha256=vl2YLL0P0bobGvx5moSAe4wI47vp25d-NpwNO95J3zU,16875
20
20
  hatch/mcp_host_config/host_management.py,sha256=sXyGluFQpfXKggxAVvV9riGRis29JnoEM2dTWSIwb24,23905
21
- hatch/mcp_host_config/models.py,sha256=1Nd3PDyGGbBm4oTgMEQ67j4b_qy23iibv9N4Tm4d9oE,29110
21
+ hatch/mcp_host_config/models.py,sha256=OM5sKtQF-1hylXijNNRctKpHFQbPA88C3DorwyhGv20,25710
22
22
  hatch/mcp_host_config/reporting.py,sha256=Q8UKBJRfvJTbb5PM9xwLEOh3OJjf19AKpWKxs-2622k,6889
23
- hatch/mcp_host_config/strategies.py,sha256=NdA8hcbAi5xGkFRy51csmdEJESg25L8JkwTDVg2AeMw,30302
24
- hatch_xclam-0.7.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
23
+ hatch/mcp_host_config/strategies.py,sha256=WyelujfNtP0Y-KP5hS6u5Z9QSbMWcIxSv30nBQ1Ug7o,23998
24
+ hatch_xclam-0.7.1.dev1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
25
25
  tests/__init__.py,sha256=4I3aQWv143Y1QY_nRIBWnY9MIL-aoQOJuVlpoPQz24E,53
26
26
  tests/run_environment_tests.py,sha256=bWCr8UsPgU80SM8f_VSi0TCwDI6JNqZpvZ2W9-b2Lqk,7302
27
27
  tests/test_cli_version.py,sha256=lU8TBZfzn_8AenFNXYrLMARht91fI5twBN13L-iJebc,4778
@@ -33,9 +33,9 @@ tests/test_hatch_installer.py,sha256=qHCEcKpbe0fHvkzib2rcUJUw1Z-PCRBBJhbjoyOYiVM
33
33
  tests/test_installer_base.py,sha256=0xZiPDMf8LeFJs2SmnYhbIgL7mgvG_HKwL1myQovEZI,11818
34
34
  tests/test_mcp_atomic_operations.py,sha256=QmwUDRNZUz6b2i50yRAMBntRDaMMYZt6flHFJfVkzNE,10563
35
35
  tests/test_mcp_backup_integration.py,sha256=Auw6Bx1EXGwmCA-mRIy31DRLuRWyV33eB7GJiOvIPXQ,12360
36
- tests/test_mcp_cli_all_host_specific_args.py,sha256=hvXUetFyoZFtHsEjpzZ0GNdEuvci3Hv8P8thVjBPm-A,18748
36
+ tests/test_mcp_cli_all_host_specific_args.py,sha256=xLmWCgvupfYo5Z107rYyuz4VJdgWFAWBAw7eKokSscw,11302
37
37
  tests/test_mcp_cli_backup_management.py,sha256=GlUUNu5K1w8S2jTQ6YISp5KeXH5c-H9F0lJX2SG0JWM,14019
38
- tests/test_mcp_cli_direct_management.py,sha256=7GRwHXezmImCUfpHhVjvSkGfT-OBi1tMMLgExMXjals,22184
38
+ tests/test_mcp_cli_direct_management.py,sha256=NOlRGP7M3CJ6dzR9JNtqIswJaCT8WIOoy2PhKXlGqSM,21906
39
39
  tests/test_mcp_cli_discovery_listing.py,sha256=kdrCU6POLyGW9ejowNV-dUVDFVseMd_vibvgIDjZUCM,26595
40
40
  tests/test_mcp_cli_host_config_integration.py,sha256=dD6maHP0wHWnFZwxJ5LgSK1GsrYqB4WdicZkotqIANo,32512
41
41
  tests/test_mcp_cli_package_management.py,sha256=YFMhyh3dueel1f2R5_VMNr9AewDmVrqGbU1kj5bhdeo,14590
@@ -59,9 +59,6 @@ tests/test_system_installer.py,sha256=bWuyEKakhvi51iM8xHJh62zv83HUTd8QnPlqUUMWx9
59
59
  tests/integration/__init__.py,sha256=2mG53dv1VqjxZYHuglneK9VgDMoWCxpfmPByXXd3zVM,125
60
60
  tests/integration/test_mcp_kiro_integration.py,sha256=9y2XPacd3Y6zkqUUcCDjzj8Jv2Humby2LZ5CLSeFYCg,5566
61
61
  tests/regression/__init__.py,sha256=0pFnFuEaMf7gPFFXMv-b_vNRNyLV-wU2lYspZHFH_Uo,127
62
- tests/regression/test_mcp_codex_backup_integration.py,sha256=15G8tCPFtukEcZvE5NBI8BvxlD27KwmE3z8-8ZZ3GxM,7107
63
- tests/regression/test_mcp_codex_host_strategy.py,sha256=6ehG8IruT9U-fO7kDMoJfogXbpq7JV4XYl-Ho68nt1U,6413
64
- tests/regression/test_mcp_codex_model_validation.py,sha256=LkGexJrDusxRg_5Bpsm7sXRt7LYwhqqj4Y7VIynslyw,4512
65
62
  tests/regression/test_mcp_kiro_backup_integration.py,sha256=oBEnSSLrnHIurkrBtjSG-HT6DyODV2z9tZeZotnsR1k,9584
66
63
  tests/regression/test_mcp_kiro_cli_integration.py,sha256=jDQE73yJTRb2e6MPShxLHmLDtN5VBMHMAUBojPEnVBI,5123
67
64
  tests/regression/test_mcp_kiro_decorator_registration.py,sha256=_H9FdKdKCv__IYBS0tfnZGUP574KoDjqKInmFiDEKPc,2556
@@ -98,8 +95,8 @@ tests/test_data/packages/schema_versions/schema_v1_1_0_pkg/main.py,sha256=_B5aqX
98
95
  tests/test_data/packages/schema_versions/schema_v1_2_0_pkg/main.py,sha256=rinhVySJpjXKd2sRCS0ps7xTrVqImWcZ8l4aYbidYR8,238
99
96
  tests/test_data/packages/schema_versions/schema_v1_2_1_pkg/hatch_mcp_server.py,sha256=FT14llzHlA4i8I__8GugzBRowhg_CbLmsOwjq0IWFsY,368
100
97
  tests/test_data/packages/schema_versions/schema_v1_2_1_pkg/mcp_server.py,sha256=BRPAyyAseE2CGR3W647SwjlluYfi7ejhZck0An5581I,467
101
- hatch_xclam-0.7.1.dist-info/METADATA,sha256=RfpwtyksekYsi6tz0Jsd36Ssgo789woFnpjyhnam8hA,5942
102
- hatch_xclam-0.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
- hatch_xclam-0.7.1.dist-info/entry_points.txt,sha256=6xbkwFUtr7nRa56vUFMyJk2wjwFQ_XVaU53ruecWKI0,47
104
- hatch_xclam-0.7.1.dist-info/top_level.txt,sha256=GZP3Ivciwal8jVITQkQr7dSNlLJRzfNOhA76VN7Jp4Y,12
105
- hatch_xclam-0.7.1.dist-info/RECORD,,
98
+ hatch_xclam-0.7.1.dev1.dist-info/METADATA,sha256=SMSh9ptwXOL_vYr8I881UViJS5LbPXXh6B9nXGsmfek,5901
99
+ hatch_xclam-0.7.1.dev1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
100
+ hatch_xclam-0.7.1.dev1.dist-info/entry_points.txt,sha256=6xbkwFUtr7nRa56vUFMyJk2wjwFQ_XVaU53ruecWKI0,47
101
+ hatch_xclam-0.7.1.dev1.dist-info/top_level.txt,sha256=GZP3Ivciwal8jVITQkQr7dSNlLJRzfNOhA76VN7Jp4Y,12
102
+ hatch_xclam-0.7.1.dev1.dist-info/RECORD,,
@@ -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, MCPServerConfigCodex
18
+ MCPServerConfigClaude
19
19
  )
20
20
 
21
21
 
@@ -298,199 +298,6 @@ 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
-
494
301
  if __name__ == '__main__':
495
302
  unittest.main()
496
303
 
@@ -40,19 +40,17 @@ 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 (27 total)
43
+ # Updated to include ALL host-specific parameters
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,
47
- False, None, None, None, None, None, False, None, None, False, False, False
46
+ None, None, None, None, False, None, None, None, None, None, None, False, False, False
48
47
  )
49
48
  except SystemExit as e:
50
49
  # If SystemExit is raised, it should be 0 (success) and handler should have been called
51
50
  if e.code == 0:
52
51
  mock_handler.assert_called_once_with(
53
52
  'claude-desktop', 'weather-server', 'python', ['weather.py'],
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
53
+ None, None, None, None, False, None, None, None, None, None, None, False, False, False
56
54
  )
57
55
  else:
58
56
  self.fail(f"main() exited with code {e.code}, expected 0")
@@ -72,12 +70,11 @@ class TestMCPConfigureCommand(unittest.TestCase):
72
70
  with patch('hatch.cli_hatch.handle_mcp_configure', return_value=0) as mock_handler:
73
71
  try:
74
72
  main()
75
- # Updated to include ALL host-specific parameters (27 total)
73
+ # Updated to include ALL host-specific parameters
76
74
  mock_handler.assert_called_once_with(
77
75
  'cursor', 'file-server', None, None,
78
76
  ['API_KEY=secret', 'DEBUG=true'], 'http://localhost:8080',
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
77
+ ['Authorization=Bearer token'], None, False, None, None, None, None, None, None, True, True, True
81
78
  )
82
79
  except SystemExit as e:
83
80
  self.assertEqual(e.code, 0)