Skip to content

Commit 67bb767

Browse files
Merge branch 'feat/augment-mcp-host-support' into dev
2 parents 038be8c + 294d0d8 commit 67bb767

File tree

11 files changed

+176
-0
lines changed

11 files changed

+176
-0
lines changed

hatch/mcp_host_config/adapters/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Each adapter handles validation and serialization for a specific MCP host.
55
"""
66

7+
from hatch.mcp_host_config.adapters.augment import AugmentAdapter
78
from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter
89
from hatch.mcp_host_config.adapters.claude import ClaudeAdapter
910
from hatch.mcp_host_config.adapters.codex import CodexAdapter
@@ -28,6 +29,7 @@
2829
"get_adapter",
2930
"get_default_registry",
3031
# Host-specific adapters
32+
"AugmentAdapter",
3133
"ClaudeAdapter",
3234
"CodexAdapter",
3335
"CursorAdapter",
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Augment Code adapter for MCP host configuration.
2+
3+
Augment Code (auggie CLI + extensions) uses the same field set as Claude:
4+
command/args/env for stdio, url/headers for sse/http, with optional type discriminator.
5+
Config file: ~/.augment/settings.json, root key: mcpServers.
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 AUGMENT_FIELDS
12+
from hatch.mcp_host_config.models import MCPServerConfig
13+
14+
15+
class AugmentAdapter(BaseAdapter):
16+
"""Adapter for Augment Code MCP host.
17+
18+
Augment Code uses the same configuration format as Claude:
19+
- Supports 'type' field for transport discrimination
20+
- Requires exactly one transport (command XOR url)
21+
"""
22+
23+
@property
24+
def host_name(self) -> str:
25+
"""Return the host identifier."""
26+
return "augment"
27+
28+
def get_supported_fields(self) -> FrozenSet[str]:
29+
"""Return fields supported by Augment Code."""
30+
return AUGMENT_FIELDS
31+
32+
def validate(self, config: MCPServerConfig) -> None:
33+
"""Validate configuration for Augment Code.
34+
35+
DEPRECATED: This method is deprecated and will be removed in v0.9.0.
36+
Use validate_filtered() instead.
37+
"""
38+
has_command = config.command is not None
39+
has_url = config.url is not None
40+
41+
if not has_command and not has_url:
42+
raise AdapterValidationError(
43+
"Either 'command' (local) or 'url' (remote) must be specified",
44+
host_name=self.host_name,
45+
)
46+
47+
if has_command and has_url:
48+
raise AdapterValidationError(
49+
"Cannot specify both 'command' and 'url' - choose one transport",
50+
host_name=self.host_name,
51+
)
52+
53+
if config.type is not None:
54+
if config.type == "stdio" and not has_command:
55+
raise AdapterValidationError(
56+
"type='stdio' requires 'command' field",
57+
field="type",
58+
host_name=self.host_name,
59+
)
60+
if config.type in ("sse", "http") and not has_url:
61+
raise AdapterValidationError(
62+
f"type='{config.type}' requires 'url' field",
63+
field="type",
64+
host_name=self.host_name,
65+
)
66+
67+
def validate_filtered(self, filtered: Dict[str, Any]) -> None:
68+
"""Validate filtered configuration for Augment Code.
69+
70+
Validates only fields that survived filtering (supported by Augment).
71+
Augment Code requires exactly one transport (command XOR url).
72+
73+
Args:
74+
filtered: Dictionary of filtered fields
75+
76+
Raises:
77+
AdapterValidationError: If validation fails
78+
"""
79+
has_command = "command" in filtered
80+
has_url = "url" in filtered
81+
82+
if not has_command and not has_url:
83+
raise AdapterValidationError(
84+
"Either 'command' (local) or 'url' (remote) must be specified",
85+
host_name=self.host_name,
86+
)
87+
88+
if has_command and has_url:
89+
raise AdapterValidationError(
90+
"Cannot specify both 'command' and 'url' - choose one transport",
91+
host_name=self.host_name,
92+
)
93+
94+
if "type" in filtered:
95+
config_type = filtered["type"]
96+
if config_type == "stdio" and not has_command:
97+
raise AdapterValidationError(
98+
"type='stdio' requires 'command' field",
99+
field="type",
100+
host_name=self.host_name,
101+
)
102+
if config_type in ("sse", "http") and not has_url:
103+
raise AdapterValidationError(
104+
f"type='{config_type}' requires 'url' field",
105+
field="type",
106+
host_name=self.host_name,
107+
)
108+
109+
def serialize(self, config: MCPServerConfig) -> Dict[str, Any]:
110+
"""Serialize configuration for Augment Code format.
111+
112+
Follows the validate-after-filter pattern:
113+
1. Filter to supported fields
114+
2. Validate filtered fields
115+
3. Return filtered (no transformations needed)
116+
"""
117+
filtered = self.filter_fields(config)
118+
self.validate_filtered(filtered)
119+
return filtered

hatch/mcp_host_config/adapters/registry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from typing import Dict, List, Optional
88

9+
from hatch.mcp_host_config.adapters.augment import AugmentAdapter
910
from hatch.mcp_host_config.adapters.base import BaseAdapter
1011
from hatch.mcp_host_config.adapters.claude import ClaudeAdapter
1112
from hatch.mcp_host_config.adapters.codex import CodexAdapter
@@ -55,6 +56,7 @@ def _register_defaults(self) -> None:
5556
self.register(KiroAdapter())
5657
self.register(CodexAdapter())
5758
self.register(OpenCodeAdapter())
59+
self.register(AugmentAdapter())
5860

5961
def register(self, adapter: BaseAdapter) -> None:
6062
"""Register an adapter instance.

