hatch-xclam 0.7.1.dev1__py3-none-any.whl → 0.7.1.dev3__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 +92 -21
- hatch/mcp_host_config/__init__.py +4 -2
- hatch/mcp_host_config/backup.py +62 -31
- hatch/mcp_host_config/models.py +98 -5
- hatch/mcp_host_config/strategies.py +172 -1
- {hatch_xclam-0.7.1.dev1.dist-info → hatch_xclam-0.7.1.dev3.dist-info}/METADATA +5 -3
- {hatch_xclam-0.7.1.dev1.dist-info → hatch_xclam-0.7.1.dev3.dist-info}/RECORD +16 -13
- 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/test_mcp_cli_all_host_specific_args.py +194 -1
- tests/test_mcp_cli_direct_management.py +8 -5
- {hatch_xclam-0.7.1.dev1.dist-info → hatch_xclam-0.7.1.dev3.dist-info}/WHEEL +0 -0
- {hatch_xclam-0.7.1.dev1.dist-info → hatch_xclam-0.7.1.dev3.dist-info}/entry_points.txt +0 -0
- {hatch_xclam-0.7.1.dev1.dist-info → hatch_xclam-0.7.1.dev3.dist-info}/licenses/LICENSE +0 -0
- {hatch_xclam-0.7.1.dev1.dist-info → hatch_xclam-0.7.1.dev3.dist-info}/top_level.txt +0 -0
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(
|
|
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,125 @@ 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
|
|
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",
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
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
|
-
|
|
1674
|
+
default=None,
|
|
1675
|
+
help="Disable the MCP server [hosts: kiro]"
|
|
1644
1676
|
)
|
|
1645
1677
|
mcp_configure_parser.add_argument(
|
|
1646
1678
|
"--auto-approve-tools",
|
|
1647
1679
|
action="append",
|
|
1648
|
-
help="Tool names to auto-approve without prompting
|
|
1680
|
+
help="Tool names to auto-approve without prompting [hosts: kiro]"
|
|
1649
1681
|
)
|
|
1650
1682
|
mcp_configure_parser.add_argument(
|
|
1651
1683
|
"--disable-tools",
|
|
1652
1684
|
action="append",
|
|
1653
|
-
help="Tool names to disable
|
|
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]"
|
|
1654
1719
|
)
|
|
1655
1720
|
|
|
1656
1721
|
mcp_configure_parser.add_argument(
|
|
1657
1722
|
"--no-backup",
|
|
1658
1723
|
action="store_true",
|
|
1659
|
-
help="Skip backup creation before configuration",
|
|
1724
|
+
help="Skip backup creation before configuration [hosts: all]",
|
|
1660
1725
|
)
|
|
1661
1726
|
mcp_configure_parser.add_argument(
|
|
1662
|
-
"--dry-run", action="store_true", help="Preview configuration without execution"
|
|
1727
|
+
"--dry-run", action="store_true", help="Preview configuration without execution [hosts: all]"
|
|
1663
1728
|
)
|
|
1664
1729
|
mcp_configure_parser.add_argument(
|
|
1665
|
-
"--auto-approve", action="store_true", help="Skip confirmation prompts"
|
|
1730
|
+
"--auto-approve", action="store_true", help="Skip confirmation prompts [hosts: all]"
|
|
1666
1731
|
)
|
|
1667
1732
|
|
|
1668
1733
|
# Remove MCP commands (object-action pattern)
|
|
@@ -2724,6 +2789,12 @@ def main():
|
|
|
2724
2789
|
getattr(args, "disabled", None),
|
|
2725
2790
|
getattr(args, "auto_approve_tools", None),
|
|
2726
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),
|
|
2727
2798
|
args.no_backup,
|
|
2728
2799
|
args.dry_run,
|
|
2729
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, MCPServerConfigKiro,
|
|
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',
|
|
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',
|
hatch/mcp_host_config/backup.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
112
|
-
data
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
hatch/mcp_host_config/models.py
CHANGED
|
@@ -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
|
}
|