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.
Files changed (87) hide show
  1. hatch/cli_hatch.py +120 -18
  2. hatch/mcp_host_config/__init__.py +4 -2
  3. hatch/mcp_host_config/backup.py +62 -31
  4. hatch/mcp_host_config/models.py +125 -1
  5. hatch/mcp_host_config/strategies.py +268 -1
  6. {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/METADATA +41 -32
  7. hatch_xclam-0.7.1.dist-info/RECORD +105 -0
  8. hatch_xclam-0.7.1.dist-info/top_level.txt +2 -0
  9. tests/integration/__init__.py +5 -0
  10. tests/integration/test_mcp_kiro_integration.py +153 -0
  11. tests/regression/__init__.py +5 -0
  12. tests/regression/test_mcp_codex_backup_integration.py +162 -0
  13. tests/regression/test_mcp_codex_host_strategy.py +163 -0
  14. tests/regression/test_mcp_codex_model_validation.py +117 -0
  15. tests/regression/test_mcp_kiro_backup_integration.py +241 -0
  16. tests/regression/test_mcp_kiro_cli_integration.py +141 -0
  17. tests/regression/test_mcp_kiro_decorator_registration.py +71 -0
  18. tests/regression/test_mcp_kiro_host_strategy.py +214 -0
  19. tests/regression/test_mcp_kiro_model_validation.py +116 -0
  20. tests/regression/test_mcp_kiro_omni_conversion.py +104 -0
  21. tests/test_data_utils.py +108 -0
  22. tests/test_mcp_cli_all_host_specific_args.py +194 -1
  23. tests/test_mcp_cli_direct_management.py +8 -5
  24. hatch_xclam-0.7.0.dev12.dist-info/RECORD +0 -152
  25. hatch_xclam-0.7.0.dev12.dist-info/top_level.txt +0 -3
  26. node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py +0 -45
  27. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +0 -365
  28. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +0 -206
  29. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +0 -1272
  30. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +0 -1547
  31. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +0 -59
  32. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +0 -152
  33. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +0 -270
  34. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +0 -574
  35. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +0 -704
  36. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/common.py +0 -709
  37. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +0 -173
  38. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +0 -169
  39. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +0 -113
  40. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +0 -55
  41. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
  42. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +0 -805
  43. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +0 -1172
  44. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +0 -1319
  45. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +0 -128
  46. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +0 -104
  47. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +0 -462
  48. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +0 -89
  49. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +0 -56
  50. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +0 -2745
  51. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +0 -3976
  52. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +0 -44
  53. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +0 -2965
  54. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +0 -67
  55. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +0 -1391
  56. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +0 -26
  57. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/input.py +0 -3112
  58. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +0 -99
  59. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +0 -767
  60. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +0 -1260
  61. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +0 -174
  62. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +0 -61
  63. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +0 -373
  64. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +0 -1939
  65. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation_test.py +0 -54
  66. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +0 -303
  67. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +0 -3196
  68. node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +0 -65
  69. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/__init__.py +0 -15
  70. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_elffile.py +0 -108
  71. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_manylinux.py +0 -252
  72. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_musllinux.py +0 -83
  73. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_parser.py +0 -359
  74. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_structures.py +0 -61
  75. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/_tokenizer.py +0 -192
  76. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/markers.py +0 -252
  77. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/metadata.py +0 -825
  78. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/py.typed +0 -0
  79. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/requirements.py +0 -90
  80. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/specifiers.py +0 -1030
  81. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/tags.py +0 -553
  82. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/utils.py +0 -172
  83. node_modules/npm/node_modules/node-gyp/gyp/pylib/packaging/version.py +0 -563
  84. node_modules/npm/node_modules/node-gyp/gyp/test_gyp.py +0 -261
  85. {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/WHEEL +0 -0
  86. {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/entry_points.txt +0 -0
  87. {hatch_xclam-0.7.0.dev12.dist-info → hatch_xclam-0.7.1.dist-info}/licenses/LICENSE +0 -0
hatch/cli_hatch.py CHANGED
@@ -713,6 +713,15 @@ def handle_mcp_configure(
713
713
  include_tools: Optional[list] = None,
714
714
  exclude_tools: Optional[list] = None,
715
715
  input: Optional[list] = None,
716
+ disabled: Optional[bool] = None,
717
+ auto_approve_tools: Optional[list] = None,
718
+ disable_tools: Optional[list] = None,
719
+ env_vars: Optional[list] = None,
720
+ startup_timeout: Optional[int] = None,
721
+ tool_timeout: Optional[int] = None,
722
+ enabled: Optional[bool] = None,
723
+ bearer_token_env_var: Optional[str] = None,
724
+ env_header: Optional[list] = None,
716
725
  no_backup: bool = False,
717
726
  dry_run: bool = False,
718
727
  auto_approve: bool = False,
@@ -826,6 +835,35 @@ def handle_mcp_configure(
826
835
  if inputs_list is not None:
827
836
  omni_config_data["inputs"] = inputs_list
828
837
 
838
+ # Host-specific fields (Kiro)
839
+ if disabled is not None:
840
+ omni_config_data["disabled"] = disabled
841
+ if auto_approve_tools is not None:
842
+ omni_config_data["autoApprove"] = auto_approve_tools
843
+ if disable_tools is not None:
844
+ omni_config_data["disabledTools"] = disable_tools
845
+
846
+ # Host-specific fields (Codex)
847
+ if env_vars is not None:
848
+ omni_config_data["env_vars"] = env_vars
849
+ if startup_timeout is not None:
850
+ omni_config_data["startup_timeout_sec"] = startup_timeout
851
+ if tool_timeout is not None:
852
+ omni_config_data["tool_timeout_sec"] = tool_timeout
853
+ if enabled is not None:
854
+ omni_config_data["enabled"] = enabled
855
+ if bearer_token_env_var is not None:
856
+ omni_config_data["bearer_token_env_var"] = bearer_token_env_var
857
+ if env_header is not None:
858
+ # Parse KEY=ENV_VAR_NAME format into dict
859
+ env_http_headers = {}
860
+ for header_spec in env_header:
861
+ if '=' in header_spec:
862
+ key, env_var_name = header_spec.split('=', 1)
863
+ env_http_headers[key] = env_var_name
864
+ if env_http_headers:
865
+ omni_config_data["env_http_headers"] = env_http_headers
866
+
829
867
  # Partial update merge logic
830
868
  if is_update:
831
869
  # Merge with existing configuration
@@ -1557,11 +1595,13 @@ def main():
1557
1595
  mcp_configure_parser = mcp_subparsers.add_parser(
1558
1596
  "configure", help="Configure MCP server directly on host"
1559
1597
  )
1560
- mcp_configure_parser.add_argument("server_name", help="Name for the MCP server")
1598
+ mcp_configure_parser.add_argument(
1599
+ "server_name", help="Name for the MCP server [hosts: all]"
1600
+ )
1561
1601
  mcp_configure_parser.add_argument(
1562
1602
  "--host",
1563
1603
  required=True,
1564
- help="Host platform to configure (e.g., claude-desktop, cursor)",
1604
+ help="Host platform to configure (e.g., claude-desktop, cursor) [hosts: all]",
1565
1605
  )
1566
1606
 
1567
1607
  # Create mutually exclusive group for server type
@@ -1569,72 +1609,125 @@ def main():
1569
1609
  server_type_group.add_argument(
1570
1610
  "--command",
1571
1611
  dest="server_command",
1572
- help="Command to execute the MCP server (for local servers)",
1612
+ help="Command to execute the MCP server (for local servers) [hosts: all]",
1573
1613
  )
1574
1614
  server_type_group.add_argument(
1575
- "--url", help="Server URL for remote MCP servers (SSE transport)"
1615
+ "--url", help="Server URL for remote MCP servers (SSE transport) [hosts: all except claude-desktop, claude-code]"
1576
1616
  )
1577
1617
  server_type_group.add_argument(
1578
- "--http-url", help="HTTP streaming endpoint URL (Gemini only)"
1618
+ "--http-url", help="HTTP streaming endpoint URL [hosts: gemini]"
1579
1619
  )
1580
1620
 
1581
1621
  mcp_configure_parser.add_argument(
1582
1622
  "--args",
1583
1623
  nargs="*",
1584
- help="Arguments for the MCP server command (only with --command)",
1624
+ help="Arguments for the MCP server command (only with --command) [hosts: all]",
1585
1625
  )
1586
1626
  mcp_configure_parser.add_argument(
1587
- "--env-var", action="append", help="Environment variables (format: KEY=VALUE)"
1627
+ "--env-var",
1628
+ action="append",
1629
+ help="Environment variables (format: KEY=VALUE) [hosts: all]",
1588
1630
  )
1589
1631
  mcp_configure_parser.add_argument(
1590
1632
  "--header",
1591
1633
  action="append",
1592
- help="HTTP headers for remote servers (format: KEY=VALUE, only with --url)",
1634
+ help="HTTP headers for remote servers (format: KEY=VALUE, only with --url) [hosts: all except claude-desktop, claude-code]",
1593
1635
  )
1594
1636
 
1595
1637
  # Host-specific arguments (Gemini)
1596
1638
  mcp_configure_parser.add_argument(
1597
- "--timeout", type=int, help="Request timeout in milliseconds (Gemini)"
1639
+ "--timeout", type=int, help="Request timeout in milliseconds [hosts: gemini]"
1598
1640
  )
1599
1641
  mcp_configure_parser.add_argument(
1600
- "--trust", action="store_true", help="Bypass tool call confirmations (Gemini)"
1642
+ "--trust", action="store_true", help="Bypass tool call confirmations [hosts: gemini]"
1601
1643
  )
1602
1644
  mcp_configure_parser.add_argument(
1603
- "--cwd", help="Working directory for stdio transport (Gemini)"
1645
+ "--cwd", help="Working directory for stdio transport [hosts: gemini, codex]"
1604
1646
  )
1605
1647
  mcp_configure_parser.add_argument(
1606
1648
  "--include-tools",
1607
1649
  nargs="*",
1608
- help="Tool allowlist - only these tools will be available (Gemini)",
1650
+ help="Tool allowlist / enabled tools [hosts: gemini, codex]",
1609
1651
  )
1610
1652
  mcp_configure_parser.add_argument(
1611
1653
  "--exclude-tools",
1612
1654
  nargs="*",
1613
- help="Tool blocklist - these tools will be excluded (Gemini)",
1655
+ help="Tool blocklist / disabled tools [hosts: gemini, codex]",
1614
1656
  )
1615
1657
 
1616
1658
  # Host-specific arguments (Cursor/VS Code/LM Studio)
1617
1659
  mcp_configure_parser.add_argument(
1618
- "--env-file", help="Path to environment file (Cursor, VS Code, LM Studio)"
1660
+ "--env-file", help="Path to environment file [hosts: cursor, vscode, lmstudio]"
1619
1661
  )
1620
1662
 
1621
1663
  # Host-specific arguments (VS Code)
1622
1664
  mcp_configure_parser.add_argument(
1623
1665
  "--input",
1624
1666
  action="append",
1625
- help="Input variable definitions in format: type,id,description[,password=true] (VS Code)",
1667
+ help="Input variable definitions in format: type,id,description[,password=true] [hosts: vscode]",
1668
+ )
1669
+
1670
+ # Host-specific arguments (Kiro)
1671
+ mcp_configure_parser.add_argument(
1672
+ "--disabled",
1673
+ action="store_true",
1674
+ default=None,
1675
+ help="Disable the MCP server [hosts: kiro]"
1676
+ )
1677
+ mcp_configure_parser.add_argument(
1678
+ "--auto-approve-tools",
1679
+ action="append",
1680
+ help="Tool names to auto-approve without prompting [hosts: kiro]"
1681
+ )
1682
+ mcp_configure_parser.add_argument(
1683
+ "--disable-tools",
1684
+ action="append",
1685
+ help="Tool names to disable [hosts: kiro]"
1686
+ )
1687
+
1688
+ # Codex-specific arguments
1689
+ mcp_configure_parser.add_argument(
1690
+ "--env-vars",
1691
+ action="append",
1692
+ help="Environment variable names to whitelist/forward [hosts: codex]"
1693
+ )
1694
+ mcp_configure_parser.add_argument(
1695
+ "--startup-timeout",
1696
+ type=int,
1697
+ help="Server startup timeout in seconds (default: 10) [hosts: codex]"
1698
+ )
1699
+ mcp_configure_parser.add_argument(
1700
+ "--tool-timeout",
1701
+ type=int,
1702
+ help="Tool execution timeout in seconds (default: 60) [hosts: codex]"
1703
+ )
1704
+ mcp_configure_parser.add_argument(
1705
+ "--enabled",
1706
+ action="store_true",
1707
+ default=None,
1708
+ help="Enable the MCP server [hosts: codex]"
1709
+ )
1710
+ mcp_configure_parser.add_argument(
1711
+ "--bearer-token-env-var",
1712
+ type=str,
1713
+ help="Name of environment variable containing bearer token for Authorization header [hosts: codex]"
1714
+ )
1715
+ mcp_configure_parser.add_argument(
1716
+ "--env-header",
1717
+ action="append",
1718
+ help="HTTP header from environment variable in KEY=ENV_VAR_NAME format [hosts: codex]"
1626
1719
  )
1627
1720
 
1628
1721
  mcp_configure_parser.add_argument(
1629
1722
  "--no-backup",
1630
1723
  action="store_true",
1631
- help="Skip backup creation before configuration",
1724
+ help="Skip backup creation before configuration [hosts: all]",
1632
1725
  )
1633
1726
  mcp_configure_parser.add_argument(
1634
- "--dry-run", action="store_true", help="Preview configuration without execution"
1727
+ "--dry-run", action="store_true", help="Preview configuration without execution [hosts: all]"
1635
1728
  )
1636
1729
  mcp_configure_parser.add_argument(
1637
- "--auto-approve", action="store_true", help="Skip confirmation prompts"
1730
+ "--auto-approve", action="store_true", help="Skip confirmation prompts [hosts: all]"
1638
1731
  )
1639
1732
 
1640
1733
  # Remove MCP commands (object-action pattern)
@@ -2693,6 +2786,15 @@ def main():
2693
2786
  getattr(args, "include_tools", None),
2694
2787
  getattr(args, "exclude_tools", None),
2695
2788
  getattr(args, "input", None),
2789
+ getattr(args, "disabled", None),
2790
+ getattr(args, "auto_approve_tools", None),
2791
+ getattr(args, "disable_tools", None),
2792
+ getattr(args, "env_vars", None),
2793
+ getattr(args, "startup_timeout", None),
2794
+ getattr(args, "tool_timeout", None),
2795
+ getattr(args, "enabled", None),
2796
+ getattr(args, "bearer_token_env_var", None),
2797
+ getattr(args, "env_header", None),
2696
2798
  args.no_backup,
2697
2799
  args.dry_run,
2698
2800
  args.auto_approve,
@@ -11,7 +11,8 @@ from .models import (
11
11
  PackageHostConfiguration, EnvironmentPackageEntry, ConfigurationResult, SyncResult,
12
12
  # Host-specific configuration models
13
13
  MCPServerConfigBase, MCPServerConfigGemini, MCPServerConfigVSCode,
14
- MCPServerConfigCursor, MCPServerConfigClaude, MCPServerConfigOmni,
14
+ MCPServerConfigCursor, MCPServerConfigClaude, MCPServerConfigKiro,
15
+ MCPServerConfigCodex, MCPServerConfigOmni,
15
16
  HOST_MODEL_REGISTRY
16
17
  )
17
18
  from .host_management import (
@@ -30,7 +31,8 @@ __all__ = [
30
31
  'PackageHostConfiguration', 'EnvironmentPackageEntry', 'ConfigurationResult', 'SyncResult',
31
32
  # Host-specific configuration models
32
33
  'MCPServerConfigBase', 'MCPServerConfigGemini', 'MCPServerConfigVSCode',
33
- 'MCPServerConfigCursor', 'MCPServerConfigClaude', 'MCPServerConfigOmni',
34
+ 'MCPServerConfigCursor', 'MCPServerConfigClaude', 'MCPServerConfigKiro',
35
+ 'MCPServerConfigCodex', 'MCPServerConfigOmni',
34
36
  'HOST_MODEL_REGISTRY',
35
37
  # User feedback reporting
36
38
  'FieldOperation', 'ConversionReport', 'generate_conversion_report', 'display_report',
@@ -9,7 +9,7 @@ import shutil
9
9
  import tempfile
10
10
  from datetime import datetime
11
11
  from pathlib import Path
12
- from typing import Dict, List, Optional, Any
12
+ from typing import Dict, List, Optional, Any, Callable, TextIO
13
13
 
14
14
  from pydantic import BaseModel, Field, validator
15
15
 
@@ -36,8 +36,8 @@ class BackupInfo(BaseModel):
36
36
  def validate_hostname(cls, v):
37
37
  """Validate hostname is supported."""
38
38
  supported_hosts = {
39
- 'claude-desktop', 'claude-code', 'vscode',
40
- 'cursor', 'lmstudio', 'gemini'
39
+ 'claude-desktop', 'claude-code', 'vscode',
40
+ 'cursor', 'lmstudio', 'gemini', 'kiro', 'codex'
41
41
  }
42
42
  if v not in supported_hosts:
43
43
  raise ValueError(f"Unsupported hostname: {v}. Supported: {supported_hosts}")
@@ -53,7 +53,9 @@ class BackupInfo(BaseModel):
53
53
  @property
54
54
  def backup_name(self) -> str:
55
55
  """Get backup filename."""
56
- return f"mcp.json.{self.hostname}.{self.timestamp.strftime('%Y%m%d_%H%M%S_%f')}"
56
+ # Extract original filename from backup path if available
57
+ # Backup filename format: {original_name}.{hostname}.{timestamp}
58
+ return self.file_path.name
57
59
 
58
60
  @property
59
61
  def age_days(self) -> int:
@@ -101,22 +103,29 @@ class BackupResult(BaseModel):
101
103
 
102
104
  class AtomicFileOperations:
103
105
  """Atomic file operations for safe configuration updates."""
104
-
105
- def atomic_write_with_backup(self, file_path: Path, data: Dict[str, Any],
106
- backup_manager: "MCPHostConfigBackupManager",
107
- hostname: str, skip_backup: bool = False) -> bool:
108
- """Atomic write with automatic backup creation.
109
-
106
+
107
+ def atomic_write_with_serializer(
108
+ self,
109
+ file_path: Path,
110
+ data: Any,
111
+ serializer: Callable[[Any, TextIO], None],
112
+ backup_manager: "MCPHostConfigBackupManager",
113
+ hostname: str,
114
+ skip_backup: bool = False
115
+ ) -> bool:
116
+ """Atomic write with custom serializer and automatic backup creation.
117
+
110
118
  Args:
111
- file_path (Path): Target file path for writing
112
- data (Dict[str, Any]): Data to write as JSON
113
- backup_manager (MCPHostConfigBackupManager): Backup manager instance
114
- hostname (str): Host identifier for backup
115
- skip_backup (bool, optional): Skip backup creation. Defaults to False.
116
-
119
+ file_path: Target file path for writing
120
+ data: Data to serialize and write
121
+ serializer: Function that writes data to file handle
122
+ backup_manager: Backup manager instance
123
+ hostname: Host identifier for backup
124
+ skip_backup: Skip backup creation
125
+
117
126
  Returns:
118
- bool: True if operation successful, False otherwise
119
-
127
+ bool: True if operation successful
128
+
120
129
  Raises:
121
130
  BackupError: If backup creation fails and skip_backup is False
122
131
  """
@@ -126,32 +135,52 @@ class AtomicFileOperations:
126
135
  backup_result = backup_manager.create_backup(file_path, hostname)
127
136
  if not backup_result.success:
128
137
  raise BackupError(f"Required backup failed: {backup_result.error_message}")
129
-
130
- # Create temporary file for atomic write
138
+
131
139
  temp_file = None
132
140
  try:
133
- # Write to temporary file first
134
141
  temp_file = file_path.with_suffix(f"{file_path.suffix}.tmp")
135
142
  with open(temp_file, 'w', encoding='utf-8') as f:
136
- json.dump(data, f, indent=2, ensure_ascii=False)
137
-
138
- # Atomic move to target location
143
+ serializer(data, f)
144
+
139
145
  temp_file.replace(file_path)
140
146
  return True
141
-
147
+
142
148
  except Exception as e:
143
- # Clean up temporary file on failure
144
149
  if temp_file and temp_file.exists():
145
150
  temp_file.unlink()
146
-
147
- # Restore from backup if available
151
+
148
152
  if backup_result and backup_result.backup_path:
149
153
  try:
150
154
  backup_manager.restore_backup(hostname, backup_result.backup_path.name)
151
155
  except Exception:
152
- pass # Log but don't raise - original error is more important
153
-
156
+ pass
157
+
154
158
  raise BackupError(f"Atomic write failed: {str(e)}")
159
+
160
+ def atomic_write_with_backup(self, file_path: Path, data: Dict[str, Any],
161
+ backup_manager: "MCPHostConfigBackupManager",
162
+ hostname: str, skip_backup: bool = False) -> bool:
163
+ """Atomic write with JSON serialization (backward compatible).
164
+
165
+ Args:
166
+ file_path (Path): Target file path for writing
167
+ data (Dict[str, Any]): Data to write as JSON
168
+ backup_manager (MCPHostConfigBackupManager): Backup manager instance
169
+ hostname (str): Host identifier for backup
170
+ skip_backup (bool, optional): Skip backup creation. Defaults to False.
171
+
172
+ Returns:
173
+ bool: True if operation successful, False otherwise
174
+
175
+ Raises:
176
+ BackupError: If backup creation fails and skip_backup is False
177
+ """
178
+ def json_serializer(data: Any, f: TextIO) -> None:
179
+ json.dump(data, f, indent=2, ensure_ascii=False)
180
+
181
+ return self.atomic_write_with_serializer(
182
+ file_path, data, json_serializer, backup_manager, hostname, skip_backup
183
+ )
155
184
 
156
185
  def atomic_copy(self, source: Path, target: Path) -> bool:
157
186
  """Atomic file copy operation.
@@ -228,8 +257,10 @@ class MCPHostConfigBackupManager:
228
257
  host_backup_dir.mkdir(exist_ok=True)
229
258
 
230
259
  # Generate timestamped backup filename with microseconds for uniqueness
260
+ # Preserve original filename instead of hardcoding 'mcp.json'
231
261
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
232
- backup_name = f"mcp.json.{hostname}.{timestamp}"
262
+ original_filename = config_path.name
263
+ backup_name = f"{original_filename}.{hostname}.{timestamp}"
233
264
  backup_path = host_backup_dir / backup_name
234
265
 
235
266
  # Get original file size
@@ -24,6 +24,8 @@ class MCPHostType(str, Enum):
24
24
  CURSOR = "cursor"
25
25
  LMSTUDIO = "lmstudio"
26
26
  GEMINI = "gemini"
27
+ KIRO = "kiro"
28
+ CODEX = "codex"
27
29
 
28
30
 
29
31
  class MCPServerConfig(BaseModel):
@@ -192,7 +194,7 @@ class EnvironmentPackageEntry(BaseModel):
192
194
  """Validate host names are supported."""
193
195
  supported_hosts = {
194
196
  'claude-desktop', 'claude-code', 'vscode',
195
- 'cursor', 'lmstudio', 'gemini'
197
+ 'cursor', 'lmstudio', 'gemini', 'kiro'
196
198
  }
197
199
  for host_name in v.keys():
198
200
  if host_name not in supported_hosts:
@@ -538,6 +540,110 @@ class MCPServerConfigClaude(MCPServerConfigBase):
538
540
  return cls.model_validate(claude_data)
539
541
 
540
542
 
543
+ class MCPServerConfigKiro(MCPServerConfigBase):
544
+ """Kiro IDE-specific MCP server configuration.
545
+
546
+ Extends base model with Kiro-specific fields for server management
547
+ and tool control.
548
+ """
549
+
550
+ # Kiro-specific fields
551
+ disabled: Optional[bool] = Field(None, description="Whether server is disabled")
552
+ autoApprove: Optional[List[str]] = Field(None, description="Auto-approved tool names")
553
+ disabledTools: Optional[List[str]] = Field(None, description="Disabled tool names")
554
+
555
+ @classmethod
556
+ def from_omni(cls, omni: 'MCPServerConfigOmni') -> 'MCPServerConfigKiro':
557
+ """Convert Omni model to Kiro-specific model."""
558
+ # Get supported fields dynamically
559
+ supported_fields = set(cls.model_fields.keys())
560
+
561
+ # Single-call field filtering
562
+ kiro_data = omni.model_dump(include=supported_fields, exclude_unset=True)
563
+
564
+ return cls.model_validate(kiro_data)
565
+
566
+
567
+ class MCPServerConfigCodex(MCPServerConfigBase):
568
+ """Codex-specific MCP server configuration.
569
+
570
+ Extends base model with Codex-specific fields including timeouts,
571
+ tool filtering, environment variable forwarding, and HTTP authentication.
572
+ """
573
+
574
+ model_config = ConfigDict(extra="forbid")
575
+
576
+ # Codex-specific STDIO fields
577
+ env_vars: Optional[List[str]] = Field(
578
+ None,
579
+ description="Environment variables to whitelist/forward"
580
+ )
581
+ cwd: Optional[str] = Field(
582
+ None,
583
+ description="Working directory to launch server from"
584
+ )
585
+
586
+ # Timeout configuration
587
+ startup_timeout_sec: Optional[int] = Field(
588
+ None,
589
+ description="Server startup timeout in seconds (default: 10)"
590
+ )
591
+ tool_timeout_sec: Optional[int] = Field(
592
+ None,
593
+ description="Tool execution timeout in seconds (default: 60)"
594
+ )
595
+
596
+ # Server control
597
+ enabled: Optional[bool] = Field(
598
+ None,
599
+ description="Enable/disable server without deleting config"
600
+ )
601
+ enabled_tools: Optional[List[str]] = Field(
602
+ None,
603
+ description="Allow-list of tools to expose from server"
604
+ )
605
+ disabled_tools: Optional[List[str]] = Field(
606
+ None,
607
+ description="Deny-list of tools to hide (applied after enabled_tools)"
608
+ )
609
+
610
+ # HTTP authentication fields
611
+ bearer_token_env_var: Optional[str] = Field(
612
+ None,
613
+ description="Name of env var containing bearer token for Authorization header"
614
+ )
615
+ http_headers: Optional[Dict[str, str]] = Field(
616
+ None,
617
+ description="Map of header names to static values"
618
+ )
619
+ env_http_headers: Optional[Dict[str, str]] = Field(
620
+ None,
621
+ description="Map of header names to env var names (values pulled from env)"
622
+ )
623
+
624
+ @classmethod
625
+ def from_omni(cls, omni: 'MCPServerConfigOmni') -> 'MCPServerConfigCodex':
626
+ """Convert Omni model to Codex-specific model.
627
+
628
+ Maps universal 'headers' field to Codex-specific 'http_headers' field.
629
+ """
630
+ supported_fields = set(cls.model_fields.keys())
631
+ codex_data = omni.model_dump(include=supported_fields, exclude_unset=True)
632
+
633
+ # Map shared CLI tool filtering flags (Gemini naming) to Codex naming.
634
+ # This lets `--include-tools/--exclude-tools` work for both Gemini and Codex.
635
+ if getattr(omni, 'includeTools', None) is not None and codex_data.get('enabled_tools') is None:
636
+ codex_data['enabled_tools'] = omni.includeTools
637
+ if getattr(omni, 'excludeTools', None) is not None and codex_data.get('disabled_tools') is None:
638
+ codex_data['disabled_tools'] = omni.excludeTools
639
+
640
+ # Map universal 'headers' to Codex 'http_headers'
641
+ if hasattr(omni, 'headers') and omni.headers is not None:
642
+ codex_data['http_headers'] = omni.headers
643
+
644
+ return cls.model_validate(codex_data)
645
+
646
+
541
647
  class MCPServerConfigOmni(BaseModel):
542
648
  """Omni configuration supporting all host-specific fields.
543
649
 
@@ -580,6 +686,22 @@ class MCPServerConfigOmni(BaseModel):
580
686
  # VS Code specific
581
687
  envFile: Optional[str] = None
582
688
  inputs: Optional[List[Dict]] = None
689
+
690
+ # Kiro specific
691
+ disabled: Optional[bool] = None
692
+ autoApprove: Optional[List[str]] = None
693
+ disabledTools: Optional[List[str]] = None
694
+
695
+ # Codex specific
696
+ env_vars: Optional[List[str]] = None
697
+ startup_timeout_sec: Optional[int] = None
698
+ tool_timeout_sec: Optional[int] = None
699
+ enabled: Optional[bool] = None
700
+ enabled_tools: Optional[List[str]] = None
701
+ disabled_tools: Optional[List[str]] = None
702
+ bearer_token_env_var: Optional[str] = None
703
+ env_http_headers: Optional[Dict[str, str]] = None
704
+ # Note: http_headers maps to universal 'headers' field, not a separate Codex field
583
705
 
584
706
  @field_validator('url')
585
707
  @classmethod
@@ -599,4 +721,6 @@ HOST_MODEL_REGISTRY: Dict[MCPHostType, type[MCPServerConfigBase]] = {
599
721
  MCPHostType.VSCODE: MCPServerConfigVSCode,
600
722
  MCPHostType.CURSOR: MCPServerConfigCursor,
601
723
  MCPHostType.LMSTUDIO: MCPServerConfigCursor, # Same as CURSOR
724
+ MCPHostType.KIRO: MCPServerConfigKiro,
725
+ MCPHostType.CODEX: MCPServerConfigCodex,
602
726
  }