hatch-xclam 0.7.1.dev1__py3-none-any.whl → 0.7.1.dev2__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 CHANGED
@@ -716,6 +716,12 @@ def handle_mcp_configure(
716
716
  disabled: Optional[bool] = None,
717
717
  auto_approve_tools: Optional[list] = None,
718
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,
719
725
  no_backup: bool = False,
720
726
  dry_run: bool = False,
721
727
  auto_approve: bool = False,
@@ -837,6 +843,27 @@ def handle_mcp_configure(
837
843
  if disable_tools is not None:
838
844
  omni_config_data["disabledTools"] = disable_tools
839
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
+
840
867
  # Partial update merge logic
841
868
  if is_update:
842
869
  # Merge with existing configuration
@@ -1568,11 +1595,13 @@ def main():
1568
1595
  mcp_configure_parser = mcp_subparsers.add_parser(
1569
1596
  "configure", help="Configure MCP server directly on host"
1570
1597
  )
1571
- 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
+ )
1572
1601
  mcp_configure_parser.add_argument(
1573
1602
  "--host",
1574
1603
  required=True,
1575
- help="Host platform to configure (e.g., claude-desktop, cursor)",
1604
+ help="Host platform to configure (e.g., claude-desktop, cursor) [hosts: all]",
1576
1605
  )
1577
1606
 
1578
1607
  # Create mutually exclusive group for server type
@@ -1580,89 +1609,123 @@ def main():
1580
1609
  server_type_group.add_argument(
1581
1610
  "--command",
1582
1611
  dest="server_command",
1583
- help="Command to execute the MCP server (for local servers)",
1612
+ help="Command to execute the MCP server (for local servers) [hosts: all]",
1584
1613
  )
1585
1614
  server_type_group.add_argument(
1586
- "--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]"
1587
1616
  )
1588
1617
  server_type_group.add_argument(
1589
- "--http-url", help="HTTP streaming endpoint URL (Gemini only)"
1618
+ "--http-url", help="HTTP streaming endpoint URL [hosts: gemini]"
1590
1619
  )
1591
1620
 
1592
1621
  mcp_configure_parser.add_argument(
1593
1622
  "--args",
1594
1623
  nargs="*",
1595
- help="Arguments for the MCP server command (only with --command)",
1624
+ help="Arguments for the MCP server command (only with --command) [hosts: all]",
1596
1625
  )
1597
1626
  mcp_configure_parser.add_argument(
1598
- "--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]",
1599
1630
  )
1600
1631
  mcp_configure_parser.add_argument(
1601
1632
  "--header",
1602
1633
  action="append",
1603
- 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]",
1604
1635
  )
1605
1636
 
1606
1637
  # Host-specific arguments (Gemini)
1607
1638
  mcp_configure_parser.add_argument(
1608
- "--timeout", type=int, help="Request timeout in milliseconds (Gemini)"
1639
+ "--timeout", type=int, help="Request timeout in milliseconds [hosts: gemini]"
1609
1640
  )
1610
1641
  mcp_configure_parser.add_argument(
1611
- "--trust", action="store_true", help="Bypass tool call confirmations (Gemini)"
1642
+ "--trust", action="store_true", help="Bypass tool call confirmations [hosts: gemini]"
1612
1643
  )
1613
1644
  mcp_configure_parser.add_argument(
1614
- "--cwd", help="Working directory for stdio transport (Gemini)"
1645
+ "--cwd", help="Working directory for stdio transport [hosts: gemini, codex]"
1615
1646
  )
1616
1647
  mcp_configure_parser.add_argument(
1617
1648
  "--include-tools",
1618
1649
  nargs="*",
1619
- help="Tool allowlist - only these tools will be available (Gemini)",
1650
+ help="Tool allowlist / enabled tools [hosts: gemini, codex]",
1620
1651
  )
1621
1652
  mcp_configure_parser.add_argument(
1622
1653
  "--exclude-tools",
1623
1654
  nargs="*",
1624
- help="Tool blocklist - these tools will be excluded (Gemini)",
1655
+ help="Tool blocklist / disabled tools [hosts: gemini, codex]",
1625
1656
  )
1626
1657
 
1627
1658
  # Host-specific arguments (Cursor/VS Code/LM Studio)
1628
1659
  mcp_configure_parser.add_argument(
1629
- "--env-file", help="Path to environment file (Cursor, VS Code, LM Studio)"
1660
+ "--env-file", help="Path to environment file [hosts: cursor, vscode, lmstudio]"
1630
1661
  )
1631
1662
 
1632
1663
  # Host-specific arguments (VS Code)
1633
1664
  mcp_configure_parser.add_argument(
1634
1665
  "--input",
1635
1666
  action="append",
1636
- 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]",
1637
1668
  )
1638
1669
 
1639
1670
  # Host-specific arguments (Kiro)
1640
1671
  mcp_configure_parser.add_argument(
1641
1672
  "--disabled",
1642
1673
  action="store_true",
1643
- help="Disable the MCP server (Kiro)"
1674
+ help="Disable the MCP server [hosts: kiro]"
1644
1675
  )
1645
1676
  mcp_configure_parser.add_argument(
1646
1677
  "--auto-approve-tools",
1647
1678
  action="append",
1648
- help="Tool names to auto-approve without prompting (Kiro)"
1679
+ help="Tool names to auto-approve without prompting [hosts: kiro]"
1649
1680
  )
