hatch-xclam 0.7.1.dev3__py3-none-any.whl → 0.8.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hatch/__init__.py +1 -1
- hatch/cli/__init__.py +71 -0
- hatch/cli/__main__.py +1035 -0
- hatch/cli/cli_env.py +865 -0
- hatch/cli/cli_mcp.py +1965 -0
- hatch/cli/cli_package.py +566 -0
- hatch/cli/cli_system.py +136 -0
- hatch/cli/cli_utils.py +1289 -0
- hatch/cli_hatch.py +160 -2838
- hatch/mcp_host_config/__init__.py +10 -10
- hatch/mcp_host_config/adapters/__init__.py +34 -0
- hatch/mcp_host_config/adapters/base.py +170 -0
- hatch/mcp_host_config/adapters/claude.py +105 -0
- hatch/mcp_host_config/adapters/codex.py +104 -0
- hatch/mcp_host_config/adapters/cursor.py +83 -0
- hatch/mcp_host_config/adapters/gemini.py +75 -0
- hatch/mcp_host_config/adapters/kiro.py +78 -0
- hatch/mcp_host_config/adapters/lmstudio.py +79 -0
- hatch/mcp_host_config/adapters/registry.py +149 -0
- hatch/mcp_host_config/adapters/vscode.py +83 -0
- hatch/mcp_host_config/backup.py +5 -3
- hatch/mcp_host_config/fields.py +126 -0
- hatch/mcp_host_config/models.py +161 -456
- hatch/mcp_host_config/reporting.py +57 -16
- hatch/mcp_host_config/strategies.py +155 -87
- hatch/template_generator.py +1 -1
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/METADATA +3 -2
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/RECORD +52 -43
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/WHEEL +1 -1
- hatch_xclam-0.8.0.dev1.dist-info/entry_points.txt +2 -0
- tests/cli_test_utils.py +280 -0
- tests/integration/cli/__init__.py +14 -0
- tests/integration/cli/test_cli_reporter_integration.py +2439 -0
- tests/integration/mcp/__init__.py +0 -0
- tests/integration/mcp/test_adapter_serialization.py +173 -0
- tests/regression/cli/__init__.py +16 -0
- tests/regression/cli/test_color_logic.py +268 -0
- tests/regression/cli/test_consequence_type.py +298 -0
- tests/regression/cli/test_error_formatting.py +328 -0
- tests/regression/cli/test_result_reporter.py +586 -0
- tests/regression/cli/test_table_formatter.py +211 -0
- tests/regression/mcp/__init__.py +0 -0
- tests/regression/mcp/test_field_filtering.py +162 -0
- tests/test_cli_version.py +7 -5
- tests/test_data/fixtures/cli_reporter_fixtures.py +184 -0
- tests/unit/__init__.py +0 -0
- tests/unit/mcp/__init__.py +0 -0
- tests/unit/mcp/test_adapter_protocol.py +138 -0
- tests/unit/mcp/test_adapter_registry.py +158 -0
- tests/unit/mcp/test_config_model.py +146 -0
- hatch_xclam-0.7.1.dev3.dist-info/entry_points.txt +0 -2
- tests/integration/test_mcp_kiro_integration.py +0 -153
- tests/regression/test_mcp_codex_backup_integration.py +0 -162
- tests/regression/test_mcp_codex_host_strategy.py +0 -163
- tests/regression/test_mcp_codex_model_validation.py +0 -117
- tests/regression/test_mcp_kiro_backup_integration.py +0 -241
- tests/regression/test_mcp_kiro_cli_integration.py +0 -141
- tests/regression/test_mcp_kiro_decorator_registration.py +0 -71
- tests/regression/test_mcp_kiro_host_strategy.py +0 -214
- tests/regression/test_mcp_kiro_model_validation.py +0 -116
- tests/regression/test_mcp_kiro_omni_conversion.py +0 -104
- tests/test_mcp_atomic_operations.py +0 -276
- tests/test_mcp_backup_integration.py +0 -308
- tests/test_mcp_cli_all_host_specific_args.py +0 -496
- tests/test_mcp_cli_backup_management.py +0 -295
- tests/test_mcp_cli_direct_management.py +0 -456
- tests/test_mcp_cli_discovery_listing.py +0 -582
- tests/test_mcp_cli_host_config_integration.py +0 -823
- tests/test_mcp_cli_package_management.py +0 -360
- tests/test_mcp_cli_partial_updates.py +0 -859
- tests/test_mcp_environment_integration.py +0 -520
- tests/test_mcp_host_config_backup.py +0 -257
- tests/test_mcp_host_configuration_manager.py +0 -331
- tests/test_mcp_host_registry_decorator.py +0 -348
- tests/test_mcp_pydantic_architecture_v4.py +0 -603
- tests/test_mcp_server_config_models.py +0 -242
- tests/test_mcp_server_config_type_field.py +0 -221
- tests/test_mcp_sync_functionality.py +0 -316
- tests/test_mcp_user_feedback_reporting.py +0 -359
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/top_level.txt +0 -0
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
This module provides MCP host configuration management functionality,
|
|
4
4
|
including backup and restore capabilities for MCP server configurations,
|
|
5
5
|
decorator-based strategy registration, and consolidated Pydantic models.
|
|
6
|
+
|
|
7
|
+
Architecture Notes (v2.0 - Unified Adapter Architecture):
|
|
8
|
+
- MCPServerConfig is the single unified model for all MCP configurations
|
|
9
|
+
- Host-specific serialization is handled by adapters in hatch/mcp_host_config/adapters/
|
|
10
|
+
- Legacy host-specific models (MCPServerConfigGemini, etc.) have been removed
|
|
6
11
|
"""
|
|
7
12
|
|
|
8
13
|
from .backup import MCPHostConfigBackupManager
|
|
9
14
|
from .models import (
|
|
10
15
|
MCPHostType, MCPServerConfig, HostConfiguration, EnvironmentData,
|
|
11
16
|
PackageHostConfiguration, EnvironmentPackageEntry, ConfigurationResult, SyncResult,
|
|
12
|
-
# Host-specific configuration models
|
|
13
|
-
MCPServerConfigBase, MCPServerConfigGemini, MCPServerConfigVSCode,
|
|
14
|
-
MCPServerConfigCursor, MCPServerConfigClaude, MCPServerConfigKiro,
|
|
15
|
-
MCPServerConfigCodex, MCPServerConfigOmni,
|
|
16
|
-
HOST_MODEL_REGISTRY
|
|
17
17
|
)
|
|
18
18
|
from .host_management import (
|
|
19
19
|
MCPHostRegistry, MCPHostStrategy, MCPHostConfigurationManager, register_host_strategy
|
|
@@ -21,20 +21,20 @@ from .host_management import (
|
|
|
21
21
|
from .reporting import (
|
|
22
22
|
FieldOperation, ConversionReport, generate_conversion_report, display_report
|
|
23
23
|
)
|
|
24
|
+
from .adapters import AdapterRegistry, get_adapter, get_default_registry
|
|
24
25
|
|
|
25
26
|
# Import strategies to trigger decorator registration
|
|
26
27
|
from . import strategies
|
|
27
28
|
|
|
28
29
|
__all__ = [
|
|
29
30
|
'MCPHostConfigBackupManager',
|
|
31
|
+
# Core models
|
|
30
32
|
'MCPHostType', 'MCPServerConfig', 'HostConfiguration', 'EnvironmentData',
|
|
31
33
|
'PackageHostConfiguration', 'EnvironmentPackageEntry', 'ConfigurationResult', 'SyncResult',
|
|
32
|
-
#
|
|
33
|
-
'
|
|
34
|
-
'MCPServerConfigCursor', 'MCPServerConfigClaude', 'MCPServerConfigKiro',
|
|
35
|
-
'MCPServerConfigCodex', 'MCPServerConfigOmni',
|
|
36
|
-
'HOST_MODEL_REGISTRY',
|
|
34
|
+
# Adapter architecture
|
|
35
|
+
'AdapterRegistry', 'get_adapter', 'get_default_registry',
|
|
37
36
|
# User feedback reporting
|
|
38
37
|
'FieldOperation', 'ConversionReport', 'generate_conversion_report', 'display_report',
|
|
38
|
+
# Host management
|
|
39
39
|
'MCPHostRegistry', 'MCPHostStrategy', 'MCPHostConfigurationManager', 'register_host_strategy'
|
|
40
40
|
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""MCP Host Config Adapters.
|
|
2
|
+
|
|
3
|
+
This module provides host-specific adapters for the Unified Adapter Architecture.
|
|
4
|
+
Each adapter handles validation and serialization for a specific MCP host.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter
|
|
8
|
+
from hatch.mcp_host_config.adapters.claude import ClaudeAdapter
|
|
9
|
+
from hatch.mcp_host_config.adapters.codex import CodexAdapter
|
|
10
|
+
from hatch.mcp_host_config.adapters.cursor import CursorAdapter
|
|
11
|
+
from hatch.mcp_host_config.adapters.gemini import GeminiAdapter
|
|
12
|
+
from hatch.mcp_host_config.adapters.kiro import KiroAdapter
|
|
13
|
+
from hatch.mcp_host_config.adapters.lmstudio import LMStudioAdapter
|
|
14
|
+
from hatch.mcp_host_config.adapters.registry import AdapterRegistry, get_adapter, get_default_registry
|
|
15
|
+
from hatch.mcp_host_config.adapters.vscode import VSCodeAdapter
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Base classes and exceptions
|
|
19
|
+
"AdapterValidationError",
|
|
20
|
+
"BaseAdapter",
|
|
21
|
+
# Registry
|
|
22
|
+
"AdapterRegistry",
|
|
23
|
+
"get_adapter",
|
|
24
|
+
"get_default_registry",
|
|
25
|
+
# Host-specific adapters
|
|
26
|
+
"ClaudeAdapter",
|
|
27
|
+
"CodexAdapter",
|
|
28
|
+
"CursorAdapter",
|
|
29
|
+
"GeminiAdapter",
|
|
30
|
+
"KiroAdapter",
|
|
31
|
+
"LMStudioAdapter",
|
|
32
|
+
"VSCodeAdapter",
|
|
33
|
+
]
|
|
34
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Base adapter class for MCP host configurations.
|
|
2
|
+
|
|
3
|
+
This module defines the abstract BaseAdapter class that all host-specific
|
|
4
|
+
adapters must implement. The adapter pattern allows for:
|
|
5
|
+
- Host-specific validation rules
|
|
6
|
+
- Host-specific serialization format
|
|
7
|
+
- Unified interface across all hosts
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Any, Dict, FrozenSet, List, Optional
|
|
12
|
+
|
|
13
|
+
from hatch.mcp_host_config.models import MCPServerConfig
|
|
14
|
+
from hatch.mcp_host_config.fields import EXCLUDED_ALWAYS
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AdapterValidationError(Exception):
|
|
18
|
+
"""Raised when adapter validation fails.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
message: Human-readable error message
|
|
22
|
+
field: The field that caused the error (if applicable)
|
|
23
|
+
host_name: The host adapter that raised the error
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
message: str,
|
|
29
|
+
field: Optional[str] = None,
|
|
30
|
+
host_name: Optional[str] = None
|
|
31
|
+
):
|
|
32
|
+
self.message = message
|
|
33
|
+
self.field = field
|
|
34
|
+
self.host_name = host_name
|
|
35
|
+
super().__init__(self._format_message())
|
|
36
|
+
|
|
37
|
+
def _format_message(self) -> str:
|
|
38
|
+
"""Format the error message with optional context."""
|
|
39
|
+
parts = []
|
|
40
|
+
if self.host_name:
|
|
41
|
+
parts.append(f"[{self.host_name}]")
|
|
42
|
+
if self.field:
|
|
43
|
+
parts.append(f"Field '{self.field}':")
|
|
44
|
+
parts.append(self.message)
|
|
45
|
+
return " ".join(parts)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BaseAdapter(ABC):
|
|
49
|
+
"""Abstract base class for host-specific MCP configuration adapters.
|
|
50
|
+
|
|
51
|
+
Each host (Claude Desktop, VSCode, Gemini, etc.) has different requirements
|
|
52
|
+
for MCP server configuration. Adapters handle:
|
|
53
|
+
|
|
54
|
+
1. **Validation**: Host-specific rules (e.g., "command and url are mutually
|
|
55
|
+
exclusive" for Claude, but not for Gemini which supports triple transport)
|
|
56
|
+
|
|
57
|
+
2. **Serialization**: Converting MCPServerConfig to the host's expected format
|
|
58
|
+
(field names, structure, excluded fields)
|
|
59
|
+
|
|
60
|
+
3. **Field Support**: Declaring which fields the host supports
|
|
61
|
+
|
|
62
|
+
Subclasses must implement:
|
|
63
|
+
- host_name: The identifier for this host
|
|
64
|
+
- get_supported_fields(): Fields this host accepts
|
|
65
|
+
- validate(): Host-specific validation logic
|
|
66
|
+
- serialize(): Convert config to host format
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
>>> class ClaudeAdapter(BaseAdapter):
|
|
70
|
+
... @property
|
|
71
|
+
... def host_name(self) -> str:
|
|
72
|
+
... return "claude-desktop"
|
|
73
|
+
...
|
|
74
|
+
... def get_supported_fields(self) -> FrozenSet[str]:
|
|
75
|
+
... return frozenset({"command", "args", "env", "url", "headers", "type"})
|
|
76
|
+
...
|
|
77
|
+
... def validate(self, config: MCPServerConfig) -> None:
|
|
78
|
+
... if config.command and config.url:
|
|
79
|
+
... raise AdapterValidationError("Cannot have both command and url")
|
|
80
|
+
...
|
|
81
|
+
... def serialize(self, config: MCPServerConfig) -> Dict[str, Any]:
|
|
82
|
+
... return {k: v for k, v in config.model_dump().items() if v is not None}
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def host_name(self) -> str:
|
|
88
|
+
"""Return the identifier for this host.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Host identifier string (e.g., "claude-desktop", "vscode", "gemini")
|
|
92
|
+
"""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def get_supported_fields(self) -> FrozenSet[str]:
|
|
97
|
+
"""Return the set of fields supported by this host.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
FrozenSet of field names that this host accepts.
|
|
101
|
+
Fields not in this set will be filtered during serialization.
|
|
102
|
+
"""
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def validate(self, config: MCPServerConfig) -> None:
|
|
107
|
+
"""Validate the configuration for this host.
|
|
108
|
+
|
|
109
|
+
This method should check host-specific rules and raise
|
|
110
|
+
AdapterValidationError if the configuration is invalid.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
config: The MCPServerConfig to validate
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
AdapterValidationError: If validation fails
|
|
117
|
+
"""
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def serialize(self, config: MCPServerConfig) -> Dict[str, Any]:
|
|
122
|
+
"""Serialize the configuration for this host.
|
|
123
|
+
|
|
124
|
+
This method should convert the MCPServerConfig to the format
|
|
125
|
+
expected by the host's configuration file.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
config: The MCPServerConfig to serialize
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Dictionary in the host's expected format
|
|
132
|
+
"""
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
def get_excluded_fields(self) -> FrozenSet[str]:
|
|
136
|
+
"""Return fields that should always be excluded from serialization.
|
|
137
|
+
|
|
138
|
+
By default, returns EXCLUDED_ALWAYS (e.g., 'name' which is Hatch metadata).
|
|
139
|
+
Subclasses can override to add host-specific exclusions.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
FrozenSet of field names to exclude
|
|
143
|
+
"""
|
|
144
|
+
return EXCLUDED_ALWAYS
|
|
145
|
+
|
|
146
|
+
def filter_fields(self, config: MCPServerConfig) -> Dict[str, Any]:
|
|
147
|
+
"""Filter config to only include supported, non-excluded, non-None fields.
|
|
148
|
+
|
|
149
|
+
This is a helper method for serialization that:
|
|
150
|
+
1. Gets all fields from the config
|
|
151
|
+
2. Filters to only supported fields
|
|
152
|
+
3. Removes excluded fields
|
|
153
|
+
4. Removes None values
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
config: The MCPServerConfig to filter
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Dictionary with only valid fields for this host
|
|
160
|
+
"""
|
|
161
|
+
supported = self.get_supported_fields()
|
|
162
|
+
excluded = self.get_excluded_fields()
|
|
163
|
+
|
|
164
|
+
result = {}
|
|
165
|
+
for field, value in config.model_dump(exclude_none=True).items():
|
|
166
|
+
if field in supported and field not in excluded:
|
|
167
|
+
result[field] = value
|
|
168
|
+
|
|
169
|
+
return result
|
|
170
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Claude Desktop/Code adapter for MCP host configuration.
|
|
2
|
+
|
|
3
|
+
Claude Desktop and Claude Code share the same configuration format:
|
|
4
|
+
- Supports 'type' field for transport discrimination
|
|
5
|
+
- Mutually exclusive: command XOR url (never both)
|
|
6
|
+
- Standard field set: command, args, env, url, headers, type
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, FrozenSet
|
|
10
|
+
|
|
11
|
+
from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter
|
|
12
|
+
from hatch.mcp_host_config.fields import CLAUDE_FIELDS
|
|
13
|
+
from hatch.mcp_host_config.models import MCPServerConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ClaudeAdapter(BaseAdapter):
|
|
17
|
+
"""Adapter for Claude Desktop and Claude Code hosts.
|
|
18
|
+
|
|
19
|
+
Claude uses a strict validation model:
|
|
20
|
+
- Local servers: command (required), args, env
|
|
21
|
+
- Remote servers: url (required), headers, env
|
|
22
|
+
- Never both command and url
|
|
23
|
+
|
|
24
|
+
Supports the 'type' field for explicit transport discrimination.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, variant: str = "desktop"):
|
|
28
|
+
"""Initialize Claude adapter.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
variant: Either "desktop" or "code" to specify the Claude variant.
|
|
32
|
+
"""
|
|
33
|
+
if variant not in ("desktop", "code"):
|
|
34
|
+
raise ValueError(f"Invalid Claude variant: {variant}. Must be 'desktop' or 'code'")
|
|
35
|
+
self._variant = variant
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def host_name(self) -> str:
|
|
39
|
+
"""Return the host identifier."""
|
|
40
|
+
return f"claude-{self._variant}"
|
|
41
|
+
|
|
42
|
+
def get_supported_fields(self) -> FrozenSet[str]:
|
|
43
|
+
"""Return fields supported by Claude."""
|
|
44
|
+
return CLAUDE_FIELDS
|
|
45
|
+
|
|
46
|
+
def validate(self, config: MCPServerConfig) -> None:
|
|
47
|
+
"""Validate configuration for Claude.
|
|
48
|
+
|
|
49
|
+
Claude requires exactly one transport:
|
|
50
|
+
- stdio (command)
|
|
51
|
+
- sse (url)
|
|
52
|
+
|
|
53
|
+
Having both command and url is invalid.
|
|
54
|
+
"""
|
|
55
|
+
has_command = config.command is not None
|
|
56
|
+
has_url = config.url is not None
|
|
57
|
+
has_http_url = config.httpUrl is not None
|
|
58
|
+
|
|
59
|
+
# Claude doesn't support httpUrl
|
|
60
|
+
if has_http_url:
|
|
61
|
+
raise AdapterValidationError(
|
|
62
|
+
"httpUrl is not supported (use 'url' for remote servers)",
|
|
63
|
+
field="httpUrl",
|
|
64
|
+
host_name=self.host_name
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Must have exactly one transport
|
|
68
|
+
if not has_command and not has_url:
|
|
69
|
+
raise AdapterValidationError(
|
|
70
|
+
"Either 'command' (local) or 'url' (remote) must be specified",
|
|
71
|
+
host_name=self.host_name
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if has_command and has_url:
|
|
75
|
+
raise AdapterValidationError(
|
|
76
|
+
"Cannot specify both 'command' and 'url' - choose one transport",
|
|
77
|
+
host_name=self.host_name
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Validate type consistency if specified
|
|
81
|
+
if config.type is not None:
|
|
82
|
+
if config.type == "stdio" and not has_command:
|
|
83
|
+
raise AdapterValidationError(
|
|
84
|
+
"type='stdio' requires 'command' field",
|
|
85
|
+
field="type",
|
|
86
|
+
host_name=self.host_name
|
|
87
|
+
)
|
|
88
|
+
if config.type in ("sse", "http") and not has_url:
|
|
89
|
+
raise AdapterValidationError(
|
|
90
|
+
f"type='{config.type}' requires 'url' field",
|
|
91
|
+
field="type",
|
|
92
|
+
host_name=self.host_name
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def serialize(self, config: MCPServerConfig) -> Dict[str, Any]:
|
|
96
|
+
"""Serialize configuration for Claude format.
|
|
97
|
+
|
|
98
|
+
Returns a dictionary suitable for Claude's config.json format.
|
|
99
|
+
"""
|
|
100
|
+
# Validate before serializing
|
|
101
|
+
self.validate(config)
|
|
102
|
+
|
|
103
|
+
# Filter to supported fields
|
|
104
|
+
return self.filter_fields(config)
|
|
105
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Codex CLI adapter for MCP host configuration.
|
|
2
|
+
|
|
3
|
+
Codex CLI has unique features:
|
|
4
|
+
- No 'type' field support
|
|
5
|
+
- Field name mappings: args→arguments, headers→http_headers
|
|
6
|
+
- Rich configuration: timeouts, env_vars, tool management, bearer tokens
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, FrozenSet
|
|
10
|
+
|
|
11
|
+
from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter
|
|
12
|
+
from hatch.mcp_host_config.fields import CODEX_FIELDS, CODEX_FIELD_MAPPINGS
|
|
13
|
+
from hatch.mcp_host_config.models import MCPServerConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CodexAdapter(BaseAdapter):
|
|
17
|
+
"""Adapter for Codex CLI MCP host.
|
|
18
|
+
|
|
19
|
+
Codex uses different field names than other hosts:
|
|
20
|
+
- 'args' → 'arguments'
|
|
21
|
+
- 'headers' → 'http_headers'
|
|
22
|
+
|
|
23
|
+
Codex also has:
|
|
24
|
+
- Working directory support (cwd)
|
|
25
|
+
- Timeout configuration (startup_timeout_sec, tool_timeout_sec)
|
|
26
|
+
- Server enable/disable (enabled)
|
|
27
|
+
- Tool filtering (enabled_tools, disabled_tools)
|
|
28
|
+
- Bearer token support (bearer_token_env_var)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def host_name(self) -> str:
|
|
33
|
+
"""Return the host identifier."""
|
|
34
|
+
return "codex"
|
|
35
|
+
|
|
36
|
+
def get_supported_fields(self) -> FrozenSet[str]:
|
|
37
|
+
"""Return fields supported by Codex."""
|
|
38
|
+
return CODEX_FIELDS
|
|
39
|
+
|
|
40
|
+
def validate(self, config: MCPServerConfig) -> None:
|
|
41
|
+
"""Validate configuration for Codex.
|
|
42
|
+
|
|
43
|
+
Codex requires exactly one transport (command XOR url).
|
|
44
|
+
Does not support 'type' field.
|
|
45
|
+
"""
|
|
46
|
+
has_command = config.command is not None
|
|
47
|
+
has_url = config.url is not None
|
|
48
|
+
has_http_url = config.httpUrl is not None
|
|
49
|
+
|
|
50
|
+
# Codex doesn't support httpUrl
|
|
51
|
+
if has_http_url:
|
|
52
|
+
raise AdapterValidationError(
|
|
53
|
+
"httpUrl is not supported (use 'url' for remote servers)",
|
|
54
|
+
field="httpUrl",
|
|
55
|
+
host_name=self.host_name
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Must have exactly one transport
|
|
59
|
+
if not has_command and not has_url:
|
|
60
|
+
raise AdapterValidationError(
|
|
61
|
+
"Either 'command' (local) or 'url' (remote) must be specified",
|
|
62
|
+
host_name=self.host_name
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if has_command and has_url:
|
|
66
|
+
raise AdapterValidationError(
|
|
67
|
+
"Cannot specify both 'command' and 'url' - choose one transport",
|
|
68
|
+
host_name=self.host_name
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# 'type' field is not supported by Codex
|
|
72
|
+
if config.type is not None:
|
|
73
|
+
raise AdapterValidationError(
|
|
74
|
+
"'type' field is not supported by Codex CLI",
|
|
75
|
+
field="type",
|
|
76
|
+
host_name=self.host_name
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Validate enabled_tools and disabled_tools mutual exclusion
|
|
80
|
+
if config.enabled_tools is not None and config.disabled_tools is not None:
|
|
81
|
+
raise AdapterValidationError(
|
|
82
|
+
"Cannot specify both 'enabled_tools' and 'disabled_tools'",
|
|
83
|
+
host_name=self.host_name
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def serialize(self, config: MCPServerConfig) -> Dict[str, Any]:
|
|
87
|
+
"""Serialize configuration for Codex format.
|
|
88
|
+
|
|
89
|
+
Applies field mappings:
|
|
90
|
+
- args → arguments
|
|
91
|
+
- headers → http_headers
|
|
92
|
+
"""
|
|
93
|
+
self.validate(config)
|
|
94
|
+
|
|
95
|
+
# Get base filtered fields
|
|
96
|
+
result = self.filter_fields(config)
|
|
97
|
+
|
|
98
|
+
# Apply field mappings
|
|
99
|
+
for universal_name, codex_name in CODEX_FIELD_MAPPINGS.items():
|
|
100
|
+
if universal_name in result:
|
|
101
|
+
result[codex_name] = result.pop(universal_name)
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Cursor adapter for MCP host configuration.
|
|
2
|
+
|
|
3
|
+
Cursor is similar to VSCode but with limited additional fields:
|
|
4
|
+
- envFile: Path to environment file (like VSCode)
|
|
5
|
+
- No 'inputs' field support (VSCode only)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, FrozenSet
|
|
9
|
+
|
|
10
|
+
from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter
|
|
11
|
+
from hatch.mcp_host_config.fields import CURSOR_FIELDS
|
|
12
|
+
from hatch.mcp_host_config.models import MCPServerConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CursorAdapter(BaseAdapter):
|
|
16
|
+
"""Adapter for Cursor MCP host.
|
|
17
|
+
|
|
18
|
+
Cursor is like a simplified VSCode:
|
|
19
|
+
- Supports Claude base fields + envFile
|
|
20
|
+
- Does NOT support inputs (VSCode-only feature)
|
|
21
|
+
- Requires exactly one transport (command XOR url)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def host_name(self) -> str:
|
|
26
|
+
"""Return the host identifier."""
|
|
27
|
+
return "cursor"
|
|
28
|
+
|
|
29
|
+
def get_supported_fields(self) -> FrozenSet[str]:
|
|
30
|
+
"""Return fields supported by Cursor."""
|
|
31
|
+
return CURSOR_FIELDS
|
|
32
|
+
|
|
33
|
+
def validate(self, config: MCPServerConfig) -> None:
|
|
34
|
+
"""Validate configuration for Cursor.
|
|
35
|
+
|
|
36
|
+
Same rules as Claude: exactly one transport required.
|
|
37
|
+
Warns if 'inputs' is specified (not supported).
|
|
38
|
+
"""
|
|
39
|
+
has_command = config.command is not None
|
|
40
|
+
has_url = config.url is not None
|
|
41
|
+
has_http_url = config.httpUrl is not None
|
|
42
|
+
|
|
43
|
+
# Cursor doesn't support httpUrl
|
|
44
|
+
if has_http_url:
|
|
45
|
+
raise AdapterValidationError(
|
|
46
|
+
"httpUrl is not supported (use 'url' for remote servers)",
|
|
47
|
+
field="httpUrl",
|
|
48
|
+
host_name=self.host_name
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Must have exactly one transport
|
|
52
|
+
if not has_command and not has_url:
|
|
53
|
+
raise AdapterValidationError(
|
|
54
|
+
"Either 'command' (local) or 'url' (remote) must be specified",
|
|
55
|
+
host_name=self.host_name
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if has_command and has_url:
|
|
59
|
+
raise AdapterValidationError(
|
|
60
|
+
"Cannot specify both 'command' and 'url' - choose one transport",
|
|
61
|
+
host_name=self.host_name
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Validate type consistency if specified
|
|
65
|
+
if config.type is not None:
|
|
66
|
+
if config.type == "stdio" and not has_command:
|
|
67
|
+
raise AdapterValidationError(
|
|
68
|
+
"type='stdio' requires 'command' field",
|
|
69
|
+
field="type",
|
|
70
|
+
host_name=self.host_name
|
|
71
|
+
)
|
|
72
|
+
if config.type in ("sse", "http") and not has_url:
|
|
73
|
+
raise AdapterValidationError(
|
|
74
|
+
f"type='{config.type}' requires 'url' field",
|
|
75
|
+
field="type",
|
|
76
|
+
host_name=self.host_name
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def serialize(self, config: MCPServerConfig) -> Dict[str, Any]:
|
|
80
|
+
"""Serialize configuration for Cursor format."""
|
|
81
|
+
self.validate(config)
|
|
82
|
+
return self.filter_fields(config)
|
|
83
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Gemini CLI adapter for MCP host configuration.
|
|
2
|
+
|
|
3
|
+
Gemini has unique features:
|
|
4
|
+
- Triple transport: command (stdio), url (SSE), httpUrl (HTTP streaming)
|
|
5
|
+
- Multiple transports can coexist (not mutually exclusive)
|
|
6
|
+
- No 'type' field support
|
|
7
|
+
- Rich OAuth configuration
|
|
8
|
+
- Working directory, timeout, trust settings
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, FrozenSet
|
|
12
|
+
|
|
13
|
+
from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter
|
|
14
|
+
from hatch.mcp_host_config.fields import GEMINI_FIELDS
|
|
15
|
+
from hatch.mcp_host_config.models import MCPServerConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GeminiAdapter(BaseAdapter):
|
|
19
|
+
"""Adapter for Gemini CLI MCP host.
|
|
20
|
+
|
|
21
|
+
Gemini is unique among MCP hosts:
|
|
22
|
+
- Supports THREE transport types (stdio, SSE, HTTP streaming)
|
|
23
|
+
- Transports are NOT mutually exclusive (can have multiple)
|
|
24
|
+
- Does NOT support 'type' field
|
|
25
|
+
- Has rich configuration: OAuth, timeout, trust, tool filtering
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def host_name(self) -> str:
|
|
30
|
+
"""Return the host identifier."""
|
|
31
|
+
return "gemini"
|
|
32
|
+
|
|
33
|
+
def get_supported_fields(self) -> FrozenSet[str]:
|
|
34
|
+
"""Return fields supported by Gemini."""
|
|
35
|
+
return GEMINI_FIELDS
|
|
36
|
+
|
|
37
|
+
def validate(self, config: MCPServerConfig) -> None:
|
|
38
|
+
"""Validate configuration for Gemini.
|
|
39
|
+
|
|
40
|
+
Gemini is flexible:
|
|
41
|
+
- At least one transport is required (command, url, or httpUrl)
|
|
42
|
+
- Multiple transports are allowed
|
|
43
|
+
- 'type' field is not supported
|
|
44
|
+
"""
|
|
45
|
+
has_command = config.command is not None
|
|
46
|
+
has_url = config.url is not None
|
|
47
|
+
has_http_url = config.httpUrl is not None
|
|
48
|
+
|
|
49
|
+
# Must have at least one transport
|
|
50
|
+
if not has_command and not has_url and not has_http_url:
|
|
51
|
+
raise AdapterValidationError(
|
|
52
|
+
"At least one transport must be specified: 'command', 'url', or 'httpUrl'",
|
|
53
|
+
host_name=self.host_name
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# 'type' field is not supported by Gemini
|
|
57
|
+
if config.type is not None:
|
|
58
|
+
raise AdapterValidationError(
|
|
59
|
+
"'type' field is not supported by Gemini CLI",
|
|
60
|
+
field="type",
|
|
61
|
+
host_name=self.host_name
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Validate includeTools and excludeTools are mutually exclusive
|
|
65
|
+
if config.includeTools is not None and config.excludeTools is not None:
|
|
66
|
+
raise AdapterValidationError(
|
|
67
|
+
"Cannot specify both 'includeTools' and 'excludeTools'",
|
|
68
|
+
host_name=self.host_name
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def serialize(self, config: MCPServerConfig) -> Dict[str, Any]:
|
|
72
|
+
"""Serialize configuration for Gemini format."""
|
|
73
|
+
self.validate(config)
|
|
74
|
+
return self.filter_fields(config)
|
|
75
|
+
|