hatch-xclam 0.7.0.dev13__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hatch/cli_hatch.py +120 -18
- hatch/mcp_host_config/__init__.py +4 -2
- hatch/mcp_host_config/backup.py +62 -31
- hatch/mcp_host_config/models.py +125 -1
- hatch/mcp_host_config/strategies.py +268 -1
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/METADATA +6 -3
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/RECORD +26 -14
- tests/integration/__init__.py +5 -0
- tests/integration/test_mcp_kiro_integration.py +153 -0
- tests/regression/__init__.py +5 -0
- tests/regression/test_mcp_codex_backup_integration.py +162 -0
- tests/regression/test_mcp_codex_host_strategy.py +163 -0
- tests/regression/test_mcp_codex_model_validation.py +117 -0
- tests/regression/test_mcp_kiro_backup_integration.py +241 -0
- tests/regression/test_mcp_kiro_cli_integration.py +141 -0
- tests/regression/test_mcp_kiro_decorator_registration.py +71 -0
- tests/regression/test_mcp_kiro_host_strategy.py +214 -0
- tests/regression/test_mcp_kiro_model_validation.py +116 -0
- tests/regression/test_mcp_kiro_omni_conversion.py +104 -0
- tests/test_data_utils.py +108 -0
- tests/test_mcp_cli_all_host_specific_args.py +194 -1
- tests/test_mcp_cli_direct_management.py +8 -5
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/WHEEL +0 -0
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/entry_points.txt +0 -0
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/licenses/LICENSE +0 -0
- {hatch_xclam-0.7.0.dev13.dist-info → hatch_xclam-0.7.1.dist-info}/top_level.txt +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(
|
|
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
|
|
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",
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
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,
|
|
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', '
|
|
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'
|
|
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
|
@@ -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
|
}
|