1650
1681
  mcp_configure_parser.add_argument(
1651
1682
  "--disable-tools",
1652
1683
  action="append",
1653
- help="Tool names to disable (Kiro)"
1684
+ help="Tool names to disable [hosts: kiro]"
1685
+ )
1686
+
1687
+ # Codex-specific arguments
1688
+ mcp_configure_parser.add_argument(
1689
+ "--env-vars",
1690
+ action="append",
1691
+ help="Environment variable names to whitelist/forward [hosts: codex]"
1692
+ )
1693
+ mcp_configure_parser.add_argument(
1694
+ "--startup-timeout",
1695
+ type=int,
1696
+ help="Server startup timeout in seconds (default: 10) [hosts: codex]"
1697
+ )
1698
+ mcp_configure_parser.add_argument(
1699
+ "--tool-timeout",
1700
+ type=int,
1701
+ help="Tool execution timeout in seconds (default: 60) [hosts: codex]"
1702
+ )
1703
+ mcp_configure_parser.add_argument(
1704
+ "--enabled",
1705
+ action="store_true",
1706
+ help="Enable the MCP server [hosts: codex]"
1707
+ )
1708
+ mcp_configure_parser.add_argument(
1709
+ "--bearer-token-env-var",
1710
+ type=str,
1711
+ help="Name of environment variable containing bearer token for Authorization header [hosts: codex]"
1712
+ )
1713
+ mcp_configure_parser.add_argument(
1714
+ "--env-header",
1715
+ action="append",
1716
+ help="HTTP header from environment variable in KEY=ENV_VAR_NAME format [hosts: codex]"
1654
1717
  )
1655
1718
 
1656
1719
  mcp_configure_parser.add_argument(
1657
1720
  "--no-backup",
1658
1721
  action="store_true",
1659
- help="Skip backup creation before configuration",
1722
+ help="Skip backup creation before configuration [hosts: all]",
1660
1723
  )
1661
1724
  mcp_configure_parser.add_argument(
1662
- "--dry-run", action="store_true", help="Preview configuration without execution"
1725
+ "--dry-run", action="store_true", help="Preview configuration without execution [hosts: all]"
1663
1726
  )
1664
1727
  mcp_configure_parser.add_argument(
1665
- "--auto-approve", action="store_true", help="Skip confirmation prompts"
1728
+ "--auto-approve", action="store_true", help="Skip confirmation prompts [hosts: all]"
1666
1729
  )
1667
1730
 
1668
1731
  # Remove MCP commands (object-action pattern)
@@ -2724,6 +2787,12 @@ def main():
2724
2787
  getattr(args, "disabled", None),
2725
2788
  getattr(args, "auto_approve_tools", None),
2726
2789
  getattr(args, "disable_tools", None),
2790
+ getattr(args, "env_vars", None),
2791
+ getattr(args, "startup_timeout", None),
2792
+ getattr(args, "tool_timeout", None),
2793
+ getattr(args, "enabled", None),
2794
+ getattr(args, "bearer_token_env_var", None),
2795
+ getattr(args, "env_header", None),
2727
2796
  args.no_backup,
2728
2797
  args.dry_run,
2729
2798
  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, MCPServerConfigKiro, 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', 'MCPServerConfigKiro', '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', 'kiro'
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
@@ -25,6 +25,7 @@ class MCPHostType(str, Enum):
25
25
  LMSTUDIO = "lmstudio"
26
26
  GEMINI = "gemini"
27
27
  KIRO = "kiro"
28
+ CODEX = "codex"
28
29
 
29
30
 
30
31
  class MCPServerConfig(BaseModel):
@@ -541,28 +542,108 @@ class MCPServerConfigClaude(MCPServerConfigBase):
541
542
 
542
543
  class MCPServerConfigKiro(MCPServerConfigBase):
543
544
  """Kiro IDE-specific MCP server configuration.
544
-
545
+
545
546
  Extends base model with Kiro-specific fields for server management
546
547
  and tool control.
547
548
  """
548
-
549
+
549
550
  # Kiro-specific fields
550
551
  disabled: Optional[bool] = Field(None, description="Whether server is disabled")
551
552
  autoApprove: Optional[List[str]] = Field(None, description="Auto-approved tool names")
552
553
  disabledTools: Optional[List[str]] = Field(None, description="Disabled tool names")
553
-
554
+
554
555
  @classmethod
555
556
  def from_omni(cls, omni: 'MCPServerConfigOmni') -> 'MCPServerConfigKiro':
556
557
  """Convert Omni model to Kiro-specific model."""
557
558
  # Get supported fields dynamically
558
559
  supported_fields = set(cls.model_fields.keys())
559
-
560
+
560
561
  # Single-call field filtering
561
562
  kiro_data = omni.model_dump(include=supported_fields, exclude_unset=True)
562
-
563
+
563
564
  return cls.model_validate(kiro_data)
564
565
 
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
+
566
647
  class MCPServerConfigOmni(BaseModel):
567
648
  """Omni configuration supporting all host-specific fields.
568
649
 
@@ -611,6 +692,17 @@ class MCPServerConfigOmni(BaseModel):
611
692
  autoApprove: Optional[List[str]] = None
612
693
  disabledTools: Optional[List[str]] = None
613
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
705
+
614
706
  @field_validator('url')
615
707
  @classmethod
616
708
  def validate_url_format(cls, v):
@@ -630,4 +722,5 @@ HOST_MODEL_REGISTRY: Dict[MCPHostType, type[MCPServerConfigBase]] = {
630
722
  MCPHostType.CURSOR: MCPServerConfigCursor,
631
723
  MCPHostType.LMSTUDIO: MCPServerConfigCursor, # Same as CURSOR
632
724
  MCPHostType.KIRO: MCPServerConfigKiro,
725
+ MCPHostType.CODEX: MCPServerConfigCodex,
633
726
  }