hatch/mcp_host_config/backup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def validate_hostname(cls, v):
4949
"kiro",
5050
"codex",
5151
"opencode",
52+
"augment",
5253
}
5354
if v not in supported_hosts:
5455
raise ValueError(f"Unsupported hostname: {v}. Supported: {supported_hosts}")

hatch/mcp_host_config/fields.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"claude-code",
3535
"vscode",
3636
"cursor",
37+
"augment",
3738
}
3839
)
3940

@@ -116,6 +117,10 @@
116117
)
117118

118119

120+
# Fields supported by Augment Code (auggie CLI + extensions); same as Claude fields
121+
# Config: ~/.augment/settings.json, key: mcpServers
122+
AUGMENT_FIELDS: FrozenSet[str] = CLAUDE_FIELDS
123+
119124
# Fields supported by OpenCode (no type field; uses local/remote type derivation,
120125
# command array merge, environment rename, and oauth nesting)
121126
OPENCODE_FIELDS: FrozenSet[str] = UNIVERSAL_FIELDS | frozenset(

hatch/mcp_host_config/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class MCPHostType(str, Enum):
3131
KIRO = "kiro"
3232
CODEX = "codex"
3333
OPENCODE = "opencode"
34+
AUGMENT = "augment"
3435

3536

3637
class MCPServerConfig(BaseModel):
@@ -366,6 +367,7 @@ def validate_host_names(cls, v):
366367
"gemini",
367368
"kiro",
368369
"opencode",
370+
"augment",
369371
}
370372
for host_name in v.keys():
371373
if host_name not in supported_hosts:

hatch/mcp_host_config/reporting.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def _get_adapter_host_name(host_type: MCPHostType) -> str:
7474
MCPHostType.KIRO: "kiro",
7575
MCPHostType.CODEX: "codex",
7676
MCPHostType.OPENCODE: "opencode",
77+
MCPHostType.AUGMENT: "augment",
7778
}
7879
return mapping.get(host_type, host_type.value)
7980

hatch/mcp_host_config/strategies.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,3 +1053,33 @@ def write_configuration(
10531053
except Exception as e:
10541054
logger.error(f"Failed to write OpenCode configuration: {e}")
10551055
return False
1056+
1057+
1058+
@register_host_strategy(MCPHostType.AUGMENT)
1059+
class AugmentHostStrategy(ClaudeHostStrategy):
1060+
"""Configuration strategy for Augment Code (auggie CLI + extensions).
1061+
1062+
Augment Code stores MCP configuration in ~/.augment/settings.json under
1063+
the 'mcpServers' key -- the same format as Claude. The settings.json file
1064+
may contain other non-MCP Augment settings which are preserved via the
1065+
inherited _preserve_claude_settings() mechanism.
1066+
"""
1067+
1068+
def get_adapter_host_name(self) -> str:
1069+
"""Return the adapter host name for Augment Code."""
1070+
return "augment"
1071+
1072+
def get_config_path(self) -> Optional[Path]:
1073+
"""Get Augment Code configuration path.
1074+
1075+
Same path on macOS, Linux, and Windows WSL.
1076+
Native Windows (non-WSL) is not yet confirmed and returns None.
1077+
"""
1078+
system = platform.system()
1079+
if system in ("Darwin", "Linux"):
1080+
return Path.home() / ".augment" / "settings.json"
1081+
return None
1082+
1083+
def is_host_available(self) -> bool:
1084+
"""Check if Augment Code is installed by checking for ~/.augment/ directory."""
1085+
return (Path.home() / ".augment").exists()

tests/test_data/mcp_adapters/canonical_configs.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@
7575
"enabled_tools": ["tool1", "tool2"],
7676
"disabled_tools": ["tool3"]
7777
},
78+
"augment": {
79+
"command": "python",
80+
"args": ["-m", "mcp_server"],
81+
"env": {"API_KEY": "test_key"},
82+
"url": null,
83+
"headers": null,
84+
"type": "stdio"
85+
},
7886
"opencode": {
7987
"command": "npx",
8088
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],

tests/test_data/mcp_adapters/host_registry.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from pathlib import Path
2020
from typing import Any, Dict, FrozenSet, List, Optional, Set, Tuple
2121

22+
from hatch.mcp_host_config.adapters.augment import AugmentAdapter
2223
from hatch.mcp_host_config.adapters.base import BaseAdapter
2324
from hatch.mcp_host_config.adapters.claude import ClaudeAdapter
2425
from hatch.mcp_host_config.adapters.codex import CodexAdapter
@@ -29,6 +30,7 @@
2930
from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter
3031
from hatch.mcp_host_config.adapters.vscode import VSCodeAdapter
3132
from hatch.mcp_host_config.fields import (
33+
AUGMENT_FIELDS,
3234
CLAUDE_FIELDS,
3335
CODEX_FIELD_MAPPINGS,
3436
CODEX_FIELDS,
@@ -58,6 +60,7 @@
5860
"kiro": KIRO_FIELDS,
5961
"codex": CODEX_FIELDS,
6062
"opencode": OPENCODE_FIELDS,
63+
"augment": AUGMENT_FIELDS,
6164
}
6265

6366
# Reverse mappings for Codex (host-native name → universal name)
@@ -97,6 +100,7 @@ def get_adapter(self) -> BaseAdapter:
97100
"kiro": KiroAdapter,
98101
"codex": CodexAdapter,
99102
"opencode": OpenCodeAdapter,
103+
"augment": AugmentAdapter,
100104
}
101105
factory = adapter_map[self.host_name]
102106
return factory()
@@ -355,6 +359,7 @@ def generate_unsupported_field_test_cases(
355359
| KIRO_FIELDS
356360
| CODEX_FIELDS
357361
| OPENCODE_FIELDS
362+
| AUGMENT_FIELDS
358363
)
359364

360365
cases: List[FilterTestCase] = []

0 commit comments

Comments
 (0)