From 1056e525eefc0da90a8cf1465ce940ef01747dcc Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Sun, 22 Feb 2026 19:29:33 +0900 Subject: [PATCH 01/71] chore: cleanup `__reports__/` Remove committed report meant to be temporarily --- ...5-documentation_deprecation_analysis_v0.md | 194 ------------------ .../02-fresh_eye_review_v0.md | 82 -------- 2 files changed, 276 deletions(-) delete mode 100644 __reports__/CLI-refactoring/05-documentation_deprecation_analysis_v0.md delete mode 100644 __reports__/standards-retrospective/02-fresh_eye_review_v0.md diff --git a/__reports__/CLI-refactoring/05-documentation_deprecation_analysis_v0.md b/__reports__/CLI-refactoring/05-documentation_deprecation_analysis_v0.md deleted file mode 100644 index c552c7e..0000000 --- a/__reports__/CLI-refactoring/05-documentation_deprecation_analysis_v0.md +++ /dev/null @@ -1,194 +0,0 @@ -# Documentation Deprecation Analysis: CLI Refactoring Impact - -**Date**: 2026-01-01 -**Phase**: Post-Implementation Documentation Review -**Scope**: Identifying deprecated documentation after CLI handler-based architecture refactoring -**Reference**: `__design__/cli-refactoring-milestone-v0.7.2-dev.1.md` - ---- - -## Executive Summary - -The CLI refactoring from monolithic `cli_hatch.py` (2,850 LOC) to handler-based architecture in `hatch/cli/` package has rendered several documentation references outdated. This report identifies affected files and specifies required updates. - -**Architecture Change Summary:** -``` -BEFORE: AFTER: -hatch/cli_hatch.py (2,850 LOC) hatch/cli/ - ├── __init__.py (57 LOC) - ├── __main__.py (840 LOC) - ├── cli_utils.py (270 LOC) - ├── cli_mcp.py (1,222 LOC) - ├── cli_env.py (375 LOC) - ├── cli_package.py (552 LOC) - └── cli_system.py (92 LOC) - - hatch/cli_hatch.py (136 LOC) ← backward compat shim -``` - ---- - -## Affected Documentation Files - -### Category 1: API Documentation (HIGH PRIORITY) - -| File | Issue | Impact | -|------|-------|--------| -| `docs/articles/api/cli.md` | References `hatch.cli_hatch` only | mkdocstrings generates incomplete API docs | - -**Current Content:** -```markdown -# CLI Module -::: hatch.cli_hatch -``` - -**Required Update:** Expand to document the full `hatch.cli` package structure with all submodules. - ---- - -### Category 2: User Documentation (HIGH PRIORITY) - -| File | Line | Issue | -|------|------|-------| -| `docs/articles/users/CLIReference.md` | 3 | States "implemented in `hatch/cli_hatch.py`" | - -**Current Content (Line 3):** -```markdown -This document is a compact reference of all Hatch CLI commands and options implemented in `hatch/cli_hatch.py` presented as tables for quick lookup. -``` - -**Required Update:** Reference the new `hatch/cli/` package structure. - ---- - -### Category 3: Developer Implementation Guides (HIGH PRIORITY) - -| File | Lines | Issue | -|------|-------|-------| -| `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` | 605, 613-626 | References `cli_hatch.py` for CLI integration | - -**Affected Sections:** - -1. **Line 605** - "Add CLI arguments in `cli_hatch.py`" -2. **Lines 613-626** - CLI Integration for Host-Specific Fields section - -**Current Content:** -```markdown -4. **Add CLI arguments** in `cli_hatch.py` (see next section) -... -1. **Update function signature** in `handle_mcp_configure()`: -```python -def handle_mcp_configure( - # ... existing params ... - your_field: Optional[str] = None, # Add your field -): -``` -``` - -**Required Update:** -- Argument parsing → `hatch/cli/__main__.py` -- Handler modifications → `hatch/cli/cli_mcp.py` - ---- - -### Category 4: Architecture Documentation (MEDIUM PRIORITY) - -| File | Line | Issue | -|------|------|-------| -| `docs/articles/devs/architecture/mcp_host_configuration.md` | 158 | References `cli_hatch.py` | - -**Current Content (Line 158):** -```markdown -1. Extend `handle_mcp_configure()` function signature in `cli_hatch.py` -``` - -**Required Update:** Reference new module locations. - ---- - -### Category 5: Architecture Diagrams (MEDIUM PRIORITY) - -| File | Line | Issue | -|------|------|-------| -| `docs/resources/diagrams/architecture.puml` | 9 | Shows CLI as single `cli_hatch` component | - -**Current Content:** -```plantuml -Container_Boundary(cli, "CLI Layer") { - Component(cli_hatch, "CLI Interface", "Python", "Command-line interface\nArgument parsing and validation") -} -``` - -**Required Update:** Reflect modular CLI architecture with handler modules. - ---- - -### Category 6: Instruction Templates (LOW PRIORITY) - -| File | Lines | Issue | -|------|-------|-------| -| `cracking-shells-playbook/instructions/documentation-api.instructions.md` | 37-41 | Uses `hatch/cli_hatch.py` as example | - -**Current Content:** -```markdown -**For a module `hatch/cli_hatch.py`, create `docs/articles/api/cli.md`:** -```markdown -# CLI Module -::: hatch.cli_hatch -``` -``` - -**Required Update:** Update example to show new CLI package pattern. - ---- - -## Files NOT to Modify - -| Category | Files | Reason | -|----------|-------|--------| -| Historical Analysis | `__reports__/CLI-refactoring/00-04*.md` | Document pre-refactoring state | -| Design Documents | `__design__/cli-refactoring-*.md` | Document refactoring plan | -| Handover Documents | `__design__/handover-*.md` | Document session context | - ---- - -## Update Strategy - -### Handler Location Mapping - -| Handler/Function | Old Location | New Location | -|------------------|--------------|--------------| -| `main()` | `hatch.cli_hatch` | `hatch.cli.__main__` | -| `handle_mcp_configure()` | `hatch.cli_hatch` | `hatch.cli.cli_mcp` | -| `handle_mcp_*()` | `hatch.cli_hatch` | `hatch.cli.cli_mcp` | -| `handle_env_*()` | `hatch.cli_hatch` | `hatch.cli.cli_env` | -| `handle_package_*()` | `hatch.cli_hatch` | `hatch.cli.cli_package` | -| `handle_create()`, `handle_validate()` | `hatch.cli_hatch` | `hatch.cli.cli_system` | -| `parse_host_list()`, utilities | `hatch.cli_hatch` | `hatch.cli.cli_utils` | -| Argument parsing | `hatch.cli_hatch` | `hatch.cli.__main__` | - -### Backward Compatibility Note - -`hatch/cli_hatch.py` remains as a backward compatibility shim that re-exports all public symbols. External consumers can still import from `hatch.cli_hatch`, but new code should use `hatch.cli.*`. - ---- - -## Implementation Checklist - -- [x] Update `docs/articles/api/cli.md` - Expand API documentation -- [x] Update `docs/articles/users/CLIReference.md` - Fix intro paragraph -- [x] Update `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` - Fix CLI integration section -- [x] Update `docs/articles/devs/architecture/mcp_host_configuration.md` - Fix CLI reference -- [x] Update `docs/resources/diagrams/architecture.puml` - Update CLI component -- [x] Update `cracking-shells-playbook/instructions/documentation-api.instructions.md` - Update example - ---- - -## Risk Assessment - -| Risk | Likelihood | Impact | Mitigation | -|------|------------|--------|------------| -| Broken mkdocstrings generation | High | Medium | Test docs build after changes | -| Developer confusion from outdated guides | Medium | High | Prioritize implementation guide updates | -| Diagram regeneration issues | Low | Low | Verify PlantUML syntax | - diff --git a/__reports__/standards-retrospective/02-fresh_eye_review_v0.md b/__reports__/standards-retrospective/02-fresh_eye_review_v0.md deleted file mode 100644 index be058a1..0000000 --- a/__reports__/standards-retrospective/02-fresh_eye_review_v0.md +++ /dev/null @@ -1,82 +0,0 @@ -# Fresh-Eye Review — Post-Implementation Gap Analysis (v0) - -Date: 2026-02-19 -Follows: `01-instructions_redesign_v3.md` implementation via `__roadmap__/instructions-redesign/` - -## Executive Summary - -After the instruction files were rewritten/edited per the v3 redesign, a fresh-eye review reveals **residual stale terminology** in 6 files that were NOT in the §11 affected list, **1 stale cross-reference** in a file that WAS edited, and **1 useful addition** (the `roadmap-execution.instructions.md`) that emerged during implementation but wasn't anticipated in the architecture report. A companion JSON schema (`roadmap-document-schema.json`) is proposed and delivered alongside this report. - -## Findings - -### F1: Stale "Phase N" Terminology in Edited Files - -These files were in the §11 scope and were edited, but retain stale Phase references: - -| File | Location | Stale Text | Suggested Fix | -|:-----|:---------|:-----------|:--------------| -| `reporting.instructions.md` | §2 "Default artifacts" | "Phase 1: Mermaid diagrams…" / "Phase 2: Risk-driven test matrix…" | Replace with "Architecture reports:" / "Test definition reports:" (drop phase numbering) | -| `reporting.instructions.md` | §"Specialized reporting guidance" | "Phase 1 architecture guidance" / "Phase 2 test definition reports" | "Architecture reporting guidance" / "Test definition reporting guidance" | -| `reporting.instructions.md` | §"Where reports go" | "Use `__design__/` for durable design/roadmaps." | "Use `__design__/` for durable architectural decisions." (roadmaps go in `__roadmap__/`, already stated in reporting-structure) | -| `reporting-architecture.instructions.md` | Title + front-matter + opening line | "Phase 1" in title, description, and body | "Stage 1" or simply "Architecture Reporting" | -| `reporting-structure.instructions.md` | §3 README convention | "Phase 1/2/3 etc." | "Stage 1/2/3 etc." or "Analysis/Roadmap/Execution" | - -**Severity**: Low — cosmetic inconsistency, but agents parsing these instructions may be confused by mixed terminology. - -### F2: Stale "Phase N" Terminology in Files Outside §11 Scope - -These files were NOT listed in §11 and were not touched during the campaign: - -| File | Location | Stale Text | Suggested Fix | -|:-----|:---------|:-----------|:--------------| -| `reporting-tests.instructions.md` | Title, front-matter, §body (6+ occurrences) | "Phase 2" throughout | "Stage 1" or "Test Definition Reporting" (tests are defined during Analysis, not a separate phase) | -| `reporting-templates.instructions.md` | Front-matter + section headers | "Phase 1" / "Phase 2" template headers | "Architecture Analysis" / "Test Definition" | -| `reporting-templates.instructions.md` | §Roadmap Recommendation | "create `__design__/_roadmap_vN.md`" | "create a roadmap directory tree under `__roadmap__//`" | -| `reporting-knowledge-transfer.instructions.md` | §"What not to do" | "link to Phase 1 artifacts" | "link to Stage 1 analysis artifacts" | -| `analytic-behavior.instructions.md` | §"Two-Phase Work Process" | "Phase 1: Analysis and Documentation" / "Phase 2: Implementation with Context Refresh" | This is a different "phase" concept (analysis vs implementation within a single session), not the old 7-phase model. **Ambiguous but arguably fine** — the two-phase work process here is about agent behavior, not the code-change workflow. Consider renaming to "Two-Step Work Process" or "Analysis-First Work Process" to avoid confusion. | -| `testing.instructions.md` | §2.3 | "Phase 2 report format" | "Test definition report format" | -| `testing.instructions.md` | §2.3 reference text | "Phase 2 in code change phases" | "Stage 1 (Analysis) in code change phases" | - -**Severity**: Medium for `reporting-tests.instructions.md` and `reporting-templates.instructions.md` (heavily used during Stage 1 work). Low for the others. - -### F3: Missing Cross-Reference in `code-change-phases.instructions.md` - -Stage 3 (Execution) describes the breadth-first algorithm but does NOT link to `roadmap-execution.instructions.md`, which contains the detailed operational manual (failure handling escalation ladder, subagent dispatch protocol, status update discipline, completion checklist). - -**Suggested fix**: Add a reference in Stage 3: -```markdown -For the detailed operational manual (failure handling, subagent dispatch, status updates), see [roadmap-execution.instructions.md](./roadmap-execution.instructions.md). -``` - -### F4: `roadmap-execution.instructions.md` — Unanticipated but Valuable - -This file was created during the campaign but was not listed in v3 §11. It fills a genuine gap: the v3 report describes WHAT the execution model is, but the execution manual describes HOW an agent should operationally navigate it (including the escalation ladder, subagent dispatch, and status update discipline). - -**Recommendation**: Acknowledge in the v3 report's §11 table as an addition, or simply note it in the campaign's amendment log. No action needed — the file is well-written and consistent with the model. - -### F5: Schema Companion Delivered - -A JSON Schema (`roadmap-document-schema.json`) has been created alongside this report. It formally defines the required and optional fields for: -- `README.md` (directory-level entry point) -- Leaf Task files -- Steps within leaf tasks -- Supporting types (status values, amendment log entries, progress entries, Mermaid node definitions) - -Location: `cracking-shells-playbook/instructions/roadmap-document-schema.json` - ---- - -## Prioritized Fix List - -| Priority | Finding | Files Affected | Effort | -|:---------|:--------|:---------------|:-------| -| 1 | F1: Stale terminology in edited files | 3 files | ~15 min (surgical text replacements) | -| 2 | F3: Missing cross-reference | 1 file | ~2 min | -| 3 | F2: Stale terminology in unscoped files | 5 files | ~45 min (more occurrences, some require judgment) | -| 4 | F4: Acknowledge execution manual | 1 file (v3 report or amendment log) | ~5 min | - -## Decision Required - -- **F1 + F3**: Straightforward fixes, recommend immediate application. -- **F2**: Larger scope. The `reporting-tests.instructions.md` and `reporting-templates.instructions.md` files have "Phase" deeply embedded. A dedicated task or amendment may be warranted. -- **F2 (analytic-behavior)**: The "Two-Phase Work Process" is arguably a different concept. Stakeholder judgment needed on whether to rename. From c544cb39303de1acb9eb9c74285fd9344b5c1c1f Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 11:08:13 +0900 Subject: [PATCH 02/71] chore: update cs-playbook submodule --- cracking-shells-playbook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cracking-shells-playbook b/cracking-shells-playbook index fd768bf..afc6c1e 160000 --- a/cracking-shells-playbook +++ b/cracking-shells-playbook @@ -1 +1 @@ -Subproject commit fd768bf6bc67c1ce916552521f781826acc65926 +Subproject commit afc6c1ed54aaa01e4666d3c2837fc9a39dcb25e8 From c08e0649d19bc296e298f95e47a20cbb846ec035 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:02:03 +0900 Subject: [PATCH 03/71] docs(mcp): update field support matrix and field mapping documentation Co-Authored-By: Claude Opus 4.6 --- .../architecture/mcp_host_configuration.md | 146 ++++++++++++++---- 1 file changed, 117 insertions(+), 29 deletions(-) diff --git a/docs/articles/devs/architecture/mcp_host_configuration.md b/docs/articles/devs/architecture/mcp_host_configuration.md index 79cfc4b..5f01936 100644 --- a/docs/articles/devs/architecture/mcp_host_configuration.md +++ b/docs/articles/devs/architecture/mcp_host_configuration.md @@ -63,9 +63,9 @@ class MCPServerConfig(BaseModel): name: Optional[str] = None # Transport fields - command: Optional[str] = None # stdio transport - url: Optional[str] = None # sse transport - httpUrl: Optional[str] = None # http transport (Gemini) + command: Optional[str] = None # stdio transport + url: Optional[str] = None # sse transport + httpUrl: Optional[str] = None # http transport (Gemini) # Universal fields (all hosts) args: Optional[List[str]] = None @@ -73,11 +73,42 @@ class MCPServerConfig(BaseModel): headers: Optional[Dict[str, str]] = None type: Optional[Literal["stdio", "sse", "http"]] = None - # Host-specific fields - envFile: Optional[str] = None # VSCode/Cursor - disabled: Optional[bool] = None # Kiro - trust: Optional[bool] = None # Gemini - # ... additional fields per host + # VSCode/Cursor fields + envFile: Optional[str] = None # Path to environment file + inputs: Optional[List[Dict]] = None # Input variable definitions (VSCode only) + + # Gemini fields (16 total including OAuth) + cwd: Optional[str] = None # Working directory (Gemini/Codex) + timeout: Optional[int] = None # Request timeout in milliseconds + trust: Optional[bool] = None # Bypass tool call confirmations + includeTools: Optional[List[str]] = None # Tools to include (allowlist) + excludeTools: Optional[List[str]] = None # Tools to exclude (blocklist) + oauth_enabled: Optional[bool] = None # Enable OAuth for this server + oauth_clientId: Optional[str] = None # OAuth client identifier + oauth_clientSecret: Optional[str] = None # OAuth client secret + oauth_authorizationUrl: Optional[str] = None # OAuth authorization endpoint + oauth_tokenUrl: Optional[str] = None # OAuth token endpoint + oauth_scopes: Optional[List[str]] = None # Required OAuth scopes + oauth_redirectUri: Optional[str] = None # Custom redirect URI + oauth_tokenParamName: Optional[str] = None # Query parameter name for tokens + oauth_audiences: Optional[List[str]] = None # OAuth audiences + authProviderType: Optional[str] = None # Authentication provider type + + # Kiro fields + disabled: Optional[bool] = None # Whether server is disabled + autoApprove: Optional[List[str]] = None # Auto-approved tool names + disabledTools: Optional[List[str]] = None # Disabled tool names + + # Codex fields (10 host-specific) + env_vars: Optional[List[str]] = None # Environment variables to whitelist/forward + startup_timeout_sec: Optional[int] = None # Server startup timeout + tool_timeout_sec: Optional[int] = None # Tool execution timeout + enabled: Optional[bool] = None # Enable/disable server + enabled_tools: Optional[List[str]] = None # Allow-list of tools + disabled_tools: Optional[List[str]] = None # Deny-list of tools + bearer_token_env_var: Optional[str] = None # Env var containing bearer token + http_headers: Optional[Dict[str, str]] = None # HTTP headers (Codex naming) + env_http_headers: Optional[Dict[str, str]] = None # Header names to env var names ``` **Design principles:** @@ -161,22 +192,39 @@ preventing false rejections during cross-host sync operations. ### Field Constants -Field support is defined in `fields.py`: +Field support is defined in `fields.py` as the single source of truth. Every host's field set is built by extending `UNIVERSAL_FIELDS` with host-specific additions: ```python -# Universal fields (all hosts) +# Universal fields (supported by ALL hosts) — 5 fields UNIVERSAL_FIELDS = frozenset({"command", "args", "env", "url", "headers"}) -# Host-specific field sets -CLAUDE_FIELDS = UNIVERSAL_FIELDS | frozenset({"type"}) -VSCODE_FIELDS = CLAUDE_FIELDS | frozenset({"envFile", "inputs"}) -GEMINI_FIELDS = UNIVERSAL_FIELDS | frozenset({"httpUrl", "timeout", "trust", ...}) -KIRO_FIELDS = UNIVERSAL_FIELDS | frozenset({"disabled", "autoApprove", ...}) +# Hosts that support the 'type' discriminator field +TYPE_SUPPORTING_HOSTS = frozenset({"claude-desktop", "claude-code", "vscode", "cursor"}) + +# Host-specific field sets — 7 constants, 8 hosts +CLAUDE_FIELDS = UNIVERSAL_FIELDS | {"type"} # 6 fields +VSCODE_FIELDS = CLAUDE_FIELDS | {"envFile", "inputs"} # 8 fields +CURSOR_FIELDS = CLAUDE_FIELDS | {"envFile"} # 7 fields +LMSTUDIO_FIELDS = CLAUDE_FIELDS # 6 fields (alias) +GEMINI_FIELDS = UNIVERSAL_FIELDS | {"httpUrl", "timeout", "trust", "cwd", + "includeTools", "excludeTools", + "oauth_enabled", "oauth_clientId", "oauth_clientSecret", + "oauth_authorizationUrl", "oauth_tokenUrl", "oauth_scopes", + "oauth_redirectUri", "oauth_tokenParamName", + "oauth_audiences", "authProviderType"} # 21 fields +KIRO_FIELDS = UNIVERSAL_FIELDS | {"disabled", "autoApprove", + "disabledTools"} # 8 fields +CODEX_FIELDS = UNIVERSAL_FIELDS | {"cwd", "env_vars", "startup_timeout_sec", + "tool_timeout_sec", "enabled", "enabled_tools", + "disabled_tools", "bearer_token_env_var", + "http_headers", "env_http_headers"} # 15 fields # Metadata fields (never serialized or reported) EXCLUDED_ALWAYS = frozenset({"name"}) ``` +Note that `LMSTUDIO_FIELDS` is a direct alias for `CLAUDE_FIELDS` — LM Studio supports the same field set as Claude Desktop and Claude Code. + ### Reporting System The reporting system (`reporting.py`) provides user-friendly feedback for MCP configuration operations. It respects adapter exclusion semantics to ensure consistency between what's reported and what's actually written to host configuration files. @@ -210,17 +258,53 @@ This ensures that: ## Field Support Matrix -| Field | Claude | VSCode | Cursor | Gemini | Kiro | Codex | -|-------|--------|--------|--------|--------|------|-------| -| command, args, env | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| url, headers | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| type | ✓ | ✓ | ✓ | - | - | - | -| envFile | - | ✓ | ✓ | - | - | - | -| inputs | - | ✓ | - | - | - | - | -| httpUrl | - | - | - | ✓ | - | - | -| trust, timeout | - | - | - | ✓ | - | - | -| disabled, autoApprove | - | - | - | - | ✓ | - | -| enabled, enabled_tools | - | - | - | - | - | ✓ | +The matrix below lists every field present in any host's field set (defined in `fields.py`). Claude Desktop, Claude Code, and LM Studio share the same field set (`CLAUDE_FIELDS`), so LM Studio is shown in its own column to make this explicit. + +| Field | Claude Desktop/Code | VSCode | Cursor | LM Studio | Gemini | Kiro | Codex | +|-------|:-------------------:|:------:|:------:|:---------:|:------:|:----:|:-----:| +| **Universal fields** | | | | | | | | +| command | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| args | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| env | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| url | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| headers | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| **Transport discriminator** | | | | | | | | +| type | ✓ | ✓ | ✓ | ✓ | - | - | - | +| **VSCode/Cursor fields** | | | | | | | | +| envFile | - | ✓ | ✓ | - | - | - | - | +| inputs | - | ✓ | - | - | - | - | - | +| **Gemini fields** | | | | | | | | +| httpUrl | - | - | - | - | ✓ | - | - | +| timeout | - | - | - | - | ✓ | - | - | +| trust | - | - | - | - | ✓ | - | - | +| cwd | - | - | - | - | ✓ | - | ✓ | +| includeTools | - | - | - | - | ✓ | - | - | +| excludeTools | - | - | - | - | ✓ | - | - | +| **Gemini OAuth fields** | | | | | | | | +| oauth_enabled | - | - | - | - | ✓ | - | - | +| oauth_clientId | - | - | - | - | ✓ | - | - | +| oauth_clientSecret | - | - | - | - | ✓ | - | - | +| oauth_authorizationUrl | - | - | - | - | ✓ | - | - | +| oauth_tokenUrl | - | - | - | - | ✓ | - | - | +| oauth_scopes | - | - | - | - | ✓ | - | - | +| oauth_redirectUri | - | - | - | - | ✓ | - | - | +| oauth_tokenParamName | - | - | - | - | ✓ | - | - | +| oauth_audiences | - | - | - | - | ✓ | - | - | +| authProviderType | - | - | - | - | ✓ | - | - | +| **Kiro fields** | | | | | | | | +| disabled | - | - | - | - | - | ✓ | - | +| autoApprove | - | - | - | - | - | ✓ | - | +| disabledTools | - | - | - | - | - | ✓ | - | +| **Codex fields** | | | | | | | | +| env_vars | - | - | - | - | - | - | ✓ | +| startup_timeout_sec | - | - | - | - | - | - | ✓ | +| tool_timeout_sec | - | - | - | - | - | - | ✓ | +| enabled | - | - | - | - | - | - | ✓ | +| enabled_tools | - | - | - | - | - | - | ✓ | +| disabled_tools | - | - | - | - | - | - | ✓ | +| bearer_token_env_var | - | - | - | - | - | - | ✓ | +| http_headers | - | - | - | - | - | - | ✓ | +| env_http_headers | - | - | - | - | - | - | ✓ | ## Integration Points @@ -345,15 +429,19 @@ The base class provides `filter_fields()` which: ### Field Mappings (Optional) -If your host uses different field names: +If your host uses different field names, define a mapping dict in `fields.py`. During serialization, the adapter's `apply_transformations()` method renames fields from the universal schema to the host-native names. Codex is currently the only host that requires this: ```python CODEX_FIELD_MAPPINGS = { - "args": "arguments", # Universal → Codex naming - "headers": "http_headers", # Universal → Codex naming + "args": "arguments", # Universal → Codex naming + "headers": "http_headers", # Universal → Codex naming + "includeTools": "enabled_tools", # Gemini naming → Codex naming (cross-host sync) + "excludeTools": "disabled_tools", # Gemini naming → Codex naming (cross-host sync) } ``` +The last two entries (`includeTools` -> `enabled_tools`, `excludeTools` -> `disabled_tools`) enable transparent cross-host sync from Gemini to Codex: a Gemini config containing `includeTools` will be serialized as `enabled_tools` in the Codex output. + ### Atomic Operations Pattern All configuration changes use atomic operations: From 69d61cc3483d00e06b030062453b35a2cfff0962 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:02:17 +0900 Subject: [PATCH 04/71] docs(mcp): update adapter template to validate_filtered() Co-Authored-By: Claude Opus 4.6 --- .../mcp_host_configuration_extension.md | 101 ++++++++++++------ 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md b/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md index 101be90..08a9a15 100644 --- a/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md +++ b/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md @@ -30,9 +30,11 @@ The Unified Adapter Architecture separates concerns: | Component | Responsibility | Interface | |-----------|----------------|-----------| -| **Adapter** | Validation + Serialization | `validate()`, `serialize()`, `get_supported_fields()` | +| **Adapter** | Validation + Serialization | `validate_filtered()`, `serialize()`, `get_supported_fields()` | | **Strategy** | File I/O | `read_configuration()`, `write_configuration()`, `get_config_path()` | +> **Note:** `validate()` is deprecated (will be removed in v0.9.0). All new adapters should implement `validate_filtered()` for the validate-after-filter pattern. See [Architecture Doc](../architecture/mcp_host_configuration.md#baseadapter-protocol) for details. + ``` MCPServerConfig (unified model) │ @@ -92,22 +94,44 @@ class YourHostAdapter(BaseAdapter): }) def validate(self, config: MCPServerConfig) -> None: - """Validate configuration for Your Host.""" - # Check transport requirements - if not config.command and not config.url: + """DEPRECATED: Will be removed in v0.9.0. Use validate_filtered() instead. + + Still required by BaseAdapter's abstract interface. Implement as a + pass-through until the abstract method is removed. + """ + pass + + def validate_filtered(self, filtered: Dict[str, Any]) -> None: + """Validate ONLY fields that survived filtering. + + This is the primary validation method. It receives a dictionary + of fields that have already been filtered to only those this host + supports, with None values and excluded fields removed. + """ + has_command = "command" in filtered + has_url = "url" in filtered + + if not has_command and not has_url: raise AdapterValidationError( "Either 'command' (local) or 'url' (remote) required", - host_name=self.host_name + host_name=self.host_name, ) # Add any host-specific validation - # if config.command and config.url: + # if has_command and has_url: # raise AdapterValidationError("Cannot have both", ...) def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: - """Serialize configuration for Your Host format.""" - self.validate(config) - return self.filter_fields(config) + """Serialize configuration for Your Host format. + + Follows the validate-after-filter pattern: + 1. Filter to supported fields + 2. Validate filtered fields + 3. Return filtered (or apply transformations if needed) + """ + filtered = self.filter_fields(config) + self.validate_filtered(filtered) + return filtered ``` **Then register in `hatch/mcp_host_config/adapters/__init__.py`:** @@ -249,19 +273,22 @@ mcp_configure_parser.add_argument( ## Field Mappings (Optional) -If your host uses different names for standard fields: +If your host uses different names for standard fields, override `apply_transformations()`: ```python # In your adapter -def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: - self.validate(config) - result = self.filter_fields(config) - - # Apply mappings (e.g., 'args' → 'arguments') +def apply_transformations(self, filtered: Dict[str, Any]) -> Dict[str, Any]: + """Apply field name mappings after validation.""" + result = filtered.copy() if "args" in result: result["arguments"] = result.pop("args") - return result + +def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: + filtered = self.filter_fields(config) + self.validate_filtered(filtered) + transformed = self.apply_transformations(filtered) + return transformed ``` Or define mappings centrally in `fields.py`: @@ -280,17 +307,21 @@ YOUR_HOST_FIELD_MAPPINGS = { Some hosts (like Gemini) support multiple transports: ```python -def validate(self, config: MCPServerConfig) -> None: - transports = sum([ - config.command is not None, - config.url is not None, - config.httpUrl is not None, - ]) - - if transports == 0: +def validate_filtered(self, filtered: Dict[str, Any]) -> None: + has_command = "command" in filtered + has_url = "url" in filtered + has_http_url = "httpUrl" in filtered + + transport_count = sum([has_command, has_url, has_http_url]) + + if transport_count == 0: raise AdapterValidationError("At least one transport required") - # Allow multiple transports if your host supports it + # Gemini requires exactly one transport (not multiple) + if transport_count > 1: + raise AdapterValidationError( + "Only one transport allowed: command, url, or httpUrl" + ) ``` ### Strict Single Transport @@ -298,9 +329,9 @@ def validate(self, config: MCPServerConfig) -> None: Some hosts (like Claude) require exactly one transport: ```python -def validate(self, config: MCPServerConfig) -> None: - has_command = config.command is not None - has_url = config.url is not None +def validate_filtered(self, filtered: Dict[str, Any]) -> None: + has_command = "command" in filtered + has_url = "url" in filtered if not has_command and not has_url: raise AdapterValidationError("Need command or url") @@ -315,14 +346,14 @@ Override `serialize()` for custom output format: ```python def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: - self.validate(config) - result = self.filter_fields(config) + filtered = self.filter_fields(config) + self.validate_filtered(filtered) # Transform to your host's expected structure - if config.type == "stdio": - result["transport"] = {"type": "stdio", "command": result.pop("command")} + if "command" in filtered: + filtered["transport"] = {"type": "stdio", "command": filtered.pop("command")} - return result + return filtered ``` ## Testing Your Implementation @@ -354,7 +385,7 @@ tests/ |-------|-------|----------| | Adapter not found | Not registered in registry | Add to `_register_defaults()` | | Field not serialized | Not in `get_supported_fields()` | Add field to set | -| Validation always fails | Logic error in `validate()` | Check conditions | +| Validation always fails | Logic error in `validate_filtered()` | Check conditions | | Name appears in output | Not filtering excluded fields | Use `filter_fields()` | ### Debugging Tips @@ -386,7 +417,7 @@ Study these for patterns: Adding a new host is now a **4-step process**: 1. **Add enum** to `MCPHostType` -2. **Create adapter** with `validate()` + `serialize()` + `get_supported_fields()` +2. **Create adapter** with `validate_filtered()` + `serialize()` + `get_supported_fields()` 3. **Create strategy** with `get_config_path()` + file I/O methods 4. **Add tests** for adapter and strategy From 0b83b6e148acfd602c5299d352ed0deb27341c3c Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:03:13 +0900 Subject: [PATCH 05/71] docs(mcp): update strategy template with interface docs Co-Authored-By: Claude Opus 4.6 --- .../mcp_host_configuration_extension.md | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md b/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md index 08a9a15..833a203 100644 --- a/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md +++ b/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md @@ -177,22 +177,67 @@ class YourHostStrategy(MCPHostStrategy): """Return the key containing MCP servers.""" return "mcpServers" # Most hosts use this - # read_configuration() and write_configuration() - # can inherit from a base class or implement from scratch + def get_adapter_host_name(self) -> str: + """Return the adapter host name for registry lookup.""" + return "your-host" + + def validate_server_config(self, server_config: MCPServerConfig) -> bool: + """Basic transport validation before adapter processing.""" + return server_config.command is not None or server_config.url is not None + + def read_configuration(self) -> HostConfiguration: + """Read and parse host configuration file.""" + # Implement JSON/TOML parsing for your host's config format + ... + + def write_configuration( + self, config: HostConfiguration, no_backup: bool = False + ) -> bool: + """Write configuration using adapter serialization.""" + # Use get_adapter(self.get_adapter_host_name()) for serialization + ... ``` +**The `@register_host_strategy` decorator** registers the strategy class in a global dictionary (`MCPHostRegistry._strategies`) keyed by `MCPHostType`. This enables `MCPHostRegistry.get_strategy(host_type)` to look up and instantiate the correct strategy at runtime. The decorator is defined in `host_management.py` as a convenience wrapper around `MCPHostRegistry.register()`. + +#### MCPHostStrategy Interface + +The base `MCPHostStrategy` class (defined in `host_management.py`) provides the full strategy interface. The table below shows which methods typically need overriding vs which can be inherited from family base classes. + +| Method | Must Override | Can Inherit | Notes | +|--------|:------------:|:-----------:|-------| +| `get_config_path()` | Always | -- | Platform-specific path to config file | +| `is_host_available()` | Always | -- | Check if host is installed on system | +| `get_config_key()` | Usually | From family | Most hosts use `"mcpServers"` (default) | +| `get_adapter_host_name()` | Usually | From family | Maps strategy to adapter registry entry | +| `validate_server_config()` | Usually | From family | Basic transport presence check | +| `read_configuration()` | Sometimes | From family | JSON read is identical across families | +| `write_configuration()` | Sometimes | From family | JSON write with adapter serialization | + +> **Cross-reference:** See the [Architecture Doc -- MCPHostStrategy](../architecture/mcp_host_configuration.md#key-components) for the full interface specification. + **Inheriting from existing strategy families:** +If your host uses a standard JSON format, inherit from an existing family base class to get `read_configuration()`, `write_configuration()`, and shared validation for free: + ```python -# If similar to Claude (standard JSON format) +# If similar to Claude (standard JSON format with mcpServers key) +@register_host_strategy(MCPHostType.YOUR_HOST) class YourHostStrategy(ClaudeHostStrategy): def get_config_path(self) -> Optional[Path]: return Path.home() / ".your_host" / "config.json" + def is_host_available(self) -> bool: + return self.get_config_path().parent.exists() + # If similar to Cursor (flexible path handling) +@register_host_strategy(MCPHostType.YOUR_HOST) class YourHostStrategy(CursorBasedHostStrategy): def get_config_path(self) -> Optional[Path]: return Path.home() / ".your_host" / "config.json" + + def is_host_available(self) -> bool: + return self.get_config_path().parent.exists() ``` ### Step 4: Add Tests From 21c30d561502e104e5541a5dbaaf36d0b79d6da9 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:06:04 +0900 Subject: [PATCH 06/71] docs(mcp): document strategy, registration, and variant pattern Document MCPHostStrategy interface, @register_host_strategy decorator, and ClaudeAdapter variant pattern. Clarify validate() deprecation in BaseAdapter Protocol section and update error handling example to use validate_filtered(). Co-Authored-By: Claude Opus 4.6 --- .../architecture/mcp_host_configuration.md | 106 +++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/docs/articles/devs/architecture/mcp_host_configuration.md b/docs/articles/devs/architecture/mcp_host_configuration.md index 5f01936..ef77670 100644 --- a/docs/articles/devs/architecture/mcp_host_configuration.md +++ b/docs/articles/devs/architecture/mcp_host_configuration.md @@ -179,8 +179,18 @@ class BaseAdapter(ABC): def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: """Convert config to host's expected format.""" ... + + def filter_fields(self, config: MCPServerConfig) -> Dict[str, Any]: + """Filter config to only include supported, non-excluded, non-None fields.""" + ... + + def get_excluded_fields(self) -> FrozenSet[str]: + """Return fields that should always be excluded (default: EXCLUDED_ALWAYS).""" + ... ``` +**Validation migration note:** `validate()` is retained as an abstract method for backward compatibility, but `validate_filtered()` is the current contract used by `serialize()`. All existing adapters implement both methods, but new adapters should implement `validate_filtered()` as the primary validation path. The `validate()` method will be removed in v0.9.0. + **Serialization pattern (validate-after-filter):** ``` @@ -190,6 +200,67 @@ filter_fields(config) → validate_filtered(filtered) → apply_transformations( This pattern ensures validation only checks fields the host actually supports, preventing false rejections during cross-host sync operations. +### MCPHostStrategy Interface + +The strategy layer handles file I/O and host detection. All strategy classes inherit from `MCPHostStrategy` (defined in `host_management.py`) and are auto-registered using the `@register_host_strategy` decorator: + +```python +class MCPHostStrategy: + """Abstract base class for host configuration strategies.""" + + def get_config_path(self) -> Optional[Path]: + """Get configuration file path for this host.""" + ... + + def is_host_available(self) -> bool: + """Check if host is available on system.""" + ... + + def get_config_key(self) -> str: + """Get the root configuration key for MCP servers (default: 'mcpServers').""" + ... + + def read_configuration(self) -> HostConfiguration: + """Read and parse host configuration.""" + ... + + def write_configuration(self, config: HostConfiguration, no_backup: bool = False) -> bool: + """Write configuration to host file.""" + ... + + def validate_server_config(self, server_config: MCPServerConfig) -> bool: + """Validate server configuration for this host.""" + ... +``` + +**Auto-registration with `@register_host_strategy`:** + +The `@register_host_strategy` decorator (a convenience wrapper around `MCPHostRegistry.register()`) registers a strategy class at import time. When `strategies.py` is imported, each decorated class is automatically added to the `MCPHostRegistry`, making it available via `MCPHostRegistry.get_strategy(host_type)`: + +```python +from hatch.mcp_host_config.host_management import MCPHostStrategy, register_host_strategy +from hatch.mcp_host_config.models import MCPHostType + +@register_host_strategy(MCPHostType.YOUR_HOST) +class YourHostStrategy(MCPHostStrategy): + def get_config_path(self) -> Optional[Path]: + return Path.home() / ".your-host" / "config.json" + + def is_host_available(self) -> bool: + return self.get_config_path().parent.exists() + + # ... remaining methods +``` + +This decorator-based registration follows the same pattern used throughout Hatch. No manual registry wiring is needed — adding the decorator is sufficient. + +**Strategy families:** + +Some strategies share implementation through base classes: + +- `ClaudeHostStrategy`: Base for `ClaudeDesktopStrategy` and `ClaudeCodeStrategy` (shared JSON read/write, `_preserve_claude_settings()`) +- `CursorBasedHostStrategy`: Base for `CursorHostStrategy` and `LMStudioHostStrategy` (shared Cursor-format JSON read/write) + ### Field Constants Field support is defined in `fields.py` as the single source of truth. Every host's field set is built by extending `UNIVERSAL_FIELDS` with host-specific additions: @@ -442,6 +513,36 @@ CODEX_FIELD_MAPPINGS = { The last two entries (`includeTools` -> `enabled_tools`, `excludeTools` -> `disabled_tools`) enable transparent cross-host sync from Gemini to Codex: a Gemini config containing `includeTools` will be serialized as `enabled_tools` in the Codex output. +### Adapter Variant Pattern + +When two hosts share the same field set and validation logic but differ only in identity, a single adapter class can serve both via a `variant` constructor parameter. This avoids code duplication without introducing an inheritance hierarchy. + +`ClaudeAdapter` demonstrates this pattern. Claude Desktop and Claude Code share identical field support (`CLAUDE_FIELDS`) and validation rules, so a single class handles both: + +```python +class ClaudeAdapter(BaseAdapter): + def __init__(self, variant: str = "desktop"): + if variant not in ("desktop", "code"): + raise ValueError(f"Invalid Claude variant: {variant}") + self._variant = variant + + @property + def host_name(self) -> str: + return f"claude-{self._variant}" # "claude-desktop" or "claude-code" + + def get_supported_fields(self) -> FrozenSet[str]: + return CLAUDE_FIELDS # Same field set for both variants +``` + +The `AdapterRegistry` registers two entries pointing to different instances of the same class: + +```python +ClaudeAdapter(variant="desktop") # registered as "claude-desktop" +ClaudeAdapter(variant="code") # registered as "claude-code" +``` + +Use this pattern when adding a new host that is functionally identical to an existing one but requires a distinct host name in the registry. + ### Atomic Operations Pattern All configuration changes use atomic operations: @@ -484,12 +585,15 @@ The system uses both exceptions and result objects: ```python try: - adapter.validate(config) + filtered = adapter.filter_fields(config) + adapter.validate_filtered(filtered) except AdapterValidationError as e: print(f"Validation failed: {e.message}") print(f"Field: {e.field}, Host: {e.host_name}") ``` +In practice, calling `adapter.serialize(config)` is preferred since it executes the full filter-validate-transform pipeline and will raise `AdapterValidationError` on validation failure. + ## Testing Strategy The test architecture uses a data-driven approach with property-based assertions: From 24c6ebf74b7595c812e1fd6689141ab5c90f21d9 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:06:04 +0900 Subject: [PATCH 07/71] docs(mcp): rewrite testing section with data-driven infra Co-Authored-By: Claude Opus 4.6 --- .../mcp_host_configuration_extension.md | 138 +++++++++++++----- 1 file changed, 104 insertions(+), 34 deletions(-) diff --git a/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md b/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md index 833a203..8f32061 100644 --- a/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md +++ b/docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md @@ -11,7 +11,7 @@ The Unified Adapter Architecture requires only **4 integration points**: | ☐ Host type enum | Always | `models.py` | | ☐ Adapter class | Always | `adapters/your_host.py`, `adapters/__init__.py` | | ☐ Strategy class | Always | `strategies.py` | -| ☐ Test infrastructure | Always | `tests/unit/mcp/`, `tests/integration/mcp/` | +| ☐ Test fixtures | Always | `tests/test_data/mcp_adapters/canonical_configs.json`, `host_registry.py` | > **Note:** No host-specific models, no `from_omni()` conversion, no model registry integration. The unified model handles all fields. @@ -240,33 +240,77 @@ class YourHostStrategy(CursorBasedHostStrategy): return self.get_config_path().parent.exists() ``` -### Step 4: Add Tests +### Step 4: Register Test Fixtures -**Unit tests** (`tests/unit/mcp/test_your_host_adapter.py`): +Hatch uses a **data-driven test infrastructure** that auto-generates parameterized tests for all adapters. Adding a new host requires fixture data updates, but **zero changes to test functions** themselves. + +#### a) Add canonical config to `tests/test_data/mcp_adapters/canonical_configs.json` + +Add an entry keyed by your host name, using **host-native field names** (i.e., the names your host's config file uses, after any field mappings). Values should represent a valid stdio-transport configuration: + +```json +{ + "your-host": { + "command": "python", + "args": ["-m", "mcp_server"], + "env": {"API_KEY": "test_key"}, + "url": null, + "headers": null, + "type": "stdio" + } +} +``` + +For hosts with field mappings (like Codex, which uses `arguments` instead of `args`), use the host-native names in the fixture: + +```json +{ + "codex": { + "command": "python", + "arguments": ["-m", "mcp_server"], + "env": {"API_KEY": "test_key"}, + "url": null, + "http_headers": null + } +} +``` + +#### b) Add field set to `FIELD_SETS` in `tests/test_data/mcp_adapters/host_registry.py` + +Map your host name to its field set constant from `fields.py`: ```python -class TestYourHostAdapter(unittest.TestCase): - def setUp(self): - self.adapter = YourHostAdapter() - - def test_host_name(self): - self.assertEqual(self.adapter.host_name, "your-host") - - def test_supported_fields(self): - fields = self.adapter.get_supported_fields() - self.assertIn("command", fields) - - def test_validate_requires_transport(self): - config = MCPServerConfig(name="test") - with self.assertRaises(AdapterValidationError): - self.adapter.validate(config) - - def test_serialize_filters_unsupported(self): - config = MCPServerConfig(name="test", command="python", httpUrl="http://x") - result = self.adapter.serialize(config) - self.assertNotIn("httpUrl", result) # Assuming not supported +FIELD_SETS: Dict[str, FrozenSet[str]] = { + # ... existing hosts ... + "your-host": YOUR_HOST_FIELDS, +} ``` +#### c) Add reverse mappings if needed + +If your host uses field mappings (like Codex), add the reverse mappings so `HostSpec.load_config()` can convert host-native names back to `MCPServerConfig` field names: + +```python +# Already defined for Codex: +CODEX_REVERSE_MAPPINGS: Dict[str, str] = {v: k for k, v in CODEX_FIELD_MAPPINGS.items()} + +# Add similar for your host if it has field mappings +``` + +#### d) Auto-generated test coverage + +Once you add the fixture entry and field set mapping, the generator functions in `host_registry.py` will automatically pick up your new host and generate parameterized test cases: + +| Generator Function | What It Generates | Coverage | +|--------------------|-------------------|----------| +| `generate_sync_test_cases()` | All cross-host sync pairs (N x N) | Your host syncing to/from every other host | +| `generate_validation_test_cases()` | Transport mutual exclusion, tool list coexistence | Validation contract tests for your host | +| `generate_unsupported_field_test_cases()` | One test per unsupported field | Verifies your adapter filters correctly | + +No changes to test files (`test_cross_host_sync.py`, `test_field_filtering.py`, etc.) are needed. The tests consume data from the registry and assertions library. + +> **When to add bespoke tests:** Only write custom unit tests if your adapter has unusual behavior not covered by the data-driven infrastructure (e.g., complex field transformations, multi-step validation, variant support like `ClaudeAdapter`'s desktop/code split). + ## Declaring Field Support ### Using Field Constants @@ -403,23 +447,49 @@ def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: ## Testing Your Implementation -### Test Categories +### What Is Auto-Generated vs Manual + +| Category | Auto-Generated | Manual (if needed) | +|----------|:--------------:|:------------------:| +| **Adapter protocol** (host_name, fields) | Data-driven via `host_registry.py` | -- | +| **Validation contracts** (transport rules) | `generate_validation_test_cases()` | Complex multi-field validation | +| **Field filtering** (unsupported fields dropped) | `generate_unsupported_field_test_cases()` | -- | +| **Cross-host sync** (N x N pairs) | `generate_sync_test_cases()` | -- | +| **Serialization format** | Property-based assertions | Custom output structure | +| **Strategy file I/O** | -- | Always manual (host-specific paths) | + +### Fixture Requirements + +To integrate with the data-driven test infrastructure, you need: + +1. **Fixture entry** in `tests/test_data/mcp_adapters/canonical_configs.json` +2. **Field set mapping** in `tests/test_data/mcp_adapters/host_registry.py` (`FIELD_SETS` dict) +3. **Reverse mappings** in `host_registry.py` (only if your host uses field mappings) + +Zero changes to test functions are needed for standard adapter behavior. The test infrastructure derives all expectations from `fields.py` through the `HostSpec` dataclass and property-based assertions in `assertions.py`. -| Category | What to Test | -|----------|--------------| -| **Protocol** | `host_name`, `get_supported_fields()` return correct values | -| **Validation** | `validate()` accepts valid configs, rejects invalid | -| **Serialization** | `serialize()` produces correct format, filters fields | -| **Integration** | Adapter works with registry, strategy reads/writes files | +> **Cross-reference:** See the [Architecture Doc -- Testing Strategy](../architecture/mcp_host_configuration.md#testing-strategy) for the full testing infrastructure design, including the three test tiers (unit, integration, regression). ### Test File Location ``` tests/ ├── unit/mcp/ -│ └── test_your_host_adapter.py # Protocol + validation + serialization -└── integration/mcp/ - └── test_your_host_strategy.py # File I/O + end-to-end +│ ├── test_adapter_protocol.py # Protocol compliance (data-driven) +│ ├── test_adapter_registry.py # Registry operations +│ └── test_config_model.py # Unified model validation +├── integration/mcp/ +│ ├── test_cross_host_sync.py # N×N cross-host sync (data-driven) +│ ├── test_host_configuration.py # Strategy file I/O +│ └── test_adapter_serialization.py # Serialization correctness +├── regression/mcp/ +│ ├── test_field_filtering.py # Unsupported field filtering (data-driven) +│ ├── test_field_filtering_v2.py # Extended field filtering +│ └── test_validation_bugs.py # Validation edge cases +└── test_data/mcp_adapters/ + ├── canonical_configs.json # Fixture: canonical config per host + ├── host_registry.py # HostRegistry + test case generators + └── assertions.py # Property-based assertion library ``` ## Troubleshooting @@ -464,6 +534,6 @@ Adding a new host is now a **4-step process**: 1. **Add enum** to `MCPHostType` 2. **Create adapter** with `validate_filtered()` + `serialize()` + `get_supported_fields()` 3. **Create strategy** with `get_config_path()` + file I/O methods -4. **Add tests** for adapter and strategy +4. **Register test fixtures** in `canonical_configs.json` and `host_registry.py` (zero test code changes for standard adapters) The unified model handles all fields. Adapters filter and validate. Strategies handle files. No model conversion needed. From 5fc6f972f575cb2e2b49bd0208d2efb906fc39b3 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:07:13 +0900 Subject: [PATCH 08/71] docs(mcp): rewrite testing section with data-driven docs Rewrite Testing Strategy section to document HostSpec, HostRegistry, generator functions, assertion functions, canonical_configs.json structure, steps for adding a new host, and deprecated test files. Co-Authored-By: Claude Opus 4.6 --- .../architecture/mcp_host_configuration.md | 122 +++++++++++++++++- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/docs/articles/devs/architecture/mcp_host_configuration.md b/docs/articles/devs/architecture/mcp_host_configuration.md index ef77670..d8a7977 100644 --- a/docs/articles/devs/architecture/mcp_host_configuration.md +++ b/docs/articles/devs/architecture/mcp_host_configuration.md @@ -596,18 +596,126 @@ In practice, calling `adapter.serialize(config)` is preferred since it executes ## Testing Strategy -The test architecture uses a data-driven approach with property-based assertions: +The test architecture uses a data-driven approach with property-based assertions. Approximately 285 test cases are auto-generated from metadata in `fields.py` and fixture data in `canonical_configs.json`. + +### Three-Tier Test Structure | Tier | Location | Purpose | Approach | |------|----------|---------|----------| | Unit | `tests/unit/mcp/` | Adapter protocol, model validation, registry | Traditional | | Integration | `tests/integration/mcp/` | Cross-host sync (64 pairs), host config (8 hosts) | Data-driven | -| Regression | `tests/regression/mcp/` | Validation bugs, field filtering (211+ tests) | Data-driven | +| Regression | `tests/regression/mcp/` | Validation bugs, field filtering (~285 auto-generated) | Data-driven | + +### Data-Driven Infrastructure + +The module at `tests/test_data/mcp_adapters/` contains three files that form the data-driven test infrastructure: + +| File | Role | +|------|------| +| `canonical_configs.json` | Fixture data: canonical config values for all 8 hosts | +| `host_registry.py` | Registry: derives host metadata from `fields.py`, generates test cases | +| `assertions.py` | Assertions: reusable property checks encoding adapter contracts | + +### `HostSpec` Dataclass + +`HostSpec` is the per-host test specification. It combines minimal fixture data (config values) with complete metadata derived from `fields.py`: + +```python +@dataclass +class HostSpec: + host_name: str # e.g., "claude-desktop", "codex" + canonical_config: Dict[str, Any] # Raw config values from fixture (host-native names) + supported_fields: FrozenSet[str] # From fields.py (e.g., CLAUDE_FIELDS) + field_mappings: Dict[str, str] # From fields.py (e.g., CODEX_FIELD_MAPPINGS) +``` + +Key methods: + +- `load_config()` -- Builds an `MCPServerConfig` from canonical config values, applying reverse field mappings for hosts with non-standard names (e.g., Codex `arguments` -> `args`) +- `get_adapter()` -- Instantiates the correct adapter for this host (handles `ClaudeAdapter` variant dispatch) +- `compute_expected_fields(input_fields)` -- Returns `(input_fields & supported_fields) - EXCLUDED_ALWAYS`, predicting which fields should survive filtering + +### `HostRegistry` Class + +`HostRegistry` bridges fixture data with `fields.py` metadata. At construction time, it loads `canonical_configs.json` and derives each host's `HostSpec` by looking up the corresponding field set in the `FIELD_SETS` mapping (which maps host names to `fields.py` constants like `CLAUDE_FIELDS`, `GEMINI_FIELDS`, etc.): + +```python +registry = HostRegistry(Path("tests/test_data/mcp_adapters/canonical_configs.json")) +``` + +Methods: + +- `all_hosts()` -- Returns all `HostSpec` instances sorted by name +- `get_host(name)` -- Returns a specific `HostSpec` by host name +- `all_pairs()` -- Generates all `(from_host, to_host)` combinations for O(n^2) cross-host sync testing (8 x 8 = 64 pairs) +- `hosts_supporting_field(field_name)` -- Finds hosts that support a specific field (e.g., all hosts supporting `httpUrl`) + +### Generator Functions + +Three generator functions create parameterized test cases from registry data. These are called at module level and fed directly to `pytest.mark.parametrize`: + +- `generate_sync_test_cases(registry)` -- Produces one `SyncTestCase` per (from, to) host pair (64 cases for 8 hosts) +- `generate_validation_test_cases(registry)` -- Produces `ValidationTestCase` entries for transport mutual exclusion (all hosts) and tool list coexistence (hosts with tool list support) +- `generate_unsupported_field_test_cases(registry)` -- For each host, computes the set of fields it does NOT support (from the union of all host field sets) and produces one `FilterTestCase` per unsupported field + +### Assertion Functions + +The `assertions.py` module contains 7 `assert_*` functions that encode adapter contracts as reusable property checks. Tests call these functions instead of writing inline assertions: + +| Function | Contract Verified | +|----------|-------------------| +| `assert_only_supported_fields()` | Result contains only fields from `fields.py` for this host (including mapped names) | +| `assert_excluded_fields_absent()` | `EXCLUDED_ALWAYS` fields (e.g., `name`) are not in result | +| `assert_transport_present()` | At least one transport field (`command`, `url`, `httpUrl`) is present | +| `assert_transport_mutual_exclusion()` | Exactly one transport field is present | +| `assert_field_mappings_applied()` | Universal field names are replaced by host-native names (e.g., no `args` in Codex output) | +| `assert_tool_lists_coexist()` | Both allowlist and denylist fields are present when applicable | +| `assert_unsupported_field_absent()` | A specific unsupported field was filtered out | + +### `canonical_configs.json` Structure + +The fixture file uses a flat JSON schema mapping host names to field-value pairs: + +```json +{ + "claude-desktop": { + "command": "python", + "args": ["-m", "mcp_server"], + "env": {"API_KEY": "test_key"}, + "url": null, + "headers": null, + "type": "stdio" + }, + "codex": { + "command": "python", + "arguments": ["-m", "mcp_server"], + "env": {"API_KEY": "test_key"}, + "url": null, + "http_headers": null, + "cwd": "/app", + "enabled_tools": ["tool1", "tool2"], + "disabled_tools": ["tool3"] + } +} +``` + +Note that Codex entries use host-native field names (e.g., `arguments` instead of `args`, `http_headers` instead of `headers`). The `HostSpec.load_config()` method applies reverse mappings (`CODEX_REVERSE_MAPPINGS`) to convert these back to universal names when constructing `MCPServerConfig` objects. + +### Adding a New Host to Tests + +Adding a new host does not require changes to any test files. The generators automatically pick up the new host. The required steps are: + +1. Add a new entry in `canonical_configs.json` with representative config values using the host's native field names +2. Add the host's field set to the `FIELD_SETS` mapping in `host_registry.py` (mapping the host name to the corresponding constant from `fields.py`) +3. Update `fields.py` with the new host's field set constant + +No changes to actual test files (`test_cross_host_sync.py`, `test_host_configuration.py`, etc.) are needed -- the generators pick up the new host automatically via the registry. + +### Deprecated Test Files -**Data-driven infrastructure** (`tests/test_data/mcp_adapters/`): +Two legacy test files are marked with `@pytest.mark.skip` and scheduled for removal in v0.9.0: -- `canonical_configs.json`: Canonical config values for all 8 hosts -- `host_registry.py`: HostRegistry derives metadata from fields.py -- `assertions.py`: Property-based assertions verify adapter contracts +- `tests/integration/mcp/test_adapter_serialization.py` -- Replaced by `test_host_configuration.py` (per-host) and `test_cross_host_sync.py` (cross-host) +- `tests/regression/mcp/test_field_filtering.py` -- Replaced by `test_field_filtering_v2.py` (data-driven) -Adding a new host requires zero test code changes — only a fixture entry and fields.py update. +These files remain in the codebase for reference during the migration period but are not executed in CI. From fc07cd1d20576e76a23891cd15a1fd3ac37a0c36 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:10:46 +0900 Subject: [PATCH 09/71] docs(roadmap): mark mcp-docs-refresh tasks as done Co-Authored-By: Claude Opus 4.6 --- __roadmap__/mcp-docs-refresh/README.md | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 __roadmap__/mcp-docs-refresh/README.md diff --git a/__roadmap__/mcp-docs-refresh/README.md b/__roadmap__/mcp-docs-refresh/README.md new file mode 100644 index 0000000..13114ed --- /dev/null +++ b/__roadmap__/mcp-docs-refresh/README.md @@ -0,0 +1,64 @@ +# MCP Host Config Dev Docs Refresh + +## Context + +Standalone doc cleanup campaign. The developer documentation for the MCP host configuration system has diverged from the codebase, primarily in the testing infrastructure section but also in the field support matrix, architectural patterns, and extension guide templates. This campaign updates both the architecture reference doc and the extension guide to match codebase reality. + +## Reference Documents + +- [R01 Gap Analysis](../../__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md) — Full docs-vs-codebase comparison with severity ratings + +## Goal + +Align MCP host config dev docs with the current codebase state so contributors get accurate guidance when adding new hosts. + +## Pre-conditions + +- [x] Gap analysis report reviewed (R01) +- [x] `dev` branch checked out + +## Success Gates + +- All documented field support matches `fields.py` constants +- All documented patterns match codebase implementations +- Testing section accurately describes `tests/test_data/mcp_adapters/` infrastructure +- Extension guide Step 2-4 templates produce working implementations when followed literally + +## Gotchas + +- `validate()` is deprecated but still abstract in `BaseAdapter` — document the migration path without removing it yet (that's a code change for v0.9.0, not a doc change). +- LM Studio is missing from the field support matrix entirely — verify whether it needs its own column or shares Claude's field set. +- Both tasks edit separate files (`mcp_host_configuration.md` vs `mcp_host_configuration_extension.md`), so they can safely execute in parallel. Cross-references between the two docs should be verified after both complete. + +## Status + +```mermaid +graph TD + update_architecture_doc[Update Architecture Doc]:::done + update_extension_guide[Update Extension Guide]:::done + + classDef done fill:#166534,color:#bbf7d0 + classDef inprogress fill:#854d0e,color:#fef08a + classDef planned fill:#374151,color:#e5e7eb + classDef amendment fill:#1e3a5f,color:#bfdbfe + classDef blocked fill:#7f1d1d,color:#fecaca +``` + +## Nodes + +| Node | Type | Status | +|:-----|:-----|:-------| +| `update_architecture_doc.md` | Leaf Task | Done | +| `update_extension_guide.md` | Leaf Task | Done | + +## Amendment Log + +| ID | Date | Source | Nodes Added | Rationale | +|:---|:-----|:-------|:------------|:----------| + +## Progress + +| Node | Branch | Commits | Notes | +|:-----|:-------|:--------|:------| +| `update_architecture_doc.md` | `task/update-architecture-doc` | 3 | Field matrix, strategy/variant patterns, testing section | +| `update_extension_guide.md` | `task/update-extension-guide` | 3 | validate_filtered template, strategy interface docs, data-driven testing | From 896f4d2c3b798e1a71ce1018052004475669b32d Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 15:11:11 +0900 Subject: [PATCH 10/71] docs(roadmap): add mcp-docs-refresh task files and gap analysis Co-Authored-By: Claude Opus 4.6 --- .../docs-vs-codebase-gap-analysis.md | 118 ++++++++++++++++++ .../update_architecture_doc.md | 71 +++++++++++ .../update_extension_guide.md | 68 ++++++++++ 3 files changed, 257 insertions(+) create mode 100644 __reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md create mode 100644 __roadmap__/mcp-docs-refresh/update_architecture_doc.md create mode 100644 __roadmap__/mcp-docs-refresh/update_extension_guide.md diff --git a/__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md b/__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md new file mode 100644 index 0000000..c82d5b6 --- /dev/null +++ b/__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md @@ -0,0 +1,118 @@ +# Gap Analysis: MCP Host Config Dev Docs vs Codebase + +## Problem Statement + +The developer documentation for the MCP host configuration system (`mcp_host_configuration.md` and `mcp_host_configuration_extension.md`) has fallen behind the codebase, particularly in the testing infrastructure sections. This creates misleading guidance for contributors adding new host support. + +## Evidence + +Systematic comparison of the three dev docs against the current codebase state (commit `c544cb3` on `dev`). + +### Tested Docs + +| Doc | Path | +|:----|:-----| +| Architecture | `docs/articles/devs/architecture/mcp_host_configuration.md` | +| Extension Guide | `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` | +| Testing Standards | `docs/articles/devs/development_processes/testing_standards.md` | + +--- + +## Findings + +### 1. Testing Infrastructure (Severity: High) + +The testing section in both docs is significantly behind the actual implementation. + +#### 1a. Data-driven infrastructure undocumented + +| Component | Location | Lines | Docs Coverage | +|:----------|:---------|------:|:--------------| +| `HostSpec` dataclass | `tests/test_data/mcp_adapters/host_registry.py` | ~95 | Brief mention, no detail | +| `HostRegistry` class (5+ methods) | `tests/test_data/mcp_adapters/host_registry.py` | ~77 | Brief mention, no detail | +| 3 generator functions | `tests/test_data/mcp_adapters/host_registry.py` | ~83 | Not documented | +| 8 assertion functions | `tests/test_data/mcp_adapters/assertions.py` | ~143 | Not documented | +| Canonical fixture data | `tests/test_data/mcp_adapters/canonical_configs.json` | full | Not documented | +| `CODEX_REVERSE_MAPPINGS` | `tests/test_data/mcp_adapters/host_registry.py:61` | ~3 | Not documented | + +#### 1b. "Zero test code changes" claim is misleading + +The extension guide claims adding a new host requires zero test code changes. In reality it requires: + +- Adding an entry to `canonical_configs.json` +- Adding a `HostSpec` entry in `host_registry.py` (lines 226-242 in `FIELD_SETS` dict + `HostRegistry._build_specs()`) + +#### 1c. Test count undersold + +- Docs say "211+ tests" +- Actual auto-generated count: ~285 (64 sync pairs + 10 validation + 211 field filtering) + +#### 1d. Deprecated test files unacknowledged + +Two files marked `@pytest.mark.skip` and scheduled for removal in v0.9.0: + +- `tests/integration/mcp/test_adapter_serialization.py` +- `tests/regression/mcp/test_field_filtering.py` + +Neither mentioned in any documentation. + +--- + +### 2. Architecture Doc Gaps (Severity: Medium-High) + +#### 2a. Field support matrix incomplete + +Missing from the matrix: + +- **Gemini**: ~10 OAuth fields (`oauth_enabled`, `oauth_clientId`, `oauth_clientSecret`, `oauth_scopes`, `oauth_redirectUrl`, `oauth_tokenUrl`, `oauth_authUrl`, `oauth_extraParams`) +- **Codex**: advanced fields (`cwd`, `env_vars`, `startup_timeout_sec`, `bearer_token_env_var`, `http_headers`) + +#### 2b. `CODEX_FIELD_MAPPINGS` incomplete + +Docs show 2 mappings (`args`->`arguments`, `headers`->`http_headers`). +Actual code has 4 (also `includeTools`->`enabled_tools`, `excludeTools`->`disabled_tools`). + +#### 2c. Missing architectural patterns + +| Pattern | Status | +|:--------|:-------| +| `@register_host_strategy(MCPHostType.X)` decorator | Not documented | +| `MCPHostStrategy` base class interface | Not documented | +| `ClaudeAdapter` variant parameter (`desktop` vs `code`) | Not documented | + +#### 2d. `validate()` deprecation inconsistency + +- Architecture doc marks `validate()` as deprecated (v0.9.0) +- Extension guide Step 2 template still uses `validate()`, not `validate_filtered()` +- No migration guidance provided + +--- + +### 3. What Is Accurate + +- Module organization matches exactly +- 8 host names/enum values correct +- `UNIVERSAL_FIELDS`, per-host field constants match +- `BaseAdapter` protocol methods match +- Three-layer architecture description accurate +- `EXCLUDED_ALWAYS` pattern correct + +--- + +## Root Cause + +The testing infrastructure was overhauled to a data-driven architecture (HostRegistry, generators, property-based assertions) but the documentation was not updated to reflect these changes. The architecture doc and extension guide were written against the pre-overhaul state. + +## Impact Assessment + +- **Scope**: Architecture doc testing section, extension guide Step 2-4, field support matrix +- **Dependencies**: Any contributor following the extension guide to add a new host will get incomplete guidance on testing requirements +- **Risk**: New host contributions may skip fixture/registry setup, causing CI gaps + +## Recommendations + +1. Rewrite the architecture doc testing section to document the full data-driven infrastructure +2. Rewrite the extension guide Step 4 with actual testing requirements +3. Fix validate() → validate_filtered() in templates +4. Complete the field support matrix +5. Document missing architectural patterns (strategy decorator, MCPHostStrategy interface, ClaudeAdapter variant) diff --git a/__roadmap__/mcp-docs-refresh/update_architecture_doc.md b/__roadmap__/mcp-docs-refresh/update_architecture_doc.md new file mode 100644 index 0000000..d01eb6b --- /dev/null +++ b/__roadmap__/mcp-docs-refresh/update_architecture_doc.md @@ -0,0 +1,71 @@ +# Update Architecture Doc + +**Goal**: Bring `docs/articles/devs/architecture/mcp_host_configuration.md` into alignment with the current codebase. +**Pre-conditions**: +- [ ] Branch `task/update-architecture-doc` created from `milestone/mcp-docs-refresh` +**Success Gates**: +- Field support matrix matches all per-host field sets in `hatch/mcp_host_config/fields.py` +- `CODEX_FIELD_MAPPINGS` shows all 4 entries (not 2) +- Strategy layer, `MCPHostStrategy` interface, and `@register_host_strategy` decorator are documented +- `ClaudeAdapter` variant pattern documented +- Testing section documents `HostSpec`, `HostRegistry`, generator functions, assertion functions, and `canonical_configs.json` structure +**References**: [R01 Gap Analysis](../../__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md) — findings 1a-1d, 2a-2d + +--- + +## Step 1: Update field support matrix and field mapping documentation + +**Goal**: Make the field support matrix and field mapping examples match the actual field constants in `fields.py` and the actual mapping dicts in adapter modules. + +**Implementation Logic**: + +1. Read `hatch/mcp_host_config/fields.py` and extract every per-host field set (`CLAUDE_FIELDS`, `VSCODE_FIELDS`, `CURSOR_FIELDS`, `GEMINI_FIELDS`, `KIRO_FIELDS`, `CODEX_FIELDS`, `LMSTUDIO_FIELDS`). +2. Rebuild the Field Support Matrix table in the architecture doc to include ALL fields present in any host's set. Add an LM Studio column. Add the missing Gemini OAuth fields (`oauth_enabled`, `oauth_clientId`, etc.) and missing Codex fields (`cwd`, `env_vars`, `startup_timeout_sec`, etc.). +3. Read `hatch/mcp_host_config/adapters/codex.py` and extract the actual `CODEX_FIELD_MAPPINGS` dict. Update the "Field Mappings (Optional)" section to show all 4 mappings, not just 2. +4. Update the `MCPServerConfig` model snippet to reflect the actual field set (currently shows `~12` fields with `# ... additional fields per host` — expand to show the full set or at minimum group by host with accurate counts). + +**Deliverables**: Updated Field Support Matrix section, updated Field Mappings section, updated model snippet in `docs/articles/devs/architecture/mcp_host_configuration.md` +**Consistency Checks**: Diff the field names in the matrix against `fields.py` constants — every field in every host set must appear in the matrix (expected: PASS) +**Commit**: `docs(mcp): update field support matrix and field mapping documentation` + +--- + +## Step 2: Document missing architectural patterns + +**Goal**: Add documentation for the strategy layer interface, the strategy registration decorator, and the Claude adapter variant pattern. + +**Implementation Logic**: + +1. Read `hatch/mcp_host_config/strategies.py` and extract the `MCPHostStrategy` base class interface (methods: `get_config_path()`, `is_host_available()`, `get_config_key()`, `read_configuration()`, `write_configuration()`, `validate_server_config()`). Also extract the `@register_host_strategy` decorator and explain its role in auto-registration. +2. Add a new subsection under "Key Components" (after BaseAdapter Protocol) titled "MCPHostStrategy Interface" that documents the strategy base class and its methods, analogous to the BaseAdapter Protocol section. +3. Read `hatch/mcp_host_config/adapters/claude.py` and document the variant pattern: a single `ClaudeAdapter` class serving both `claude-desktop` and `claude-code` via a `variant` constructor parameter. Explain this in the "Design Patterns" section. +4. In the BaseAdapter Protocol section, clarify the `validate()` deprecation: state that `validate()` is retained for backward compatibility but `validate_filtered()` is the current contract used by `serialize()`. The extension guide template (Step 2) should implement `validate_filtered()` as the primary validation path. +5. In the Error Handling section, update the example to use `validate_filtered()` instead of `validate()`. + +**Deliverables**: New "MCPHostStrategy Interface" subsection, updated "Design Patterns" section, clarified deprecation note in BaseAdapter Protocol, updated Error Handling example in `docs/articles/devs/architecture/mcp_host_configuration.md` +**Consistency Checks**: Verify every method documented in the MCPHostStrategy section exists in `hatch/mcp_host_config/strategies.py` (expected: PASS) +**Commit**: `docs(mcp): document strategy interface, registration decorator, and adapter variant pattern` + +--- + +## Step 3: Rewrite testing infrastructure section + +**Goal**: Replace the brief testing section with comprehensive documentation of the data-driven testing architecture. + +**Implementation Logic**: + +1. Read `tests/test_data/mcp_adapters/host_registry.py`, `assertions.py`, and `canonical_configs.json` to understand the full infrastructure. +2. Rewrite the "Testing Strategy" section to cover: + - **Three-tier table** (keep, but update test counts to ~285 total auto-generated). + - **Data-driven infrastructure** subsection: Explain the module at `tests/test_data/mcp_adapters/` with its three files and their roles. + - **`HostSpec` dataclass**: Document its attributes (`name`, `adapter`, `fields`, `field_mappings`) and key methods (`load_config()`, `get_adapter()`, `compute_expected_fields()`). + - **`HostRegistry` class**: Document how it derives metadata from `fields.py` at import time and provides `all_hosts()`, `get_host()`, `all_pairs()`, `hosts_supporting_field()`. + - **Generator functions**: Document `generate_sync_test_cases()`, `generate_validation_test_cases()`, `generate_unsupported_field_test_cases()` and how they feed `pytest.mark.parametrize`. + - **Assertion functions**: List the 8 `assert_*` functions in `assertions.py` and explain they encode adapter contracts as reusable property checks. + - **`canonical_configs.json` structure**: Show the JSON schema (host name -> field name -> value) and note the reverse mapping mechanism for Codex. +3. Fix the "zero test code changes" claim. Replace with accurate guidance: adding a new host requires (a) a new entry in `canonical_configs.json`, (b) adding the host's field set to `FIELD_SETS` in `host_registry.py`, and (c) updating `fields.py`. No changes to actual test files are needed — the generators pick up the new host automatically. +4. Acknowledge the two deprecated test files (`test_adapter_serialization.py`, `test_field_filtering.py`) with a note that they are `@pytest.mark.skip` and scheduled for removal in v0.9.0. + +**Deliverables**: Rewritten "Testing Strategy" section in `docs/articles/devs/architecture/mcp_host_configuration.md` +**Consistency Checks**: Verify every class/function name referenced in the testing section exists in `tests/test_data/mcp_adapters/` (expected: PASS) +**Commit**: `docs(mcp): rewrite testing section with data-driven infrastructure documentation` diff --git a/__roadmap__/mcp-docs-refresh/update_extension_guide.md b/__roadmap__/mcp-docs-refresh/update_extension_guide.md new file mode 100644 index 0000000..b2f8e0a --- /dev/null +++ b/__roadmap__/mcp-docs-refresh/update_extension_guide.md @@ -0,0 +1,68 @@ +# Update Extension Guide + +**Goal**: Bring `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` into alignment with the current codebase. +**Pre-conditions**: +- [ ] Branch `task/update-extension-guide` created from `milestone/mcp-docs-refresh` +**Success Gates**: +- Step 2 adapter template uses `validate_filtered()` in the `serialize()` method (not `validate()`) +- Step 3 strategy template includes `@register_host_strategy` decorator and documents `MCPHostStrategy` interface +- Step 4 documents the actual test data fixture requirements (`canonical_configs.json`, `host_registry.py`) +- "Testing Your Implementation" section cross-references the architecture doc's testing section +**References**: [R01 Gap Analysis](../../__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md) — findings 1b, 2d + +--- + +## Step 1: Fix Step 2 adapter template to use validate_filtered() + +**Goal**: Replace the deprecated `validate()` pattern in the adapter template with the current `validate_filtered()` contract. + +**Implementation Logic**: + +1. In the Step 2 template code block, change the `serialize()` method from calling `self.validate(config)` to calling `self.filter_fields(config)` then `self.validate_filtered(filtered)` then returning the filtered result. This matches the actual pattern used by all current adapters. +2. Update the `validate()` method stub to include a docstring marking it as deprecated with a pointer to `validate_filtered()`. Keep it as a pass-through since it's still abstract in `BaseAdapter`. +3. Add a `validate_filtered()` method to the template with the transport validation logic that's currently only in `validate()`. +4. Update the "Interface" table at the top of the guide: change `validate()` to `validate_filtered()` in the Adapter row, or list both with a deprecation note. +5. In "Common Patterns" section, update the "Multiple Transport Support" and "Strict Single Transport" examples to use `validate_filtered(self, filtered)` signatures (checking `"command" in filtered` instead of `config.command is not None`). +6. In "Field Mappings (Optional)" section, update the `serialize()` example to use `validate_filtered()`. + +**Deliverables**: Updated Step 2 template, updated Common Patterns section, updated Field Mappings section in `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` +**Consistency Checks**: Confirm every adapter in `hatch/mcp_host_config/adapters/` uses `validate_filtered()` inside `serialize()` (expected: PASS) +**Commit**: `docs(mcp): update extension guide adapter template to use validate_filtered()` + +--- + +## Step 2: Update Step 3 strategy template with registration decorator + +**Goal**: Document the `@register_host_strategy` decorator and the `MCPHostStrategy` base class interface in the strategy template. + +**Implementation Logic**: + +1. The Step 3 template already shows `@register_host_strategy(MCPHostType.YOUR_HOST)` — verify it's correct and add a brief explanation of what the decorator does (registers the strategy in a global dict so `get_strategy_for_host()` can look it up). +2. Add a brief list of `MCPHostStrategy` methods that can be overridden vs inherited. Currently the template shows `get_config_path()`, `is_host_available()`, `get_config_key()` but doesn't mention `read_configuration()`, `write_configuration()`, or `validate_server_config()`. Add a table showing which methods typically need overriding vs which inherit well from base/family classes. +3. Cross-reference the architecture doc's "MCPHostStrategy Interface" subsection. + +**Deliverables**: Updated Step 3 section in `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` +**Consistency Checks**: Verify the decorator name and signature match `hatch/mcp_host_config/strategies.py` (expected: PASS) +**Commit**: `docs(mcp): update extension guide strategy template with interface documentation` + +--- + +## Step 3: Rewrite Step 4 and Testing Your Implementation section + +**Goal**: Replace the current testing guidance with accurate documentation of the data-driven testing infrastructure requirements. + +**Implementation Logic**: + +1. Rewrite Step 4 to explain what's actually needed when adding a new host: + - **a)** Add a fixture entry to `tests/test_data/mcp_adapters/canonical_configs.json` — show the JSON structure (host name key, field-name-to-value mapping using host-native field names). + - **b)** Add the host's field set to `FIELD_SETS` in `tests/test_data/mcp_adapters/host_registry.py` — one line mapping host name to the `fields.py` constant. + - **c)** If the host uses field mappings (like Codex), add reverse mappings to `REVERSE_MAPPINGS` in `host_registry.py`. + - **d)** Explain that the generator functions (`generate_sync_test_cases`, `generate_validation_test_cases`, `generate_unsupported_field_test_cases`) will automatically pick up the new host and generate parameterized test cases. No changes to test files themselves. +2. Remove the misleading unit test template (Step 4 currently shows a `TestYourHostAdapter` class with handwritten test methods). Replace with a note that unit tests for adapter protocol compliance, field filtering, and cross-host sync are all auto-generated. Only add bespoke unit tests if the adapter has unusual behavior (e.g., complex field transformations). +3. Update the "Testing Your Implementation" section to cross-reference the architecture doc's testing section. Replace the "Test Categories" table with a table showing what's auto-generated vs what needs manual tests. +4. Update the "Test File Location" tree to include `tests/test_data/mcp_adapters/` and show the actual regression test directory. +5. Fix the "zero test code changes" claim in both the extension guide and any cross-references. State the accurate requirement: fixture data updates in `canonical_configs.json` and `host_registry.py`, but zero changes to test functions. + +**Deliverables**: Rewritten Step 4 section, rewritten "Testing Your Implementation" section, updated test file tree in `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` +**Consistency Checks**: Follow the documented steps mentally for a hypothetical new host and verify each instruction points to real files that exist in the codebase (expected: PASS) +**Commit**: `docs(mcp): rewrite extension guide testing section with data-driven infrastructure` From e48ea10f8e33a4ca4bcb36a07769f32d56f10a0f Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 17:02:13 +0900 Subject: [PATCH 11/71] chore(roadmap): add adding-mcp-hosts-skill campaign Reports: skill design analysis + discovery questionnaire Roadmap: 5 parallel content leaves + packaging step Co-Authored-By: Claude Opus 4.6 --- .../discovery-questionnaire.md | 205 ++++++++++++++++++ .../skill-design-analysis.md | 124 +++++++++++ __roadmap__/adding-mcp-hosts-skill/README.md | 76 +++++++ .../adding-mcp-hosts-skill/package/README.md | 47 ++++ .../package/package_skill.md | 43 ++++ .../write_adapter_contract.md | 54 +++++ .../write_discovery_guide.md | 45 ++++ .../adding-mcp-hosts-skill/write_skill_md.md | 84 +++++++ .../write_strategy_contract.md | 50 +++++ .../write_testing_fixtures.md | 54 +++++ 10 files changed, 782 insertions(+) create mode 100644 __reports__/mcp_support_extension_skill/discovery-questionnaire.md create mode 100644 __reports__/mcp_support_extension_skill/skill-design-analysis.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/README.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/package/README.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/package/package_skill.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/write_adapter_contract.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/write_discovery_guide.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/write_skill_md.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/write_strategy_contract.md create mode 100644 __roadmap__/adding-mcp-hosts-skill/write_testing_fixtures.md diff --git a/__reports__/mcp_support_extension_skill/discovery-questionnaire.md b/__reports__/mcp_support_extension_skill/discovery-questionnaire.md new file mode 100644 index 0000000..8214efa --- /dev/null +++ b/__reports__/mcp_support_extension_skill/discovery-questionnaire.md @@ -0,0 +1,205 @@ +# Discovery Questionnaire: Adding a New MCP Host + +## Purpose + +Exhaustive list of information the agent needs to add support for a new MCP host platform. Derived from tracing every codepath that makes a host-specific decision across 10+ source files. + +This questionnaire serves two purposes: +1. **Discovery checklist** — what the agent must find via web research or codebase retrieval +2. **User escalation template** — what to ask the user when discovery tools are unavailable or insufficient + +--- + +## Category A: Host Identity & Config Location + +Discoverable via official docs, GitHub repos, or config file examples. + +| # | Question | Why it matters | File(s) affected | +|---|----------|----------------|------------------| +| A1 | What is the host's canonical name? (e.g., `"kiro"`, `"claude-desktop"`) | Becomes the `MCPHostType` enum value and the adapter `host_name`. Convention: lowercase with hyphens. | `models.py`, every other file | +| A2 | Where is the config file on each platform? (macOS, Linux, Windows paths) | Strategy `get_config_path()` — every existing host has platform-specific path logic. | `strategies.py` | +| A3 | What is the config file format? (JSON or TOML) | Determines strategy `read_configuration()` / `write_configuration()` implementation and which strategy family to inherit from. Only Codex uses TOML; all others use JSON. | `strategies.py` | +| A4 | What is the root key for MCP servers in the config file? | Strategy `get_config_key()`. Known values: `"mcpServers"` (most hosts), `"servers"` (VSCode), `"mcp_servers"` (Codex). | `strategies.py` | +| A5 | How to detect if host is installed on the system? | Strategy `is_host_available()`. Most hosts check for a directory's existence (e.g., `~/.kiro/settings/`). | `strategies.py` | + +### Existing host examples for reference + +| Host | Format | Root Key | macOS Path | Detection | +|------|--------|----------|------------|-----------| +| claude-desktop | JSON | `mcpServers` | `~/Library/Application Support/Claude/claude_desktop_config.json` | Config parent dir exists | +| claude-code | JSON | `mcpServers` | `~/.claude.json` | File exists | +| vscode | JSON | `servers` | `~/Library/Application Support/Code/User/mcp.json` | Code dir exists | +| cursor | JSON | `mcpServers` | `~/.cursor/mcp.json` | `.cursor/` exists | +| lmstudio | JSON | `mcpServers` | `~/.lmstudio/mcp.json` | `.lmstudio/` exists | +| gemini | JSON | `mcpServers` | `~/.gemini/settings.json` | `.gemini/` exists | +| kiro | JSON | `mcpServers` | `~/.kiro/settings/mcp.json` | `.kiro/settings/` exists | +| codex | TOML | `mcp_servers` | `~/.codex/config.toml` | `.codex/` exists | + +--- + +## Category B: Field Support + +Partially discoverable from host documentation. This is where web research helps most — host docs usually list supported config fields. + +| # | Question | Why it matters | File(s) affected | +|---|----------|----------------|------------------| +| B1 | Which transport types does the host support? (stdio, sse, http) | Drives validation rules in `validate_filtered()`. Most hosts support stdio + sse. Gemini also supports http via `httpUrl`. | adapter | +| B2 | Does the host support the `type` discriminator field? (the `"type": "stdio"` / `"sse"` field) | Determines whether host joins `TYPE_SUPPORTING_HOSTS` in `fields.py`. Claude, VSCode, Cursor, LM Studio support it. Gemini, Kiro, Codex do not. | `fields.py` | +| B3 | What host-specific fields exist beyond the universal set? (List each with: field name, type, description, required/optional) | Defines the field set constant in `fields.py` and potentially new `MCPServerConfig` field declarations in `models.py`. | `fields.py`, `models.py` | +| B4 | Does the host use different names for standard fields? (e.g., Codex uses `arguments` instead of `args`, `http_headers` instead of `headers`) | Determines whether a `FIELD_MAPPINGS` dict and `apply_transformations()` override are needed. | `fields.py`, adapter | +| B5 | Are there fields semantically equivalent to another host's fields? (e.g., Gemini `includeTools` ≈ Codex `enabled_tools`) | Cross-host sync field mappings. Codex maps `includeTools` → `enabled_tools` and `excludeTools` → `disabled_tools` to enable transparent Gemini→Codex sync. Without mappings, sync silently drops the field. | `fields.py`, adapter | + +### Universal fields (supported by ALL hosts) + +Every host inherits these 5 fields from `UNIVERSAL_FIELDS`: + +| Field | Type | Description | +|-------|------|-------------| +| `command` | `str` | stdio transport — command to execute | +| `args` | `List[str]` | Command arguments | +| `env` | `Dict[str, str]` | Environment variables | +| `url` | `str` | sse/http transport — server URL | +| `headers` | `Dict[str, str]` | HTTP headers for remote transports | + +--- + +## Category C: Validation & Serialization Rules + +Often discoverable from host documentation, sometimes ambiguous. + +| # | Question | Why it matters | File(s) affected | +|---|----------|----------------|------------------| +| C1 | Transport mutual exclusion: can the host have multiple transports simultaneously, or exactly one? | Core validation logic in `validate_filtered()`. Most hosts require exactly one. Gemini supports three (`command`, `url`, `httpUrl`) but still requires exactly one at a time. | adapter | +| C2 | Are any fields mutually exclusive? (beyond transports) | Additional validation rules in `validate_filtered()`. | adapter | +| C3 | Are any fields conditionally required? (e.g., "if `oauth_enabled` is true, then `oauth_clientId` is required") | Additional validation rules in `validate_filtered()`. | adapter | +| C4 | Does serialization require structural transformation beyond field renaming? (e.g., nesting fields under a sub-key, wrapping transport in a sub-object) | Whether a custom `serialize()` override is needed instead of the standard filter→validate→transform pipeline. | adapter | +| C5 | Does the config file contain non-MCP sections that must be preserved on write? (e.g., Codex preserves `[features]`, Gemini preserves other settings keys) | Strategy `write_configuration()` must read-before-write and merge, not overwrite. | `strategies.py` | + +--- + +## Category D: Architectural Fit + +Requires judgment based on comparing the new host against existing implementations. Rarely discoverable from external docs alone. + +| # | Question | Why it matters | File(s) affected | +|---|----------|----------------|------------------| +| D1 | Is this host functionally identical to an existing host? (same fields, same validation, different name only) | Variant pattern: reuse an existing adapter class with a `variant` constructor parameter (like `ClaudeAdapter(variant="desktop")` / `ClaudeAdapter(variant="code")`) instead of creating a new class. | adapter, `registry.py` | +| D2 | Does this host share config format and I/O logic with an existing host? | Strategy family: inherit from `ClaudeHostStrategy` or `CursorBasedHostStrategy` instead of bare `MCPHostStrategy`, getting `read_configuration()` and `write_configuration()` for free. | `strategies.py` | + +### Existing strategy families + +| Family Base Class | Members | What it provides | +|-------------------|---------|------------------| +| `ClaudeHostStrategy` | `ClaudeDesktopStrategy`, `ClaudeCodeStrategy` | Shared JSON read/write, `_preserve_claude_settings()` | +| `CursorBasedHostStrategy` | `CursorHostStrategy`, `LMStudioHostStrategy` | Shared Cursor-format JSON read/write | +| `MCPHostStrategy` (standalone) | `VSCodeHostStrategy`, `GeminiHostStrategy`, `KiroHostStrategy`, `CodexHostStrategy` | No shared logic — each implements its own I/O | + +--- + +## 5. Escalation Tiers + +When the agent must ask the user (because discovery tools are unavailable or returned insufficient data), present questions in tiers to avoid overwhelming with a single wall of questions. + +### Tier 1: Blocking — cannot proceed without answers + +Ask these first. Every answer feeds directly into a required file modification. + +| Question IDs | Summary | +|--------------|---------| +| A1 | Host canonical name | +| A2 | Config file path per platform | +| A3 | Config file format (JSON/TOML) | +| A4 | Root key for MCP servers | +| B1 | Supported transport types | +| B3 | Host-specific fields (names, types, descriptions) | + +### Tier 2: Important — ask if Tier 1 reveals complexity + +Ask these after Tier 1 if the host has non-standard behavior. + +| Question IDs | Trigger condition | +|--------------|-------------------| +| B4 | Host uses different names for standard fields | +| B5 | Host has tool filtering fields that map to another host's equivalents | +| C1 | Unclear whether transports are mutually exclusive | +| C4 | Config format requires structural nesting beyond flat key-value | +| C5 | Config file has non-MCP sections | + +### Tier 3: Clarification — ask only if ambiguous + +Ask these only if reading existing adapters and strategies leaves the answer unclear. + +| Question IDs | Trigger condition | +|--------------|-------------------| +| A5 | Host detection mechanism is non-obvious | +| B2 | Unclear whether host uses `type` discriminator field | +| C2 | Possible field mutual exclusion beyond transports | +| C3 | Possible conditional field requirements | +| D1 | Host looks identical to an existing one | +| D2 | Host I/O looks similar to an existing strategy family | + +--- + +## 6. Discovery Output Format + +Whether the information comes from web research or user answers, the agent should produce a structured **Host Spec** before proceeding to implementation. This artifact feeds steps 2-5 of the skill workflow. + +```yaml +host: + name: "your-host" # A1 + config_format: "json" # A3 + config_key: "mcpServers" # A4 + +paths: # A2 + darwin: "~/.your-host/config.json" + linux: "~/.config/your-host/config.json" + windows: "~/.your-host/config.json" + +detection: # A5 + method: "directory_exists" + path: "~/.your-host/" + +transports: # B1 + supported: ["stdio", "sse"] + mutual_exclusion: true # C1 + +fields: # B2, B3 + type_discriminator: true + host_specific: + - name: "your_field" + type: "Optional[str]" + description: "Description" + - name: "another_field" + type: "Optional[bool]" + description: "Description" + +field_mappings: {} # B4, B5 (empty if no mappings) + +validation: # C2, C3 + mutual_exclusions: [] + conditional_requirements: [] + +serialization: # C4 + structural_transform: false + +config_file: # C5 + preserved_sections: [] + +architecture: # D1, D2 + variant_of: null + strategy_family: null +``` + +--- + +## 7. Key Design Decisions for Skill Authoring + +| Decision | Recommendation | Rationale | +|----------|---------------|-----------| +| Architecture doc content in skill? | No — stays as developer documentation | Agent doesn't need architectural understanding to follow the recipe | +| Field support matrix in skill? | No — agent reads `fields.py` directly | Avoids stale duplication; agent can inspect the source of truth | +| MCPServerConfig model listing? | No — agent reads `models.py` directly | Same rationale | +| Testing infrastructure deep-dive? | Minimal — just "add these fixtures, run these commands" | Agent doesn't need to understand generators to add fixture data | +| Discovery step as first step? | Yes | Biggest bottleneck is knowing what fields the host supports; makes the rest mechanical | +| Structured output from discovery? | Yes — Host Spec YAML | Decouples information gathering from implementation; same spec whether from web or user | +| Progressive disclosure? | Yes — adapter/strategy/testing contracts in `references/` | Keeps SKILL.md lean; loaded only when host has non-standard needs | diff --git a/__reports__/mcp_support_extension_skill/skill-design-analysis.md b/__reports__/mcp_support_extension_skill/skill-design-analysis.md new file mode 100644 index 0000000..0621fc9 --- /dev/null +++ b/__reports__/mcp_support_extension_skill/skill-design-analysis.md @@ -0,0 +1,124 @@ +# Skill Design Analysis: MCP Host Configuration Extension + +## Purpose + +Design report for converting the MCP host configuration extension workflow into a Claude Code agent skill. The skill enables an LLM agent to autonomously add support for a new MCP host platform to the Hatch CLI. + +--- + +## 1. Skill Relevance Assessment + +### Why it fits + +The core use case — "add support for a new MCP host" — is a bounded, repeatable, multi-step procedure with low tolerance for deviation. That is the sweet spot for skills. An agent needs: + +1. Procedural steps it cannot infer (which files to touch, in what order) +2. Codebase-specific contracts (field set declaration, registry wiring, decorator usage) +3. Template code that must follow exact patterns (the `validate_filtered()` pipeline, `@register_host_strategy`) +4. Testing fixture requirements (`canonical_configs.json` structure, `FIELD_SETS` mapping) + +None of this is general knowledge. An LLM cannot derive it from first principles. + +### What does not translate directly from existing docs + +The two developer docs (`mcp_host_configuration.md` and `mcp_host_configuration_extension.md`) cannot be transplanted as-is. They need restructuring to match how skills work. + +#### 1. Architecture doc is reference material, not instructions + +It explains *how the system works*, not *what to do*. In skill terms, it belongs in `references/`, not in SKILL.md. Much of it is context Claude already handles well — what Pydantic does, what ABC means, what "declarative" means. The field support matrix and testing infrastructure sections are the only parts an agent genuinely needs. + +#### 2. Extension guide has too much "why" + +Sections like "When You Need This", "The Pattern: Adapter + Strategy" overview, the ASCII diagram, the troubleshooting table, and the "Reference: Existing Adapters" table are developer onboarding material. An agent skill should be imperative: "do X, then Y." The existing doc explains concepts; a skill should prescribe actions. + +#### 3. Both docs use relative file paths + +They say `models.py` and `adapters/your_host.py`. A skill needs absolute paths from the repo root (`hatch/mcp_host_config/models.py`) so the agent can operate without ambiguity. + +#### 4. No verification workflow + +The extension guide says *what* to create but does not tell the agent how to verify its work. A skill should include the exact commands to run after each step. + +#### 5. Information that should be discovered, not loaded + +The full field support matrix (35 rows), the MCPServerConfig model (50 lines), and the HostSpec/HostRegistry documentation are heavyweight. An agent adding a new host does not need all of that in context — it needs to know *where to look* and *what to do*. The skill should point to files, not reproduce them. + +#### 6. Extension guide understates the integration surface + +The guide advertises 4 files to modify. The actual count is 10-11 (see Section 4). Two of the missing files (`backup.py`, `reporting.py`) are boilerplate one-liners but the agent will miss them without explicit instructions. + +--- + +## 2. Proposed Skill Structure + +``` +add-mcp-host/ +├── SKILL.md # ~150 lines: trigger, 5-step workflow, verification +└── references/ + ├── discovery-guide.md # How to research host requirements using available tools + ├── adapter-contract.md # BaseAdapter interface, validate_filtered pattern, + │ # field mappings, variant pattern + ├── strategy-contract.md # MCPHostStrategy interface, decorator, families + └── testing-fixtures.md # canonical_configs.json schema, FIELD_SETS, + # reverse mappings, what gets auto-generated +``` + +### SKILL.md scope + +- The 5-step checklist (discover → enum → adapter+strategy → wiring → test fixtures) with exact file paths +- Template snippets (lean — just the required method signatures, not full docstrings) +- Verification commands per step +- Conditional reads: "Read `references/adapter-contract.md` if the host needs field mappings or custom serialization" + +### What stays outside the skill + +The architecture doc (`mcp_host_configuration.md`) remains as developer documentation. The skill should reference it for humans but an agent does not need architectural understanding to follow the recipe. + +--- + +## 3. Proposed Workflow: 5 Steps + +The current extension guide has 4 steps. The skill adds a prior discovery step, making 5: + +| Step | Name | Description | +|------|------|-------------| +| 1 | **Discover host requirements** | Research the target host's MCP config spec using web tools, Context7, or user escalation | +| 2 | **Add enum and field set** | `models.py` enum + `fields.py` field constant + optionally new MCPServerConfig fields | +| 3 | **Create adapter and strategy** | Adapter class + strategy class with `@register_host_strategy` | +| 4 | **Wire integration points** | `adapters/__init__.py`, `adapters/registry.py`, `backup.py`, `reporting.py` | +| 5 | **Register test fixtures** | `canonical_configs.json` + `host_registry.py` entries | + +Step 1's output (a structured field spec) feeds all decisions in steps 2-5. + +### Discovery step: tool priority ladder + +The agent should try discovery tools in order and fall through gracefully: + +1. **Web search + fetch** — find the host's official MCP config docs +2. **Context7** — query library documentation +3. **Codebase retrieval** — check if the host's config format is already partially documented in the repo +4. **Escalate to user** — structured questionnaire (see Section 5) + +If web tools are unavailable in the agent's environment, it must escalate immediately. + +--- + +## 4. Complete File Modification Surface + +Every file the agent must touch when adding a new host: + +| # | File (from repo root) | Always? | What to add | +|---|------|---------|-------------| +| 1 | `hatch/mcp_host_config/models.py` | Yes | `MCPHostType` enum value | +| 2 | `hatch/mcp_host_config/fields.py` | Yes | Field set constant (e.g., `NEW_HOST_FIELDS`), optionally field mappings dict | +| 3 | `hatch/mcp_host_config/adapters/new_host.py` | Yes | New adapter class (or variant registration if identical to existing host) | +| 4 | `hatch/mcp_host_config/adapters/__init__.py` | Yes | Export new adapter class | +| 5 | `hatch/mcp_host_config/adapters/registry.py` | Yes | `_register_defaults()` entry | +| 6 | `hatch/mcp_host_config/strategies.py` | Yes | Strategy class with `@register_host_strategy` decorator | +| 7 | `hatch/mcp_host_config/backup.py` | Yes | Add hostname string to `supported_hosts` set in `BackupInfo.validate_hostname()` | +| 8 | `hatch/mcp_host_config/reporting.py` | Yes | Add `MCPHostType → host_name` entry in `_get_adapter_host_name()` mapping | +| 9 | `tests/test_data/mcp_adapters/canonical_configs.json` | Yes | Canonical config fixture using host-native field names | +| 10 | `tests/test_data/mcp_adapters/host_registry.py` | Yes | `FIELD_SETS` entry, `adapter_map` entry in `HostSpec.get_adapter()`, optionally reverse mappings | +| 11 | `hatch/mcp_host_config/models.py` (MCPServerConfig) | Conditional | New field declarations — only if host introduces fields not already in the model | + +Files 7 and 8 are boilerplate one-liners but are absent from the current extension guide. The agent will miss them without explicit instructions. diff --git a/__roadmap__/adding-mcp-hosts-skill/README.md b/__roadmap__/adding-mcp-hosts-skill/README.md new file mode 100644 index 0000000..211266b --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/README.md @@ -0,0 +1,76 @@ +# Adding MCP Hosts Skill + +## Context + +Standalone skill authoring campaign. Converts the MCP host configuration extension workflow into a Claude Code agent skill. The skill enables an LLM agent to autonomously add support for a new MCP host platform to the Hatch CLI — from discovery through implementation to test verification. + +## Reference Documents + +- [R01 Skill Design Analysis](../../__reports__/mcp_support_extension_skill/skill-design-analysis.md) — Skill relevance assessment, proposed structure, 5-step workflow, complete 10-11 file modification surface +- [R02 Discovery Questionnaire](../../__reports__/mcp_support_extension_skill/discovery-questionnaire.md) — 17 questions across 4 categories, 3 escalation tiers, Host Spec YAML output format +- [R03 Best Practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — Official skill authoring best practices (web) + +## Goal + +Produce a packaged `adding-mcp-hosts.skill` file that an agent can use to add support for any new MCP host platform. + +## Pre-conditions + +- [x] Design reports reviewed (R01, R02) +- [x] Best practices consulted (R03) +- [x] MCP docs refresh campaign completed (architecture doc + extension guide up to date) + +## Success Gates + +- SKILL.md under 500 lines with 5-step workflow checklist +- 4 reference files with progressive disclosure (discovery, adapter, strategy, testing) +- Skill passes `package_skill.py` validation (frontmatter, naming, description) +- Packaged `.skill` file produced + +## Gotchas + +- No `init_skill.py` step — each parallel task creates its target file directly. Tasks must `mkdir -p` the skill directory before writing. +- `package_skill.py` imports `quick_validate` from its own directory — must set PYTHONPATH accordingly. +- Skill name must be kebab-case, max 64 chars, no reserved words ("anthropic", "claude"). Using `adding-mcp-hosts`. +- Description must be third-person, max 1024 chars, no angle brackets. +- Reference files should be one level deep from SKILL.md (no nested references). +- All 5 content leaves target different files in `__design__/skills/adding-mcp-hosts/` — worktree merges will be conflict-free. + +## Status + +```mermaid +graph TD + write_discovery_guide[Write Discovery Guide]:::planned + write_adapter_contract[Write Adapter Contract]:::planned + write_strategy_contract[Write Strategy Contract]:::planned + write_testing_fixtures[Write Testing Fixtures]:::planned + write_skill_md[Write Skill MD]:::planned + package[Package]:::planned + + classDef done fill:#166534,color:#bbf7d0 + classDef inprogress fill:#854d0e,color:#fef08a + classDef planned fill:#374151,color:#e5e7eb + classDef amendment fill:#1e3a5f,color:#bfdbfe + classDef blocked fill:#7f1d1d,color:#fecaca +``` + +## Nodes + +| Node | Type | Status | +|:-----|:-----|:-------| +| `write_discovery_guide.md` | Leaf Task | Planned | +| `write_adapter_contract.md` | Leaf Task | Planned | +| `write_strategy_contract.md` | Leaf Task | Planned | +| `write_testing_fixtures.md` | Leaf Task | Planned | +| `write_skill_md.md` | Leaf Task | Planned | +| `package/` | Directory | Planned | + +## Amendment Log + +| ID | Date | Source | Nodes Added | Rationale | +|:---|:-----|:-------|:------------|:----------| + +## Progress + +| Node | Branch | Commits | Notes | +|:-----|:-------|:--------|:------| diff --git a/__roadmap__/adding-mcp-hosts-skill/package/README.md b/__roadmap__/adding-mcp-hosts-skill/package/README.md new file mode 100644 index 0000000..0e73d26 --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/package/README.md @@ -0,0 +1,47 @@ +# Package + +## Context + +Final validation and packaging of the adding-mcp-hosts skill. All 5 content files (SKILL.md + 4 references) must be complete and merged before packaging can validate the full skill structure. + +## Goal + +Validate skill structure and produce the distributable `.skill` package. + +## Pre-conditions + +- [ ] All 5 depth-0 leaves merged into milestone + +## Success Gates + +- `package_skill.py` validation passes +- `adding-mcp-hosts.skill` file produced + +## Status + +```mermaid +graph TD + package_skill[Package Skill]:::planned + + classDef done fill:#166534,color:#bbf7d0 + classDef inprogress fill:#854d0e,color:#fef08a + classDef planned fill:#374151,color:#e5e7eb + classDef amendment fill:#1e3a5f,color:#bfdbfe + classDef blocked fill:#7f1d1d,color:#fecaca +``` + +## Nodes + +| Node | Type | Status | +|:-----|:-----|:-------| +| `package_skill.md` | Leaf Task | Planned | + +## Amendment Log + +| ID | Date | Source | Nodes Added | Rationale | +|:---|:-----|:-------|:------------|:----------| + +## Progress + +| Node | Branch | Commits | Notes | +|:-----|:-------|:--------|:------| diff --git a/__roadmap__/adding-mcp-hosts-skill/package/package_skill.md b/__roadmap__/adding-mcp-hosts-skill/package/package_skill.md new file mode 100644 index 0000000..60cb425 --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/package/package_skill.md @@ -0,0 +1,43 @@ +# Package Skill + +**Goal**: Validate and package the skill into a distributable `.skill` file. +**Pre-conditions**: +- [ ] Branch `task/package-skill` created from `milestone/adding-mcp-hosts-skill` +- [ ] All 5 content files present in `__design__/skills/adding-mcp-hosts/` +**Success Gates**: +- `package_skill.py` exits 0 +- `.skill` file contains exactly 5 files: SKILL.md + 4 references +**References**: Skill creator scripts at `~/.claude/plugins/cache/anthropic-agent-skills/example-skills/*/skills/skill-creator/scripts/` + +--- + +## Step 1: Validate and package + +**Goal**: Run the packaging script and verify the output. + +**Implementation Logic**: +1. Locate the skill-creator scripts directory (glob for `**/skill-creator/scripts/package_skill.py` under `~/.claude/plugins/cache/`) +2. Create output directory: `mkdir -p __design__/skills/dist/` +3. Run packaging with PYTHONPATH set for the `quick_validate` import: + ```bash + PYTHONPATH= python /package_skill.py __design__/skills/adding-mcp-hosts/ __design__/skills/dist/ + ``` +4. If validation fails: + - Read the error message + - Fix the issue in SKILL.md (most likely frontmatter problem) + - Re-run packaging +5. Verify the produced `.skill` file: + ```bash + python -c "import zipfile; [print(f) for f in zipfile.ZipFile('__design__/skills/dist/adding-mcp-hosts.skill').namelist()]" + ``` + Expected contents: + - `adding-mcp-hosts/SKILL.md` + - `adding-mcp-hosts/references/discovery-guide.md` + - `adding-mcp-hosts/references/adapter-contract.md` + - `adding-mcp-hosts/references/strategy-contract.md` + - `adding-mcp-hosts/references/testing-fixtures.md` +6. Report the `.skill` file path to the user. + +**Deliverables**: `__design__/skills/dist/adding-mcp-hosts.skill` +**Consistency Checks**: `package_skill.py` exit code 0; zip contains exactly 5 files +**Commit**: `chore(skill): package adding-mcp-hosts skill` diff --git a/__roadmap__/adding-mcp-hosts-skill/write_adapter_contract.md b/__roadmap__/adding-mcp-hosts-skill/write_adapter_contract.md new file mode 100644 index 0000000..53fe401 --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/write_adapter_contract.md @@ -0,0 +1,54 @@ +# Write Adapter Contract + +**Goal**: Document the adapter interface contract for implementing a new host adapter. +**Pre-conditions**: +- [ ] Branch `task/write-adapter-contract` created from `milestone/adding-mcp-hosts-skill` +**Success Gates**: +- `__design__/skills/adding-mcp-hosts/references/adapter-contract.md` exists +- Covers all 7 subsections and references all 10 files from R01 §4 +**References**: [R01 §4](../../__reports__/mcp_support_extension_skill/skill-design-analysis.md) — Complete file modification surface + +--- + +## Step 1: Write adapter-contract.md + +**Goal**: Create the reference file documenting everything an agent needs to implement a host adapter. + +**Implementation Logic**: +Create `__design__/skills/adding-mcp-hosts/references/adapter-contract.md` (`mkdir -p` the path first). Derive from R01 §4 cross-referenced with the codebase. Read the following files to extract exact patterns: +- `hatch/mcp_host_config/fields.py` — field set constants, UNIVERSAL_FIELDS, TYPE_SUPPORTING_HOSTS, existing FIELD_MAPPINGS +- `hatch/mcp_host_config/models.py` — MCPHostType enum, MCPServerConfig model +- `hatch/mcp_host_config/adapters/` — BaseAdapter ABC, existing adapter implementations +- `hatch/mcp_host_config/adapters/__init__.py` — export pattern +- `hatch/mcp_host_config/adapters/registry.py` — `_register_defaults()` pattern +- `hatch/mcp_host_config/backup.py` — `supported_hosts` set in `BackupInfo.validate_hostname()` +- `hatch/mcp_host_config/reporting.py` — `MCPHostType → host_name` mapping in `_get_adapter_host_name()` + +Structure: + +1. **MCPHostType enum** — How to add the enum value. Convention: `UPPER_SNAKE = "kebab-case"`. File: `hatch/mcp_host_config/models.py`. + +2. **Field set declaration** — How to define `_FIELDS` frozenset in `hatch/mcp_host_config/fields.py`. Pattern: `UNIVERSAL_FIELDS | {host-specific fields}`. Include `TYPE_SUPPORTING_HOSTS` membership decision. + +3. **MCPServerConfig fields** — When to add new field declarations to `MCPServerConfig`. Only needed if host introduces fields not already in the model. File: `hatch/mcp_host_config/models.py`. + +4. **Adapter class** — `BaseAdapter` interface. Lean template with required method signatures: + - `get_supported_fields()` → return the field set constant + - `validate_filtered()` → transport mutual exclusion + host-specific rules + - `apply_transformations()` → field renaming via mappings dict (if applicable) + - `serialize()` → standard pipeline (filter → validate → transform), override only if structural transformation needed + Show the `validate_filtered()` template snippet from the extension guide. + +5. **Field mappings** — When to define `_FIELD_MAPPINGS` dict. Pattern: `{"standard_name": "host_name"}`. Reference `CODEX_FIELD_MAPPINGS` as canonical example. + +6. **Variant pattern** — When to reuse an existing adapter with a variant parameter instead of a new class. Reference `ClaudeAdapter(variant="desktop"|"code")` as canonical example. + +7. **Wiring and integration points** — All 4 one-liner integration files: + - `adapters/__init__.py` — export new adapter class + - `adapters/registry.py` — `_register_defaults()` entry mapping `MCPHostType → adapter instance` + - `backup.py` — add hostname string to `supported_hosts` set + - `reporting.py` — add `MCPHostType → host_name` entry in mapping dict + +**Deliverables**: `__design__/skills/adding-mcp-hosts/references/adapter-contract.md` (~120-160 lines) +**Consistency Checks**: File covers all 7 subsections; references all source files listed above +**Commit**: `feat(skill): add adapter contract reference` diff --git a/__roadmap__/adding-mcp-hosts-skill/write_discovery_guide.md b/__roadmap__/adding-mcp-hosts-skill/write_discovery_guide.md new file mode 100644 index 0000000..ad2cd6b --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/write_discovery_guide.md @@ -0,0 +1,45 @@ +# Write Discovery Guide + +**Goal**: Write the discovery workflow reference for researching a new host's MCP config requirements. +**Pre-conditions**: +- [ ] Branch `task/write-discovery-guide` created from `milestone/adding-mcp-hosts-skill` +**Success Gates**: +- `__design__/skills/adding-mcp-hosts/references/discovery-guide.md` exists +- Contains all 4 question categories, 3 escalation tiers, and Host Spec YAML template +**References**: [R02](../../__reports__/mcp_support_extension_skill/discovery-questionnaire.md) — Primary source for all content + +--- + +## Step 1: Write discovery-guide.md + +**Goal**: Create the reference file that guides an agent through host requirement discovery. + +**Implementation Logic**: +Create `__design__/skills/adding-mcp-hosts/references/discovery-guide.md` (`mkdir -p` the path first). Derive content from R02. Structure: + +1. **Tool priority ladder** — Ordered fallback chain: + - Web search + fetch → find official MCP config docs for the target host + - Context7 → query library documentation for the host + - Codebase retrieval → check if host config format is already partially documented + - User escalation → structured questionnaire (see below) + For each level: what to search for, what "success" looks like, when to fall through. + +2. **Structured questionnaire** — All 17 questions from R02 across 4 categories: + - **Category A: Host Identity & Config Location** (A1-A5) — canonical name, config paths per platform, format (JSON/TOML), root key, detection method + - **Category B: Field Support** (B1-B5) — transport types, type discriminator, host-specific fields, field name mappings, cross-host equivalents + - **Category C: Validation & Serialization** (C1-C5) — transport mutual exclusion, field mutual exclusions, conditional requirements, structural transforms, preserved config sections + - **Category D: Architectural Fit** (D1-D2) — variant of existing host, strategy family match + Each question: ID, question text, why it matters, which file(s) it affects. + +3. **Escalation tiers** — Progressive disclosure for user questioning: + - **Tier 1 (Blocking)**: A1, A2, A3, A4, B1, B3 — cannot proceed without these + - **Tier 2 (Complexity-triggered)**: B4, B5, C1, C4, C5 — ask if Tier 1 reveals non-standard behavior + - **Tier 3 (Ambiguity-only)**: A5, B2, C2, C3, D1, D2 — ask only if reading existing code leaves answer unclear + +4. **Existing host reference table** — All 8 current hosts (claude-desktop, claude-code, vscode, cursor, lmstudio, gemini, kiro, codex) with: format, root key, macOS path, detection method. Gives the agent comparison points. + +5. **Host Spec YAML output format** — The structured artifact the discovery step produces. Include the full YAML template from R02 §6 with all fields annotated by question ID. + +**Deliverables**: `__design__/skills/adding-mcp-hosts/references/discovery-guide.md` (~150-200 lines) +**Consistency Checks**: File contains sections for all 4 categories, all 3 tiers, reference table, and YAML template +**Commit**: `feat(skill): add discovery guide reference` diff --git a/__roadmap__/adding-mcp-hosts-skill/write_skill_md.md b/__roadmap__/adding-mcp-hosts-skill/write_skill_md.md new file mode 100644 index 0000000..fdb7cb5 --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/write_skill_md.md @@ -0,0 +1,84 @@ +# Write Skill MD + +**Goal**: Write the main SKILL.md with frontmatter and 5-step workflow body. +**Pre-conditions**: +- [ ] Branch `task/write-skill-md` created from `milestone/adding-mcp-hosts-skill` +**Success Gates**: +- `__design__/skills/adding-mcp-hosts/SKILL.md` exists with valid frontmatter +- Body under 500 lines with 5-step workflow +- Links to all 4 reference files by relative path +**References**: +- [R01 §2-3](../../__reports__/mcp_support_extension_skill/skill-design-analysis.md) — Proposed structure and workflow +- [R03](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — Frontmatter rules, description guidelines + +--- + +## Step 1: Write SKILL.md + +**Goal**: Create the main skill file with frontmatter and workflow body. + +**Implementation Logic**: +Create `__design__/skills/adding-mcp-hosts/SKILL.md` (`mkdir -p` the path first). + +**Frontmatter** (YAML): +- `name`: `adding-mcp-hosts` +- `description`: Third-person, ~200-300 chars. Must cover: + - WHAT: Adds support for a new MCP host platform to the Hatch CLI multi-host configuration system + - WHEN: When asked to add, integrate, or extend MCP host support for a new IDE, editor, or AI coding tool (e.g., Windsurf, Zed, Copilot) + - HOW: 5-step workflow from discovery through test verification + No angle brackets. No reserved words. Max 1024 chars. + +**Body** (Markdown, target 150-200 lines). Use imperative form throughout. Structure: + +1. **Workflow checklist** — Copy-paste progress tracker: + ``` + - [ ] Step 1: Discover host requirements + - [ ] Step 2: Add enum and field set + - [ ] Step 3: Create adapter and strategy + - [ ] Step 4: Wire integration points + - [ ] Step 5: Register test fixtures + ``` + +2. **Step 1: Discover host requirements** (~15 lines): + - "Read [references/discovery-guide.md](references/discovery-guide.md) for the full discovery workflow." + - Summarize: use web tools to research, fall back to user questionnaire, produce Host Spec YAML. + - Output: structured Host Spec feeding all subsequent steps. + +3. **Step 2: Add enum and field set** (~20 lines, inline): + - Add `MCPHostType` enum value in `hatch/mcp_host_config/models.py` + - Add `_FIELDS` frozenset in `hatch/mcp_host_config/fields.py` (pattern: `UNIVERSAL_FIELDS | {extras}`) + - Optionally add new `MCPServerConfig` fields if host introduces novel fields + - Verification: `python -c "from hatch.mcp_host_config.models import MCPHostType; print(MCPHostType.YOUR_HOST)"` + +4. **Step 3: Create adapter and strategy** (~20 lines): + - "Read [references/adapter-contract.md](references/adapter-contract.md) for the adapter interface." + - "Read [references/strategy-contract.md](references/strategy-contract.md) for the strategy interface." + - Mention variant pattern shortcut (if host is functionally identical to existing host) + - Mention strategy family decision (inherit from ClaudeHostStrategy, CursorBasedHostStrategy, or standalone) + - Verification: import and instantiate the adapter. + +5. **Step 4: Wire integration points** (~20 lines, inline): + - `adapters/__init__.py` — export new adapter + - `adapters/registry.py` — add `_register_defaults()` entry + - `backup.py` — add hostname to `supported_hosts` set + - `reporting.py` — add `MCPHostType → host_name` mapping + - Verification: `python -c "from hatch.mcp_host_config.adapters.registry import AdapterRegistry; ..."` + +6. **Step 5: Register test fixtures** (~15 lines): + - "Read [references/testing-fixtures.md](references/testing-fixtures.md) for fixture schemas and registration." + - Add entry to `tests/test_data/mcp_adapters/canonical_configs.json` + - Add entries to `tests/test_data/mcp_adapters/host_registry.py` + - Verification: `python -m pytest tests/integration/mcp/ tests/unit/mcp/ tests/regression/mcp/ -v` + +7. **Cross-references table** (~10 lines): + | Reference | Covers | Read when | + | discovery-guide.md | Host research, questionnaire, Host Spec YAML | Step 1 (always) | + | adapter-contract.md | BaseAdapter interface, field sets, registry wiring | Step 3 (always) | + | strategy-contract.md | MCPHostStrategy interface, families, platform paths | Step 3 (always) | + | testing-fixtures.md | Fixture schema, auto-generated tests, pytest commands | Step 5 (always) | + +No conceptual explanations. No "what is Pydantic." No architecture overview. Just the recipe. + +**Deliverables**: `__design__/skills/adding-mcp-hosts/SKILL.md` (~150-200 lines) +**Consistency Checks**: `python quick_validate.py __design__/skills/adding-mcp-hosts/` (expected: PASS) +**Commit**: `feat(skill): write SKILL.md with 5-step workflow` diff --git a/__roadmap__/adding-mcp-hosts-skill/write_strategy_contract.md b/__roadmap__/adding-mcp-hosts-skill/write_strategy_contract.md new file mode 100644 index 0000000..f435526 --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/write_strategy_contract.md @@ -0,0 +1,50 @@ +# Write Strategy Contract + +**Goal**: Document the strategy interface contract for implementing host file I/O. +**Pre-conditions**: +- [ ] Branch `task/write-strategy-contract` created from `milestone/adding-mcp-hosts-skill` +**Success Gates**: +- `__design__/skills/adding-mcp-hosts/references/strategy-contract.md` exists +- Documents all 5 abstract methods, decorator pattern, and all 3 strategy families +**References**: Codebase `hatch/mcp_host_config/strategies.py` — all existing strategy implementations + +--- + +## Step 1: Write strategy-contract.md + +**Goal**: Create the reference file documenting everything an agent needs to implement a host strategy. + +**Implementation Logic**: +Create `__design__/skills/adding-mcp-hosts/references/strategy-contract.md` (`mkdir -p` the path first). Derive from codebase inspection of `hatch/mcp_host_config/strategies.py`. Read the file to extract exact patterns for all existing strategies. + +Structure: + +1. **MCPHostStrategy interface** — Abstract methods to implement: + - `get_config_path() → Path` — platform-specific config file location (`sys.platform` dispatch) + - `get_config_key() → str` — root key for MCP servers in config (e.g., `"mcpServers"`, `"servers"`) + - `read_configuration() → dict` — read and parse the config file + - `write_configuration(config: dict)` — write config, preserving non-MCP sections if applicable + - `is_host_available() → bool` — detect whether host is installed on the system + Include lean method signature template. + +2. **@register_host_strategy decorator** — Usage: `@register_host_strategy(MCPHostType.YOUR_HOST)`. Explain that this auto-registers the strategy so `HostConfigurationManager` can discover it by host type. File: `hatch/mcp_host_config/strategies.py`. + +3. **Strategy families** — Decision tree for base class selection: + - `ClaudeHostStrategy` → if host shares Claude's JSON format with settings preservation. Members: `ClaudeDesktopStrategy`, `ClaudeCodeStrategy`. Provides `_preserve_claude_settings()`. + - `CursorBasedHostStrategy` → if host shares Cursor's simple JSON format (flat JSON, `mcpServers` key). Members: `CursorHostStrategy`, `LMStudioHostStrategy`. + - `MCPHostStrategy` (standalone) → if host has unique I/O needs. Members: `VSCodeHostStrategy`, `GeminiHostStrategy`, `KiroHostStrategy`, `CodexHostStrategy`. + +4. **Platform path patterns** — Common patterns from existing strategies: + - Simple home-relative: `Path.home() / ".host-dir" / "config.json"` (Cursor, LM Studio, Gemini, Kiro, Codex) + - macOS Application Support: `Path.home() / "Library" / "Application Support" / "AppName" / "config.json"` (Claude Desktop, VSCode) + - XDG on Linux: `Path.home() / ".config" / "host-dir" / "config.json"` (VSCode) + +5. **Config preservation** — Read-before-write pattern for files with non-MCP sections: + - Codex: preserves `[features]` and other TOML sections + - Gemini: preserves other keys in `settings.json` + - Claude Desktop: preserves non-mcpServers keys + Describe the merge pattern: read existing → update MCP section → write back. + +**Deliverables**: `__design__/skills/adding-mcp-hosts/references/strategy-contract.md` (~80-120 lines) +**Consistency Checks**: File documents all 5 abstract methods, decorator, 3 families with members, path patterns, preservation pattern +**Commit**: `feat(skill): add strategy contract reference` diff --git a/__roadmap__/adding-mcp-hosts-skill/write_testing_fixtures.md b/__roadmap__/adding-mcp-hosts-skill/write_testing_fixtures.md new file mode 100644 index 0000000..feac990 --- /dev/null +++ b/__roadmap__/adding-mcp-hosts-skill/write_testing_fixtures.md @@ -0,0 +1,54 @@ +# Write Testing Fixtures + +**Goal**: Document the test fixture registration process and verification commands. +**Pre-conditions**: +- [ ] Branch `task/write-testing-fixtures` created from `milestone/adding-mcp-hosts-skill` +**Success Gates**: +- `__design__/skills/adding-mcp-hosts/references/testing-fixtures.md` exists +- Documents fixture schema, registry entries, auto-generation counts, and verification commands +**References**: Codebase `tests/test_data/mcp_adapters/` — canonical_configs.json, host_registry.py, assertions.py + +--- + +## Step 1: Write testing-fixtures.md + +**Goal**: Create the reference file documenting how to register test fixtures for a new host. + +**Implementation Logic**: +Create `__design__/skills/adding-mcp-hosts/references/testing-fixtures.md` (`mkdir -p` the path first). Derive from inspection of `tests/test_data/mcp_adapters/`. Read the following files: +- `tests/test_data/mcp_adapters/canonical_configs.json` — fixture structure and existing entries +- `tests/test_data/mcp_adapters/host_registry.py` — `HostSpec`, `HostRegistry`, `FIELD_SETS`, generator functions +- `tests/test_data/mcp_adapters/assertions.py` — property-based assertion library +- `tests/integration/mcp/test_host_configuration.py` — how canonical configs drive parametrized tests +- `tests/integration/mcp/test_cross_host_sync.py` — how sync test matrix auto-expands + +Structure: + +1. **canonical_configs.json entry** — Schema for the fixture. Each host entry: + - Uses host-native field names (post-mapping if host has FIELD_MAPPINGS) + - Sets `null` for unsupported fields + - Must include at least one transport (command/url/httpUrl) + Show a minimal example entry derived from an existing host. + +2. **host_registry.py entries** — Three additions: + - `FIELD_SETS` dict — maps host name string → `fields.py` field set constant (e.g., `"your-host": YOUR_HOST_FIELDS`) + - `adapter_map` in `HostSpec.get_adapter()` — maps host name → adapter instance (e.g., `"your-host": YourHostAdapter()`) + - Reverse mappings (conditional) — only for hosts with `FIELD_MAPPINGS`. Maps host-native names back to canonical names for test verification. + +3. **What auto-generates** — Adding fixture data produces ~20+ test cases without writing any test code: + - 1 host configuration test (serialization roundtrip per host) + - 16 new cross-host sync tests (8 from-host + 8 to-host pair combinations) + - Validation property tests (transport mutual exclusion, tool list coexistence if applicable) + - Field filtering regression tests (one per unsupported field) + +4. **Verification commands** — Exact pytest invocations to run after registration: + - Full suite: `python -m pytest tests/integration/mcp/ tests/unit/mcp/ tests/regression/mcp/ -v` + - Quick smoke: `python -m pytest tests/integration/mcp/test_host_configuration.py -v` + - Protocol compliance: `python -m pytest tests/unit/mcp/test_adapter_protocol.py -v` + - Cross-host sync: `python -m pytest tests/integration/mcp/test_cross_host_sync.py -v` + +5. **Expected results** — New host name appears in parametrized test IDs (e.g., `test_configure_host[your-host]`). All tests pass. No regressions in existing host tests. + +**Deliverables**: `__design__/skills/adding-mcp-hosts/references/testing-fixtures.md` (~80-120 lines) +**Consistency Checks**: File documents fixture schema, all 3 registry entries, auto-gen counts, 4 pytest commands +**Commit**: `feat(skill): add testing fixtures reference` From cf9b807bcb2fb87219cc4cef5ba69d5fe2e2815e Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 17:05:51 +0900 Subject: [PATCH 12/71] feat(skill): add strategy contract reference Co-Authored-By: Claude Opus 4.6 --- .../references/strategy-contract.md | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 __design__/skills/adding-mcp-hosts/references/strategy-contract.md diff --git a/__design__/skills/adding-mcp-hosts/references/strategy-contract.md b/__design__/skills/adding-mcp-hosts/references/strategy-contract.md new file mode 100644 index 0000000..c932e9b --- /dev/null +++ b/__design__/skills/adding-mcp-hosts/references/strategy-contract.md @@ -0,0 +1,226 @@ +# Strategy Contract Reference + +## 1. MCPHostStrategy Interface + +Implement all methods from the abstract base class in `hatch/mcp_host_config/host_management.py`: + +```python +class MCPHostStrategy: + def get_config_path(self) -> Optional[Path]: + """Get configuration file path for this host.""" + raise NotImplementedError + + def get_config_key(self) -> str: + """Get the root configuration key for MCP servers.""" + return "mcpServers" # Default for most platforms + + def read_configuration(self) -> HostConfiguration: + """Read and parse host configuration.""" + raise NotImplementedError + + def write_configuration(self, config: HostConfiguration, no_backup: bool = False) -> bool: + """Write configuration to host file.""" + raise NotImplementedError + + def is_host_available(self) -> bool: + """Check if host is available on system.""" + raise NotImplementedError + + def validate_server_config(self, server_config: MCPServerConfig) -> bool: + """Validate server configuration for this host.""" + raise NotImplementedError +``` + +Use `platform.system()` inside `get_config_path()` to dispatch per OS (`"Darwin"`, `"Windows"`, `"Linux"`). Return `None` for unsupported platforms. + +Override `get_config_key()` only when the host uses a non-default root key (e.g., `"servers"` for VS Code, `"mcp_servers"` for Codex). + +Every strategy must also define `get_adapter_host_name() -> str` to return the adapter identifier used by `get_adapter()` for serialization. + +## 2. @register_host_strategy Decorator + +Register each concrete strategy with the host registry in `strategies.py`: + +```python +@register_host_strategy(MCPHostType.YOUR_HOST) +class YourHostStrategy(MCPHostStrategy): + ... +``` + +The decorator calls `MCPHostRegistry.register(host_type)`, which maps the `MCPHostType` enum value to the strategy class. `MCPHostConfigurationManager` discovers strategies through this registry at runtime -- no manual wiring required. + +Add the new host to the `MCPHostType` enum in `hatch/mcp_host_config/models.py` before using it: + +```python +class MCPHostType(str, Enum): + YOUR_HOST = "your-host" +``` + +## 3. Strategy Families + +Choose a base class based on how the host's config file behaves: + +### ClaudeHostStrategy + +Inherit when the host shares Claude's JSON format with settings preservation. + +**Members:** `ClaudeDesktopStrategy`, `ClaudeCodeStrategy`. + +**Provides for free:** +- `get_config_key()` returns `"mcpServers"` +- `validate_server_config()` accepting command or URL transports +- `_preserve_claude_settings()` -- copies all non-MCP keys from existing config before writing +- `read_configuration()` and `write_configuration()` with JSON I/O and atomic writes + +**Choose when:** the host stores MCP servers under `"mcpServers"` in a JSON file that also contains non-MCP settings (theme, auto_update, etc.) that must survive writes. + +### CursorBasedHostStrategy + +Inherit when the host shares Cursor's simple JSON-only format. + +**Members:** `CursorHostStrategy`, `LMStudioHostStrategy`. + +**Provides for free:** +- `get_config_key()` returns `"mcpServers"` +- `validate_server_config()` accepting command or URL transports +- `read_configuration()` and `write_configuration()` with JSON I/O, atomic writes, and existing-config preservation + +**Choose when:** the host uses a dedicated `mcp.json` file (or similar) where the entire file is MCP config in simple JSON format, keyed by `"mcpServers"`. + +### MCPHostStrategy (standalone) + +Inherit directly when the host has unique I/O needs that neither family covers. + +**Members:** `VSCodeHostStrategy`, `GeminiHostStrategy`, `KiroHostStrategy`, `CodexHostStrategy`. + +**Provides for free:** only the default `get_config_key()` returning `"mcpServers"`. + +**Choose when:** +- The config key differs (VS Code uses `"servers"`, Codex uses `"mcp_servers"`) +- The file format is not JSON (Codex uses TOML) +- The host needs custom atomic write logic (Kiro uses `AtomicFileOperations`) +- The host needs write verification (Gemini reads back JSON after writing) + +## 4. Platform Path Patterns + +### Simple home-relative + +Flat dotfile directory under `$HOME`. No platform dispatch needed. + +```python +# CursorHostStrategy +def get_config_path(self) -> Optional[Path]: + return Path.home() / ".cursor" / "mcp.json" + +# LMStudioHostStrategy +def get_config_path(self) -> Optional[Path]: + return Path.home() / ".lmstudio" / "mcp.json" + +# GeminiHostStrategy +def get_config_path(self) -> Optional[Path]: + return Path.home() / ".gemini" / "settings.json" + +# CodexHostStrategy +def get_config_path(self) -> Optional[Path]: + return Path.home() / ".codex" / "config.toml" + +# KiroHostStrategy +def get_config_path(self) -> Optional[Path]: + return Path.home() / ".kiro" / "settings" / "mcp.json" + +# ClaudeCodeStrategy +def get_config_path(self) -> Optional[Path]: + return Path.home() / ".claude.json" +``` + +### macOS Application Support + cross-platform dispatch + +Use `platform.system()` to select OS-appropriate paths. + +```python +# ClaudeDesktopStrategy +def get_config_path(self) -> Optional[Path]: + system = platform.system() + if system == "Darwin": + return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json" + elif system == "Windows": + return Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json" + elif system == "Linux": + return Path.home() / ".config" / "Claude" / "claude_desktop_config.json" + return None + +# VSCodeHostStrategy +def get_config_path(self) -> Optional[Path]: + system = platform.system() + if system == "Windows": + return Path.home() / "AppData" / "Roaming" / "Code" / "User" / "mcp.json" + elif system == "Darwin": + return Path.home() / "Library" / "Application Support" / "Code" / "User" / "mcp.json" + elif system == "Linux": + return Path.home() / ".config" / "Code" / "User" / "mcp.json" + return None +``` + +## 5. Config Preservation + +Every `write_configuration()` must follow the read-before-write pattern when the config file contains non-MCP sections. The merge flow: + +1. Read the existing file into a dict +2. Update only the MCP servers section (keyed by `get_config_key()`) +3. Write the full dict back atomically (write to `.tmp`, then `replace()`) + +### Claude family -- _preserve_claude_settings + +Preserves keys like theme and auto_update alongside `mcpServers`: + +```python +# ClaudeHostStrategy._preserve_claude_settings +def _preserve_claude_settings(self, existing_config: Dict, new_servers: Dict) -> Dict: + preserved_config = existing_config.copy() + preserved_config[self.get_config_key()] = new_servers + return preserved_config +``` + +### Gemini -- preserve other JSON keys + +Reads existing config, sets `mcpServers`, writes back. Adds a verification step: + +```python +# GeminiHostStrategy.write_configuration (excerpt) +existing_config = {} +if config_path.exists(): + with open(config_path, "r") as f: + existing_config = json.load(f) + +existing_config[self.get_config_key()] = servers_dict + +# Write then verify +with open(temp_path, "w") as f: + json.dump(existing_config, f, indent=2, ensure_ascii=False) +with open(temp_path, "r") as f: + json.load(f) # Verify valid JSON +temp_path.replace(config_path) +``` + +### Codex -- TOML with [features] preservation + +Reads existing TOML, preserves the `[features]` section and all other top-level keys: + +```python +# CodexHostStrategy.write_configuration (excerpt) +existing_data = {} +if config_path.exists(): + with open(config_path, "rb") as f: + existing_data = tomllib.load(f) + +if "features" in existing_data: + self._preserved_features = existing_data["features"] + +final_data = {} +if self._preserved_features: + final_data["features"] = self._preserved_features +final_data[self.get_config_key()] = servers_data +for key, value in existing_data.items(): + if key not in ("features", self.get_config_key()): + final_data[key] = value +``` From 070894c9cb67dad83a7da05175dddfcd0f52a3c9 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 17:06:22 +0900 Subject: [PATCH 13/71] feat(skill): add testing fixtures reference Co-Authored-By: Claude Opus 4.6 --- .../references/testing-fixtures.md | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 __design__/skills/adding-mcp-hosts/references/testing-fixtures.md diff --git a/__design__/skills/adding-mcp-hosts/references/testing-fixtures.md b/__design__/skills/adding-mcp-hosts/references/testing-fixtures.md new file mode 100644 index 0000000..b83e5a3 --- /dev/null +++ b/__design__/skills/adding-mcp-hosts/references/testing-fixtures.md @@ -0,0 +1,121 @@ +# Testing Fixtures Reference + +Register test fixtures for a new MCP host so that all data-driven tests auto-generate. + +## 1. canonical_configs.json entry + +Add one entry to `tests/test_data/mcp_adapters/canonical_configs.json`. + +Schema rules: +- Key is the host name string (e.g., `"newhost"`). +- Use host-native field names (post-mapping). If the host has `FIELD_MAPPINGS` that rename `args` to `arguments`, write `"arguments"` in the fixture. +- Set `null` for unsupported transport fields so the fixture documents their absence. +- Include at least one transport field (`command`, `url`, or `httpUrl`) with a non-null value. + +Minimal example (modeled on the `lmstudio` entry, which uses `CLAUDE_FIELDS`): + +```json +"newhost": { + "command": "python", + "args": ["-m", "mcp_server"], + "env": {"API_KEY": "test_key"}, + "url": null, + "headers": null, + "type": "stdio" +} +``` + +For hosts with extra fields, add them alongside the universals (see `gemini` or `codex` entries for examples with `httpUrl`, `timeout`, `includeTools`, `cwd`, etc.). + +## 2. host_registry.py entries + +Make three additions in `tests/test_data/mcp_adapters/host_registry.py`. + +**A. `FIELD_SETS` dict** -- map host name string to the `fields.py` constant: + +```python +FIELD_SETS: Dict[str, FrozenSet[str]] = { + # ... existing entries ... + "newhost": NEWHOST_FIELDS, +} +``` + +Import `NEWHOST_FIELDS` from `hatch.mcp_host_config.fields` at the top of the file. + +**B. `adapter_map` in `HostSpec.get_adapter()`** -- map host name to adapter factory: + +```python +adapter_map = { + # ... existing entries ... + "newhost": NewHostAdapter, +} +``` + +Import `NewHostAdapter` from `hatch.mcp_host_config.adapters.newhost` at the top of the file. + +**C. Reverse mappings (conditional)** -- only required if the host defines `FIELD_MAPPINGS` in `fields.py`. Add a reverse dict and wire it into `HostSpec.load_config()`. Follow the Codex pattern: + +```python +# At module level +NEWHOST_REVERSE_MAPPINGS: Dict[str, str] = {v: k for k, v in NEWHOST_FIELD_MAPPINGS.items()} + +# In HostRegistry.__init__, inside the loop +if host_name == "newhost": + mappings = dict(NEWHOST_FIELD_MAPPINGS) + +# In HostSpec.load_config(), extend the reverse lookup +universal_key = CODEX_REVERSE_MAPPINGS.get(key, key) +universal_key = NEWHOST_REVERSE_MAPPINGS.get(universal_key, universal_key) +``` + +Skip this step entirely if the new host uses standard field names with no mappings. + +## 3. What auto-generates + +Adding one host (going from 8 to 9 hosts) produces these new test cases without writing any test code: + +| Test file | Generator | Current (8 hosts) | New cases added | +|---|---|---|---| +| `test_host_configuration.py` | `ALL_HOSTS` parametrize | 8 | +1 (serialization roundtrip) | +| `test_cross_host_sync.py` | `generate_sync_test_cases` | 64 (8x8) | +17 (9x9 - 8x8 = 17 new pairs) | +| `test_validation_bugs.py` (transport) | `generate_validation_test_cases` | 8 | +1 (transport mutual exclusion) | +| `test_validation_bugs.py` (tool lists) | `generate_validation_test_cases` | 2 (gemini, codex) | +1 if host has tool lists, else +0 | +| `test_field_filtering_v2.py` | `generate_unsupported_field_test_cases` | 211 | +N (one per unsupported field for the new host) | + +**Minimum new test cases**: 1 + 17 + 1 + 0 + N = **19 + N** (where N = total_possible_fields - host_supported_fields). With the current 36-field union, a host supporting 6 fields adds 30 filtering tests, totaling **49** new test cases. + +## 4. Verification commands + +Run from the repository root. + +Full MCP test suite: +``` +python -m pytest tests/integration/mcp/ tests/unit/mcp/ tests/regression/mcp/ -v +``` + +Quick smoke (host configuration roundtrip only): +``` +python -m pytest tests/integration/mcp/test_host_configuration.py -v +``` + +Protocol compliance (adapter contract checks): +``` +python -m pytest tests/unit/mcp/test_adapter_protocol.py -v +``` + +Cross-host sync (all pair combinations): +``` +python -m pytest tests/integration/mcp/test_cross_host_sync.py -v +``` + +Field filtering regression: +``` +python -m pytest tests/regression/mcp/test_field_filtering_v2.py -v +``` + +## 5. Expected results + +- The new host name appears in parametrized test IDs (e.g., `test_configure_host[newhost]`, `sync_claude-desktop_to_newhost`, `newhost_filters_envFile`). +- All tests pass. Zero failures, zero errors. +- Existing test IDs remain unchanged. No regressions in prior host tests. +- Total test count increases by the amounts in section 3. From 336fcedfdc1a24c31c9505437add0e7c490a2afe Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 17:06:43 +0900 Subject: [PATCH 14/71] feat(skill): add adapter contract reference Co-Authored-By: Claude Opus 4.6 --- .../references/adapter-contract.md | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 __design__/skills/adding-mcp-hosts/references/adapter-contract.md diff --git a/__design__/skills/adding-mcp-hosts/references/adapter-contract.md b/__design__/skills/adding-mcp-hosts/references/adapter-contract.md new file mode 100644 index 0000000..6100db9 --- /dev/null +++ b/__design__/skills/adding-mcp-hosts/references/adapter-contract.md @@ -0,0 +1,157 @@ +# Adapter Contract Reference + +Interface contract for implementing a new MCP host adapter in the Hatch CLI. + +## 1. MCPHostType Enum + +File: `hatch/mcp_host_config/models.py`. Convention: `UPPER_SNAKE = "kebab-case"`. + +```python +class MCPHostType(str, Enum): + # ... existing members ... + NEW_HOST = "new-host" +``` + +The enum value string is the canonical host identifier used everywhere. + +## 2. Field Set Declaration + +File: `hatch/mcp_host_config/fields.py`. Define a `_FIELDS` frozenset. + +```python +# Without 'type' support — build from UNIVERSAL_FIELDS +NEW_HOST_FIELDS: FrozenSet[str] = UNIVERSAL_FIELDS | frozenset({"host_specific_field"}) + +# With 'type' support — build from CLAUDE_FIELDS (which is UNIVERSAL_FIELDS | {"type"}) +NEW_HOST_FIELDS: FrozenSet[str] = CLAUDE_FIELDS | frozenset({"host_specific_field"}) +``` + +If the host supports the `type` discriminator, also add its kebab-case name to `TYPE_SUPPORTING_HOSTS`. Hosts without `type` support (Gemini, Kiro, Codex) omit this. + +## 3. MCPServerConfig Fields + +File: `hatch/mcp_host_config/models.py`. Add new fields to `MCPServerConfig` only when the host introduces fields not already in the model. Every field: `Optional`, default `None`. + +```python +disabled: Optional[bool] = Field(None, description="Whether server is disabled") +``` + +If the host reuses existing fields only (e.g., LMStudio reuses `CLAUDE_FIELDS`), skip this step. The model uses `extra="allow"` but explicit declarations are preferred. + +## 4. Adapter Class + +File: `hatch/mcp_host_config/adapters/.py`. Extend `BaseAdapter`. + +```python +from typing import Any, Dict, FrozenSet +from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter +from hatch.mcp_host_config.fields import NEW_HOST_FIELDS +from hatch.mcp_host_config.models import MCPServerConfig + +class NewHostAdapter(BaseAdapter): + + @property + def host_name(self) -> str: + return "new-host" + + def get_supported_fields(self) -> FrozenSet[str]: + return NEW_HOST_FIELDS + + def validate(self, config: MCPServerConfig) -> None: + pass # DEPRECATED — kept for ABC compliance until v0.9.0 + + def validate_filtered(self, filtered: Dict[str, Any]) -> None: + has_command = "command" in filtered + has_url = "url" in filtered + if not has_command and not has_url: + raise AdapterValidationError( + "Either 'command' (local) or 'url' (remote) must be specified", + host_name=self.host_name, + ) + if has_command and has_url: + raise AdapterValidationError( + "Cannot specify both 'command' and 'url' - choose one transport", + host_name=self.host_name, + ) + + def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: + filtered = self.filter_fields(config) + self.validate_filtered(filtered) + return filtered # add apply_transformations() call if field mappings exist +``` + +**validate_filtered() rules:** Transport mutual exclusion (`command` XOR `url` for most hosts; Gemini enforces exactly-one-of-three including `httpUrl`). If host supports `type`, verify consistency (`type='stdio'` requires `command`, etc.). + +**serialize() pipeline:** Always `filter_fields` -> `validate_filtered` -> optionally `apply_transformations` -> return. + +## 5. Field Mappings + +File: `hatch/mcp_host_config/fields.py`. Define only when the host uses different field names. Pattern: `{"universal_name": "host_name"}`. Canonical example: + +```python +CODEX_FIELD_MAPPINGS: dict[str, str] = { + "args": "arguments", + "headers": "http_headers", + "includeTools": "enabled_tools", + "excludeTools": "disabled_tools", +} +``` + +Reference in `apply_transformations()`: + +```python +def apply_transformations(self, filtered: Dict[str, Any]) -> Dict[str, Any]: + result = filtered.copy() + for universal_name, host_name in NEW_HOST_FIELD_MAPPINGS.items(): + if universal_name in result: + result[host_name] = result.pop(universal_name) + return result +``` + +Skip entirely if the host uses standard field names (most do). + +## 6. Variant Pattern + +Reuse one adapter class with a `variant` parameter when two host identifiers share identical fields and validation. Canonical example: + +```python +class ClaudeAdapter(BaseAdapter): + def __init__(self, variant: str = "desktop"): + if variant not in ("desktop", "code"): + raise ValueError(f"Invalid Claude variant: {variant}") + self._variant = variant + + @property + def host_name(self) -> str: + return f"claude-{self._variant}" +``` + +Use when field set, validation, and serialization are identical. If any diverge, create a separate class. + +## 7. Wiring and Integration Points + +Four files require one-liner additions for every new host. + +**`hatch/mcp_host_config/adapters/__init__.py`** -- Add import and `__all__` entry: +```python +from hatch.mcp_host_config.adapters.new_host import NewHostAdapter +# add "NewHostAdapter" to __all__ +``` + +**`hatch/mcp_host_config/adapters/registry.py`** -- Add to `_register_defaults()`: +```python +self.register(NewHostAdapter()) # import at top of file +``` + +**`hatch/mcp_host_config/backup.py`** -- Add hostname string to `supported_hosts` set in `BackupInfo.validate_hostname()`: +```python +supported_hosts = { + # ... existing hosts ... + "new-host", +} +``` + +**`hatch/mcp_host_config/reporting.py`** -- Add entry to mapping dict in `_get_adapter_host_name()`: +```python +MCPHostType.NEW_HOST: "new-host", +``` From 8061c5f422194547572642840d50a209e9d3745e Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 17:07:08 +0900 Subject: [PATCH 15/71] feat(skill): add discovery guide reference Co-Authored-By: Claude Opus 4.6 --- .../references/discovery-guide.md | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 __design__/skills/adding-mcp-hosts/references/discovery-guide.md diff --git a/__design__/skills/adding-mcp-hosts/references/discovery-guide.md b/__design__/skills/adding-mcp-hosts/references/discovery-guide.md new file mode 100644 index 0000000..1486a56 --- /dev/null +++ b/__design__/skills/adding-mcp-hosts/references/discovery-guide.md @@ -0,0 +1,218 @@ +# Discovery Guide: Host Requirement Research + +Reference for the discovery step when adding a new MCP host to Hatch. +Produces a Host Spec YAML artifact consumed by all subsequent steps. + +--- + +## 1. Tool Priority Ladder + +Research the target host in order. Fall through when the current level yields no definitive answer. + +### Level 1: Web Search + Fetch + +| Action | Detail | +|--------|--------| +| Search query | `" MCP server configuration" site:github.com OR site:docs.*` | +| Fetch targets | Official docs page, README, or config schema file | +| Success | Config path, format, root key, and supported fields are all documented | +| Fall through | Docs missing, incomplete, or ambiguous on field support | + +### Level 2: Context7 Library Docs + +| Action | Detail | +|--------|--------| +| Resolve library | `resolve-library-id` with the host package name | +| Query | `"MCP server configuration format and supported fields"` | +| Success | Field names, types, and validation rules documented | +| Fall through | Host not indexed, or MCP config undocumented | + +### Level 3: Codebase Retrieval + +| Action | Detail | +|--------|--------| +| Query | `"configuration strategy for "` against project root | +| Inspect | `hatch/mcp_host_config/strategies.py`, `fields.py`, `models.py` | +| Success | Existing strategy, adapter, or field set provides needed data | +| Fall through | Host is entirely new with no existing references | + +### Level 4: User Escalation + +| Action | Detail | +|--------|--------| +| Start with | Tier 1 (blocking questions) | +| Expand to | Tier 2 if Tier 1 reveals non-standard behavior | +| Ask Tier 3 | Only for remaining ambiguities after Tiers 1-2 | +| Success | All blocking questions answered; remaining gaps have safe defaults | + +--- + +## 2. Structured Questionnaire + +17 questions across 4 categories. + +### Category A: Host Identity & Config Location + +| ID | Question | Why It Matters | Files Affected | +|----|----------|----------------|----------------| +| A1 | What is the host's canonical name? (lowercase, hyphens, e.g. `"kiro"`) | Becomes `MCPHostType` enum value and adapter `host_name`. | `models.py`, all files | +| A2 | Where is the config file on each platform? (macOS, Linux, Windows paths) | Strategy `get_config_path()` requires platform-specific path logic. | `strategies.py` | +| A3 | What is the config file format? (JSON or TOML) | Determines strategy read/write implementation and which strategy family to inherit. | `strategies.py` | +| A4 | What is the root key for MCP servers in the config file? | Strategy `get_config_key()`. Known values: `mcpServers`, `servers`, `mcp_servers`. | `strategies.py` | +| A5 | How to detect if the host is installed on the system? | Strategy `is_host_available()`. Most hosts check for a directory's existence. | `strategies.py` | + +### Category B: Field Support + +| ID | Question | Why It Matters | Files Affected | +|----|----------|----------------|----------------| +| B1 | Which transport types does the host support? (stdio, sse, http) | Drives validation rules in `validate_filtered()`. | adapter | +| B2 | Does the host support the `type` discriminator field? (`"type": "stdio"` / `"sse"`) | Determines membership in `TYPE_SUPPORTING_HOSTS` in `fields.py`. | `fields.py` | +| B3 | What host-specific fields exist beyond the universal set? (name, type, description, required/optional for each) | Defines the field set constant in `fields.py` and new `MCPServerConfig` field declarations. | `fields.py`, `models.py` | +| B4 | Does the host use different names for standard fields? (e.g. Codex: `arguments` instead of `args`) | Determines whether a `FIELD_MAPPINGS` dict and `apply_transformations()` override are needed. | `fields.py`, adapter | +| B5 | Are there fields semantically equivalent to another host's fields? (e.g. Gemini `includeTools` = Codex `enabled_tools`) | Cross-host sync field mappings. Without mappings, sync silently drops the field. | `fields.py`, adapter | + +### Category C: Validation & Serialization + +| ID | Question | Why It Matters | Files Affected | +|----|----------|----------------|----------------| +| C1 | Can the host have multiple transports simultaneously, or exactly one? | Core validation in `validate_filtered()`. Most hosts require exactly one. | adapter | +| C2 | Are any fields mutually exclusive? (beyond transports) | Additional validation rules. | adapter | +| C3 | Are any fields conditionally required? (e.g. `oauth_enabled=true` requires `oauth_clientId`) | Additional validation rules. | adapter | +| C4 | Does serialization require structural transformation beyond field renaming? | Whether a custom `serialize()` override is needed. | adapter | +| C5 | Does the config file contain non-MCP sections that must be preserved on write? | Strategy `write_configuration()` must read-before-write and merge. | `strategies.py` | + +### Category D: Architectural Fit + +| ID | Question | Why It Matters | Files Affected | +|----|----------|----------------|----------------| +| D1 | Is this host functionally identical to an existing host? (same fields, same validation, different name only) | Variant pattern: reuse an existing adapter with a `variant` parameter instead of a new class. | adapter, `registry.py` | +| D2 | Does this host share config format and I/O logic with an existing host? | Strategy family: inherit from `ClaudeHostStrategy` or `CursorBasedHostStrategy` instead of bare `MCPHostStrategy`. | `strategies.py` | + +--- + +## 3. Escalation Tiers + +Present questions progressively. Do not ask Tier 2 or 3 unless triggered. + +### Tier 1: Blocking -- cannot proceed without answers (A1, A2, A3, A4, B1, B3) + +| ID | Summary | +|----|---------| +| A1 | Host canonical name | +| A2 | Config file path per platform | +| A3 | Config file format (JSON/TOML) | +| A4 | Root key for MCP servers | +| B1 | Supported transport types | +| B3 | Host-specific fields beyond universal set | + +### Tier 2: Complexity-triggered -- ask if Tier 1 reveals non-standard behavior + +| ID | Trigger condition | +|----|-------------------| +| B4 | Host uses different names for standard fields | +| B5 | Host has tool filtering fields that map to another host's equivalents | +| C1 | Unclear whether transports are mutually exclusive | +| C4 | Config format requires structural nesting beyond flat key-value | +| C5 | Config file has non-MCP sections | + +### Tier 3: Ambiguity-only -- ask only if reading existing adapters leaves it unclear + +| ID | Trigger condition | +|----|-------------------| +| A5 | Host detection mechanism is non-obvious | +| B2 | Unclear whether host uses `type` discriminator | +| C2 | Possible field mutual exclusion beyond transports | +| C3 | Possible conditional field requirements | +| D1 | Host looks identical to an existing one | +| D2 | Host I/O looks similar to an existing strategy family | + +--- + +## 4. Existing Host Reference Table + +| Host | Format | Root Key | macOS Path | Detection | +|------|--------|----------|------------|-----------| +| `claude-desktop` | JSON | `mcpServers` | `~/Library/Application Support/Claude/claude_desktop_config.json` | Config parent dir exists | +| `claude-code` | JSON | `mcpServers` | `~/.claude.json` | File exists | +| `vscode` | JSON | `servers` | `~/Library/Application Support/Code/User/mcp.json` | Code User dir exists | +| `cursor` | JSON | `mcpServers` | `~/.cursor/mcp.json` | `.cursor/` exists | +| `lmstudio` | JSON | `mcpServers` | `~/.lmstudio/mcp.json` | `.lmstudio/` exists | +| `gemini` | JSON | `mcpServers` | `~/.gemini/settings.json` | `.gemini/` exists | +| `kiro` | JSON | `mcpServers` | `~/.kiro/settings/mcp.json` | `.kiro/settings/` exists | +| `codex` | TOML | `mcp_servers` | `~/.codex/config.toml` | `.codex/` exists | + +### Strategy Families + +| Family Base Class | Members | Provides | +|-------------------|---------|----------| +| `ClaudeHostStrategy` | `ClaudeDesktopStrategy`, `ClaudeCodeStrategy` | Shared JSON read/write, `_preserve_claude_settings()` | +| `CursorBasedHostStrategy` | `CursorHostStrategy`, `LMStudioHostStrategy` | Shared Cursor-format JSON read/write | +| `MCPHostStrategy` (standalone) | `VSCodeHostStrategy`, `GeminiHostStrategy`, `KiroHostStrategy`, `CodexHostStrategy` | No shared logic -- each owns its I/O | + +### Type Discriminator Support + +Hosts in `TYPE_SUPPORTING_HOSTS`: `claude-desktop`, `claude-code`, `vscode`, `cursor`. + +All other hosts (`lmstudio`, `gemini`, `kiro`, `codex`) do NOT emit the `type` field. + +--- + +## 5. Host Spec YAML Output Format + +Fill every field; use `null` or `[]` for inapplicable values. + +```yaml +host: + name: "" # A1 + config_format: "json" # A3 — "json" or "toml" + config_key: "mcpServers" # A4 + +paths: # A2 + darwin: "~/path/to/config.json" + linux: "~/path/to/config.json" + windows: "~/path/to/config.json" + +detection: # A5 + method: "directory_exists" # "directory_exists" | "file_exists" + path: "~/./" + +transports: # B1 + supported: ["stdio", "sse"] + mutual_exclusion: true # C1 + +fields: # B2, B3 + type_discriminator: true # B2 — join TYPE_SUPPORTING_HOSTS? + host_specific: # B3 — list each non-universal field + - name: "field_name" + type: "Optional[str]" + description: "What this field does" + - name: "another_field" + type: "Optional[bool]" + description: "What this field does" + +field_mappings: # B4, B5 + args: "arguments" # universal name -> host name (B4) + includeTools: "enabled_tools" # cross-host equivalent (B5) + +validation: # C2, C3 + mutual_exclusions: [] # field pairs that cannot coexist + conditional_requirements: [] # {if: "field=value", then: "required_field"} + +serialization: # C4 + structural_transform: false # true if custom serialize() needed + +config_file: # C5 + preserved_sections: [] # non-MCP keys to preserve on write + +architecture: # D1, D2 + variant_of: null # existing adapter to reuse, or null + strategy_family: null # base class to inherit, or null +``` + +Validate the completed spec against these rules before proceeding: +- `host.name` matches lowercase-with-hyphens pattern +- `paths` has at least `darwin` or `linux` defined +- `transports.supported` is non-empty +- If `field_mappings` is non-empty, verify each source field exists in another host's field set +- If `architecture.variant_of` is set, confirm the named adapter exists in the registry +- If `architecture.strategy_family` is set, confirm the named base class exists in `strategies.py` From 8984a3ada9a65cd35ed6604a01fea749eafb764c Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 17:07:39 +0900 Subject: [PATCH 16/71] feat(skill): write SKILL.md with 5-step workflow Co-Authored-By: Claude Opus 4.6 --- __design__/skills/adding-mcp-hosts/SKILL.md | 202 ++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 __design__/skills/adding-mcp-hosts/SKILL.md diff --git a/__design__/skills/adding-mcp-hosts/SKILL.md b/__design__/skills/adding-mcp-hosts/SKILL.md new file mode 100644 index 0000000..e9c4ec6 --- /dev/null +++ b/__design__/skills/adding-mcp-hosts/SKILL.md @@ -0,0 +1,202 @@ +--- +name: adding-mcp-hosts +description: | + Adds support for a new MCP host platform to the Hatch CLI multi-host + configuration system. Use when asked to add, integrate, or extend MCP host + support for a new IDE, editor, or AI coding tool (e.g., Windsurf, Zed, + Copilot). Follows a 5-step workflow: discover host requirements via web + research or user questionnaire, add enum and field set declarations, create + adapter and strategy implementations, wire integration points across 4 + registration files, and register test fixtures that auto-generate 20+ test + cases without writing test code. +--- + +## Workflow Checklist + +``` +- [ ] Step 1: Discover host requirements +- [ ] Step 2: Add enum and field set +- [ ] Step 3: Create adapter and strategy +- [ ] Step 4: Wire integration points +- [ ] Step 5: Register test fixtures +``` + +--- + +## Step 1: Discover Host Requirements + +Read [references/discovery-guide.md](references/discovery-guide.md) for the full discovery workflow. + +Use web search and fetch tools to find the target host's official MCP configuration docs. +Identify: config file path per platform, config format (JSON/JSONC/TOML), top-level key +wrapping server entries, every supported field name and type, and any field name differences +from the universal set (`command`, `args`, `env`, `url`, `headers`). + +If web tools are unavailable or return insufficient data, present the structured +questionnaire from the discovery guide to the user. + +Produce a Host Spec YAML block capturing: host slug, config paths (macOS/Linux/Windows), +config format, supported fields, field mappings, and strategy family. This output feeds +all subsequent steps. + +--- + +## Step 2: Add Enum and Field Set + +Add `MCPHostType` enum value in `hatch/mcp_host_config/models.py`: + +```python +class MCPHostType(str, Enum): + # ... existing members ... + YOUR_HOST = "your-host" # lowercase-hyphenated, matching Host Spec slug +``` + +Add field set constant in `hatch/mcp_host_config/fields.py`: + +```python +YOUR_HOST_FIELDS: FrozenSet[str] = UNIVERSAL_FIELDS | frozenset( + { + # host-specific fields from Host Spec + } +) +``` + +Include `"type"` (via `CLAUDE_FIELDS` base) only if the host uses a transport type +discriminator. If the host uses different field names for universal concepts, add a +mappings dict (see `CODEX_FIELD_MAPPINGS` pattern in `fields.py`). + +If the host introduces fields not in `MCPServerConfig`, add them as `Optional` fields +with `Field(None, description="...")` under a new section comment block in `models.py`. + +Verify: + +```bash +python -c "from hatch.mcp_host_config.models import MCPHostType; print(MCPHostType.YOUR_HOST)" +python -c "from hatch.mcp_host_config.fields import YOUR_HOST_FIELDS; print(YOUR_HOST_FIELDS)" +``` + +--- + +## Step 3: Create Adapter and Strategy + +Read [references/adapter-contract.md](references/adapter-contract.md) for the `BaseAdapter` +interface, the `validate_filtered()` pipeline, and field mapping details. + +Read [references/strategy-contract.md](references/strategy-contract.md) for the +`MCPHostStrategy` interface, `@register_host_strategy` decorator, platform path resolution, +and config serialization. + +### Adapter + +Create `hatch/mcp_host_config/adapters/your_host.py`. Implement `BaseAdapter` with: + +- `host_name` property returning the slug +- `get_supported_fields()` returning the field set from Step 2 +- `validate_filtered(filtered)` enforcing host-specific transport rules +- `serialize(config)` calling `filter_fields()` then `validate_filtered()` then returning + the dict (apply field mappings if needed) + +**Variant shortcut:** If the new host is functionally identical to an existing host, +register it as a variant instead of creating a new file. See +`ClaudeAdapter(variant=...)` in `hatch/mcp_host_config/adapters/claude.py`. + +### Strategy + +Add a strategy class in `hatch/mcp_host_config/strategies.py` decorated with +`@register_host_strategy(MCPHostType.YOUR_HOST)`. Decide the family: + +- `ClaudeHostStrategy` -- JSON format with `mcpServers` key +- `CursorBasedHostStrategy` -- `.cursor/mcp.json`-like layout +- `MCPHostStrategy` (direct) -- standalone hosts with unique formats + +Implement `get_config_path()`, `get_config_key()`, `validate_server_config()`, +`read_config()`, and `write_config()`. + +Verify: + +```bash +python -c "from hatch.mcp_host_config.adapters.your_host import YourHostAdapter; print(YourHostAdapter().host_name)" +``` + +--- + +## Step 4: Wire Integration Points + +Four files need one-liner additions. + +**`hatch/mcp_host_config/adapters/__init__.py`** -- Import and add to `__all__`: + +```python +from hatch.mcp_host_config.adapters.your_host import YourHostAdapter +# Append "YourHostAdapter" to __all__ +``` + +**`hatch/mcp_host_config/adapters/registry.py`** -- Import adapter, add +`self.register(YourHostAdapter())` inside `_register_defaults()`. + +**`hatch/mcp_host_config/backup.py`** -- Add `"your-host"` to the `supported_hosts` set +in `BackupInfo.validate_hostname()`. Also update the `supported_hosts` set in +`EnvironmentPackageEntry.validate_host_names()` in `models.py`. + +**`hatch/mcp_host_config/reporting.py`** -- Add `MCPHostType.YOUR_HOST: "your-host"` to +the `mapping` dict in `_get_adapter_host_name()`. + +Verify: + +```bash +python -c " +from hatch.mcp_host_config.adapters.registry import AdapterRegistry +r = AdapterRegistry() +print('your-host' in r.get_supported_hosts()) +" +``` + +--- + +## Step 5: Register Test Fixtures + +Read [references/testing-fixtures.md](references/testing-fixtures.md) for fixture schemas, +auto-generated test case details, and pytest commands. + +Add canonical config entry in `tests/test_data/mcp_adapters/canonical_configs.json`: + +```json +"your-host": { + "command": "python", + "args": ["-m", "mcp_server"], + "env": {"API_KEY": "test_key"}, + "url": null, + "headers": null +} +``` + +Include all host-specific fields with representative values. Use `null` for unused +transport fields. + +Add host registry entries in `tests/test_data/mcp_adapters/host_registry.py`: + +1. Import the new field set and adapter. +2. Add `FIELD_SETS` entry: `"your-host": YOUR_HOST_FIELDS`. +3. Add `adapter_map` entry in `HostSpec.get_adapter()`. +4. Add reverse mappings if the host has field name mappings. +5. Add the new field set to `all_possible_fields` in `generate_unsupported_field_test_cases()`. + +Verify: + +```bash +python -m pytest tests/integration/mcp/ tests/unit/mcp/ tests/regression/mcp/ -v +``` + +All existing tests must pass. The new host auto-generates test cases for cross-host sync +(N x N matrix), field filtering, transport validation, and property checks. + +--- + +## Cross-References + +| Reference | Covers | Read when | +|---|---|---| +| [references/discovery-guide.md](references/discovery-guide.md) | Host research, questionnaire, Host Spec YAML | Step 1 (always) | +| [references/adapter-contract.md](references/adapter-contract.md) | BaseAdapter interface, field sets, registry wiring | Step 3 (always) | +| [references/strategy-contract.md](references/strategy-contract.md) | MCPHostStrategy interface, families, platform paths | Step 3 (always) | +| [references/testing-fixtures.md](references/testing-fixtures.md) | Fixture schema, auto-generated tests, pytest commands | Step 5 (always) | From e5fbfa234cde68ea287c17128fc106d38992c1f0 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Feb 2026 17:26:37 +0900 Subject: [PATCH 17/71] chore(skill): package adding-mcp-hosts skill Co-Authored-By: Claude Opus 4.6 --- __design__/skills/dist/adding-mcp-hosts.skill | Bin 0 -> 14081 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 __design__/skills/dist/adding-mcp-hosts.skill diff --git a/__design__/skills/dist/adding-mcp-hosts.skill b/__design__/skills/dist/adding-mcp-hosts.skill new file mode 100644 index 0000000000000000000000000000000000000000..b32f5af2579439343e5da6d07a8f53497e5c2482 GIT binary patch literal 14081 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 Date: Tue, 24 Feb 2026 17:27:23 +0900 Subject: [PATCH 18/71] docs(roadmap): mark adding-mcp-hosts-skill campaign as done Co-Authored-By: Claude Opus 4.6 --- __roadmap__/adding-mcp-hosts-skill/README.md | 30 +++++++++++-------- .../adding-mcp-hosts-skill/package/README.md | 7 +++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/__roadmap__/adding-mcp-hosts-skill/README.md b/__roadmap__/adding-mcp-hosts-skill/README.md index 211266b..b942597 100644 --- a/__roadmap__/adding-mcp-hosts-skill/README.md +++ b/__roadmap__/adding-mcp-hosts-skill/README.md @@ -40,12 +40,12 @@ Produce a packaged `adding-mcp-hosts.skill` file that an agent can use to add su ```mermaid graph TD - write_discovery_guide[Write Discovery Guide]:::planned - write_adapter_contract[Write Adapter Contract]:::planned - write_strategy_contract[Write Strategy Contract]:::planned - write_testing_fixtures[Write Testing Fixtures]:::planned - write_skill_md[Write Skill MD]:::planned - package[Package]:::planned + write_discovery_guide[Write Discovery Guide]:::done + write_adapter_contract[Write Adapter Contract]:::done + write_strategy_contract[Write Strategy Contract]:::done + write_testing_fixtures[Write Testing Fixtures]:::done + write_skill_md[Write Skill MD]:::done + package[Package]:::done classDef done fill:#166534,color:#bbf7d0 classDef inprogress fill:#854d0e,color:#fef08a @@ -58,12 +58,12 @@ graph TD | Node | Type | Status | |:-----|:-----|:-------| -| `write_discovery_guide.md` | Leaf Task | Planned | -| `write_adapter_contract.md` | Leaf Task | Planned | -| `write_strategy_contract.md` | Leaf Task | Planned | -| `write_testing_fixtures.md` | Leaf Task | Planned | -| `write_skill_md.md` | Leaf Task | Planned | -| `package/` | Directory | Planned | +| `write_discovery_guide.md` | Leaf Task | Done | +| `write_adapter_contract.md` | Leaf Task | Done | +| `write_strategy_contract.md` | Leaf Task | Done | +| `write_testing_fixtures.md` | Leaf Task | Done | +| `write_skill_md.md` | Leaf Task | Done | +| `package/` | Directory | Done | ## Amendment Log @@ -74,3 +74,9 @@ graph TD | Node | Branch | Commits | Notes | |:-----|:-------|:--------|:------| +| `write_discovery_guide.md` | `task/write-discovery-guide` | 1 | 218 lines, 5 sections | +| `write_adapter_contract.md` | `task/write-adapter-contract` | 1 | 157 lines, 7 subsections | +| `write_strategy_contract.md` | `task/write-strategy-contract` | 1 | 226 lines, 5 sections | +| `write_testing_fixtures.md` | `task/write-testing-fixtures` | 1 | 121 lines, 5 sections | +| `write_skill_md.md` | `task/write-skill-md` | 1 | 202 lines, 5-step workflow | +| `package/package_skill.md` | `task/package-skill` | 1 | Validated + packaged .skill | diff --git a/__roadmap__/adding-mcp-hosts-skill/package/README.md b/__roadmap__/adding-mcp-hosts-skill/package/README.md index 0e73d26..cb3eac5 100644 --- a/__roadmap__/adding-mcp-hosts-skill/package/README.md +++ b/__roadmap__/adding-mcp-hosts-skill/package/README.md @@ -10,7 +10,7 @@ Validate skill structure and produce the distributable `.skill` package. ## Pre-conditions -- [ ] All 5 depth-0 leaves merged into milestone +- [x] All 5 depth-0 leaves merged into milestone ## Success Gates @@ -21,7 +21,7 @@ Validate skill structure and produce the distributable `.skill` package. ```mermaid graph TD - package_skill[Package Skill]:::planned + package_skill[Package Skill]:::done classDef done fill:#166534,color:#bbf7d0 classDef inprogress fill:#854d0e,color:#fef08a @@ -34,7 +34,7 @@ graph TD | Node | Type | Status | |:-----|:-----|:-------| -| `package_skill.md` | Leaf Task | Planned | +| `package_skill.md` | Leaf Task | Done | ## Amendment Log @@ -45,3 +45,4 @@ graph TD | Node | Branch | Commits | Notes | |:-----|:-------|:--------|:------| +| `package_skill.md` | `task/package-skill` | 1 | Validated + packaged | From f739fed5645b87561120e0ca3673efa29fb852c2 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 10:53:09 +0900 Subject: [PATCH 19/71] chore: move skills directory location From the `__design__/` to `.claude/` --- .../skills/adding-mcp-hosts/SKILL.md | 0 .../references/adapter-contract.md | 0 .../references/discovery-guide.md | 0 .../references/strategy-contract.md | 0 .../references/testing-fixtures.md | 0 __design__/skills/dist/adding-mcp-hosts.skill | Bin 14081 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) rename {__design__ => .claude}/skills/adding-mcp-hosts/SKILL.md (100%) rename {__design__ => .claude}/skills/adding-mcp-hosts/references/adapter-contract.md (100%) rename {__design__ => .claude}/skills/adding-mcp-hosts/references/discovery-guide.md (100%) rename {__design__ => .claude}/skills/adding-mcp-hosts/references/strategy-contract.md (100%) rename {__design__ => .claude}/skills/adding-mcp-hosts/references/testing-fixtures.md (100%) delete mode 100644 __design__/skills/dist/adding-mcp-hosts.skill diff --git a/__design__/skills/adding-mcp-hosts/SKILL.md b/.claude/skills/adding-mcp-hosts/SKILL.md similarity index 100% rename from __design__/skills/adding-mcp-hosts/SKILL.md rename to .claude/skills/adding-mcp-hosts/SKILL.md diff --git a/__design__/skills/adding-mcp-hosts/references/adapter-contract.md b/.claude/skills/adding-mcp-hosts/references/adapter-contract.md similarity index 100% rename from __design__/skills/adding-mcp-hosts/references/adapter-contract.md rename to .claude/skills/adding-mcp-hosts/references/adapter-contract.md diff --git a/__design__/skills/adding-mcp-hosts/references/discovery-guide.md b/.claude/skills/adding-mcp-hosts/references/discovery-guide.md similarity index 100% rename from __design__/skills/adding-mcp-hosts/references/discovery-guide.md rename to .claude/skills/adding-mcp-hosts/references/discovery-guide.md diff --git a/__design__/skills/adding-mcp-hosts/references/strategy-contract.md b/.claude/skills/adding-mcp-hosts/references/strategy-contract.md similarity index 100% rename from __design__/skills/adding-mcp-hosts/references/strategy-contract.md rename to .claude/skills/adding-mcp-hosts/references/strategy-contract.md diff --git a/__design__/skills/adding-mcp-hosts/references/testing-fixtures.md b/.claude/skills/adding-mcp-hosts/references/testing-fixtures.md similarity index 100% rename from __design__/skills/adding-mcp-hosts/references/testing-fixtures.md rename to .claude/skills/adding-mcp-hosts/references/testing-fixtures.md diff --git a/__design__/skills/dist/adding-mcp-hosts.skill b/__design__/skills/dist/adding-mcp-hosts.skill deleted file mode 100644 index b32f5af2579439343e5da6d07a8f53497e5c2482..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14081 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 Date: Wed, 25 Feb 2026 15:26:40 +0900 Subject: [PATCH 20/71] docs(adding-mcp-hosts): use parallel research over priority ladder Replace the sequential tool priority ladder with a parallel research approach: web search, Context7, and codebase retrieval are now used together rather than as ordered fallbacks. Add output file requirements (00-parameter_analysis and 01-architecture_analysis under __reports__//) so discoveries are captured as artifacts rather than only in chat. --- .claude/skills/adding-mcp-hosts/SKILL.md | 18 +++--- .../references/discovery-guide.md | 55 +++++++------------ 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/.claude/skills/adding-mcp-hosts/SKILL.md b/.claude/skills/adding-mcp-hosts/SKILL.md index e9c4ec6..dea6fe1 100644 --- a/.claude/skills/adding-mcp-hosts/SKILL.md +++ b/.claude/skills/adding-mcp-hosts/SKILL.md @@ -27,17 +27,17 @@ description: | Read [references/discovery-guide.md](references/discovery-guide.md) for the full discovery workflow. -Use web search and fetch tools to find the target host's official MCP configuration docs. -Identify: config file path per platform, config format (JSON/JSONC/TOML), top-level key -wrapping server entries, every supported field name and type, and any field name differences -from the universal set (`command`, `args`, `env`, `url`, `headers`). +Use web search, Context7, and codebase retrieval to find the target host's MCP +configuration: config file path per platform, format (JSON/JSONC/TOML), top-level key, +every supported field name and type, and any field name differences from the universal +set (`command`, `args`, `env`, `url`, `headers`). -If web tools are unavailable or return insufficient data, present the structured -questionnaire from the discovery guide to the user. +If research leaves blockers unresolved, present the structured questionnaire from the +discovery guide to the user. -Produce a Host Spec YAML block capturing: host slug, config paths (macOS/Linux/Windows), -config format, supported fields, field mappings, and strategy family. This output feeds -all subsequent steps. +Write `__reports__//00-parameter_analysis_v0.md` (field-level discovery) and +`__reports__//01-architecture_analysis_v0.md` (integration analysis and NO-GO +assessment). Also produce the Host Spec YAML block — it feeds all subsequent steps. --- diff --git a/.claude/skills/adding-mcp-hosts/references/discovery-guide.md b/.claude/skills/adding-mcp-hosts/references/discovery-guide.md index 1486a56..407dcec 100644 --- a/.claude/skills/adding-mcp-hosts/references/discovery-guide.md +++ b/.claude/skills/adding-mcp-hosts/references/discovery-guide.md @@ -5,45 +5,20 @@ Produces a Host Spec YAML artifact consumed by all subsequent steps. --- -## 1. Tool Priority Ladder +## 1. Research Tools -Research the target host in order. Fall through when the current level yields no definitive answer. +Use all available tools. Web search and Context7 are complementary — do not treat either as a fallback for the other. -### Level 1: Web Search + Fetch +| Tool | What to find | +|------|-------------| +| Web search (multiple queries) | Official docs; changelog; known issues mentioning config changes | +| Page fetch | Config type definitions in source (`types.ts`, `*.schema.json`) — source code beats docs pages | +| Context7 (`resolve-library-id` + query) | SDK-level field names, types, validation rules | +| Codebase retrieval (Hatch repo) | Fields and strategy families already in `models.py`, `fields.py`, `strategies.py` | -| Action | Detail | -|--------|--------| -| Search query | `" MCP server configuration" site:github.com OR site:docs.*` | -| Fetch targets | Official docs page, README, or config schema file | -| Success | Config path, format, root key, and supported fields are all documented | -| Fall through | Docs missing, incomplete, or ambiguous on field support | +A single docs page is not enough. After finding the docs, locate the config type definition in the host's source and use it to verify field names, types, and optionality. If two sources disagree, fetch a third or escalate — never guess. -### Level 2: Context7 Library Docs - -| Action | Detail | -|--------|--------| -| Resolve library | `resolve-library-id` with the host package name | -| Query | `"MCP server configuration format and supported fields"` | -| Success | Field names, types, and validation rules documented | -| Fall through | Host not indexed, or MCP config undocumented | - -### Level 3: Codebase Retrieval - -| Action | Detail | -|--------|--------| -| Query | `"configuration strategy for "` against project root | -| Inspect | `hatch/mcp_host_config/strategies.py`, `fields.py`, `models.py` | -| Success | Existing strategy, adapter, or field set provides needed data | -| Fall through | Host is entirely new with no existing references | - -### Level 4: User Escalation - -| Action | Detail | -|--------|--------| -| Start with | Tier 1 (blocking questions) | -| Expand to | Tier 2 if Tier 1 reveals non-standard behavior | -| Ask Tier 3 | Only for remaining ambiguities after Tiers 1-2 | -| Success | All blocking questions answered; remaining gaps have safe defaults | +Use the questionnaire (§2) only for information that research could not resolve. --- @@ -216,3 +191,13 @@ Validate the completed spec against these rules before proceeding: - If `field_mappings` is non-empty, verify each source field exists in another host's field set - If `architecture.variant_of` is set, confirm the named adapter exists in the registry - If `architecture.strategy_family` is set, confirm the named base class exists in `strategies.py` + +--- + +## 6. Output Files + +Write both files to `__reports__//` before proceeding to Step 2. Do not summarize findings only in chat. + +**`00-parameter_analysis_v0.md`** — field-level discovery: what the host config actually looks like, field names and types per transport, serialization requirements, and the resulting `HOSTNAME_FIELDS` contract. + +**`01-architecture_analysis_v0.md`** — integration analysis: current state inventory of the Hatch codebase, how the new host fits the adapter/strategy pattern, NO-GO assessment with any implementation-critical invariants, component contracts, and risk register. From 2bae600611d433be499299727c93e2649d7a8868 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:37:07 +0900 Subject: [PATCH 21/71] feat(mcp-models): add opencode oauth fields to MCPServerConfig --- hatch/mcp_host_config/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hatch/mcp_host_config/models.py b/hatch/mcp_host_config/models.py index b7d146f..ecc6218 100644 --- a/hatch/mcp_host_config/models.py +++ b/hatch/mcp_host_config/models.py @@ -30,6 +30,7 @@ class MCPHostType(str, Enum): GEMINI = "gemini" KIRO = "kiro" CODEX = "codex" + OPENCODE = "opencode" class MCPServerConfig(BaseModel): @@ -166,6 +167,17 @@ class MCPServerConfig(BaseModel): None, description="Header names to env var names" ) + # ======================================================================== + # OpenCode-Specific Fields + # ======================================================================== + opencode_oauth_scope: Optional[str] = Field( + None, description="OAuth scope for OpenCode server (maps to oauth.scope)" + ) + opencode_oauth_disable: Optional[bool] = Field( + None, + description="Disable OAuth for OpenCode server (serializes as oauth: false)", + ) + # ======================================================================== # Minimal Validators (host-specific validation is in adapters) # ======================================================================== @@ -353,6 +365,7 @@ def validate_host_names(cls, v): "lmstudio", "gemini", "kiro", + "opencode", } for host_name in v.keys(): if host_name not in supported_hosts: From b9ddf4345ccd32b7dd9ae3a46712c56014d63692 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:37:11 +0900 Subject: [PATCH 22/71] feat(mcp-fields): add OPENCODE_FIELDS constant --- hatch/mcp_host_config/fields.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hatch/mcp_host_config/fields.py b/hatch/mcp_host_config/fields.py index 2531cd0..880dbe2 100644 --- a/hatch/mcp_host_config/fields.py +++ b/hatch/mcp_host_config/fields.py @@ -116,6 +116,21 @@ ) +# Fields supported by OpenCode (no type field; uses local/remote type derivation, +# command array merge, environment rename, and oauth nesting) +OPENCODE_FIELDS: FrozenSet[str] = UNIVERSAL_FIELDS | frozenset( + { + "args", # Merged with command into command array (I-2: must stay in field set) + "enabled", # Enable/disable server without deleting config + "timeout", # Request timeout in milliseconds + "oauth_clientId", # OAuth client identifier (nested under 'oauth' key) + "oauth_clientSecret", # OAuth client secret (nested under 'oauth' key) + "opencode_oauth_scope", # OAuth scope (nested under 'oauth.scope') + "opencode_oauth_disable", # Disable OAuth (serializes as oauth: false) + } +) + + # ============================================================================ # Field Mappings (universal name → host-specific name) # ============================================================================ From 28e3bdf39f3d0664c807145c94faae7ba917d460 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:38:17 +0900 Subject: [PATCH 23/71] feat(mcp-adapter): add OpenCodeAdapter with serialize transforms --- hatch/mcp_host_config/adapters/opencode.py | 154 +++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 hatch/mcp_host_config/adapters/opencode.py diff --git a/hatch/mcp_host_config/adapters/opencode.py b/hatch/mcp_host_config/adapters/opencode.py new file mode 100644 index 0000000..8109c5a --- /dev/null +++ b/hatch/mcp_host_config/adapters/opencode.py @@ -0,0 +1,154 @@ +"""OpenCode adapter for MCP host configuration. + +OpenCode uses a discriminated-union format with structural differences +from the universal schema: +- 'type' field is 'local' or 'remote' (not 'stdio'/'sse'/'http') +- 'command' is an array: [executable, ...args] (not a string) +- 'env' is renamed to 'environment' +- OAuth is nested under an 'oauth' key, or set to false to disable +""" + +from typing import Any, Dict, FrozenSet + +from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter +from hatch.mcp_host_config.fields import OPENCODE_FIELDS +from hatch.mcp_host_config.models import MCPServerConfig + + +class OpenCodeAdapter(BaseAdapter): + """Adapter for OpenCode MCP host. + + OpenCode uses a discriminated-union format where transport type is + derived from configuration presence: + - Local (stdio): command string + args list merged into command array, + env renamed to environment, type set to 'local' + - Remote (sse): url preserved, headers preserved, type set to 'remote' + + OAuth configuration is nested: + - opencode_oauth_disable=True serializes as oauth: false + - oauth_clientId/clientSecret/opencode_oauth_scope serialize as + oauth: {clientId, clientSecret, scope} (omitting null values) + """ + + @property + def host_name(self) -> str: + """Return the host identifier.""" + return "opencode" + + def get_supported_fields(self) -> FrozenSet[str]: + """Return fields supported by OpenCode.""" + return OPENCODE_FIELDS + + def validate(self, config: MCPServerConfig) -> None: + """Validate configuration for OpenCode. + + DEPRECATED: This method is deprecated and will be removed in v0.9.0. + Use validate_filtered() instead. + + OpenCode requires exactly one transport (command XOR url). + """ + has_command = config.command is not None + has_url = config.url is not None + + if not has_command and not has_url: + raise AdapterValidationError( + "Either 'command' (local) or 'url' (remote) must be specified", + host_name=self.host_name, + ) + + if has_command and has_url: + raise AdapterValidationError( + "Cannot specify both 'command' and 'url' - choose one transport", + host_name=self.host_name, + ) + + def validate_filtered(self, filtered: Dict[str, Any]) -> None: + """Validate filtered configuration for OpenCode. + + Validates only fields that survived filtering (supported by OpenCode). + OpenCode requires exactly one transport (command XOR url). + + Args: + filtered: Dictionary of filtered fields + + Raises: + AdapterValidationError: If validation fails + """ + has_command = "command" in filtered + has_url = "url" in filtered + + if not has_command and not has_url: + raise AdapterValidationError( + "Either 'command' (local) or 'url' (remote) must be specified", + host_name=self.host_name, + ) + + if has_command and has_url: + raise AdapterValidationError( + "Cannot specify both 'command' and 'url' - choose one transport", + host_name=self.host_name, + ) + + def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: + """Serialize configuration to OpenCode format. + + OpenCode structural transforms: + 1. Filter to supported fields + 2. Validate transport mutual exclusion + 3. Build output dict with OpenCode-native structure: + - Derive type: 'local' (command present) or 'remote' (url present) + - Local: merge command + args into command array, rename env→environment + - Remote: preserve url and headers as-is + - Both: include enabled, timeout if set + - OAuth: emit oauth: false if disabled, or oauth: {...} object if configured + + Args: + config: The MCPServerConfig to serialize + + Returns: + Dictionary in OpenCode's expected format + """ + # Filter to supported fields + filtered = self.filter_fields(config) + + # Validate transport mutual exclusion + self.validate_filtered(filtered) + + result: Dict[str, Any] = {} + + if "command" in filtered: + # Local transport: derive type, merge command+args, rename env + result["type"] = "local" + command = filtered["command"] + args = filtered.get("args") or [] + result["command"] = [command] + args + if "env" in filtered: + result["environment"] = filtered["env"] + else: + # Remote transport: derive type, preserve url and headers + result["type"] = "remote" + result["url"] = filtered["url"] + if "headers" in filtered: + result["headers"] = filtered["headers"] + + # Optional shared fields + if "enabled" in filtered: + result["enabled"] = filtered["enabled"] + if "timeout" in filtered: + result["timeout"] = filtered["timeout"] + + # OAuth configuration + if filtered.get("opencode_oauth_disable"): + result["oauth"] = False + else: + oauth: Dict[str, Any] = {} + if filtered.get("oauth_clientId"): + oauth["clientId"] = filtered["oauth_clientId"] + if filtered.get("oauth_clientSecret"): + oauth["clientSecret"] = filtered["oauth_clientSecret"] + if filtered.get("opencode_oauth_scope"): + oauth["scope"] = filtered["opencode_oauth_scope"] + if oauth: + result["oauth"] = oauth + + return result From 8bb590ad16bb09780064f3b676dc94e024795ea0 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:42:36 +0900 Subject: [PATCH 24/71] feat(mcp-strategy): add OpenCodeHostStrategy with JSONC read/write --- hatch/mcp_host_config/strategies.py | 169 ++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/hatch/mcp_host_config/strategies.py b/hatch/mcp_host_config/strategies.py index 5f1523d..79bc86d 100644 --- a/hatch/mcp_host_config/strategies.py +++ b/hatch/mcp_host_config/strategies.py @@ -8,6 +8,7 @@ import platform import json +import re import tomllib # Python 3.11+ built-in import tomli_w # TOML writing from pathlib import Path @@ -880,3 +881,171 @@ def _to_toml_server_from_dict(self, data: Dict[str, Any]) -> Dict[str, Any]: result["http_headers"] = result.pop("headers") return result + + +@register_host_strategy(MCPHostType.OPENCODE) +class OpenCodeHostStrategy(MCPHostStrategy): + """Configuration strategy for OpenCode AI editor. + + OpenCode stores MCP configuration in opencode.json under the 'mcp' key. + The config file may contain JSONC-style // comments which are stripped + before JSON parsing. + + OpenCode uses a discriminated-union format that differs from canonical form: + - 'type' is 'local' or 'remote' (not 'stdio'/'sse') + - 'command' is an array: [executable, ...args] + - 'env' is 'environment' in the file + - OAuth is nested under an 'oauth' key, or set to false to disable + + The pre-processor in read_configuration() normalises raw server data back + into MCPServerConfig-compatible form before Pydantic construction. + """ + + def get_adapter_host_name(self) -> str: + """Return the adapter host name for OpenCode.""" + return "opencode" + + def get_config_path(self) -> Optional[Path]: + """Get OpenCode configuration path (platform-aware).""" + system = platform.system() + if system == "Windows": + return Path.home() / "AppData" / "Roaming" / "opencode" / "opencode.json" + # macOS and Linux both use XDG-style ~/.config/ + return Path.home() / ".config" / "opencode" / "opencode.json" + + def get_config_key(self) -> str: + """OpenCode uses 'mcp' key.""" + return "mcp" + + def is_host_available(self) -> bool: + """Check if OpenCode is available by checking for its config directory.""" + config_path = self.get_config_path() + return config_path is not None and config_path.parent.exists() + + def validate_server_config(self, server_config: MCPServerConfig) -> bool: + """OpenCode validation - supports both local and remote servers.""" + return server_config.command is not None or server_config.url is not None + + @staticmethod + def _pre_process_server(raw: Dict[str, Any]) -> Dict[str, Any]: + """Normalise a raw OpenCode server entry into MCPServerConfig-compatible form. + + Transforms: + - Strips 'type' key (raw values 'local'/'remote' are invalid for MCPServerConfig) + - Splits command array: command[0] → command str, command[1:] → args list + - Renames 'environment' → 'env' + - Unnests 'oauth' dict → oauth_clientId, oauth_clientSecret, opencode_oauth_scope + - oauth: false → opencode_oauth_disable=True + + Args: + raw: Raw server dict read from opencode.json + + Returns: + Dict suitable for MCPServerConfig(**result) construction + """ + data = dict(raw) + + # Strip transport type discriminator (opencode uses 'local'/'remote') + data.pop("type", None) + + # Split command array into command string + args list + if "command" in data and isinstance(data["command"], list): + command_list = data["command"] + data["command"] = command_list[0] if command_list else "" + if len(command_list) > 1: + data["args"] = command_list[1:] + + # Rename environment → env + if "environment" in data: + data["env"] = data.pop("environment") + + # Unnest oauth + oauth_value = data.pop("oauth", None) + if oauth_value is False: + data["opencode_oauth_disable"] = True + elif isinstance(oauth_value, dict): + if "clientId" in oauth_value: + data["oauth_clientId"] = oauth_value["clientId"] + if "clientSecret" in oauth_value: + data["oauth_clientSecret"] = oauth_value["clientSecret"] + if "scope" in oauth_value: + data["opencode_oauth_scope"] = oauth_value["scope"] + + return data + + def read_configuration(self) -> HostConfiguration: + """Read OpenCode configuration file with JSONC comment stripping.""" + config_path = self.get_config_path() + if not config_path or not config_path.exists(): + return HostConfiguration() + + try: + raw_text = config_path.read_text(encoding="utf-8") + + # Strip // line comments (JSONC support) + stripped = re.sub(r"//[^\n]*", "", raw_text) + + config_data = json.loads(stripped) + mcp_servers = config_data.get(self.get_config_key(), {}) + + servers = {} + for name, server_data in mcp_servers.items(): + try: + processed = self._pre_process_server(server_data) + servers[name] = MCPServerConfig(**processed) + except Exception as e: + logger.warning(f"Invalid OpenCode server config for {name}: {e}") + continue + + return HostConfiguration(servers=servers) + + except Exception as e: + logger.error(f"Failed to read OpenCode configuration: {e}") + return HostConfiguration() + + def write_configuration( + self, config: HostConfiguration, no_backup: bool = False + ) -> bool: + """Write OpenCode configuration with read-before-write to preserve other keys.""" + config_path = self.get_config_path() + if not config_path: + return False + + try: + config_path.parent.mkdir(parents=True, exist_ok=True) + + # Read existing config to preserve non-mcp keys (theme, model, etc.) + existing_data: Dict[str, Any] = {} + if config_path.exists(): + try: + raw_text = config_path.read_text(encoding="utf-8") + stripped = re.sub(r"//[^\n]*", "", raw_text) + existing_data = json.loads(stripped) + except Exception: + pass + + # Serialize all servers using the OpenCode adapter + adapter = get_adapter(self.get_adapter_host_name()) + servers_dict = {} + for name, server_config in config.servers.items(): + servers_dict[name] = adapter.serialize(server_config) + + existing_data[self.get_config_key()] = servers_dict + + # Write atomically with backup support + backup_manager = MCPHostConfigBackupManager() + atomic_ops = AtomicFileOperations() + + atomic_ops.atomic_write_with_backup( + file_path=config_path, + data=existing_data, + backup_manager=backup_manager, + hostname="opencode", + skip_backup=no_backup, + ) + + return True + + except Exception as e: + logger.error(f"Failed to write OpenCode configuration: {e}") + return False From 20e0fc87339fadd6945b8e03798f9d81bb80f53b Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:44:30 +0900 Subject: [PATCH 25/71] feat(mcp-registry): register OpenCodeAdapter in adapter registry --- hatch/mcp_host_config/adapters/__init__.py | 2 ++ hatch/mcp_host_config/adapters/registry.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/hatch/mcp_host_config/adapters/__init__.py b/hatch/mcp_host_config/adapters/__init__.py index a949b0e..ece02f1 100644 --- a/hatch/mcp_host_config/adapters/__init__.py +++ b/hatch/mcp_host_config/adapters/__init__.py @@ -11,6 +11,7 @@ from hatch.mcp_host_config.adapters.gemini import GeminiAdapter from hatch.mcp_host_config.adapters.kiro import KiroAdapter from hatch.mcp_host_config.adapters.lmstudio import LMStudioAdapter +from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter from hatch.mcp_host_config.adapters.registry import ( AdapterRegistry, get_adapter, @@ -33,5 +34,6 @@ "GeminiAdapter", "KiroAdapter", "LMStudioAdapter", + "OpenCodeAdapter", "VSCodeAdapter", ] diff --git a/hatch/mcp_host_config/adapters/registry.py b/hatch/mcp_host_config/adapters/registry.py index 39065b4..88e4458 100644 --- a/hatch/mcp_host_config/adapters/registry.py +++ b/hatch/mcp_host_config/adapters/registry.py @@ -13,6 +13,7 @@ from hatch.mcp_host_config.adapters.gemini import GeminiAdapter from hatch.mcp_host_config.adapters.kiro import KiroAdapter from hatch.mcp_host_config.adapters.lmstudio import LMStudioAdapter +from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter from hatch.mcp_host_config.adapters.vscode import VSCodeAdapter @@ -53,6 +54,7 @@ def _register_defaults(self) -> None: self.register(GeminiAdapter()) self.register(KiroAdapter()) self.register(CodexAdapter()) + self.register(OpenCodeAdapter()) def register(self, adapter: BaseAdapter) -> None: """Register an adapter instance. From 7d0b0757f899b7608f2331449043978a1ab16b0f Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:44:35 +0900 Subject: [PATCH 26/71] feat(mcp-wiring): add opencode to backup and reporting --- hatch/mcp_host_config/backup.py | 1 + hatch/mcp_host_config/reporting.py | 1 + 2 files changed, 2 insertions(+) diff --git a/hatch/mcp_host_config/backup.py b/hatch/mcp_host_config/backup.py index 26ab840..7d6ff1d 100644 --- a/hatch/mcp_host_config/backup.py +++ b/hatch/mcp_host_config/backup.py @@ -48,6 +48,7 @@ def validate_hostname(cls, v): "gemini", "kiro", "codex", + "opencode", } if v not in supported_hosts: raise ValueError(f"Unsupported hostname: {v}. Supported: {supported_hosts}") diff --git a/hatch/mcp_host_config/reporting.py b/hatch/mcp_host_config/reporting.py index 8791f93..22a9f8e 100644 --- a/hatch/mcp_host_config/reporting.py +++ b/hatch/mcp_host_config/reporting.py @@ -73,6 +73,7 @@ def _get_adapter_host_name(host_type: MCPHostType) -> str: MCPHostType.GEMINI: "gemini", MCPHostType.KIRO: "kiro", MCPHostType.CODEX: "codex", + MCPHostType.OPENCODE: "opencode", } return mapping.get(host_type, host_type.value) From 5ae3b570e48e214bd07bfd856ccacda8275ed4d1 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:44:40 +0900 Subject: [PATCH 27/71] test(mcp-fixtures): add opencode entry to canonical_configs.json --- tests/test_data/mcp_adapters/canonical_configs.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_data/mcp_adapters/canonical_configs.json b/tests/test_data/mcp_adapters/canonical_configs.json index 49bc2ac..b90299f 100644 --- a/tests/test_data/mcp_adapters/canonical_configs.json +++ b/tests/test_data/mcp_adapters/canonical_configs.json @@ -74,5 +74,16 @@ "cwd": "/app", "enabled_tools": ["tool1", "tool2"], "disabled_tools": ["tool3"] + }, + "opencode": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], + "env": {"MY_VAR": "value"}, + "url": null, + "headers": null, + "enabled": true, + "timeout": 5000, + "opencode_oauth_scope": null, + "opencode_oauth_disable": null } } From 734b3c0d28cfc563e4cc2d7324f7650dc03874f6 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 13:44:44 +0900 Subject: [PATCH 28/71] test(mcp-fixtures): register opencode in host_registry.py --- tests/test_data/mcp_adapters/host_registry.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_data/mcp_adapters/host_registry.py b/tests/test_data/mcp_adapters/host_registry.py index 34d6a49..f1e6ff1 100644 --- a/tests/test_data/mcp_adapters/host_registry.py +++ b/tests/test_data/mcp_adapters/host_registry.py @@ -26,6 +26,7 @@ from hatch.mcp_host_config.adapters.gemini import GeminiAdapter from hatch.mcp_host_config.adapters.kiro import KiroAdapter from hatch.mcp_host_config.adapters.lmstudio import LMStudioAdapter +from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter from hatch.mcp_host_config.adapters.vscode import VSCodeAdapter from hatch.mcp_host_config.fields import ( CLAUDE_FIELDS, @@ -36,6 +37,7 @@ GEMINI_FIELDS, KIRO_FIELDS, LMSTUDIO_FIELDS, + OPENCODE_FIELDS, TYPE_SUPPORTING_HOSTS, VSCODE_FIELDS, ) @@ -55,6 +57,7 @@ "gemini": GEMINI_FIELDS, "kiro": KIRO_FIELDS, "codex": CODEX_FIELDS, + "opencode": OPENCODE_FIELDS, } # Reverse mappings for Codex (host-native name → universal name) @@ -93,6 +96,7 @@ def get_adapter(self) -> BaseAdapter: "gemini": GeminiAdapter, "kiro": KiroAdapter, "codex": CodexAdapter, + "opencode": OpenCodeAdapter, } factory = adapter_map[self.host_name] return factory() @@ -350,6 +354,7 @@ def generate_unsupported_field_test_cases( | GEMINI_FIELDS | KIRO_FIELDS | CODEX_FIELDS + | OPENCODE_FIELDS ) cases: List[FilterTestCase] = [] From ee1d915d705df692036c372d9e8ccec834d313d0 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 14:02:26 +0900 Subject: [PATCH 29/71] fix(mcp-opencode): make serialize() canonical-form; add test fixes --- hatch/mcp_host_config/adapters/opencode.py | 44 +++++++++++-------- hatch/mcp_host_config/strategies.py | 7 ++- .../regression/mcp/test_field_filtering_v2.py | 3 ++ tests/unit/mcp/test_adapter_registry.py | 1 + 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/hatch/mcp_host_config/adapters/opencode.py b/hatch/mcp_host_config/adapters/opencode.py index 8109c5a..e1f4af5 100644 --- a/hatch/mcp_host_config/adapters/opencode.py +++ b/hatch/mcp_host_config/adapters/opencode.py @@ -90,34 +90,43 @@ def validate_filtered(self, filtered: Dict[str, Any]) -> None: ) def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: - """Serialize configuration to OpenCode format. - - OpenCode structural transforms: - 1. Filter to supported fields - 2. Validate transport mutual exclusion - 3. Build output dict with OpenCode-native structure: - - Derive type: 'local' (command present) or 'remote' (url present) - - Local: merge command + args into command array, rename env→environment - - Remote: preserve url and headers as-is - - Both: include enabled, timeout if set - - OAuth: emit oauth: false if disabled, or oauth: {...} object if configured + """Serialize configuration for OpenCode (canonical form). + + Returns a filtered, validated dict using MCPServerConfig field names. + Structural transforms (command array merge, env→environment rename, + type derivation, oauth nesting) are applied by the strategy's + write_configuration() via to_native_format(). Args: config: The MCPServerConfig to serialize Returns: - Dictionary in OpenCode's expected format + Filtered dict with MCPServerConfig-canonical field names """ - # Filter to supported fields filtered = self.filter_fields(config) - - # Validate transport mutual exclusion self.validate_filtered(filtered) + return filtered + + @staticmethod + def to_native_format(filtered: Dict[str, Any]) -> Dict[str, Any]: + """Convert canonical-form dict to OpenCode-native file format. + + Applies OpenCode structural transforms: + - Derives type: 'local' (command present) or 'remote' (url present) + - Local: merges command + args into command array, renames env→environment + - Remote: preserves url and headers as-is + - Handles enabled, timeout if present + - OAuth: emits oauth: false or oauth: {clientId, clientSecret, scope} + Args: + filtered: Canonical-form dict from serialize() + + Returns: + Dict in OpenCode's native file format + """ result: Dict[str, Any] = {} if "command" in filtered: - # Local transport: derive type, merge command+args, rename env result["type"] = "local" command = filtered["command"] args = filtered.get("args") or [] @@ -125,19 +134,16 @@ def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: if "env" in filtered: result["environment"] = filtered["env"] else: - # Remote transport: derive type, preserve url and headers result["type"] = "remote" result["url"] = filtered["url"] if "headers" in filtered: result["headers"] = filtered["headers"] - # Optional shared fields if "enabled" in filtered: result["enabled"] = filtered["enabled"] if "timeout" in filtered: result["timeout"] = filtered["timeout"] - # OAuth configuration if filtered.get("opencode_oauth_disable"): result["oauth"] = False else: diff --git a/hatch/mcp_host_config/strategies.py b/hatch/mcp_host_config/strategies.py index 79bc86d..c5054ab 100644 --- a/hatch/mcp_host_config/strategies.py +++ b/hatch/mcp_host_config/strategies.py @@ -19,6 +19,7 @@ from .models import MCPHostType, MCPServerConfig, HostConfiguration from .backup import MCPHostConfigBackupManager, AtomicFileOperations from .adapters import get_adapter +from .adapters.opencode import OpenCodeAdapter logger = logging.getLogger(__name__) @@ -1024,11 +1025,13 @@ def write_configuration( except Exception: pass - # Serialize all servers using the OpenCode adapter + # Serialize all servers using the OpenCode adapter, then apply + # structural transforms to produce OpenCode-native file format adapter = get_adapter(self.get_adapter_host_name()) servers_dict = {} for name, server_config in config.servers.items(): - servers_dict[name] = adapter.serialize(server_config) + canonical = adapter.serialize(server_config) + servers_dict[name] = OpenCodeAdapter.to_native_format(canonical) existing_data[self.get_config_key()] = servers_dict diff --git a/tests/regression/mcp/test_field_filtering_v2.py b/tests/regression/mcp/test_field_filtering_v2.py index 7ba770d..c511532 100644 --- a/tests/regression/mcp/test_field_filtering_v2.py +++ b/tests/regression/mcp/test_field_filtering_v2.py @@ -77,6 +77,9 @@ def regression_test(func): "env_vars": ["VAR1"], "enabled_tools": ["tool1"], "disabled_tools": ["tool2"], + # OpenCode-specific fields + "opencode_oauth_disable": False, + "opencode_oauth_scope": "read", # Dict fields "env": {"TEST": "value"}, "headers": {"X-Test": "value"}, diff --git a/tests/unit/mcp/test_adapter_registry.py b/tests/unit/mcp/test_adapter_registry.py index 9408835..b6b7736 100644 --- a/tests/unit/mcp/test_adapter_registry.py +++ b/tests/unit/mcp/test_adapter_registry.py @@ -38,6 +38,7 @@ def test_AR01_registry_has_all_default_hosts(self): "gemini", "kiro", "lmstudio", + "opencode", "vscode", } From d8f3a75d2b8a6bb39cba6cb185435e42158c9f73 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 15:26:32 +0900 Subject: [PATCH 30/71] fix(mcp-opencode): anchor JSONC comment regex to line start The previous pattern r"//[^\n]*" matched // anywhere on a line, including inside JSON string values such as URLs. This corrupted entries like "$schema": "https://opencode.ai/config.json" by stripping the // and everything after it, producing an unterminated string and a JSON parse failure. Anchor the pattern to the start of each line ((?m)^\s*//[^\n]*) so only standalone JSONC comment lines are stripped, leaving // inside string values untouched. --- hatch/mcp_host_config/strategies.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hatch/mcp_host_config/strategies.py b/hatch/mcp_host_config/strategies.py index c5054ab..59a0cb3 100644 --- a/hatch/mcp_host_config/strategies.py +++ b/hatch/mcp_host_config/strategies.py @@ -983,8 +983,9 @@ def read_configuration(self) -> HostConfiguration: try: raw_text = config_path.read_text(encoding="utf-8") - # Strip // line comments (JSONC support) - stripped = re.sub(r"//[^\n]*", "", raw_text) + # Strip // line comments (JSONC support) — only strip lines that + # START with optional whitespace + //, never inside string values + stripped = re.sub(r"(?m)^\s*//[^\n]*", "", raw_text) config_data = json.loads(stripped) mcp_servers = config_data.get(self.get_config_key(), {}) From a35d3a2ddb5c056b19f94f258b324fcb5dbea447 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Feb 2026 17:38:29 +0900 Subject: [PATCH 31/71] fix(mcp-opencode): anchor JSONC comment regex in write_configuration d8f3a75 anchored the pattern in read_configuration but left the same unanchored r"//[^\n]*" in write_configuration's read-before-write block. The unanchored pattern matched // inside URL string values such as "$schema": "https://opencode.ai/config.json", corrupting the JSON, causing silent fallback to empty existing_data and overwriting the config file with only {"mcp": {}}, erasing all other top-level keys. Apply the same fix: r"(?m)^\s*//[^\n]*" --- hatch/mcp_host_config/strategies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hatch/mcp_host_config/strategies.py b/hatch/mcp_host_config/strategies.py index 59a0cb3..1e11e64 100644 --- a/hatch/mcp_host_config/strategies.py +++ b/hatch/mcp_host_config/strategies.py @@ -1021,7 +1021,7 @@ def write_configuration( if config_path.exists(): try: raw_text = config_path.read_text(encoding="utf-8") - stripped = re.sub(r"//[^\n]*", "", raw_text) + stripped = re.sub(r"(?m)^\s*//[^\n]*", "", raw_text) existing_data = json.loads(stripped) except Exception: pass From e31b347bbb389111177286a1e48865cbf7331ae3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 26 Feb 2026 07:35:48 +0000 Subject: [PATCH 32/71] chore(release): 0.8.1-dev.1 ## 0.8.1-dev.1 (2026-02-26) * Merge branch 'feat/opencode-mcp-host-support' into dev ([793707d](https://github.com/CrackingShells/Hatch/commit/793707d)) * Merge branch 'milestone/adding-mcp-hosts-skill' into dev ([bce3851](https://github.com/CrackingShells/Hatch/commit/bce3851)) * Merge branch 'task/update-extension-guide' into milestone/mcp-docs-refresh ([d2a0df9](https://github.com/CrackingShells/Hatch/commit/d2a0df9)) * Merge branch 'task/write-adapter-contract' into milestone/adding-mcp-hosts-skill ([c639322](https://github.com/CrackingShells/Hatch/commit/c639322)) * Merge branch 'task/write-skill-md' into milestone/adding-mcp-hosts-skill ([d618f71](https://github.com/CrackingShells/Hatch/commit/d618f71)) * Merge branch 'task/write-strategy-contract' into milestone/adding-mcp-hosts-skill ([13b195c](https://github.com/CrackingShells/Hatch/commit/13b195c)) * Merge branch 'task/write-testing-fixtures' into milestone/adding-mcp-hosts-skill ([3cc4175](https://github.com/CrackingShells/Hatch/commit/3cc4175)) * Merge pull request #47 from LittleCoinCoin/dev ([9d873aa](https://github.com/CrackingShells/Hatch/commit/9d873aa)), closes [#47](https://github.com/CrackingShells/Hatch/issues/47) * fix(mcp-opencode): anchor JSONC comment regex in write_configuration ([a35d3a2](https://github.com/CrackingShells/Hatch/commit/a35d3a2)) * fix(mcp-opencode): anchor JSONC comment regex to line start ([d8f3a75](https://github.com/CrackingShells/Hatch/commit/d8f3a75)) * fix(mcp-opencode): make serialize() canonical-form; add test fixes ([ee1d915](https://github.com/CrackingShells/Hatch/commit/ee1d915)) * test(mcp-fixtures): add opencode entry to canonical_configs.json ([5ae3b57](https://github.com/CrackingShells/Hatch/commit/5ae3b57)) * test(mcp-fixtures): register opencode in host_registry.py ([734b3c0](https://github.com/CrackingShells/Hatch/commit/734b3c0)) * feat(mcp-adapter): add OpenCodeAdapter with serialize transforms ([28e3bdf](https://github.com/CrackingShells/Hatch/commit/28e3bdf)) * feat(mcp-fields): add OPENCODE_FIELDS constant ([b9ddf43](https://github.com/CrackingShells/Hatch/commit/b9ddf43)) * feat(mcp-models): add opencode oauth fields to MCPServerConfig ([2bae600](https://github.com/CrackingShells/Hatch/commit/2bae600)) * feat(mcp-registry): register OpenCodeAdapter in adapter registry ([20e0fc8](https://github.com/CrackingShells/Hatch/commit/20e0fc8)) * feat(mcp-strategy): add OpenCodeHostStrategy with JSONC read/write ([8bb590a](https://github.com/CrackingShells/Hatch/commit/8bb590a)) * feat(mcp-wiring): add opencode to backup and reporting ([7d0b075](https://github.com/CrackingShells/Hatch/commit/7d0b075)) * feat(skill): add adapter contract reference ([336fced](https://github.com/CrackingShells/Hatch/commit/336fced)) * feat(skill): add discovery guide reference ([8061c5f](https://github.com/CrackingShells/Hatch/commit/8061c5f)) * feat(skill): add strategy contract reference ([cf9b807](https://github.com/CrackingShells/Hatch/commit/cf9b807)) * feat(skill): add testing fixtures reference ([070894c](https://github.com/CrackingShells/Hatch/commit/070894c)) * feat(skill): write SKILL.md with 5-step workflow ([8984a3a](https://github.com/CrackingShells/Hatch/commit/8984a3a)) * docs(adding-mcp-hosts): use parallel research over priority ladder ([6f6165a](https://github.com/CrackingShells/Hatch/commit/6f6165a)) * docs(mcp): document strategy, registration, and variant pattern ([21c30d5](https://github.com/CrackingShells/Hatch/commit/21c30d5)) * docs(mcp): rewrite testing section with data-driven docs ([5fc6f97](https://github.com/CrackingShells/Hatch/commit/5fc6f97)) * docs(mcp): rewrite testing section with data-driven infra ([24c6ebf](https://github.com/CrackingShells/Hatch/commit/24c6ebf)) * docs(mcp): update adapter template to validate_filtered() ([69d61cc](https://github.com/CrackingShells/Hatch/commit/69d61cc)) * docs(mcp): update field support matrix and field mapping documentation ([c08e064](https://github.com/CrackingShells/Hatch/commit/c08e064)) * docs(mcp): update strategy template with interface docs ([0b83b6e](https://github.com/CrackingShells/Hatch/commit/0b83b6e)) * docs(roadmap): add mcp-docs-refresh task files and gap analysis ([896f4d2](https://github.com/CrackingShells/Hatch/commit/896f4d2)) * docs(roadmap): mark adding-mcp-hosts-skill campaign as done ([b7e6c95](https://github.com/CrackingShells/Hatch/commit/b7e6c95)) * docs(roadmap): mark mcp-docs-refresh tasks as done ([fc07cd1](https://github.com/CrackingShells/Hatch/commit/fc07cd1)) * chore: cleanup `__reports__/` ([1056e52](https://github.com/CrackingShells/Hatch/commit/1056e52)) * chore: move skills directory location ([f739fed](https://github.com/CrackingShells/Hatch/commit/f739fed)) * chore: update cs-playbook submodule ([c544cb3](https://github.com/CrackingShells/Hatch/commit/c544cb3)) * chore(roadmap): add adding-mcp-hosts-skill campaign ([e48ea10](https://github.com/CrackingShells/Hatch/commit/e48ea10)) * chore(skill): package adding-mcp-hosts skill ([e5fbfa2](https://github.com/CrackingShells/Hatch/commit/e5fbfa2)) --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8acdc4a..67db21f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +## 0.8.1-dev.1 (2026-02-26) + +* Merge branch 'feat/opencode-mcp-host-support' into dev ([793707d](https://github.com/CrackingShells/Hatch/commit/793707d)) +* Merge branch 'milestone/adding-mcp-hosts-skill' into dev ([bce3851](https://github.com/CrackingShells/Hatch/commit/bce3851)) +* Merge branch 'task/update-extension-guide' into milestone/mcp-docs-refresh ([d2a0df9](https://github.com/CrackingShells/Hatch/commit/d2a0df9)) +* Merge branch 'task/write-adapter-contract' into milestone/adding-mcp-hosts-skill ([c639322](https://github.com/CrackingShells/Hatch/commit/c639322)) +* Merge branch 'task/write-skill-md' into milestone/adding-mcp-hosts-skill ([d618f71](https://github.com/CrackingShells/Hatch/commit/d618f71)) +* Merge branch 'task/write-strategy-contract' into milestone/adding-mcp-hosts-skill ([13b195c](https://github.com/CrackingShells/Hatch/commit/13b195c)) +* Merge branch 'task/write-testing-fixtures' into milestone/adding-mcp-hosts-skill ([3cc4175](https://github.com/CrackingShells/Hatch/commit/3cc4175)) +* Merge pull request #47 from LittleCoinCoin/dev ([9d873aa](https://github.com/CrackingShells/Hatch/commit/9d873aa)), closes [#47](https://github.com/CrackingShells/Hatch/issues/47) +* fix(mcp-opencode): anchor JSONC comment regex in write_configuration ([a35d3a2](https://github.com/CrackingShells/Hatch/commit/a35d3a2)) +* fix(mcp-opencode): anchor JSONC comment regex to line start ([d8f3a75](https://github.com/CrackingShells/Hatch/commit/d8f3a75)) +* fix(mcp-opencode): make serialize() canonical-form; add test fixes ([ee1d915](https://github.com/CrackingShells/Hatch/commit/ee1d915)) +* test(mcp-fixtures): add opencode entry to canonical_configs.json ([5ae3b57](https://github.com/CrackingShells/Hatch/commit/5ae3b57)) +* test(mcp-fixtures): register opencode in host_registry.py ([734b3c0](https://github.com/CrackingShells/Hatch/commit/734b3c0)) +* feat(mcp-adapter): add OpenCodeAdapter with serialize transforms ([28e3bdf](https://github.com/CrackingShells/Hatch/commit/28e3bdf)) +* feat(mcp-fields): add OPENCODE_FIELDS constant ([b9ddf43](https://github.com/CrackingShells/Hatch/commit/b9ddf43)) +* feat(mcp-models): add opencode oauth fields to MCPServerConfig ([2bae600](https://github.com/CrackingShells/Hatch/commit/2bae600)) +* feat(mcp-registry): register OpenCodeAdapter in adapter registry ([20e0fc8](https://github.com/CrackingShells/Hatch/commit/20e0fc8)) +* feat(mcp-strategy): add OpenCodeHostStrategy with JSONC read/write ([8bb590a](https://github.com/CrackingShells/Hatch/commit/8bb590a)) +* feat(mcp-wiring): add opencode to backup and reporting ([7d0b075](https://github.com/CrackingShells/Hatch/commit/7d0b075)) +* feat(skill): add adapter contract reference ([336fced](https://github.com/CrackingShells/Hatch/commit/336fced)) +* feat(skill): add discovery guide reference ([8061c5f](https://github.com/CrackingShells/Hatch/commit/8061c5f)) +* feat(skill): add strategy contract reference ([cf9b807](https://github.com/CrackingShells/Hatch/commit/cf9b807)) +* feat(skill): add testing fixtures reference ([070894c](https://github.com/CrackingShells/Hatch/commit/070894c)) +* feat(skill): write SKILL.md with 5-step workflow ([8984a3a](https://github.com/CrackingShells/Hatch/commit/8984a3a)) +* docs(adding-mcp-hosts): use parallel research over priority ladder ([6f6165a](https://github.com/CrackingShells/Hatch/commit/6f6165a)) +* docs(mcp): document strategy, registration, and variant pattern ([21c30d5](https://github.com/CrackingShells/Hatch/commit/21c30d5)) +* docs(mcp): rewrite testing section with data-driven docs ([5fc6f97](https://github.com/CrackingShells/Hatch/commit/5fc6f97)) +* docs(mcp): rewrite testing section with data-driven infra ([24c6ebf](https://github.com/CrackingShells/Hatch/commit/24c6ebf)) +* docs(mcp): update adapter template to validate_filtered() ([69d61cc](https://github.com/CrackingShells/Hatch/commit/69d61cc)) +* docs(mcp): update field support matrix and field mapping documentation ([c08e064](https://github.com/CrackingShells/Hatch/commit/c08e064)) +* docs(mcp): update strategy template with interface docs ([0b83b6e](https://github.com/CrackingShells/Hatch/commit/0b83b6e)) +* docs(roadmap): add mcp-docs-refresh task files and gap analysis ([896f4d2](https://github.com/CrackingShells/Hatch/commit/896f4d2)) +* docs(roadmap): mark adding-mcp-hosts-skill campaign as done ([b7e6c95](https://github.com/CrackingShells/Hatch/commit/b7e6c95)) +* docs(roadmap): mark mcp-docs-refresh tasks as done ([fc07cd1](https://github.com/CrackingShells/Hatch/commit/fc07cd1)) +* chore: cleanup `__reports__/` ([1056e52](https://github.com/CrackingShells/Hatch/commit/1056e52)) +* chore: move skills directory location ([f739fed](https://github.com/CrackingShells/Hatch/commit/f739fed)) +* chore: update cs-playbook submodule ([c544cb3](https://github.com/CrackingShells/Hatch/commit/c544cb3)) +* chore(roadmap): add adding-mcp-hosts-skill campaign ([e48ea10](https://github.com/CrackingShells/Hatch/commit/e48ea10)) +* chore(skill): package adding-mcp-hosts skill ([e5fbfa2](https://github.com/CrackingShells/Hatch/commit/e5fbfa2)) + ## 0.8.0 (2026-02-20) * Merge pull request #44 from LittleCoinCoin/dev ([1157922](https://github.com/CrackingShells/Hatch/commit/1157922)), closes [#44](https://github.com/CrackingShells/Hatch/issues/44) diff --git a/pyproject.toml b/pyproject.toml index a486279..737a82e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hatch-xclam" -version = "0.8.0" +version = "0.8.1-dev.1" description = "Package manager for the Cracking Shells ecosystem" readme = "README.md" requires-python = ">=3.12" From 038be8c01b06644e68106c141ddd808743499107 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 17:28:29 +0900 Subject: [PATCH 33/71] chore: clean up temporary reports --- .../docs-vs-codebase-gap-analysis.md | 118 ---------- .../discovery-questionnaire.md | 205 ------------------ .../skill-design-analysis.md | 124 ----------- 3 files changed, 447 deletions(-) delete mode 100644 __reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md delete mode 100644 __reports__/mcp_support_extension_skill/discovery-questionnaire.md delete mode 100644 __reports__/mcp_support_extension_skill/skill-design-analysis.md diff --git a/__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md b/__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md deleted file mode 100644 index c82d5b6..0000000 --- a/__reports__/mcp-docs-refresh/docs-vs-codebase-gap-analysis.md +++ /dev/null @@ -1,118 +0,0 @@ -# Gap Analysis: MCP Host Config Dev Docs vs Codebase - -## Problem Statement - -The developer documentation for the MCP host configuration system (`mcp_host_configuration.md` and `mcp_host_configuration_extension.md`) has fallen behind the codebase, particularly in the testing infrastructure sections. This creates misleading guidance for contributors adding new host support. - -## Evidence - -Systematic comparison of the three dev docs against the current codebase state (commit `c544cb3` on `dev`). - -### Tested Docs - -| Doc | Path | -|:----|:-----| -| Architecture | `docs/articles/devs/architecture/mcp_host_configuration.md` | -| Extension Guide | `docs/articles/devs/implementation_guides/mcp_host_configuration_extension.md` | -| Testing Standards | `docs/articles/devs/development_processes/testing_standards.md` | - ---- - -## Findings - -### 1. Testing Infrastructure (Severity: High) - -The testing section in both docs is significantly behind the actual implementation. - -#### 1a. Data-driven infrastructure undocumented - -| Component | Location | Lines | Docs Coverage | -|:----------|:---------|------:|:--------------| -| `HostSpec` dataclass | `tests/test_data/mcp_adapters/host_registry.py` | ~95 | Brief mention, no detail | -| `HostRegistry` class (5+ methods) | `tests/test_data/mcp_adapters/host_registry.py` | ~77 | Brief mention, no detail | -| 3 generator functions | `tests/test_data/mcp_adapters/host_registry.py` | ~83 | Not documented | -| 8 assertion functions | `tests/test_data/mcp_adapters/assertions.py` | ~143 | Not documented | -| Canonical fixture data | `tests/test_data/mcp_adapters/canonical_configs.json` | full | Not documented | -| `CODEX_REVERSE_MAPPINGS` | `tests/test_data/mcp_adapters/host_registry.py:61` | ~3 | Not documented | - -#### 1b. "Zero test code changes" claim is misleading - -The extension guide claims adding a new host requires zero test code changes. In reality it requires: - -- Adding an entry to `canonical_configs.json` -- Adding a `HostSpec` entry in `host_registry.py` (lines 226-242 in `FIELD_SETS` dict + `HostRegistry._build_specs()`) - -#### 1c. Test count undersold - -- Docs say "211+ tests" -- Actual auto-generated count: ~285 (64 sync pairs + 10 validation + 211 field filtering) - -#### 1d. Deprecated test files unacknowledged - -Two files marked `@pytest.mark.skip` and scheduled for removal in v0.9.0: - -- `tests/integration/mcp/test_adapter_serialization.py` -- `tests/regression/mcp/test_field_filtering.py` - -Neither mentioned in any documentation. - ---- - -### 2. Architecture Doc Gaps (Severity: Medium-High) - -#### 2a. Field support matrix incomplete - -Missing from the matrix: - -- **Gemini**: ~10 OAuth fields (`oauth_enabled`, `oauth_clientId`, `oauth_clientSecret`, `oauth_scopes`, `oauth_redirectUrl`, `oauth_tokenUrl`, `oauth_authUrl`, `oauth_extraParams`) -- **Codex**: advanced fields (`cwd`, `env_vars`, `startup_timeout_sec`, `bearer_token_env_var`, `http_headers`) - -#### 2b. `CODEX_FIELD_MAPPINGS` incomplete - -Docs show 2 mappings (`args`->`arguments`, `headers`->`http_headers`). -Actual code has 4 (also `includeTools`->`enabled_tools`, `excludeTools`->`disabled_tools`). - -#### 2c. Missing architectural patterns - -| Pattern | Status | -|:--------|:-------| -| `@register_host_strategy(MCPHostType.X)` decorator | Not documented | -| `MCPHostStrategy` base class interface | Not documented | -| `ClaudeAdapter` variant parameter (`desktop` vs `code`) | Not documented | - -#### 2d. `validate()` deprecation inconsistency - -- Architecture doc marks `validate()` as deprecated (v0.9.0) -- Extension guide Step 2 template still uses `validate()`, not `validate_filtered()` -- No migration guidance provided - ---- - -### 3. What Is Accurate - -- Module organization matches exactly -- 8 host names/enum values correct -- `UNIVERSAL_FIELDS`, per-host field constants match -- `BaseAdapter` protocol methods match -- Three-layer architecture description accurate -- `EXCLUDED_ALWAYS` pattern correct - ---- - -## Root Cause - -The testing infrastructure was overhauled to a data-driven architecture (HostRegistry, generators, property-based assertions) but the documentation was not updated to reflect these changes. The architecture doc and extension guide were written against the pre-overhaul state. - -## Impact Assessment - -- **Scope**: Architecture doc testing section, extension guide Step 2-4, field support matrix -- **Dependencies**: Any contributor following the extension guide to add a new host will get incomplete guidance on testing requirements -- **Risk**: New host contributions may skip fixture/registry setup, causing CI gaps - -## Recommendations - -1. Rewrite the architecture doc testing section to document the full data-driven infrastructure -2. Rewrite the extension guide Step 4 with actual testing requirements -3. Fix validate() → validate_filtered() in templates -4. Complete the field support matrix -5. Document missing architectural patterns (strategy decorator, MCPHostStrategy interface, ClaudeAdapter variant) diff --git a/__reports__/mcp_support_extension_skill/discovery-questionnaire.md b/__reports__/mcp_support_extension_skill/discovery-questionnaire.md deleted file mode 100644 index 8214efa..0000000 --- a/__reports__/mcp_support_extension_skill/discovery-questionnaire.md +++ /dev/null @@ -1,205 +0,0 @@ -# Discovery Questionnaire: Adding a New MCP Host - -## Purpose - -Exhaustive list of information the agent needs to add support for a new MCP host platform. Derived from tracing every codepath that makes a host-specific decision across 10+ source files. - -This questionnaire serves two purposes: -1. **Discovery checklist** — what the agent must find via web research or codebase retrieval -2. **User escalation template** — what to ask the user when discovery tools are unavailable or insufficient - ---- - -## Category A: Host Identity & Config Location - -Discoverable via official docs, GitHub repos, or config file examples. - -| # | Question | Why it matters | File(s) affected | -|---|----------|----------------|------------------| -| A1 | What is the host's canonical name? (e.g., `"kiro"`, `"claude-desktop"`) | Becomes the `MCPHostType` enum value and the adapter `host_name`. Convention: lowercase with hyphens. | `models.py`, every other file | -| A2 | Where is the config file on each platform? (macOS, Linux, Windows paths) | Strategy `get_config_path()` — every existing host has platform-specific path logic. | `strategies.py` | -| A3 | What is the config file format? (JSON or TOML) | Determines strategy `read_configuration()` / `write_configuration()` implementation and which strategy family to inherit from. Only Codex uses TOML; all others use JSON. | `strategies.py` | -| A4 | What is the root key for MCP servers in the config file? | Strategy `get_config_key()`. Known values: `"mcpServers"` (most hosts), `"servers"` (VSCode), `"mcp_servers"` (Codex). | `strategies.py` | -| A5 | How to detect if host is installed on the system? | Strategy `is_host_available()`. Most hosts check for a directory's existence (e.g., `~/.kiro/settings/`). | `strategies.py` | - -### Existing host examples for reference - -| Host | Format | Root Key | macOS Path | Detection | -|------|--------|----------|------------|-----------| -| claude-desktop | JSON | `mcpServers` | `~/Library/Application Support/Claude/claude_desktop_config.json` | Config parent dir exists | -| claude-code | JSON | `mcpServers` | `~/.claude.json` | File exists | -| vscode | JSON | `servers` | `~/Library/Application Support/Code/User/mcp.json` | Code dir exists | -| cursor | JSON | `mcpServers` | `~/.cursor/mcp.json` | `.cursor/` exists | -| lmstudio | JSON | `mcpServers` | `~/.lmstudio/mcp.json` | `.lmstudio/` exists | -| gemini | JSON | `mcpServers` | `~/.gemini/settings.json` | `.gemini/` exists | -| kiro | JSON | `mcpServers` | `~/.kiro/settings/mcp.json` | `.kiro/settings/` exists | -| codex | TOML | `mcp_servers` | `~/.codex/config.toml` | `.codex/` exists | - ---- - -## Category B: Field Support - -Partially discoverable from host documentation. This is where web research helps most — host docs usually list supported config fields. - -| # | Question | Why it matters | File(s) affected | -|---|----------|----------------|------------------| -| B1 | Which transport types does the host support? (stdio, sse, http) | Drives validation rules in `validate_filtered()`. Most hosts support stdio + sse. Gemini also supports http via `httpUrl`. | adapter | -| B2 | Does the host support the `type` discriminator field? (the `"type": "stdio"` / `"sse"` field) | Determines whether host joins `TYPE_SUPPORTING_HOSTS` in `fields.py`. Claude, VSCode, Cursor, LM Studio support it. Gemini, Kiro, Codex do not. | `fields.py` | -| B3 | What host-specific fields exist beyond the universal set? (List each with: field name, type, description, required/optional) | Defines the field set constant in `fields.py` and potentially new `MCPServerConfig` field declarations in `models.py`. | `fields.py`, `models.py` | -| B4 | Does the host use different names for standard fields? (e.g., Codex uses `arguments` instead of `args`, `http_headers` instead of `headers`) | Determines whether a `FIELD_MAPPINGS` dict and `apply_transformations()` override are needed. | `fields.py`, adapter | -| B5 | Are there fields semantically equivalent to another host's fields? (e.g., Gemini `includeTools` ≈ Codex `enabled_tools`) | Cross-host sync field mappings. Codex maps `includeTools` → `enabled_tools` and `excludeTools` → `disabled_tools` to enable transparent Gemini→Codex sync. Without mappings, sync silently drops the field. | `fields.py`, adapter | - -### Universal fields (supported by ALL hosts) - -Every host inherits these 5 fields from `UNIVERSAL_FIELDS`: - -| Field | Type | Description | -|-------|------|-------------| -| `command` | `str` | stdio transport — command to execute | -| `args` | `List[str]` | Command arguments | -| `env` | `Dict[str, str]` | Environment variables | -| `url` | `str` | sse/http transport — server URL | -| `headers` | `Dict[str, str]` | HTTP headers for remote transports | - ---- - -## Category C: Validation & Serialization Rules - -Often discoverable from host documentation, sometimes ambiguous. - -| # | Question | Why it matters | File(s) affected | -|---|----------|----------------|------------------| -| C1 | Transport mutual exclusion: can the host have multiple transports simultaneously, or exactly one? | Core validation logic in `validate_filtered()`. Most hosts require exactly one. Gemini supports three (`command`, `url`, `httpUrl`) but still requires exactly one at a time. | adapter | -| C2 | Are any fields mutually exclusive? (beyond transports) | Additional validation rules in `validate_filtered()`. | adapter | -| C3 | Are any fields conditionally required? (e.g., "if `oauth_enabled` is true, then `oauth_clientId` is required") | Additional validation rules in `validate_filtered()`. | adapter | -| C4 | Does serialization require structural transformation beyond field renaming? (e.g., nesting fields under a sub-key, wrapping transport in a sub-object) | Whether a custom `serialize()` override is needed instead of the standard filter→validate→transform pipeline. | adapter | -| C5 | Does the config file contain non-MCP sections that must be preserved on write? (e.g., Codex preserves `[features]`, Gemini preserves other settings keys) | Strategy `write_configuration()` must read-before-write and merge, not overwrite. | `strategies.py` | - ---- - -## Category D: Architectural Fit - -Requires judgment based on comparing the new host against existing implementations. Rarely discoverable from external docs alone. - -| # | Question | Why it matters | File(s) affected | -|---|----------|----------------|------------------| -| D1 | Is this host functionally identical to an existing host? (same fields, same validation, different name only) | Variant pattern: reuse an existing adapter class with a `variant` constructor parameter (like `ClaudeAdapter(variant="desktop")` / `ClaudeAdapter(variant="code")`) instead of creating a new class. | adapter, `registry.py` | -| D2 | Does this host share config format and I/O logic with an existing host? | Strategy family: inherit from `ClaudeHostStrategy` or `CursorBasedHostStrategy` instead of bare `MCPHostStrategy`, getting `read_configuration()` and `write_configuration()` for free. | `strategies.py` | - -### Existing strategy families - -| Family Base Class | Members | What it provides | -|-------------------|---------|------------------| -| `ClaudeHostStrategy` | `ClaudeDesktopStrategy`, `ClaudeCodeStrategy` | Shared JSON read/write, `_preserve_claude_settings()` | -| `CursorBasedHostStrategy` | `CursorHostStrategy`, `LMStudioHostStrategy` | Shared Cursor-format JSON read/write | -| `MCPHostStrategy` (standalone) | `VSCodeHostStrategy`, `GeminiHostStrategy`, `KiroHostStrategy`, `CodexHostStrategy` | No shared logic — each implements its own I/O | - ---- - -## 5. Escalation Tiers - -When the agent must ask the user (because discovery tools are unavailable or returned insufficient data), present questions in tiers to avoid overwhelming with a single wall of questions. - -### Tier 1: Blocking — cannot proceed without answers - -Ask these first. Every answer feeds directly into a required file modification. - -| Question IDs | Summary | -|--------------|---------| -| A1 | Host canonical name | -| A2 | Config file path per platform | -| A3 | Config file format (JSON/TOML) | -| A4 | Root key for MCP servers | -| B1 | Supported transport types | -| B3 | Host-specific fields (names, types, descriptions) | - -### Tier 2: Important — ask if Tier 1 reveals complexity - -Ask these after Tier 1 if the host has non-standard behavior. - -| Question IDs | Trigger condition | -|--------------|-------------------| -| B4 | Host uses different names for standard fields | -| B5 | Host has tool filtering fields that map to another host's equivalents | -| C1 | Unclear whether transports are mutually exclusive | -| C4 | Config format requires structural nesting beyond flat key-value | -| C5 | Config file has non-MCP sections | - -### Tier 3: Clarification — ask only if ambiguous - -Ask these only if reading existing adapters and strategies leaves the answer unclear. - -| Question IDs | Trigger condition | -|--------------|-------------------| -| A5 | Host detection mechanism is non-obvious | -| B2 | Unclear whether host uses `type` discriminator field | -| C2 | Possible field mutual exclusion beyond transports | -| C3 | Possible conditional field requirements | -| D1 | Host looks identical to an existing one | -| D2 | Host I/O looks similar to an existing strategy family | - ---- - -## 6. Discovery Output Format - -Whether the information comes from web research or user answers, the agent should produce a structured **Host Spec** before proceeding to implementation. This artifact feeds steps 2-5 of the skill workflow. - -```yaml -host: - name: "your-host" # A1 - config_format: "json" # A3 - config_key: "mcpServers" # A4 - -paths: # A2 - darwin: "~/.your-host/config.json" - linux: "~/.config/your-host/config.json" - windows: "~/.your-host/config.json" - -detection: # A5 - method: "directory_exists" - path: "~/.your-host/" - -transports: # B1 - supported: ["stdio", "sse"] - mutual_exclusion: true # C1 - -fields: # B2, B3 - type_discriminator: true - host_specific: - - name: "your_field" - type: "Optional[str]" - description: "Description" - - name: "another_field" - type: "Optional[bool]" - description: "Description" - -field_mappings: {} # B4, B5 (empty if no mappings) - -validation: # C2, C3 - mutual_exclusions: [] - conditional_requirements: [] - -serialization: # C4 - structural_transform: false - -config_file: # C5 - preserved_sections: [] - -architecture: # D1, D2 - variant_of: null - strategy_family: null -``` - ---- - -## 7. Key Design Decisions for Skill Authoring - -| Decision | Recommendation | Rationale | -|----------|---------------|-----------| -| Architecture doc content in skill? | No — stays as developer documentation | Agent doesn't need architectural understanding to follow the recipe | -| Field support matrix in skill? | No — agent reads `fields.py` directly | Avoids stale duplication; agent can inspect the source of truth | -| MCPServerConfig model listing? | No — agent reads `models.py` directly | Same rationale | -| Testing infrastructure deep-dive? | Minimal — just "add these fixtures, run these commands" | Agent doesn't need to understand generators to add fixture data | -| Discovery step as first step? | Yes | Biggest bottleneck is knowing what fields the host supports; makes the rest mechanical | -| Structured output from discovery? | Yes — Host Spec YAML | Decouples information gathering from implementation; same spec whether from web or user | -| Progressive disclosure? | Yes — adapter/strategy/testing contracts in `references/` | Keeps SKILL.md lean; loaded only when host has non-standard needs | diff --git a/__reports__/mcp_support_extension_skill/skill-design-analysis.md b/__reports__/mcp_support_extension_skill/skill-design-analysis.md deleted file mode 100644 index 0621fc9..0000000 --- a/__reports__/mcp_support_extension_skill/skill-design-analysis.md +++ /dev/null @@ -1,124 +0,0 @@ -# Skill Design Analysis: MCP Host Configuration Extension - -## Purpose - -Design report for converting the MCP host configuration extension workflow into a Claude Code agent skill. The skill enables an LLM agent to autonomously add support for a new MCP host platform to the Hatch CLI. - ---- - -## 1. Skill Relevance Assessment - -### Why it fits - -The core use case — "add support for a new MCP host" — is a bounded, repeatable, multi-step procedure with low tolerance for deviation. That is the sweet spot for skills. An agent needs: - -1. Procedural steps it cannot infer (which files to touch, in what order) -2. Codebase-specific contracts (field set declaration, registry wiring, decorator usage) -3. Template code that must follow exact patterns (the `validate_filtered()` pipeline, `@register_host_strategy`) -4. Testing fixture requirements (`canonical_configs.json` structure, `FIELD_SETS` mapping) - -None of this is general knowledge. An LLM cannot derive it from first principles. - -### What does not translate directly from existing docs - -The two developer docs (`mcp_host_configuration.md` and `mcp_host_configuration_extension.md`) cannot be transplanted as-is. They need restructuring to match how skills work. - -#### 1. Architecture doc is reference material, not instructions - -It explains *how the system works*, not *what to do*. In skill terms, it belongs in `references/`, not in SKILL.md. Much of it is context Claude already handles well — what Pydantic does, what ABC means, what "declarative" means. The field support matrix and testing infrastructure sections are the only parts an agent genuinely needs. - -#### 2. Extension guide has too much "why" - -Sections like "When You Need This", "The Pattern: Adapter + Strategy" overview, the ASCII diagram, the troubleshooting table, and the "Reference: Existing Adapters" table are developer onboarding material. An agent skill should be imperative: "do X, then Y." The existing doc explains concepts; a skill should prescribe actions. - -#### 3. Both docs use relative file paths - -They say `models.py` and `adapters/your_host.py`. A skill needs absolute paths from the repo root (`hatch/mcp_host_config/models.py`) so the agent can operate without ambiguity. - -#### 4. No verification workflow - -The extension guide says *what* to create but does not tell the agent how to verify its work. A skill should include the exact commands to run after each step. - -#### 5. Information that should be discovered, not loaded - -The full field support matrix (35 rows), the MCPServerConfig model (50 lines), and the HostSpec/HostRegistry documentation are heavyweight. An agent adding a new host does not need all of that in context — it needs to know *where to look* and *what to do*. The skill should point to files, not reproduce them. - -#### 6. Extension guide understates the integration surface - -The guide advertises 4 files to modify. The actual count is 10-11 (see Section 4). Two of the missing files (`backup.py`, `reporting.py`) are boilerplate one-liners but the agent will miss them without explicit instructions. - ---- - -## 2. Proposed Skill Structure - -``` -add-mcp-host/ -├── SKILL.md # ~150 lines: trigger, 5-step workflow, verification -└── references/ - ├── discovery-guide.md # How to research host requirements using available tools - ├── adapter-contract.md # BaseAdapter interface, validate_filtered pattern, - │ # field mappings, variant pattern - ├── strategy-contract.md # MCPHostStrategy interface, decorator, families - └── testing-fixtures.md # canonical_configs.json schema, FIELD_SETS, - # reverse mappings, what gets auto-generated -``` - -### SKILL.md scope - -- The 5-step checklist (discover → enum → adapter+strategy → wiring → test fixtures) with exact file paths -- Template snippets (lean — just the required method signatures, not full docstrings) -- Verification commands per step -- Conditional reads: "Read `references/adapter-contract.md` if the host needs field mappings or custom serialization" - -### What stays outside the skill - -The architecture doc (`mcp_host_configuration.md`) remains as developer documentation. The skill should reference it for humans but an agent does not need architectural understanding to follow the recipe. - ---- - -## 3. Proposed Workflow: 5 Steps - -The current extension guide has 4 steps. The skill adds a prior discovery step, making 5: - -| Step | Name | Description | -|------|------|-------------| -| 1 | **Discover host requirements** | Research the target host's MCP config spec using web tools, Context7, or user escalation | -| 2 | **Add enum and field set** | `models.py` enum + `fields.py` field constant + optionally new MCPServerConfig fields | -| 3 | **Create adapter and strategy** | Adapter class + strategy class with `@register_host_strategy` | -| 4 | **Wire integration points** | `adapters/__init__.py`, `adapters/registry.py`, `backup.py`, `reporting.py` | -| 5 | **Register test fixtures** | `canonical_configs.json` + `host_registry.py` entries | - -Step 1's output (a structured field spec) feeds all decisions in steps 2-5. - -### Discovery step: tool priority ladder - -The agent should try discovery tools in order and fall through gracefully: - -1. **Web search + fetch** — find the host's official MCP config docs -2. **Context7** — query library documentation -3. **Codebase retrieval** — check if the host's config format is already partially documented in the repo -4. **Escalate to user** — structured questionnaire (see Section 5) - -If web tools are unavailable in the agent's environment, it must escalate immediately. - ---- - -## 4. Complete File Modification Surface - -Every file the agent must touch when adding a new host: - -| # | File (from repo root) | Always? | What to add | -|---|------|---------|-------------| -| 1 | `hatch/mcp_host_config/models.py` | Yes | `MCPHostType` enum value | -| 2 | `hatch/mcp_host_config/fields.py` | Yes | Field set constant (e.g., `NEW_HOST_FIELDS`), optionally field mappings dict | -| 3 | `hatch/mcp_host_config/adapters/new_host.py` | Yes | New adapter class (or variant registration if identical to existing host) | -| 4 | `hatch/mcp_host_config/adapters/__init__.py` | Yes | Export new adapter class | -| 5 | `hatch/mcp_host_config/adapters/registry.py` | Yes | `_register_defaults()` entry | -| 6 | `hatch/mcp_host_config/strategies.py` | Yes | Strategy class with `@register_host_strategy` decorator | -| 7 | `hatch/mcp_host_config/backup.py` | Yes | Add hostname string to `supported_hosts` set in `BackupInfo.validate_hostname()` | -| 8 | `hatch/mcp_host_config/reporting.py` | Yes | Add `MCPHostType → host_name` entry in `_get_adapter_host_name()` mapping | -| 9 | `tests/test_data/mcp_adapters/canonical_configs.json` | Yes | Canonical config fixture using host-native field names | -| 10 | `tests/test_data/mcp_adapters/host_registry.py` | Yes | `FIELD_SETS` entry, `adapter_map` entry in `HostSpec.get_adapter()`, optionally reverse mappings | -| 11 | `hatch/mcp_host_config/models.py` (MCPServerConfig) | Conditional | New field declarations — only if host introduces fields not already in the model | - -Files 7 and 8 are boilerplate one-liners but are absent from the current extension guide. The agent will miss them without explicit instructions. From 8b225940d7f8e05d6cfde182fcfe12ff3014631d Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 18:20:24 +0900 Subject: [PATCH 34/71] feat(mcp-augment): add enum value and constant - Add MCPHostType.AUGMENT = "augment" enum member - Add "augment" to EnvironmentPackageEntry.validate_host_names supported set - Add AUGMENT_FIELDS = CLAUDE_FIELDS (command/args/env/url/headers/type) - Add "augment" to TYPE_SUPPORTING_HOSTS (type discriminator is used) Co-Authored-By: Claude Sonnet 4.6 --- hatch/mcp_host_config/fields.py | 5 +++++ hatch/mcp_host_config/models.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/hatch/mcp_host_config/fields.py b/hatch/mcp_host_config/fields.py index 880dbe2..b574c9c 100644 --- a/hatch/mcp_host_config/fields.py +++ b/hatch/mcp_host_config/fields.py @@ -34,6 +34,7 @@ "claude-code", "vscode", "cursor", + "augment", } ) @@ -116,6 +117,10 @@ ) +# Fields supported by Augment Code (auggie CLI + extensions); same as Claude fields +# Config: ~/.augment/settings.json, key: mcpServers +AUGMENT_FIELDS: FrozenSet[str] = CLAUDE_FIELDS + # Fields supported by OpenCode (no type field; uses local/remote type derivation, # command array merge, environment rename, and oauth nesting) OPENCODE_FIELDS: FrozenSet[str] = UNIVERSAL_FIELDS | frozenset( diff --git a/hatch/mcp_host_config/models.py b/hatch/mcp_host_config/models.py index ecc6218..ccc7b75 100644 --- a/hatch/mcp_host_config/models.py +++ b/hatch/mcp_host_config/models.py @@ -31,6 +31,7 @@ class MCPHostType(str, Enum): KIRO = "kiro" CODEX = "codex" OPENCODE = "opencode" + AUGMENT = "augment" class MCPServerConfig(BaseModel): @@ -366,6 +367,7 @@ def validate_host_names(cls, v): "gemini", "kiro", "opencode", + "augment", } for host_name in v.keys(): if host_name not in supported_hosts: From 5af34d1c81cc23c6bcb3940ca9b64fdd49222dd1 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 18:20:33 +0900 Subject: [PATCH 35/71] feat(mcp-augment): implement AugmentAdapter New adapter for Augment Code (auggie CLI + extensions) following the LMStudio pattern. Validates exactly-one transport (command XOR url) and type-field consistency (type=stdio requires command, etc.). No field mappings or structural transforms needed. Co-Authored-By: Claude Sonnet 4.6 --- hatch/mcp_host_config/adapters/augment.py | 119 ++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 hatch/mcp_host_config/adapters/augment.py diff --git a/hatch/mcp_host_config/adapters/augment.py b/hatch/mcp_host_config/adapters/augment.py new file mode 100644 index 0000000..7853447 --- /dev/null +++ b/hatch/mcp_host_config/adapters/augment.py @@ -0,0 +1,119 @@ +"""Augment Code adapter for MCP host configuration. + +Augment Code (auggie CLI + extensions) uses the same field set as Claude: +command/args/env for stdio, url/headers for sse/http, with optional type discriminator. +Config file: ~/.augment/settings.json, root key: mcpServers. +""" + +from typing import Any, Dict, FrozenSet + +from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter +from hatch.mcp_host_config.fields import AUGMENT_FIELDS +from hatch.mcp_host_config.models import MCPServerConfig + + +class AugmentAdapter(BaseAdapter): + """Adapter for Augment Code MCP host. + + Augment Code uses the same configuration format as Claude: + - Supports 'type' field for transport discrimination + - Requires exactly one transport (command XOR url) + """ + + @property + def host_name(self) -> str: + """Return the host identifier.""" + return "augment" + + def get_supported_fields(self) -> FrozenSet[str]: + """Return fields supported by Augment Code.""" + return AUGMENT_FIELDS + + def validate(self, config: MCPServerConfig) -> None: + """Validate configuration for Augment Code. + + DEPRECATED: This method is deprecated and will be removed in v0.9.0. + Use validate_filtered() instead. + """ + has_command = config.command is not None + has_url = config.url is not None + + if not has_command and not has_url: + raise AdapterValidationError( + "Either 'command' (local) or 'url' (remote) must be specified", + host_name=self.host_name, + ) + + if has_command and has_url: + raise AdapterValidationError( + "Cannot specify both 'command' and 'url' - choose one transport", + host_name=self.host_name, + ) + + if config.type is not None: + if config.type == "stdio" and not has_command: + raise AdapterValidationError( + "type='stdio' requires 'command' field", + field="type", + host_name=self.host_name, + ) + if config.type in ("sse", "http") and not has_url: + raise AdapterValidationError( + f"type='{config.type}' requires 'url' field", + field="type", + host_name=self.host_name, + ) + + def validate_filtered(self, filtered: Dict[str, Any]) -> None: + """Validate filtered configuration for Augment Code. + + Validates only fields that survived filtering (supported by Augment). + Augment Code requires exactly one transport (command XOR url). + + Args: + filtered: Dictionary of filtered fields + + Raises: + AdapterValidationError: If validation fails + """ + has_command = "command" in filtered + has_url = "url" in filtered + + if not has_command and not has_url: + raise AdapterValidationError( + "Either 'command' (local) or 'url' (remote) must be specified", + host_name=self.host_name, + ) + + if has_command and has_url: + raise AdapterValidationError( + "Cannot specify both 'command' and 'url' - choose one transport", + host_name=self.host_name, + ) + + if "type" in filtered: + config_type = filtered["type"] + if config_type == "stdio" and not has_command: + raise AdapterValidationError( + "type='stdio' requires 'command' field", + field="type", + host_name=self.host_name, + ) + if config_type in ("sse", "http") and not has_url: + raise AdapterValidationError( + f"type='{config_type}' requires 'url' field", + field="type", + host_name=self.host_name, + ) + + def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: + """Serialize configuration for Augment Code format. + + Follows the validate-after-filter pattern: + 1. Filter to supported fields + 2. Validate filtered fields + 3. Return filtered (no transformations needed) + """ + filtered = self.filter_fields(config) + self.validate_filtered(filtered) + return filtered From b13d9d02aad031139568a22a36059341da6dcb8b Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 18:20:40 +0900 Subject: [PATCH 36/71] feat(mcp-augment): implement AugmentHostStrategy Extends ClaudeHostStrategy for ~/.augment/settings.json with mcpServers key. Same JSON format and settings-preservation logic as Claude family. Path is identical on macOS and Linux (no platform dispatch needed); returns None for native Windows (WSL uses the Linux path). Co-Authored-By: Claude Sonnet 4.6 --- hatch/mcp_host_config/strategies.py | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/hatch/mcp_host_config/strategies.py b/hatch/mcp_host_config/strategies.py index 1e11e64..69ebcd2 100644 --- a/hatch/mcp_host_config/strategies.py +++ b/hatch/mcp_host_config/strategies.py @@ -1053,3 +1053,33 @@ def write_configuration( except Exception as e: logger.error(f"Failed to write OpenCode configuration: {e}") return False + + +@register_host_strategy(MCPHostType.AUGMENT) +class AugmentHostStrategy(ClaudeHostStrategy): + """Configuration strategy for Augment Code (auggie CLI + extensions). + + Augment Code stores MCP configuration in ~/.augment/settings.json under + the 'mcpServers' key -- the same format as Claude. The settings.json file + may contain other non-MCP Augment settings which are preserved via the + inherited _preserve_claude_settings() mechanism. + """ + + def get_adapter_host_name(self) -> str: + """Return the adapter host name for Augment Code.""" + return "augment" + + def get_config_path(self) -> Optional[Path]: + """Get Augment Code configuration path. + + Same path on macOS, Linux, and Windows WSL. + Native Windows (non-WSL) is not yet confirmed and returns None. + """ + system = platform.system() + if system in ("Darwin", "Linux"): + return Path.home() / ".augment" / "settings.json" + return None + + def is_host_available(self) -> bool: + """Check if Augment Code is installed by checking for ~/.augment/ directory.""" + return (Path.home() / ".augment").exists() From 367b736c07ed8b99ae2ecb80dac42e12c52b4540 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 18:23:03 +0900 Subject: [PATCH 37/71] feat(mcp-augment): wire AugmentAdapter into integration points Four one-liner additions required per the adapter contract: - adapters/__init__.py: import AugmentAdapter + __all__ entry - adapters/registry.py: register(AugmentAdapter()) in _register_defaults() - backup.py: "augment" added to BackupInfo.validate_hostname supported set - reporting.py: MCPHostType.AUGMENT mapped in _get_adapter_host_name() Co-Authored-By: Claude Sonnet 4.6 --- hatch/mcp_host_config/adapters/__init__.py | 2 ++ hatch/mcp_host_config/adapters/registry.py | 2 ++ hatch/mcp_host_config/backup.py | 1 + hatch/mcp_host_config/reporting.py | 1 + 4 files changed, 6 insertions(+) diff --git a/hatch/mcp_host_config/adapters/__init__.py b/hatch/mcp_host_config/adapters/__init__.py index ece02f1..8e5dc32 100644 --- a/hatch/mcp_host_config/adapters/__init__.py +++ b/hatch/mcp_host_config/adapters/__init__.py @@ -4,6 +4,7 @@ Each adapter handles validation and serialization for a specific MCP host. """ +from hatch.mcp_host_config.adapters.augment import AugmentAdapter from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter from hatch.mcp_host_config.adapters.claude import ClaudeAdapter from hatch.mcp_host_config.adapters.codex import CodexAdapter @@ -28,6 +29,7 @@ "get_adapter", "get_default_registry", # Host-specific adapters + "AugmentAdapter", "ClaudeAdapter", "CodexAdapter", "CursorAdapter", diff --git a/hatch/mcp_host_config/adapters/registry.py b/hatch/mcp_host_config/adapters/registry.py index 88e4458..ed08be2 100644 --- a/hatch/mcp_host_config/adapters/registry.py +++ b/hatch/mcp_host_config/adapters/registry.py @@ -6,6 +6,7 @@ from typing import Dict, List, Optional +from hatch.mcp_host_config.adapters.augment import AugmentAdapter from hatch.mcp_host_config.adapters.base import BaseAdapter from hatch.mcp_host_config.adapters.claude import ClaudeAdapter from hatch.mcp_host_config.adapters.codex import CodexAdapter @@ -55,6 +56,7 @@ def _register_defaults(self) -> None: self.register(KiroAdapter()) self.register(CodexAdapter()) self.register(OpenCodeAdapter()) + self.register(AugmentAdapter()) def register(self, adapter: BaseAdapter) -> None: """Register an adapter instance. diff --git a/hatch/mcp_host_config/backup.py b/hatch/mcp_host_config/backup.py index 7d6ff1d..5ebac87 100644 --- a/hatch/mcp_host_config/backup.py +++ b/hatch/mcp_host_config/backup.py @@ -49,6 +49,7 @@ def validate_hostname(cls, v): "kiro", "codex", "opencode", + "augment", } if v not in supported_hosts: raise ValueError(f"Unsupported hostname: {v}. Supported: {supported_hosts}") diff --git a/hatch/mcp_host_config/reporting.py b/hatch/mcp_host_config/reporting.py index 22a9f8e..d4744aa 100644 --- a/hatch/mcp_host_config/reporting.py +++ b/hatch/mcp_host_config/reporting.py @@ -74,6 +74,7 @@ def _get_adapter_host_name(host_type: MCPHostType) -> str: MCPHostType.KIRO: "kiro", MCPHostType.CODEX: "codex", MCPHostType.OPENCODE: "opencode", + MCPHostType.AUGMENT: "augment", } return mapping.get(host_type, host_type.value) From 294d0d8f64de71aa0097de2371dc17d5af9d5f79 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 18:23:58 +0900 Subject: [PATCH 38/71] test(mcp-augment): register test fixtures and update tests - canonical_configs.json: add augment entry (stdio config with type field) - host_registry.py: AUGMENT_FIELDS in FIELD_SETS, AugmentAdapter in get_adapter(), AUGMENT_FIELDS added to all_possible_fields union for filter test generation - test_adapter_registry.py: add "augment" to expected default hosts set Auto-generates 19+ new test cases: 1 host-config roundtrip, 19 cross-host sync pairs (10x10 - 9x9), 1 transport mutual exclusion, ~28 field filtering. Co-Authored-By: Claude Sonnet 4.6 --- tests/test_data/mcp_adapters/canonical_configs.json | 8 ++++++++ tests/test_data/mcp_adapters/host_registry.py | 5 +++++ tests/unit/mcp/test_adapter_registry.py | 1 + 3 files changed, 14 insertions(+) diff --git a/tests/test_data/mcp_adapters/canonical_configs.json b/tests/test_data/mcp_adapters/canonical_configs.json index b90299f..63dde7f 100644 --- a/tests/test_data/mcp_adapters/canonical_configs.json +++ b/tests/test_data/mcp_adapters/canonical_configs.json @@ -75,6 +75,14 @@ "enabled_tools": ["tool1", "tool2"], "disabled_tools": ["tool3"] }, + "augment": { + "command": "python", + "args": ["-m", "mcp_server"], + "env": {"API_KEY": "test_key"}, + "url": null, + "headers": null, + "type": "stdio" + }, "opencode": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], diff --git a/tests/test_data/mcp_adapters/host_registry.py b/tests/test_data/mcp_adapters/host_registry.py index f1e6ff1..502e2ab 100644 --- a/tests/test_data/mcp_adapters/host_registry.py +++ b/tests/test_data/mcp_adapters/host_registry.py @@ -19,6 +19,7 @@ from pathlib import Path from typing import Any, Dict, FrozenSet, List, Optional, Set, Tuple +from hatch.mcp_host_config.adapters.augment import AugmentAdapter from hatch.mcp_host_config.adapters.base import BaseAdapter from hatch.mcp_host_config.adapters.claude import ClaudeAdapter from hatch.mcp_host_config.adapters.codex import CodexAdapter @@ -29,6 +30,7 @@ from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter from hatch.mcp_host_config.adapters.vscode import VSCodeAdapter from hatch.mcp_host_config.fields import ( + AUGMENT_FIELDS, CLAUDE_FIELDS, CODEX_FIELD_MAPPINGS, CODEX_FIELDS, @@ -58,6 +60,7 @@ "kiro": KIRO_FIELDS, "codex": CODEX_FIELDS, "opencode": OPENCODE_FIELDS, + "augment": AUGMENT_FIELDS, } # Reverse mappings for Codex (host-native name → universal name) @@ -97,6 +100,7 @@ def get_adapter(self) -> BaseAdapter: "kiro": KiroAdapter, "codex": CodexAdapter, "opencode": OpenCodeAdapter, + "augment": AugmentAdapter, } factory = adapter_map[self.host_name] return factory() @@ -355,6 +359,7 @@ def generate_unsupported_field_test_cases( | KIRO_FIELDS | CODEX_FIELDS | OPENCODE_FIELDS + | AUGMENT_FIELDS ) cases: List[FilterTestCase] = [] diff --git a/tests/unit/mcp/test_adapter_registry.py b/tests/unit/mcp/test_adapter_registry.py index b6b7736..a393461 100644 --- a/tests/unit/mcp/test_adapter_registry.py +++ b/tests/unit/mcp/test_adapter_registry.py @@ -31,6 +31,7 @@ def setUp(self): def test_AR01_registry_has_all_default_hosts(self): """AR-01: Registry initializes with all default host adapters.""" expected_hosts = { + "augment", "claude-desktop", "claude-code", "codex", From 3a589080761cf3808f04955c5ff2c66e8413d6c9 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 19:33:15 +0900 Subject: [PATCH 39/71] docs(adding-mcp-hosts): add test_adapter_protocol.py to fixture guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skill's Step 5 only covered data-driven fixtures (canonical_configs.json and host_registry.py), missing the static ALL_ADAPTERS and HOST_ADAPTER_MAP lists in test_adapter_protocol.py. Without this section, new hosts pass all tests while silently lacking AP-01…AP-06 protocol compliance coverage. Co-Authored-By: Claude Opus 4.6 --- .../references/testing-fixtures.md | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.claude/skills/adding-mcp-hosts/references/testing-fixtures.md b/.claude/skills/adding-mcp-hosts/references/testing-fixtures.md index b83e5a3..3785cfe 100644 --- a/.claude/skills/adding-mcp-hosts/references/testing-fixtures.md +++ b/.claude/skills/adding-mcp-hosts/references/testing-fixtures.md @@ -27,7 +27,33 @@ Minimal example (modeled on the `lmstudio` entry, which uses `CLAUDE_FIELDS`): For hosts with extra fields, add them alongside the universals (see `gemini` or `codex` entries for examples with `httpUrl`, `timeout`, `includeTools`, `cwd`, etc.). -## 2. host_registry.py entries +## 2. test_adapter_protocol.py entries + +`tests/unit/mcp/test_adapter_protocol.py` has two **static** lists that are NOT auto-updated by the data-driven infrastructure. Both must be updated manually: + +**`ALL_ADAPTERS`** -- append the new adapter class: + +```python +ALL_ADAPTERS = [ + # ... existing entries ... + NewHostAdapter, +] +``` + +**`HOST_ADAPTER_MAP`** -- add the `MCPHostType → adapter class` mapping: + +```python +HOST_ADAPTER_MAP = { + # ... existing entries ... + MCPHostType.NEW_HOST: NewHostAdapter, +} +``` + +Import `NewHostAdapter` and `MCPHostType.NEW_HOST` at the top of the file alongside the existing imports. Missing either entry means the AP-01…AP-06 protocol compliance tests silently skip the new adapter — they pass without covering it. + +--- + +## 3. host_registry.py entries Make three additions in `tests/test_data/mcp_adapters/host_registry.py`. From 9d7f0e599195480b1d7cd42946c13bdbfd176a42 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 19:33:07 +0900 Subject: [PATCH 40/71] fix(mcp-hosts): close validation and test coverage gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adversarial check of the Augment MCP host integration revealed three gaps: - EnvironmentPackageEntry.validate_host_names() was missing "codex", silently rejecting any package configured for the Codex host - test_adapter_protocol.py ALL_ADAPTERS and HOST_ADAPTER_MAP still had the pre-augment/opencode 8-host list, so AP-01…AP-06 protocol tests never exercised the two newest adapters - AdapterRegistry docstring example showed stale 8-host output Co-Authored-By: Claude Opus 4.6 --- hatch/mcp_host_config/adapters/registry.py | 2 +- hatch/mcp_host_config/models.py | 1 + tests/unit/mcp/test_adapter_protocol.py | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hatch/mcp_host_config/adapters/registry.py b/hatch/mcp_host_config/adapters/registry.py index ed08be2..22a0122 100644 --- a/hatch/mcp_host_config/adapters/registry.py +++ b/hatch/mcp_host_config/adapters/registry.py @@ -34,7 +34,7 @@ class AdapterRegistry: 'claude-desktop' >>> registry.get_supported_hosts() - ['claude-code', 'claude-desktop', 'codex', 'cursor', 'gemini', 'kiro', 'lmstudio', 'vscode'] + ['augment', 'claude-code', 'claude-desktop', 'codex', 'cursor', 'gemini', 'kiro', 'lmstudio', 'opencode', 'vscode'] """ def __init__(self): diff --git a/hatch/mcp_host_config/models.py b/hatch/mcp_host_config/models.py index ccc7b75..610971b 100644 --- a/hatch/mcp_host_config/models.py +++ b/hatch/mcp_host_config/models.py @@ -366,6 +366,7 @@ def validate_host_names(cls, v): "lmstudio", "gemini", "kiro", + "codex", "opencode", "augment", } diff --git a/tests/unit/mcp/test_adapter_protocol.py b/tests/unit/mcp/test_adapter_protocol.py index e733a61..4878878 100644 --- a/tests/unit/mcp/test_adapter_protocol.py +++ b/tests/unit/mcp/test_adapter_protocol.py @@ -9,6 +9,7 @@ from hatch.mcp_host_config.models import MCPServerConfig, MCPHostType from hatch.mcp_host_config.adapters import ( get_adapter, + AugmentAdapter, ClaudeAdapter, CodexAdapter, CursorAdapter, @@ -17,20 +18,24 @@ LMStudioAdapter, VSCodeAdapter, ) +from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter # All adapter classes to test ALL_ADAPTERS = [ + AugmentAdapter, ClaudeAdapter, CodexAdapter, CursorAdapter, GeminiAdapter, KiroAdapter, LMStudioAdapter, + OpenCodeAdapter, VSCodeAdapter, ] # Map host types to their expected adapter classes HOST_ADAPTER_MAP = { + MCPHostType.AUGMENT: AugmentAdapter, MCPHostType.CLAUDE_DESKTOP: ClaudeAdapter, MCPHostType.CLAUDE_CODE: ClaudeAdapter, MCPHostType.CODEX: CodexAdapter, @@ -38,6 +43,7 @@ MCPHostType.GEMINI: GeminiAdapter, MCPHostType.KIRO: KiroAdapter, MCPHostType.LMSTUDIO: LMStudioAdapter, + MCPHostType.OPENCODE: OpenCodeAdapter, MCPHostType.VSCODE: VSCodeAdapter, } From fb2ee4cb8a35f2df1421f1dda8c1ed38e54d3038 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 4 Mar 2026 16:41:15 +0900 Subject: [PATCH 41/71] refactor(logging): remove forced setLevel(INFO) from all module loggers --- hatch/environment_manager.py | 1 - hatch/installers/dependency_installation_orchestrator.py | 1 - hatch/installers/docker_installer.py | 1 - hatch/installers/python_installer.py | 1 - hatch/installers/system_installer.py | 1 - hatch/package_loader.py | 1 - hatch/python_environment_manager.py | 1 - 7 files changed, 7 deletions(-) diff --git a/hatch/environment_manager.py b/hatch/environment_manager.py index 19a5e6f..70413a0 100644 --- a/hatch/environment_manager.py +++ b/hatch/environment_manager.py @@ -62,7 +62,6 @@ def __init__( """ self.logger = logging.getLogger("hatch.environment_manager") - self.logger.setLevel(logging.INFO) # Set up environment directories self.environments_dir = environments_dir or (Path.home() / ".hatch" / "envs") self.environments_dir.mkdir(exist_ok=True) diff --git a/hatch/installers/dependency_installation_orchestrator.py b/hatch/installers/dependency_installation_orchestrator.py index 3be72ba..13ecac1 100644 --- a/hatch/installers/dependency_installation_orchestrator.py +++ b/hatch/installers/dependency_installation_orchestrator.py @@ -67,7 +67,6 @@ def __init__( registry_data (Dict[str, Any]): Registry data for dependency resolution. """ self.logger = logging.getLogger("hatch.dependency_orchestrator") - self.logger.setLevel(logging.INFO) self.package_loader = package_loader self.registry_service = registry_service self.registry_data = registry_data diff --git a/hatch/installers/docker_installer.py b/hatch/installers/docker_installer.py index e3a7da7..150a2dc 100644 --- a/hatch/installers/docker_installer.py +++ b/hatch/installers/docker_installer.py @@ -20,7 +20,6 @@ from .registry import installer_registry logger = logging.getLogger("hatch.installers.docker_installer") -logger.setLevel(logging.INFO) # Handle docker-py import with graceful fallback DOCKER_AVAILABLE = False diff --git a/hatch/installers/python_installer.py b/hatch/installers/python_installer.py index 420471c..70419d2 100644 --- a/hatch/installers/python_installer.py +++ b/hatch/installers/python_installer.py @@ -31,7 +31,6 @@ class PythonInstaller(DependencyInstaller): def __init__(self): """Initialize the PythonInstaller.""" self.logger = logging.getLogger("hatch.installers.python_installer") - self.logger.setLevel(logging.INFO) @property def installer_type(self) -> str: diff --git a/hatch/installers/system_installer.py b/hatch/installers/system_installer.py index 95820bc..1c0f475 100644 --- a/hatch/installers/system_installer.py +++ b/hatch/installers/system_installer.py @@ -33,7 +33,6 @@ class SystemInstaller(DependencyInstaller): def __init__(self): """Initialize the SystemInstaller.""" self.logger = logging.getLogger("hatch.installers.system_installer") - self.logger.setLevel(logging.INFO) @property def installer_type(self) -> str: diff --git a/hatch/package_loader.py b/hatch/package_loader.py index 5ee3a33..24c59db 100644 --- a/hatch/package_loader.py +++ b/hatch/package_loader.py @@ -31,7 +31,6 @@ def __init__(self, cache_dir: Optional[Path] = None): Defaults to ~/.hatch/packages. """ self.logger = logging.getLogger("hatch.package_loader") - self.logger.setLevel(logging.INFO) # Set up cache directory if cache_dir is None: diff --git a/hatch/python_environment_manager.py b/hatch/python_environment_manager.py index 5b4936d..c139f4f 100644 --- a/hatch/python_environment_manager.py +++ b/hatch/python_environment_manager.py @@ -41,7 +41,6 @@ def __init__(self, environments_dir: Optional[Path] = None): Defaults to ~/.hatch/envs. """ self.logger = logging.getLogger("hatch.python_environment_manager") - self.logger.setLevel(logging.INFO) # Set up environment directories self.environments_dir = environments_dir or (Path.home() / ".hatch" / "envs") From df97e586e2bebc4b3ffa49ccf19c670ae53edcf3 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 4 Mar 2026 16:41:40 +0900 Subject: [PATCH 42/71] refactor(registry): demote startup and fetch INFO logs to DEBUG --- hatch/registry_retriever.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hatch/registry_retriever.py b/hatch/registry_retriever.py index d905484..232e96c 100644 --- a/hatch/registry_retriever.py +++ b/hatch/registry_retriever.py @@ -37,7 +37,6 @@ def __init__( local_registry_cache_path (Path, optional): Path to local registry file. Defaults to None. """ self.logger = logging.getLogger("hatch.registry_retriever") - self.logger.setLevel(logging.INFO) self.cache_ttl = cache_ttl self.simulation_mode = simulation_mode @@ -59,7 +58,7 @@ def __init__( # Use file:// URL format for local files self.registry_url = f"file://{str(self.registry_cache_path.absolute())}" - self.logger.info( + self.logger.debug( f"Operating in simulation mode with registry at: {self.registry_cache_path}" ) else: @@ -69,7 +68,7 @@ def __init__( # We'll set the initial URL to today, but might fall back to yesterday self.registry_url = f"https://github.com/CrackingShells/Hatch-Registry/releases/download/{self.today_str}/hatch_packages_registry.json" - self.logger.info( + self.logger.debug( f"Operating in online mode with registry at: {self.registry_url}" ) @@ -180,7 +179,7 @@ def _fetch_remote_registry(self) -> Dict[str, Any]: """ if self.simulation_mode: try: - self.logger.info(f"Fetching registry from {self.registry_url}") + self.logger.debug(f"Fetching registry from {self.registry_url}") with open(self.registry_cache_path, "r") as f: return json.load(f) except Exception as e: @@ -193,7 +192,7 @@ def _fetch_remote_registry(self) -> Dict[str, Any]: self.registry_url = f"https://github.com/CrackingShells/Hatch-Registry/releases/download/{date}/hatch_packages_registry.json" self.is_delayed = False # Reset delayed flag for today's registry else: - self.logger.info( + self.logger.warning( f"Today's registry ({date}) not found, falling back to yesterday's" ) # Fall back to yesterday's registry @@ -211,7 +210,7 @@ def _fetch_remote_registry(self) -> Dict[str, Any]: self.is_delayed = True # Set delayed flag for yesterday's registry try: - self.logger.info(f"Fetching registry from {self.registry_url}") + self.logger.debug(f"Fetching registry from {self.registry_url}") response = requests.get(self.registry_url, timeout=30) response.raise_for_status() return response.json() From 09dd5175e2b1e9324c98a3f60667ccbed07ab7f3 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 4 Mar 2026 16:42:00 +0900 Subject: [PATCH 43/71] feat(registry): add transient dim status on cache refresh --- hatch/registry_retriever.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hatch/registry_retriever.py b/hatch/registry_retriever.py index 232e96c..f276a6a 100644 --- a/hatch/registry_retriever.py +++ b/hatch/registry_retriever.py @@ -6,12 +6,23 @@ import json import logging +import sys import requests import datetime from pathlib import Path from typing import Dict, Any, Optional +def _print_registry_status(msg: str) -> None: + if sys.stderr.isatty(): + print(f"\033[2m{msg}\033[0m", end="\r", file=sys.stderr, flush=True) + + +def _clear_registry_status() -> None: + if sys.stderr.isatty(): + print(" " * 60, end="\r", file=sys.stderr, flush=True) + + class RegistryRetriever: """Manages the retrieval and caching of the Hatch package registry. @@ -297,8 +308,9 @@ def get_registry(self, force_refresh: bool = False) -> Dict[str, Any]: # In simulation mode, we must have a local registry file registry_data = self._read_local_cache() else: - # In online mode, fetch from remote URL + _print_registry_status(" Refreshing registry cache...") registry_data = self._fetch_remote_registry() + _clear_registry_status() # Update local cache # Note that in case of simulation mode AND default cache path, From 1e3817f99ac36fb4be48e0ac7199fa5c51185fab Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 4 Mar 2026 16:42:07 +0900 Subject: [PATCH 44/71] feat(cli): add --log-level flag and default log output to WARNING --- hatch/cli/__main__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hatch/cli/__main__.py b/hatch/cli/__main__.py index f1d6e98..18aa11b 100644 --- a/hatch/cli/__main__.py +++ b/hatch/cli/__main__.py @@ -972,7 +972,7 @@ def main() -> int: """ # Configure logging logging.basicConfig( - level=logging.INFO, + level=logging.WARNING, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) @@ -1010,8 +1010,15 @@ def main() -> int: default=Path.home() / ".hatch" / "cache", help="Directory to store cached packages", ) + parser.add_argument( + "--log-level", + default="WARNING", + choices=["DEBUG", "INFO", "WARNING", "ERROR"], + help="Log verbosity level (default: WARNING)", + ) args = parser.parse_args() + logging.getLogger().setLevel(getattr(logging, args.log_level)) # Initialize managers (lazy - only when needed) from hatch.environment_manager import HatchEnvironmentManager From 5aa2e9d4c26cefbac929d99259a33a05957cd4b7 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 4 Mar 2026 16:42:08 +0900 Subject: [PATCH 45/71] docs(logging): expose --log-level flag in CLI reference global options --- docs/articles/users/CLIReference.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/users/CLIReference.md b/docs/articles/users/CLIReference.md index 4eb6951..7c9c8d3 100644 --- a/docs/articles/users/CLIReference.md +++ b/docs/articles/users/CLIReference.md @@ -50,6 +50,7 @@ These flags are accepted by the top-level parser and apply to all commands unles | `--envs-dir` | path | Directory to store environments | `~/.hatch/envs` | | `--cache-ttl` | int | Cache time-to-live in seconds | `86400` (1 day) | | `--cache-dir` | path | Directory to store cached packages | `~/.hatch/cache` | +| `--log-level` | choice | Log verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` | `WARNING` | Example: From 3f58954c0e7b6392c21a6016ea230fecccc2adbb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 4 Mar 2026 08:17:35 +0000 Subject: [PATCH 46/71] chore(release): 0.8.1-dev.2 ## 0.8.1-dev.2 (2026-03-04) * Merge branch 'feat/augment-mcp-host-support' into dev ([67bb767](https://github.com/CrackingShells/Hatch/commit/67bb767)) * Merge branch `milestone/fix-logging-clutter` into dev ([5fd15dd](https://github.com/CrackingShells/Hatch/commit/5fd15dd)) * Merge pull request #48 from LittleCoinCoin/dev ([0bc06fb](https://github.com/CrackingShells/Hatch/commit/0bc06fb)), closes [#48](https://github.com/CrackingShells/Hatch/issues/48) * docs(adding-mcp-hosts): add test_adapter_protocol.py to fixture guide ([3a58908](https://github.com/CrackingShells/Hatch/commit/3a58908)) * docs(logging): expose --log-level flag in CLI reference global options ([5aa2e9d](https://github.com/CrackingShells/Hatch/commit/5aa2e9d)) * feat(cli): add --log-level flag and default log output to WARNING ([1e3817f](https://github.com/CrackingShells/Hatch/commit/1e3817f)) * feat(mcp-augment): add enum value and constant ([8b22594](https://github.com/CrackingShells/Hatch/commit/8b22594)) * feat(mcp-augment): implement AugmentAdapter ([5af34d1](https://github.com/CrackingShells/Hatch/commit/5af34d1)) * feat(mcp-augment): implement AugmentHostStrategy ([b13d9d0](https://github.com/CrackingShells/Hatch/commit/b13d9d0)) * feat(mcp-augment): wire AugmentAdapter into integration points ([367b736](https://github.com/CrackingShells/Hatch/commit/367b736)) * feat(registry): add transient dim status on cache refresh ([09dd517](https://github.com/CrackingShells/Hatch/commit/09dd517)) * refactor(logging): remove forced setLevel(INFO) from all module loggers ([fb2ee4c](https://github.com/CrackingShells/Hatch/commit/fb2ee4c)) * refactor(registry): demote startup and fetch INFO logs to DEBUG ([df97e58](https://github.com/CrackingShells/Hatch/commit/df97e58)) * fix(mcp-hosts): close validation and test coverage gaps ([9d7f0e5](https://github.com/CrackingShells/Hatch/commit/9d7f0e5)) * test(mcp-augment): register test fixtures and update tests ([294d0d8](https://github.com/CrackingShells/Hatch/commit/294d0d8)) * chore: clean up temporary reports ([038be8c](https://github.com/CrackingShells/Hatch/commit/038be8c)) --- CHANGELOG.md | 19 +++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67db21f..5cdaad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 0.8.1-dev.2 (2026-03-04) + +* Merge branch 'feat/augment-mcp-host-support' into dev ([67bb767](https://github.com/CrackingShells/Hatch/commit/67bb767)) +* Merge branch `milestone/fix-logging-clutter` into dev ([5fd15dd](https://github.com/CrackingShells/Hatch/commit/5fd15dd)) +* Merge pull request #48 from LittleCoinCoin/dev ([0bc06fb](https://github.com/CrackingShells/Hatch/commit/0bc06fb)), closes [#48](https://github.com/CrackingShells/Hatch/issues/48) +* docs(adding-mcp-hosts): add test_adapter_protocol.py to fixture guide ([3a58908](https://github.com/CrackingShells/Hatch/commit/3a58908)) +* docs(logging): expose --log-level flag in CLI reference global options ([5aa2e9d](https://github.com/CrackingShells/Hatch/commit/5aa2e9d)) +* feat(cli): add --log-level flag and default log output to WARNING ([1e3817f](https://github.com/CrackingShells/Hatch/commit/1e3817f)) +* feat(mcp-augment): add enum value and constant ([8b22594](https://github.com/CrackingShells/Hatch/commit/8b22594)) +* feat(mcp-augment): implement AugmentAdapter ([5af34d1](https://github.com/CrackingShells/Hatch/commit/5af34d1)) +* feat(mcp-augment): implement AugmentHostStrategy ([b13d9d0](https://github.com/CrackingShells/Hatch/commit/b13d9d0)) +* feat(mcp-augment): wire AugmentAdapter into integration points ([367b736](https://github.com/CrackingShells/Hatch/commit/367b736)) +* feat(registry): add transient dim status on cache refresh ([09dd517](https://github.com/CrackingShells/Hatch/commit/09dd517)) +* refactor(logging): remove forced setLevel(INFO) from all module loggers ([fb2ee4c](https://github.com/CrackingShells/Hatch/commit/fb2ee4c)) +* refactor(registry): demote startup and fetch INFO logs to DEBUG ([df97e58](https://github.com/CrackingShells/Hatch/commit/df97e58)) +* fix(mcp-hosts): close validation and test coverage gaps ([9d7f0e5](https://github.com/CrackingShells/Hatch/commit/9d7f0e5)) +* test(mcp-augment): register test fixtures and update tests ([294d0d8](https://github.com/CrackingShells/Hatch/commit/294d0d8)) +* chore: clean up temporary reports ([038be8c](https://github.com/CrackingShells/Hatch/commit/038be8c)) + ## 0.8.1-dev.1 (2026-02-26) * Merge branch 'feat/opencode-mcp-host-support' into dev ([793707d](https://github.com/CrackingShells/Hatch/commit/793707d)) diff --git a/pyproject.toml b/pyproject.toml index 737a82e..4dd5107 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hatch-xclam" -version = "0.8.1-dev.1" +version = "0.8.1-dev.2" description = "Package manager for the Cracking Shells ecosystem" readme = "README.md" requires-python = ">=3.12" From db0fb9101c27f8047181bf076a9c44df1e0f7e57 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Sun, 8 Mar 2026 17:31:28 +0900 Subject: [PATCH 47/71] ci: semantic-release streamlining Agent-Id: agent-f1f3a32a-cf6a-4d05-b1be-28d9269e7688 --- .../prerelease-discord-notification.yml | 35 ------ .github/workflows/publish.yml | 39 +++--- .../release-discord-notification.yml | 34 ------ .github/workflows/semantic-release.yml | 112 +++++++++++++++++- .releaserc.json | 2 +- 5 files changed, 136 insertions(+), 86 deletions(-) delete mode 100644 .github/workflows/prerelease-discord-notification.yml delete mode 100644 .github/workflows/release-discord-notification.yml diff --git a/.github/workflows/prerelease-discord-notification.yml b/.github/workflows/prerelease-discord-notification.yml deleted file mode 100644 index 44e6090..0000000 --- a/.github/workflows/prerelease-discord-notification.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Discord Pre-release Notification - -on: - release: - types: [prereleased] - -jobs: - notify-discord: - runs-on: ubuntu-latest - if: github.event.release.target_commitish == 'dev' - steps: - - name: Send Discord Pre-release Notification - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_HATCH_ANNOUNCEMENTS }} - nodetail: true - # No content field = no mention for pre-releases - title: "🧪 Hatch Pre-release Available for Testing" - description: | - **Version `${{ github.event.release.tag_name }}`** is now available for testing! - - ⚠️ **This is a pre-release** - expect potential bugs and breaking changes - 🔬 Perfect for testing new features and providing feedback - 📋 Click [here](${{ github.event.release.html_url }}) to view what's new and download - - 💻 Install with pip: - ```bash - pip install hatch-xclam==${{ github.event.release.tag_name }} - ``` - - Help us make *Hatch!* better by testing and reporting [issues](https://github.com/CrackingShells/Hatch/issues)! 🐛➡️✨ - color: 0xff9500 # Orange color for pre-release - username: "Cracking Shells Pre-release Bot" - image: "https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_dark_bg_transparent.png" - avatar_url: "https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_core_dark_bg.png" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7f49df7..14e31f4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,19 +1,23 @@ name: Publish to PyPI on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+*' - workflow_dispatch: + workflow_call: + # Automatic release path: semantic-release passes the exact tag to test and publish. inputs: tag: - description: 'Git tag to publish (e.g., v1.0.0)' + description: 'Git tag to test and publish (for release orchestration)' required: true type: string - ref: - description: 'Branch or commit to checkout' - required: false - default: 'main' + outputs: + tag: + description: 'Published git tag' + value: ${{ jobs.publish-pypi.outputs.tag }} + workflow_dispatch: + # Manual recovery path: choose the exact tag to test and publish. + inputs: + tag: + description: 'Git tag to test and publish (e.g., v1.0.0)' + required: true type: string jobs: @@ -24,7 +28,9 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event.inputs.ref || github.ref }} + # Automatic path: semantic-release passes the released tag directly. + # Manual path: validate the same tag that will be published. + ref: ${{ inputs.tag }} - name: Setup Python uses: actions/setup-python@v5 @@ -44,6 +50,8 @@ jobs: name: Publish to PyPI runs-on: ubuntu-latest needs: test + outputs: + tag: ${{ steps.published_tag.outputs.tag }} environment: name: pypi url: https://pypi.org/project/hatch-xclam/ @@ -55,11 +63,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event.inputs.ref || github.ref }} - - - name: Checkout specific tag for manual dispatch - if: github.event_name == 'workflow_dispatch' - run: git checkout ${{ github.event.inputs.tag }} + ref: ${{ inputs.tag }} - name: Setup Python uses: actions/setup-python@v5 @@ -80,3 +84,8 @@ jobs: print-hash: true verbose: true skip-existing: true + + - name: Record published tag + id: published_tag + run: | + echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release-discord-notification.yml b/.github/workflows/release-discord-notification.yml deleted file mode 100644 index fe9261f..0000000 --- a/.github/workflows/release-discord-notification.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Discord Release Notification - -on: - release: - types: [released] - -jobs: - notify-discord: - runs-on: ubuntu-latest - if: github.event.release.target_commitish == 'main' - steps: - - name: Send Discord Notification - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_HATCH_ANNOUNCEMENTS }} - nodetail: true - content: "<@&1418053865818951721>" - title: "🎉 New *Hatch!* Release Available!" - description: | - **Version `${{ github.event.release.tag_name }}`** has been released! - - 🚀 Get the latest features and improvements - 📚 Click [here](${{ github.event.release.html_url }}) to view the changelog and download - - 💻 Install with pip: - ```bash - pip install hatch-xclam - ``` - - Happy MCP coding with *Hatch!* 🐣 - color: 0x00ff88 - username: "Cracking Shells Release Bot" - image: "https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_light_bg_transparent.png" - avatar_url: "https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_icon_light_bg.png" diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index b6647b6..50fb9c4 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -8,6 +8,7 @@ on: jobs: test: + if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} runs-on: ubuntu-latest steps: - name: Checkout @@ -30,8 +31,12 @@ jobs: python -c "import hatch; print('Hatch package imports successfully')" release: + if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} needs: test runs-on: ubuntu-latest + outputs: + published: ${{ steps.semantic_release.outputs.published }} + tag: ${{ steps.semantic_release.outputs.tag }} steps: - name: Generate GitHub App Token id: generate_token @@ -58,10 +63,115 @@ jobs: run: npm audit signatures - name: Release + id: semantic_release env: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} GH_TOKEN: ${{ steps.generate_token.outputs.token }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - npx semantic-release + node <<'EOF' + const fs = require('fs'); + + (async () => { + const semanticReleaseModule = await import('semantic-release'); + const semanticRelease = semanticReleaseModule.default || semanticReleaseModule; + const result = await semanticRelease(); + + if (!process.env.GITHUB_OUTPUT) { + throw new Error('GITHUB_OUTPUT is not set'); + } + + if (!result) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'published=false\n'); + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'tag=\n'); + return; + } + + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'published=true\n'); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${result.nextRelease.gitTag}\n`); + })().catch((error) => { + console.error(error); + process.exit(1); + }); + EOF + + publish: + name: Publish released package + needs: release + if: ${{ needs.release.outputs.published == 'true' }} + uses: ./.github/workflows/publish.yml + with: + tag: ${{ needs.release.outputs.tag }} + secrets: inherit + + notify-discord: + name: Notify Discord + needs: + - release + - publish + if: ${{ needs.release.outputs.published == 'true' && needs.publish.result == 'success' }} + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Resolve GitHub release + id: release + uses: actions/github-script@v8 + env: + TAG_NAME: ${{ needs.publish.outputs.tag }} + with: + script: | + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: process.env.TAG_NAME, + }); + + core.setOutput('tag_name', release.tag_name); + core.setOutput('html_url', release.html_url); + core.setOutput('is_prerelease', String(release.prerelease)); + + - name: Build Discord payload + id: discord + uses: actions/github-script@v8 + env: + TAG_NAME: ${{ steps.release.outputs.tag_name }} + HTML_URL: ${{ steps.release.outputs.html_url }} + IS_PRERELEASE: ${{ steps.release.outputs.is_prerelease }} + with: + script: | + const isPrerelease = process.env.IS_PRERELEASE === 'true'; + const tagName = process.env.TAG_NAME; + const htmlUrl = process.env.HTML_URL; + + core.setOutput('content', isPrerelease ? '' : '<@&1418053865818951721>'); + core.setOutput('title', isPrerelease + ? '🧪 Hatch Pre-release Available for Testing' + : '🎉 New *Hatch!* Release Available!'); + core.setOutput('description', isPrerelease + ? `**Version \`${tagName}\`** is now available for testing!\n\n⚠️ **This is a pre-release** - expect potential bugs and breaking changes\n🔬 Perfect for testing new features and providing feedback\n📋 Click [here](${htmlUrl}) to view what's new and download\n\n💻 Install with pip:\n\`\`\`bash\npip install hatch-xclam==${tagName}\n\`\`\`\n\nHelp us make *Hatch!* better by testing and reporting [issues](https://github.com/CrackingShells/Hatch/issues)! 🐛➡️✨` + : `**Version \`${tagName}\`** has been released!\n\n🚀 Get the latest features and improvements\n📚 Click [here](${htmlUrl}) to view the changelog and download\n\n💻 Install with pip:\n\`\`\`bash\npip install hatch-xclam\n\`\`\`\n\nHappy MCP coding with *Hatch!* 🐣`); + core.setOutput('color', isPrerelease ? '0xff9500' : '0x00ff88'); + core.setOutput('username', isPrerelease + ? 'Cracking Shells Pre-release Bot' + : 'Cracking Shells Release Bot'); + core.setOutput('image', isPrerelease + ? 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_dark_bg_transparent.png' + : 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_light_bg_transparent.png'); + core.setOutput('avatar_url', isPrerelease + ? 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_core_dark_bg.png' + : 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_icon_light_bg.png'); + + - name: Send Discord notification + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_HATCH_ANNOUNCEMENTS }} + nodetail: true + content: ${{ steps.discord.outputs.content }} + title: ${{ steps.discord.outputs.title }} + description: ${{ steps.discord.outputs.description }} + color: ${{ steps.discord.outputs.color }} + username: ${{ steps.discord.outputs.username }} + image: ${{ steps.discord.outputs.image }} + avatar_url: ${{ steps.discord.outputs.avatar_url }} diff --git a/.releaserc.json b/.releaserc.json index ed0509c..61963ca 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -51,7 +51,7 @@ "@semantic-release/git", { "assets": ["CHANGELOG.md", "pyproject.toml"], - "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" } ], [ From f213971f78f6e55786d87f4a30d3b314af18bd17 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 11 Mar 2026 10:53:11 +0900 Subject: [PATCH 48/71] feat(mcp-hosts): let hatch manage mistral vibe configs Add the adapter, strategy, model wiring, and core validations needed to treat Mistral Vibe as a first-class MCP host inside the shared host-configuration layer. --- hatch/mcp_host_config/adapters/__init__.py | 2 + .../mcp_host_config/adapters/mistral_vibe.py | 116 ++++++++++++++ hatch/mcp_host_config/adapters/registry.py | 4 +- hatch/mcp_host_config/backup.py | 1 + hatch/mcp_host_config/fields.py | 17 +- hatch/mcp_host_config/host_management.py | 1 + hatch/mcp_host_config/models.py | 35 ++++- hatch/mcp_host_config/reporting.py | 1 + hatch/mcp_host_config/strategies.py | 148 ++++++++++++++++++ tests/unit/mcp/test_adapter_protocol.py | 3 + tests/unit/mcp/test_adapter_registry.py | 3 + tests/unit/mcp/test_config_model.py | 12 ++ tests/unit/mcp/test_mistral_vibe_adapter.py | 41 +++++ tests/unit/mcp/test_mistral_vibe_strategy.py | 91 +++++++++++ 14 files changed, 470 insertions(+), 5 deletions(-) create mode 100644 hatch/mcp_host_config/adapters/mistral_vibe.py create mode 100644 tests/unit/mcp/test_mistral_vibe_adapter.py create mode 100644 tests/unit/mcp/test_mistral_vibe_strategy.py diff --git a/hatch/mcp_host_config/adapters/__init__.py b/hatch/mcp_host_config/adapters/__init__.py index 8e5dc32..ec750fd 100644 --- a/hatch/mcp_host_config/adapters/__init__.py +++ b/hatch/mcp_host_config/adapters/__init__.py @@ -12,6 +12,7 @@ from hatch.mcp_host_config.adapters.gemini import GeminiAdapter from hatch.mcp_host_config.adapters.kiro import KiroAdapter from hatch.mcp_host_config.adapters.lmstudio import LMStudioAdapter +from hatch.mcp_host_config.adapters.mistral_vibe import MistralVibeAdapter from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter from hatch.mcp_host_config.adapters.registry import ( AdapterRegistry, @@ -36,6 +37,7 @@ "GeminiAdapter", "KiroAdapter", "LMStudioAdapter", + "MistralVibeAdapter", "OpenCodeAdapter", "VSCodeAdapter", ] diff --git a/hatch/mcp_host_config/adapters/mistral_vibe.py b/hatch/mcp_host_config/adapters/mistral_vibe.py new file mode 100644 index 0000000..8f51419 --- /dev/null +++ b/hatch/mcp_host_config/adapters/mistral_vibe.py @@ -0,0 +1,116 @@ +"""Mistral Vibe adapter for MCP host configuration. + +Mistral Vibe uses TOML `[[mcp_servers]]` entries with an explicit `transport` +field instead of the Claude-style `type` discriminator. +""" + +from typing import Any, Dict, FrozenSet + +from hatch.mcp_host_config.adapters.base import AdapterValidationError, BaseAdapter +from hatch.mcp_host_config.fields import MISTRAL_VIBE_FIELDS +from hatch.mcp_host_config.models import MCPServerConfig + + +class MistralVibeAdapter(BaseAdapter): + """Adapter for Mistral Vibe MCP server configuration.""" + + @property + def host_name(self) -> str: + """Return the host identifier.""" + return "mistral-vibe" + + def get_supported_fields(self) -> FrozenSet[str]: + """Return fields supported by Mistral Vibe.""" + return MISTRAL_VIBE_FIELDS + + def validate(self, config: MCPServerConfig) -> None: + """Deprecated compatibility wrapper for legacy adapter tests.""" + self.validate_filtered(self.filter_fields(config)) + + def validate_filtered(self, filtered: Dict[str, Any]) -> None: + """Validate Mistral Vibe transport rules on filtered fields.""" + has_command = "command" in filtered + has_url = "url" in filtered + transport_count = sum([has_command, has_url]) + + if transport_count == 0: + raise AdapterValidationError( + "Either 'command' or 'url' must be specified", + host_name=self.host_name, + ) + + if transport_count > 1: + raise AdapterValidationError( + "Cannot specify multiple transports - choose exactly one of 'command' or 'url'", + host_name=self.host_name, + ) + + transport = filtered.get("transport") + if transport == "stdio" and not has_command: + raise AdapterValidationError( + "transport='stdio' requires 'command' field", + field="transport", + host_name=self.host_name, + ) + if transport in ("http", "streamable-http") and not has_url: + raise AdapterValidationError( + f"transport='{transport}' requires 'url' field", + field="transport", + host_name=self.host_name, + ) + + def apply_transformations( + self, filtered: Dict[str, Any], transport_hint: str | None = None + ) -> Dict[str, Any]: + """Apply Mistral Vibe field/value transformations.""" + result = dict(filtered) + + transport = ( + result.get("transport") or transport_hint or self._infer_transport(result) + ) + result["transport"] = transport + + return result + + def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: + """Serialize configuration for Mistral Vibe format.""" + filtered = self.filter_fields(config) + + # Support cross-host sync hints without advertising these as native fields. + if ( + "command" not in filtered + and "url" not in filtered + and config.httpUrl is not None + ): + filtered["url"] = config.httpUrl + + transport_hint = self._infer_transport(filtered, config=config) + if transport_hint is not None: + filtered["transport"] = transport_hint + + self.validate_filtered(filtered) + return self.apply_transformations(filtered) + + def _infer_transport( + self, filtered: Dict[str, Any], config: MCPServerConfig | None = None + ) -> str | None: + """Infer Vibe transport from canonical MCP fields.""" + if "transport" in filtered: + return filtered["transport"] + if "command" in filtered: + return "stdio" + + config_type = config.type if config is not None else None + if config_type == "stdio": + return "stdio" + if config_type == "http": + return "http" + if config_type == "sse": + return "streamable-http" + + if config is not None and config.httpUrl is not None: + return "http" + if "url" in filtered: + return "streamable-http" + + return None diff --git a/hatch/mcp_host_config/adapters/registry.py b/hatch/mcp_host_config/adapters/registry.py index 22a0122..53ae661 100644 --- a/hatch/mcp_host_config/adapters/registry.py +++ b/hatch/mcp_host_config/adapters/registry.py @@ -14,6 +14,7 @@ from hatch.mcp_host_config.adapters.gemini import GeminiAdapter from hatch.mcp_host_config.adapters.kiro import KiroAdapter from hatch.mcp_host_config.adapters.lmstudio import LMStudioAdapter +from hatch.mcp_host_config.adapters.mistral_vibe import MistralVibeAdapter from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter from hatch.mcp_host_config.adapters.vscode import VSCodeAdapter @@ -34,7 +35,7 @@ class AdapterRegistry: 'claude-desktop' >>> registry.get_supported_hosts() - ['augment', 'claude-code', 'claude-desktop', 'codex', 'cursor', 'gemini', 'kiro', 'lmstudio', 'opencode', 'vscode'] + ['augment', 'claude-code', 'claude-desktop', 'codex', 'cursor', 'gemini', 'kiro', 'lmstudio', 'mistral-vibe', 'opencode', 'vscode'] """ def __init__(self): @@ -55,6 +56,7 @@ def _register_defaults(self) -> None: self.register(GeminiAdapter()) self.register(KiroAdapter()) self.register(CodexAdapter()) + self.register(MistralVibeAdapter()) self.register(OpenCodeAdapter()) self.register(AugmentAdapter()) diff --git a/hatch/mcp_host_config/backup.py b/hatch/mcp_host_config/backup.py index 5ebac87..9c36ec8 100644 --- a/hatch/mcp_host_config/backup.py +++ b/hatch/mcp_host_config/backup.py @@ -48,6 +48,7 @@ def validate_hostname(cls, v): "gemini", "kiro", "codex", + "mistral-vibe", "opencode", "augment", } diff --git a/hatch/mcp_host_config/fields.py b/hatch/mcp_host_config/fields.py index b574c9c..942fef9 100644 --- a/hatch/mcp_host_config/fields.py +++ b/hatch/mcp_host_config/fields.py @@ -27,7 +27,7 @@ # ============================================================================ # Hosts that support the 'type' discriminator field (stdio/sse/http) -# Note: Gemini, Kiro, Codex do NOT support this field +# Note: Gemini, Kiro, Codex, and Mistral Vibe do NOT support this field TYPE_SUPPORTING_HOSTS: FrozenSet[str] = frozenset( { "claude-desktop", @@ -117,6 +117,21 @@ ) +# Fields supported by Mistral Vibe (TOML array-of-tables with explicit transport) +MISTRAL_VIBE_FIELDS: FrozenSet[str] = UNIVERSAL_FIELDS | frozenset( + { + "transport", # Vibe transport discriminator: stdio/http/streamable-http + "prompt", # Optional per-server prompt override + "sampling_enabled", # Enable model sampling for tool calls + "api_key_env", # Env var containing API key for remote servers + "api_key_header", # Header name for API key injection + "api_key_format", # Header formatting template for API key injection + "startup_timeout_sec", # Server startup timeout + "tool_timeout_sec", # Tool execution timeout + } +) + + # Fields supported by Augment Code (auggie CLI + extensions); same as Claude fields # Config: ~/.augment/settings.json, key: mcpServers AUGMENT_FIELDS: FrozenSet[str] = CLAUDE_FIELDS diff --git a/hatch/mcp_host_config/host_management.py b/hatch/mcp_host_config/host_management.py index d4177f0..1e577aa 100644 --- a/hatch/mcp_host_config/host_management.py +++ b/hatch/mcp_host_config/host_management.py @@ -30,6 +30,7 @@ class MCPHostRegistry: _family_mappings: Dict[str, List[MCPHostType]] = { "claude": [MCPHostType.CLAUDE_DESKTOP, MCPHostType.CLAUDE_CODE], "cursor": [MCPHostType.CURSOR, MCPHostType.LMSTUDIO], + "mistral": [MCPHostType.MISTRAL_VIBE], } @classmethod diff --git a/hatch/mcp_host_config/models.py b/hatch/mcp_host_config/models.py index 610971b..79b4116 100644 --- a/hatch/mcp_host_config/models.py +++ b/hatch/mcp_host_config/models.py @@ -30,6 +30,7 @@ class MCPHostType(str, Enum): GEMINI = "gemini" KIRO = "kiro" CODEX = "codex" + MISTRAL_VIBE = "mistral-vibe" OPENCODE = "opencode" AUGMENT = "augment" @@ -62,6 +63,10 @@ class MCPServerConfig(BaseModel): type: Optional[Literal["stdio", "sse", "http"]] = Field( None, description="Transport type (stdio for local, sse/http for remote)" ) + transport: Optional[Literal["stdio", "http", "streamable-http"]] = Field( + None, + description="Host-native transport discriminator (e.g. Mistral Vibe)", + ) # stdio transport (local server) command: Optional[str] = Field( @@ -138,15 +143,15 @@ class MCPServerConfig(BaseModel): disabledTools: Optional[List[str]] = Field(None, description="Disabled tool names") # ======================================================================== - # Codex-Specific Fields + # Codex / Mistral Vibe-Specific Fields # ======================================================================== env_vars: Optional[List[str]] = Field( None, description="Environment variables to whitelist/forward" ) - startup_timeout_sec: Optional[int] = Field( + startup_timeout_sec: Optional[float] = Field( None, description="Server startup timeout in seconds" ) - tool_timeout_sec: Optional[int] = Field( + tool_timeout_sec: Optional[float] = Field( None, description="Tool execution timeout in seconds" ) enabled: Optional[bool] = Field( @@ -167,6 +172,19 @@ class MCPServerConfig(BaseModel): env_http_headers: Optional[Dict[str, str]] = Field( None, description="Header names to env var names" ) + prompt: Optional[str] = Field(None, description="Per-server prompt override") + sampling_enabled: Optional[bool] = Field( + None, description="Whether sampling is enabled for tool calls" + ) + api_key_env: Optional[str] = Field( + None, description="Env var containing API key for remote server auth" + ) + api_key_header: Optional[str] = Field( + None, description="HTTP header name used for API key injection" + ) + api_key_format: Optional[str] = Field( + None, description="Formatting template for API key header values" + ) # ======================================================================== # OpenCode-Specific Fields @@ -239,6 +257,8 @@ def is_stdio(self) -> bool: 1. Explicit type="stdio" field takes precedence 2. Otherwise, presence of 'command' field indicates stdio """ + if self.transport is not None: + return self.transport == "stdio" if self.type is not None: return self.type == "stdio" return self.command is not None @@ -253,6 +273,8 @@ def is_sse(self) -> bool: 1. Explicit type="sse" field takes precedence 2. Otherwise, presence of 'url' field indicates SSE """ + if self.transport is not None: + return False if self.type is not None: return self.type == "sse" return self.url is not None @@ -267,6 +289,8 @@ def is_http(self) -> bool: 1. Explicit type="http" field takes precedence 2. Otherwise, presence of 'httpUrl' field indicates HTTP streaming """ + if self.transport is not None: + return self.transport in ("http", "streamable-http") if self.type is not None: return self.type == "http" return self.httpUrl is not None @@ -278,8 +302,12 @@ def get_transport_type(self) -> Optional[str]: "stdio" for command-based local servers "sse" for URL-based remote servers (SSE transport) "http" for httpUrl-based remote servers (Gemini HTTP streaming) + "streamable-http" for hosts that expose that transport natively None if transport cannot be determined """ + if self.transport is not None: + return self.transport + # Explicit type takes precedence if self.type is not None: return self.type @@ -367,6 +395,7 @@ def validate_host_names(cls, v): "gemini", "kiro", "codex", + "mistral-vibe", "opencode", "augment", } diff --git a/hatch/mcp_host_config/reporting.py b/hatch/mcp_host_config/reporting.py index d4744aa..fd9724d 100644 --- a/hatch/mcp_host_config/reporting.py +++ b/hatch/mcp_host_config/reporting.py @@ -73,6 +73,7 @@ def _get_adapter_host_name(host_type: MCPHostType) -> str: MCPHostType.GEMINI: "gemini", MCPHostType.KIRO: "kiro", MCPHostType.CODEX: "codex", + MCPHostType.MISTRAL_VIBE: "mistral-vibe", MCPHostType.OPENCODE: "opencode", MCPHostType.AUGMENT: "augment", } diff --git a/hatch/mcp_host_config/strategies.py b/hatch/mcp_host_config/strategies.py index 69ebcd2..a6c8925 100644 --- a/hatch/mcp_host_config/strategies.py +++ b/hatch/mcp_host_config/strategies.py @@ -1055,6 +1055,154 @@ def write_configuration( return False +@register_host_strategy(MCPHostType.MISTRAL_VIBE) +class MistralVibeHostStrategy(MCPHostStrategy): + """Configuration strategy for Mistral Vibe's TOML-based MCP settings.""" + + def get_adapter_host_name(self) -> str: + """Return the adapter host name for Mistral Vibe.""" + return "mistral-vibe" + + def _project_config_path(self) -> Path: + return Path.cwd() / ".vibe" / "config.toml" + + def _global_config_path(self) -> Path: + return Path.home() / ".vibe" / "config.toml" + + def get_config_path(self) -> Optional[Path]: + """Get Mistral Vibe configuration path. + + Vibe prefers project-local `./.vibe/config.toml` when it exists, and + otherwise falls back to the user-global `~/.vibe/config.toml`. + """ + project_path = self._project_config_path() + global_path = self._global_config_path() + + if project_path.exists(): + return project_path + if global_path.exists(): + return global_path + if project_path.parent.exists(): + return project_path + return global_path + + def get_config_key(self) -> str: + """Mistral Vibe uses the `mcp_servers` top-level key.""" + return "mcp_servers" + + def is_host_available(self) -> bool: + """Check if Mistral Vibe is available by checking config directories.""" + return ( + self._project_config_path().parent.exists() + or self._global_config_path().parent.exists() + ) + + def validate_server_config(self, server_config: MCPServerConfig) -> bool: + """Vibe supports local stdio and remote HTTP transports.""" + return any( + value is not None + for value in ( + server_config.command, + server_config.url, + server_config.httpUrl, + ) + ) + + def read_configuration(self) -> HostConfiguration: + """Read Mistral Vibe TOML configuration.""" + config_path = self.get_config_path() + if not config_path or not config_path.exists(): + return HostConfiguration(servers={}) + + try: + with open(config_path, "rb") as f: + toml_data = tomllib.load(f) + + raw_servers = toml_data.get(self.get_config_key(), []) + if not isinstance(raw_servers, list): + logger.warning( + "Invalid Mistral Vibe configuration: mcp_servers must be a list" + ) + return HostConfiguration(servers={}) + + servers = {} + for server_data in raw_servers: + try: + normalized = dict(server_data) + name = normalized.pop("name", None) + if not name: + logger.warning("Skipping unnamed Mistral Vibe MCP server entry") + continue + + transport = normalized.get("transport") + if transport == "stdio": + normalized.setdefault("type", "stdio") + elif transport in ("http", "streamable-http"): + normalized.setdefault("type", "http") + + servers[name] = MCPServerConfig(name=name, **normalized) + except Exception as e: + logger.warning(f"Invalid Mistral Vibe server config: {e}") + continue + + return HostConfiguration(servers=servers) + except Exception as e: + logger.error(f"Failed to read Mistral Vibe configuration: {e}") + return HostConfiguration(servers={}) + + def write_configuration( + self, config: HostConfiguration, no_backup: bool = False + ) -> bool: + """Write Mistral Vibe TOML configuration while preserving other keys.""" + config_path = self.get_config_path() + if not config_path: + return False + + try: + config_path.parent.mkdir(parents=True, exist_ok=True) + + existing_data: Dict[str, Any] = {} + if config_path.exists(): + try: + with open(config_path, "rb") as f: + existing_data = tomllib.load(f) + except Exception: + pass + + adapter = get_adapter(self.get_adapter_host_name()) + servers_data = [] + for name, server_config in config.servers.items(): + serialized = adapter.serialize(server_config) + servers_data.append({"name": name, **serialized}) + + final_data = { + key: value + for key, value in existing_data.items() + if key != self.get_config_key() + } + final_data[self.get_config_key()] = servers_data + + backup_manager = MCPHostConfigBackupManager() + atomic_ops = AtomicFileOperations() + + def toml_serializer(data: Any, f: TextIO) -> None: + f.write(tomli_w.dumps(data)) + + atomic_ops.atomic_write_with_serializer( + file_path=config_path, + data=final_data, + serializer=toml_serializer, + backup_manager=backup_manager, + hostname="mistral-vibe", + skip_backup=no_backup, + ) + + return True + except Exception as e: + logger.error(f"Failed to write Mistral Vibe configuration: {e}") + return False + + @register_host_strategy(MCPHostType.AUGMENT) class AugmentHostStrategy(ClaudeHostStrategy): """Configuration strategy for Augment Code (auggie CLI + extensions). diff --git a/tests/unit/mcp/test_adapter_protocol.py b/tests/unit/mcp/test_adapter_protocol.py index 4878878..1e6c24e 100644 --- a/tests/unit/mcp/test_adapter_protocol.py +++ b/tests/unit/mcp/test_adapter_protocol.py @@ -16,6 +16,7 @@ GeminiAdapter, KiroAdapter, LMStudioAdapter, + MistralVibeAdapter, VSCodeAdapter, ) from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter @@ -29,6 +30,7 @@ GeminiAdapter, KiroAdapter, LMStudioAdapter, + MistralVibeAdapter, OpenCodeAdapter, VSCodeAdapter, ] @@ -43,6 +45,7 @@ MCPHostType.GEMINI: GeminiAdapter, MCPHostType.KIRO: KiroAdapter, MCPHostType.LMSTUDIO: LMStudioAdapter, + MCPHostType.MISTRAL_VIBE: MistralVibeAdapter, MCPHostType.OPENCODE: OpenCodeAdapter, MCPHostType.VSCODE: VSCodeAdapter, } diff --git a/tests/unit/mcp/test_adapter_registry.py b/tests/unit/mcp/test_adapter_registry.py index a393461..204f6c7 100644 --- a/tests/unit/mcp/test_adapter_registry.py +++ b/tests/unit/mcp/test_adapter_registry.py @@ -17,6 +17,7 @@ GeminiAdapter, KiroAdapter, LMStudioAdapter, + MistralVibeAdapter, VSCodeAdapter, ) @@ -39,6 +40,7 @@ def test_AR01_registry_has_all_default_hosts(self): "gemini", "kiro", "lmstudio", + "mistral-vibe", "opencode", "vscode", } @@ -57,6 +59,7 @@ def test_AR02_get_adapter_returns_correct_type(self): ("gemini", GeminiAdapter), ("kiro", KiroAdapter), ("lmstudio", LMStudioAdapter), + ("mistral-vibe", MistralVibeAdapter), ("vscode", VSCodeAdapter), ] diff --git a/tests/unit/mcp/test_config_model.py b/tests/unit/mcp/test_config_model.py index 5e60df5..b3fc0d7 100644 --- a/tests/unit/mcp/test_config_model.py +++ b/tests/unit/mcp/test_config_model.py @@ -37,6 +37,18 @@ def test_UM03_valid_http_config_gemini(self): # httpUrl is considered remote self.assertTrue(config.is_remote_server) + def test_UM03b_valid_streamable_http_transport(self): + """UM-03b: Valid remote config with native transport field.""" + config = MCPServerConfig( + name="test", + url="https://example.com/mcp", + transport="streamable-http", + ) + + self.assertEqual(config.transport, "streamable-http") + self.assertEqual(config.get_transport_type(), "streamable-http") + self.assertTrue(config.is_remote_server) + def test_UM04_allows_command_and_url(self): """UM-04: Unified model allows both command and url (adapters validate).""" # The unified model is permissive - adapters enforce host-specific rules diff --git a/tests/unit/mcp/test_mistral_vibe_adapter.py b/tests/unit/mcp/test_mistral_vibe_adapter.py new file mode 100644 index 0000000..08e4b8a --- /dev/null +++ b/tests/unit/mcp/test_mistral_vibe_adapter.py @@ -0,0 +1,41 @@ +"""Unit tests for the Mistral Vibe adapter.""" + +import unittest + +from hatch.mcp_host_config.adapters.mistral_vibe import MistralVibeAdapter +from hatch.mcp_host_config.models import MCPServerConfig + + +class TestMistralVibeAdapter(unittest.TestCase): + """Verify Mistral-specific filtering and transport mapping.""" + + def test_serialize_filters_type_but_preserves_sse_transport_hint(self): + """Canonical type hints should map to transport without serializing type.""" + result = MistralVibeAdapter().serialize( + MCPServerConfig( + name="weather", + url="https://example.com/mcp", + type="sse", + ) + ) + + self.assertEqual(result["url"], "https://example.com/mcp") + self.assertEqual(result["transport"], "streamable-http") + self.assertNotIn("type", result) + + def test_serialize_maps_http_url_without_exposing_httpUrl(self): + """httpUrl input should serialize as Mistral's url+transport format.""" + result = MistralVibeAdapter().serialize( + MCPServerConfig( + name="weather", + httpUrl="https://example.com/http", + ) + ) + + self.assertEqual(result["url"], "https://example.com/http") + self.assertEqual(result["transport"], "http") + self.assertNotIn("httpUrl", result) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/mcp/test_mistral_vibe_strategy.py b/tests/unit/mcp/test_mistral_vibe_strategy.py new file mode 100644 index 0000000..7ffdc75 --- /dev/null +++ b/tests/unit/mcp/test_mistral_vibe_strategy.py @@ -0,0 +1,91 @@ +"""Unit tests for Mistral Vibe host strategy.""" + +import os +import tempfile +import tomllib +import unittest +from pathlib import Path + +from hatch.mcp_host_config.models import HostConfiguration, MCPServerConfig +from hatch.mcp_host_config.strategies import MistralVibeHostStrategy + + +class TestMistralVibeHostStrategy(unittest.TestCase): + """Verify Mistral Vibe TOML read/write behavior.""" + + def test_read_configuration_parses_array_of_tables(self): + """Reads [[mcp_servers]] entries into HostConfiguration.""" + with tempfile.TemporaryDirectory() as tmpdir: + cwd = os.getcwd() + try: + os.chdir(tmpdir) + config_dir = Path(tmpdir) / ".vibe" + config_dir.mkdir() + (config_dir / "config.toml").write_text( + 'model = "mistral-medium"\n\n' + "[[mcp_servers]]\n" + 'name = "weather"\n' + 'transport = "streamable-http"\n' + 'url = "https://example.com/mcp"\n' + 'prompt = "Be concise"\n', + encoding="utf-8", + ) + + strategy = MistralVibeHostStrategy() + result = strategy.read_configuration() + + self.assertIn("weather", result.servers) + server = result.servers["weather"] + self.assertEqual(server.transport, "streamable-http") + self.assertEqual(server.type, "http") + self.assertEqual(server.url, "https://example.com/mcp") + self.assertEqual(server.prompt, "Be concise") + finally: + os.chdir(cwd) + + def test_write_configuration_preserves_other_top_level_keys(self): + """Writes mcp_servers while preserving unrelated Vibe settings.""" + with tempfile.TemporaryDirectory() as tmpdir: + cwd = os.getcwd() + try: + os.chdir(tmpdir) + config_dir = Path(tmpdir) / ".vibe" + config_dir.mkdir() + config_path = config_dir / "config.toml" + config_path.write_text( + 'model = "mistral-medium"\n' 'theme = "dark"\n', + encoding="utf-8", + ) + + strategy = MistralVibeHostStrategy() + config = HostConfiguration( + servers={ + "weather": MCPServerConfig( + name="weather", + url="https://example.com/mcp", + transport="streamable-http", + headers={"Authorization": "Bearer token"}, + ) + } + ) + + self.assertTrue(strategy.write_configuration(config, no_backup=True)) + + with open(config_path, "rb") as f: + written = tomllib.load(f) + + self.assertEqual(written["model"], "mistral-medium") + self.assertEqual(written["theme"], "dark") + self.assertEqual(written["mcp_servers"][0]["name"], "weather") + self.assertEqual( + written["mcp_servers"][0]["transport"], "streamable-http" + ) + self.assertEqual( + written["mcp_servers"][0]["url"], "https://example.com/mcp" + ) + finally: + os.chdir(cwd) + + +if __name__ == "__main__": + unittest.main() From 0e801d06d092787e997b8717c8b5bccf48572faa Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 11 Mar 2026 10:53:20 +0900 Subject: [PATCH 49/71] feat(cli): let shared mcp configure target mistral vibe Reuse the existing MCP configure flags where possible and map them onto Mistral Vibe's native transport and auth fields so the host works through the same CLI workflow as the other integrations. --- hatch/cli/__main__.py | 35 ++- hatch/cli/cli_mcp.py | 106 +++++++- .../cli/test_cli_reporter_integration.py | 236 +++++++++++------- 3 files changed, 281 insertions(+), 96 deletions(-) diff --git a/hatch/cli/__main__.py b/hatch/cli/__main__.py index 18aa11b..746ada7 100644 --- a/hatch/cli/__main__.py +++ b/hatch/cli/__main__.py @@ -585,10 +585,10 @@ def _setup_mcp_commands(subparsers): ) server_type_group.add_argument( "--url", - help="Server URL for remote MCP servers (SSE transport) [hosts: all except claude-desktop, claude-code]", + help="Server URL for remote MCP servers (SSE/streamable transport) [hosts: all except claude-desktop, claude-code]", ) server_type_group.add_argument( - "--http-url", help="HTTP streaming endpoint URL [hosts: gemini]" + "--http-url", help="HTTP streaming endpoint URL [hosts: gemini, mistral-vibe]" ) mcp_configure_parser.add_argument( @@ -667,12 +667,12 @@ def _setup_mcp_commands(subparsers): mcp_configure_parser.add_argument( "--startup-timeout", type=int, - help="Server startup timeout in seconds (default: 10) [hosts: codex]", + help="Server startup timeout in seconds (default: 10) [hosts: codex, mistral-vibe]", ) mcp_configure_parser.add_argument( "--tool-timeout", type=int, - help="Tool execution timeout in seconds (default: 60) [hosts: codex]", + help="Tool execution timeout in seconds (default: 60) [hosts: codex, mistral-vibe]", ) mcp_configure_parser.add_argument( "--enabled", @@ -683,12 +683,35 @@ def _setup_mcp_commands(subparsers): mcp_configure_parser.add_argument( "--bearer-token-env-var", type=str, - help="Name of environment variable containing bearer token for Authorization header [hosts: codex]", + help="Name of environment variable containing bearer token for Authorization header [hosts: codex, mistral-vibe]", ) mcp_configure_parser.add_argument( "--env-header", action="append", - help="HTTP header from environment variable in KEY=ENV_VAR_NAME format [hosts: codex]", + help="HTTP header from environment variable in KEY=ENV_VAR_NAME format [hosts: codex, mistral-vibe]", + ) + + # Mistral Vibe-specific arguments + mcp_configure_parser.add_argument( + "--prompt", help="Per-server prompt override [hosts: mistral-vibe]" + ) + mcp_configure_parser.add_argument( + "--sampling-enabled", + action="store_true", + default=None, + help="Enable model sampling for tool calls [hosts: mistral-vibe]", + ) + mcp_configure_parser.add_argument( + "--api-key-env", + help="Environment variable containing API key for remote auth [hosts: mistral-vibe]", + ) + mcp_configure_parser.add_argument( + "--api-key-header", + help="HTTP header name used for API key injection [hosts: mistral-vibe]", + ) + mcp_configure_parser.add_argument( + "--api-key-format", + help="Formatting template for API key header values [hosts: mistral-vibe]", ) mcp_configure_parser.add_argument( diff --git a/hatch/cli/cli_mcp.py b/hatch/cli/cli_mcp.py index 457e2f4..4563fae 100644 --- a/hatch/cli/cli_mcp.py +++ b/hatch/cli/cli_mcp.py @@ -13,6 +13,7 @@ - codex: OpenAI Codex - lm-studio: LM Studio - gemini: Google Gemini + - mistral-vibe: Mistral Vibe CLI Command Groups: Discovery: @@ -71,6 +72,72 @@ ) +def _apply_mistral_vibe_cli_mappings( + config_data: dict, + *, + command: Optional[str], + url: Optional[str], + http_url: Optional[str], + bearer_token_env_var: Optional[str], + env_header: Optional[list], + api_key_env: Optional[str], + api_key_header: Optional[str], + api_key_format: Optional[str], +) -> dict: + """Map generic CLI flags to Mistral Vibe's host-native MCP fields.""" + result = dict(config_data) + result.pop("cwd", None) + + if command is not None: + result["transport"] = "stdio" + elif http_url is not None: + result.pop("httpUrl", None) + result["url"] = http_url + result["transport"] = "http" + elif url is not None: + result["transport"] = "streamable-http" + + if env_header and len(env_header) > 1: + raise ValidationError( + "mistral-vibe supports at most one --env-header mapping", + field="--env-header", + suggestion=( + "Use a single KEY=ENV_VAR pair or the dedicated --api-key-* flags" + ), + ) + + mapped_api_key_env = api_key_env + mapped_api_key_header = api_key_header + mapped_api_key_format = api_key_format + + if env_header: + header_name, env_var_name = env_header[0].split("=", 1) + if mapped_api_key_header is None: + mapped_api_key_header = header_name + if mapped_api_key_env is None: + mapped_api_key_env = env_var_name + + if bearer_token_env_var is not None: + if mapped_api_key_env is None: + mapped_api_key_env = bearer_token_env_var + if mapped_api_key_header is None: + mapped_api_key_header = "Authorization" + if mapped_api_key_format is None: + mapped_api_key_format = "Bearer {api_key}" + + if mapped_api_key_env is not None: + result["api_key_env"] = mapped_api_key_env + if mapped_api_key_header is not None: + result["api_key_header"] = mapped_api_key_header + if mapped_api_key_format is not None: + result["api_key_format"] = mapped_api_key_format + + result.pop("bearer_token_env_var", None) + result.pop("env_http_headers", None) + + return result + + def handle_mcp_discover_hosts(args: Namespace) -> int: """Handle 'hatch mcp discover hosts' command. @@ -1493,6 +1560,11 @@ def handle_mcp_configure(args: Namespace) -> int: startup_timeout: Optional[int] = getattr(args, "startup_timeout", None) tool_timeout: Optional[int] = getattr(args, "tool_timeout", None) enabled: Optional[bool] = getattr(args, "enabled", None) + prompt: Optional[str] = getattr(args, "prompt", None) + sampling_enabled: Optional[bool] = getattr(args, "sampling_enabled", None) + api_key_env: Optional[str] = getattr(args, "api_key_env", None) + api_key_header: Optional[str] = getattr(args, "api_key_header", None) + api_key_format: Optional[str] = getattr(args, "api_key_format", None) bearer_token_env_var: Optional[str] = getattr( args, "bearer_token_env_var", None ) @@ -1604,7 +1676,7 @@ def handle_mcp_configure(args: Namespace) -> int: config_data["timeout"] = timeout if trust: config_data["trust"] = trust - if cwd is not None: + if cwd is not None and host_type != MCPHostType.MISTRAL_VIBE: config_data["cwd"] = cwd if http_url is not None: config_data["httpUrl"] = http_url @@ -1636,11 +1708,21 @@ def handle_mcp_configure(args: Namespace) -> int: config_data["startup_timeout_sec"] = startup_timeout if tool_timeout is not None: config_data["tool_timeout_sec"] = tool_timeout + if prompt is not None: + config_data["prompt"] = prompt + if sampling_enabled is not None: + config_data["sampling_enabled"] = sampling_enabled + if api_key_env is not None: + config_data["api_key_env"] = api_key_env + if api_key_header is not None: + config_data["api_key_header"] = api_key_header + if api_key_format is not None: + config_data["api_key_format"] = api_key_format if enabled is not None: config_data["enabled"] = enabled - if bearer_token_env_var is not None: + if bearer_token_env_var is not None and host_type != MCPHostType.MISTRAL_VIBE: config_data["bearer_token_env_var"] = bearer_token_env_var - if env_header is not None: + if env_header is not None and host_type != MCPHostType.MISTRAL_VIBE: env_http_headers = {} for header_spec in env_header: if "=" in header_spec: @@ -1649,6 +1731,19 @@ def handle_mcp_configure(args: Namespace) -> int: if env_http_headers: config_data["env_http_headers"] = env_http_headers + if host_type == MCPHostType.MISTRAL_VIBE: + config_data = _apply_mistral_vibe_cli_mappings( + config_data, + command=command, + url=url, + http_url=http_url, + bearer_token_env_var=bearer_token_env_var, + env_header=env_header, + api_key_env=api_key_env, + api_key_header=api_key_header, + api_key_format=api_key_format, + ) + # Partial update merge logic if is_update: existing_data = existing_config.model_dump( @@ -1661,6 +1756,7 @@ def handle_mcp_configure(args: Namespace) -> int: existing_data.pop("command", None) existing_data.pop("args", None) existing_data.pop("type", None) + existing_data.pop("transport", None) if command is not None and ( existing_config.url is not None @@ -1670,6 +1766,10 @@ def handle_mcp_configure(args: Namespace) -> int: existing_data.pop("httpUrl", None) existing_data.pop("headers", None) existing_data.pop("type", None) + existing_data.pop("transport", None) + existing_data.pop("api_key_env", None) + existing_data.pop("api_key_header", None) + existing_data.pop("api_key_format", None) merged_data = {**existing_data, **config_data} config_data = merged_data diff --git a/tests/integration/cli/test_cli_reporter_integration.py b/tests/integration/cli/test_cli_reporter_integration.py index a0490f5..373ad5f 100644 --- a/tests/integration/cli/test_cli_reporter_integration.py +++ b/tests/integration/cli/test_cli_reporter_integration.py @@ -30,6 +30,46 @@ def _handler_uses_result_reporter(handler_module_source: str) -> bool: class TestMCPConfigureHandlerIntegration: """Integration tests for handle_mcp_configure → ResultReporter flow.""" + @staticmethod + def _base_configure_args(**overrides): + """Create a baseline Namespace for handle_mcp_configure tests.""" + data = dict( + host="claude-desktop", + server_name="test-server", + server_command="python", + args=["server.py"], + env_var=None, + url=None, + header=None, + timeout=None, + trust=False, + cwd=None, + env_file=None, + http_url=None, + include_tools=None, + exclude_tools=None, + input=None, + disabled=None, + auto_approve_tools=None, + disable_tools=None, + env_vars=None, + startup_timeout=None, + tool_timeout=None, + enabled=None, + prompt=None, + sampling_enabled=None, + api_key_env=None, + api_key_header=None, + api_key_format=None, + bearer_token_env_var=None, + env_header=None, + no_backup=True, + dry_run=False, + auto_approve=True, + ) + data.update(overrides) + return Namespace(**data) + def test_handler_imports_result_reporter(self): """Handler module should import ResultReporter from cli_utils. @@ -60,35 +100,7 @@ def test_handler_uses_result_reporter_for_output(self): from hatch.cli.cli_mcp import handle_mcp_configure # Create mock args for a simple configure operation - args = Namespace( - host="claude-desktop", - server_name="test-server", - server_command="python", - args=["server.py"], - env_var=None, - url=None, - header=None, - timeout=None, - trust=False, - cwd=None, - env_file=None, - http_url=None, - include_tools=None, - exclude_tools=None, - input=None, - disabled=None, - auto_approve_tools=None, - disable_tools=None, - env_vars=None, - startup_timeout=None, - tool_timeout=None, - enabled=None, - bearer_token_env_var=None, - env_header=None, - no_backup=True, - dry_run=False, - auto_approve=True, # Skip confirmation - ) + args = self._base_configure_args(auto_approve=True) # Mock the MCPHostConfigurationManager with patch( @@ -123,35 +135,7 @@ def test_handler_dry_run_shows_preview(self): """ from hatch.cli.cli_mcp import handle_mcp_configure - args = Namespace( - host="claude-desktop", - server_name="test-server", - server_command="python", - args=["server.py"], - env_var=None, - url=None, - header=None, - timeout=None, - trust=False, - cwd=None, - env_file=None, - http_url=None, - include_tools=None, - exclude_tools=None, - input=None, - disabled=None, - auto_approve_tools=None, - disable_tools=None, - env_vars=None, - startup_timeout=None, - tool_timeout=None, - enabled=None, - bearer_token_env_var=None, - env_header=None, - no_backup=True, - dry_run=True, # Dry-run enabled - auto_approve=True, - ) + args = self._base_configure_args(dry_run=True, auto_approve=True) with patch( "hatch.cli.cli_mcp.MCPHostConfigurationManager" @@ -185,35 +169,7 @@ def test_handler_shows_prompt_before_confirmation(self): """ from hatch.cli.cli_mcp import handle_mcp_configure - args = Namespace( - host="claude-desktop", - server_name="test-server", - server_command="python", - args=["server.py"], - env_var=None, - url=None, - header=None, - timeout=None, - trust=False, - cwd=None, - env_file=None, - http_url=None, - include_tools=None, - exclude_tools=None, - input=None, - disabled=None, - auto_approve_tools=None, - disable_tools=None, - env_vars=None, - startup_timeout=None, - tool_timeout=None, - enabled=None, - bearer_token_env_var=None, - env_header=None, - no_backup=True, - dry_run=False, - auto_approve=False, # Will prompt for confirmation - ) + args = self._base_configure_args(auto_approve=False) with patch( "hatch.cli.cli_mcp.MCPHostConfigurationManager" @@ -240,6 +196,112 @@ def test_handler_shows_prompt_before_confirmation(self): "hatch mcp configure" in output or "[CONFIGURE]" in output ), "Handler should show consequence preview before confirmation" + def test_mistral_vibe_maps_http_and_api_key_flags(self): + """Mistral Vibe should map reusable CLI flags to host-native fields.""" + from hatch.cli.cli_mcp import handle_mcp_configure + + args = self._base_configure_args( + host="mistral-vibe", + server_command=None, + args=None, + http_url="https://example.com/mcp", + startup_timeout=15, + tool_timeout=90, + bearer_token_env_var="MISTRAL_API_KEY", + prompt="Be concise.", + sampling_enabled=True, + auto_approve=True, + ) + + with patch( + "hatch.cli.cli_mcp.MCPHostConfigurationManager" + ) as mock_manager_class: + mock_manager = MagicMock() + mock_manager.get_server_config.return_value = None + mock_result = MagicMock(success=True, backup_path=None) + mock_manager.configure_server.return_value = mock_result + mock_manager_class.return_value = mock_manager + + result = handle_mcp_configure(args) + + assert result == EXIT_SUCCESS + + passed_config = mock_manager.configure_server.call_args.kwargs["server_config"] + assert passed_config.url == "https://example.com/mcp" + assert passed_config.transport == "http" + assert passed_config.httpUrl is None + assert passed_config.startup_timeout_sec == 15 + assert passed_config.tool_timeout_sec == 90 + assert passed_config.prompt == "Be concise." + assert passed_config.sampling_enabled is True + assert passed_config.api_key_env == "MISTRAL_API_KEY" + assert passed_config.api_key_header == "Authorization" + assert passed_config.api_key_format == "Bearer {api_key}" + assert passed_config.cwd is None + + def test_mistral_vibe_maps_env_header_to_api_key_fields(self): + """Single --env-header should map to Mistral Vibe api_key_* fields.""" + from hatch.cli.cli_mcp import handle_mcp_configure + + args = self._base_configure_args( + host="mistral-vibe", + server_command=None, + args=None, + url="https://example.com/mcp", + env_header=["X-API-Key=MISTRAL_TOKEN"], + api_key_format="Token {api_key}", + auto_approve=True, + ) + + with patch( + "hatch.cli.cli_mcp.MCPHostConfigurationManager" + ) as mock_manager_class: + mock_manager = MagicMock() + mock_manager.get_server_config.return_value = None + mock_result = MagicMock(success=True, backup_path=None) + mock_manager.configure_server.return_value = mock_result + mock_manager_class.return_value = mock_manager + + result = handle_mcp_configure(args) + + assert result == EXIT_SUCCESS + + passed_config = mock_manager.configure_server.call_args.kwargs["server_config"] + assert passed_config.url == "https://example.com/mcp" + assert passed_config.transport == "streamable-http" + assert passed_config.api_key_env == "MISTRAL_TOKEN" + assert passed_config.api_key_header == "X-API-Key" + assert passed_config.api_key_format == "Token {api_key}" + + def test_mistral_vibe_does_not_forward_cwd(self): + """Mistral Vibe should ignore --cwd because the host config has no cwd field.""" + from hatch.cli.cli_mcp import handle_mcp_configure + + args = self._base_configure_args( + host="mistral-vibe", + server_command="python", + args=["server.py"], + cwd="/tmp/mistral", + auto_approve=True, + ) + + with patch( + "hatch.cli.cli_mcp.MCPHostConfigurationManager" + ) as mock_manager_class: + mock_manager = MagicMock() + mock_manager.get_server_config.return_value = None + mock_result = MagicMock(success=True, backup_path=None) + mock_manager.configure_server.return_value = mock_result + mock_manager_class.return_value = mock_manager + + result = handle_mcp_configure(args) + + assert result == EXIT_SUCCESS + passed_config = mock_manager.configure_server.call_args.kwargs["server_config"] + assert passed_config.command == "python" + assert passed_config.transport == "stdio" + assert passed_config.cwd is None + class TestMCPSyncHandlerIntegration: """Integration tests for handle_mcp_sync → ResultReporter flow.""" From 1a81ae04dcfe8c67401d1dd5d68055bc16f09896 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 11 Mar 2026 10:53:30 +0900 Subject: [PATCH 50/71] test(mcp-hosts): keep mistral vibe in shared adapter coverage Extend the canonical fixture registry and generated filtering matrix so future MCP host changes exercise Mistral Vibe through the same shared coverage paths as the other supported hosts. --- tests/regression/mcp/test_field_filtering_v2.py | 6 ++++++ .../test_data/mcp_adapters/canonical_configs.json | 15 +++++++++++++++ tests/test_data/mcp_adapters/host_registry.py | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/tests/regression/mcp/test_field_filtering_v2.py b/tests/regression/mcp/test_field_filtering_v2.py index c511532..9ca2929 100644 --- a/tests/regression/mcp/test_field_filtering_v2.py +++ b/tests/regression/mcp/test_field_filtering_v2.py @@ -57,6 +57,11 @@ def regression_test(func): "oauth_redirectUri": "http://localhost:3000/callback", "oauth_tokenParamName": "access_token", "bearer_token_env_var": "BEARER_TOKEN", + "prompt": "Be concise.", + "api_key_env": "MISTRAL_API_KEY", + "api_key_header": "Authorization", + "api_key_format": "Bearer {api_key}", + "transport": "streamable-http", # Integer fields "timeout": 30000, "startup_timeout_sec": 10, @@ -66,6 +71,7 @@ def regression_test(func): "oauth_enabled": False, "disabled": False, "enabled": True, + "sampling_enabled": True, # List[str] fields "args": ["--test"], "includeTools": ["tool1"], diff --git a/tests/test_data/mcp_adapters/canonical_configs.json b/tests/test_data/mcp_adapters/canonical_configs.json index 63dde7f..1d53c30 100644 --- a/tests/test_data/mcp_adapters/canonical_configs.json +++ b/tests/test_data/mcp_adapters/canonical_configs.json @@ -75,6 +75,21 @@ "enabled_tools": ["tool1", "tool2"], "disabled_tools": ["tool3"] }, + "mistral-vibe": { + "command": null, + "args": null, + "env": null, + "url": "https://example.com/mcp", + "headers": {"Authorization": "Bearer test-token"}, + "transport": "streamable-http", + "prompt": "Use concise answers.", + "startup_timeout_sec": 15, + "tool_timeout_sec": 90, + "sampling_enabled": true, + "api_key_env": "MISTRAL_API_KEY", + "api_key_header": "Authorization", + "api_key_format": "Bearer {api_key}" + }, "augment": { "command": "python", "args": ["-m", "mcp_server"], diff --git a/tests/test_data/mcp_adapters/host_registry.py b/tests/test_data/mcp_adapters/host_registry.py index 502e2ab..e8fb5bb 100644 --- a/tests/test_data/mcp_adapters/host_registry.py +++ b/tests/test_data/mcp_adapters/host_registry.py @@ -27,6 +27,7 @@ from hatch.mcp_host_config.adapters.gemini import GeminiAdapter from hatch.mcp_host_config.adapters.kiro import KiroAdapter from hatch.mcp_host_config.adapters.lmstudio import LMStudioAdapter +from hatch.mcp_host_config.adapters.mistral_vibe import MistralVibeAdapter from hatch.mcp_host_config.adapters.opencode import OpenCodeAdapter from hatch.mcp_host_config.adapters.vscode import VSCodeAdapter from hatch.mcp_host_config.fields import ( @@ -39,6 +40,7 @@ GEMINI_FIELDS, KIRO_FIELDS, LMSTUDIO_FIELDS, + MISTRAL_VIBE_FIELDS, OPENCODE_FIELDS, TYPE_SUPPORTING_HOSTS, VSCODE_FIELDS, @@ -59,6 +61,7 @@ "gemini": GEMINI_FIELDS, "kiro": KIRO_FIELDS, "codex": CODEX_FIELDS, + "mistral-vibe": MISTRAL_VIBE_FIELDS, "opencode": OPENCODE_FIELDS, "augment": AUGMENT_FIELDS, } @@ -99,6 +102,7 @@ def get_adapter(self) -> BaseAdapter: "gemini": GeminiAdapter, "kiro": KiroAdapter, "codex": CodexAdapter, + "mistral-vibe": MistralVibeAdapter, "opencode": OpenCodeAdapter, "augment": AugmentAdapter, } @@ -358,6 +362,7 @@ def generate_unsupported_field_test_cases( | GEMINI_FIELDS | KIRO_FIELDS | CODEX_FIELDS + | MISTRAL_VIBE_FIELDS | OPENCODE_FIELDS | AUGMENT_FIELDS ) From 4ff275863cce0adc2999cec06963546421cca7f5 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 11 Mar 2026 10:53:37 +0900 Subject: [PATCH 51/71] docs(mcp-hosts): capture mistral vibe host analysis Keep the implementation notes for the new host in a dedicated reports commit so the code history stays focused while the discovery context remains available for later review. --- .../mistral_vibe/00-parameter_analysis_v0.md | 55 +++++++++++++++++++ .../01-architecture_analysis_v0.md | 26 +++++++++ __reports__/mistral_vibe/README.md | 12 ++++ 3 files changed, 93 insertions(+) create mode 100644 __reports__/mistral_vibe/00-parameter_analysis_v0.md create mode 100644 __reports__/mistral_vibe/01-architecture_analysis_v0.md create mode 100644 __reports__/mistral_vibe/README.md diff --git a/__reports__/mistral_vibe/00-parameter_analysis_v0.md b/__reports__/mistral_vibe/00-parameter_analysis_v0.md new file mode 100644 index 0000000..750a7b7 --- /dev/null +++ b/__reports__/mistral_vibe/00-parameter_analysis_v0.md @@ -0,0 +1,55 @@ +# Mistral Vibe Parameter Analysis + +## Model + +| Item | Finding | +| --- | --- | +| Host | Mistral Vibe | +| Config path | `./.vibe/config.toml` first, fallback `~/.vibe/config.toml` | +| Config key | `mcp_servers` | +| Structure | TOML array-of-tables: `[[mcp_servers]]` | +| Server identity | Inline `name` field per entry | + +## Field Summary + +| Category | Fields | +| --- | --- | +| Transport | `transport`, `command`, `args`, `url` | +| Common | `headers`, `prompt`, `startup_timeout_sec`, `tool_timeout_sec`, `sampling_enabled` | +| Auth | `api_key_env`, `api_key_header`, `api_key_format` | +| Local-only | `env` | + +## Host Spec + +```yaml +host: mistral-vibe +format: toml +config_key: mcp_servers +config_paths: + - ./.vibe/config.toml + - ~/.vibe/config.toml +transport_discriminator: transport +supported_transports: + - stdio + - http + - streamable-http +canonical_mapping: + type_to_transport: + stdio: stdio + http: http + sse: streamable-http + httpUrl_to_url: true +extra_fields: + - prompt + - sampling_enabled + - api_key_env + - api_key_header + - api_key_format + - startup_timeout_sec + - tool_timeout_sec +``` + +## Sources + +- Mistral Vibe README and docs pages for config path precedence +- Upstream source definitions for MCP transport variants in `vibe/core/config` diff --git a/__reports__/mistral_vibe/01-architecture_analysis_v0.md b/__reports__/mistral_vibe/01-architecture_analysis_v0.md new file mode 100644 index 0000000..9cf392c --- /dev/null +++ b/__reports__/mistral_vibe/01-architecture_analysis_v0.md @@ -0,0 +1,26 @@ +# Mistral Vibe Architecture Analysis + +## Model + +| Layer | Change | +| --- | --- | +| Unified model | Add Vibe-native fields and host enum | +| Adapter | New `MistralVibeAdapter` to map canonical fields to Vibe TOML entries | +| Strategy | New TOML strategy for `[[mcp_servers]]` read/write with key preservation | +| Registries | Add adapter, strategy, backup/reporting, and fixture registration | +| Tests | Extend generic adapter suites and add focused TOML strategy tests | + +## Integration Notes + +| Concern | Decision | +| --- | --- | +| Local vs global config | Prefer existing project-local file, otherwise global fallback | +| Remote transport mapping | Canonical `type=sse` maps to Vibe `streamable-http` | +| Cross-host sync | Accept canonical `type` and `httpUrl`, serialize to `transport` + `url` | +| Non-MCP settings | Preserve other top-level TOML keys on write | + +## Assessment + +- **GO** — current adapter/strategy architecture already supports one more standalone TOML host. +- No dependency installation is required. +- Main regression surface is registry completeness and TOML round-tripping, covered by targeted tests. diff --git a/__reports__/mistral_vibe/README.md b/__reports__/mistral_vibe/README.md new file mode 100644 index 0000000..9e566e1 --- /dev/null +++ b/__reports__/mistral_vibe/README.md @@ -0,0 +1,12 @@ +# Mistral Vibe Reports + +## Status + +- Latest discovery: `00-parameter_analysis_v0.md` +- Latest architecture analysis: `01-architecture_analysis_v0.md` +- Current assessment: `GO` + +## Documents + +1. `00-parameter_analysis_v0.md` — upstream config path/schema discovery and host spec +2. `01-architecture_analysis_v0.md` — integration plan, touched files, and go/no-go assessment From 5130c84a4ddc387e53d140ec56c6c455ca27b55c Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Mon, 16 Mar 2026 22:49:53 +0900 Subject: [PATCH 52/71] docs(mcp-hosts): add mistral vibe to supported platforms Agent-Id: agent-7d172f15-6b95-455e-a27a-bc58509026fe --- README.md | 1 + docs/articles/api/cli/mcp.md | 1 + docs/articles/devs/architecture/mcp_backup_system.md | 1 + docs/articles/devs/architecture/mcp_host_configuration.md | 1 + docs/articles/users/CLIReference.md | 3 +++ docs/articles/users/MCPHostConfiguration.md | 1 + .../04-mcp-host-configuration/01-host-platform-overview.md | 1 + 7 files changed, 9 insertions(+) diff --git a/README.md b/README.md index b6cb5d5..f547137 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Hatch supports deployment to the following MCP host platforms: - **Codex** — OpenAI Codex with MCP server configuration support - **LM Studio** — Local LLM inference platform with MCP server integration - **Google Gemini CLI** — Command-line interface for Google's Gemini model with MCP support +- **Mistral Vibe** — Mistral Vibe IDE with MCP support ## Quick Start diff --git a/docs/articles/api/cli/mcp.md b/docs/articles/api/cli/mcp.md index a0b824f..bd4650e 100644 --- a/docs/articles/api/cli/mcp.md +++ b/docs/articles/api/cli/mcp.md @@ -24,6 +24,7 @@ This module provides handlers for: - codex: OpenAI Codex - lm-studio: LM Studio - gemini: Google Gemini +- mistral-vibe: Mistral Vibe IDE ## Handler Functions diff --git a/docs/articles/devs/architecture/mcp_backup_system.md b/docs/articles/devs/architecture/mcp_backup_system.md index 8aaafc6..46f705d 100644 --- a/docs/articles/devs/architecture/mcp_backup_system.md +++ b/docs/articles/devs/architecture/mcp_backup_system.md @@ -146,6 +146,7 @@ The system supports all MCP host platforms: | `cursor` | Cursor IDE MCP integration | | `lmstudio` | LM Studio MCP support | | `gemini` | Google Gemini MCP integration | +| `mistral-vibe` | Mistral Vibe IDE MCP integration | ## Performance Characteristics diff --git a/docs/articles/devs/architecture/mcp_host_configuration.md b/docs/articles/devs/architecture/mcp_host_configuration.md index d8a7977..42de522 100644 --- a/docs/articles/devs/architecture/mcp_host_configuration.md +++ b/docs/articles/devs/architecture/mcp_host_configuration.md @@ -143,6 +143,7 @@ supported = registry.get_supported_hosts() # List all hosts - `claude-desktop`, `claude-code` - `vscode`, `cursor`, `lmstudio` - `gemini`, `kiro`, `codex` +- `mistral-vibe` ### BaseAdapter Protocol diff --git a/docs/articles/users/CLIReference.md b/docs/articles/users/CLIReference.md index 7c9c8d3..09ee038 100644 --- a/docs/articles/users/CLIReference.md +++ b/docs/articles/users/CLIReference.md @@ -1017,6 +1017,7 @@ Available MCP Host Platforms: claude-desktop ✓ Available /Users/user/.config/claude/... cursor ✓ Available /Users/user/.cursor/mcp.json vscode ✗ Not Found - + mistral-vibe ✓ Available /Users/user/.config/mistral/mcp.toml ``` **Key Details**: @@ -1039,6 +1040,8 @@ Available MCP host platforms: Config path: ~/.cursor/config.json vscode: ✗ Not detected Config path: ~/.vscode/config.json + mistral-vibe: ✓ Available + Config path: ~/.config/mistral/mcp.toml ``` #### `hatch mcp discover servers` diff --git a/docs/articles/users/MCPHostConfiguration.md b/docs/articles/users/MCPHostConfiguration.md index 9e2d2bf..8f9f034 100644 --- a/docs/articles/users/MCPHostConfiguration.md +++ b/docs/articles/users/MCPHostConfiguration.md @@ -23,6 +23,7 @@ Hatch currently supports configuration for these MCP host platforms: - **Codex** - OpenAI Codex with MCP server configuration support - **LM Studio** - Local language model interface - **Gemini** - Google's AI development environment +- **Mistral Vibe** - Mistral Vibe IDE with MCP support ## Hands-on Learning diff --git a/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md b/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md index 05a14f9..3cacb91 100644 --- a/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md +++ b/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md @@ -57,6 +57,7 @@ Hatch currently supports configuration for these MCP host platforms: - [**Codex**](https://github.com/openai/codex) - OpenAI Codex with MCP server configuration support - [**LM Studio**](https://lmstudio.ai/) - Local language model interface - [**Gemini**](https://github.com/google-gemini/gemini-cli) - Google's AI Command Line Interface +- [**Mistral Vibe**](https://mistral.ai/vibe) - Mistral Vibe IDE with MCP support ## Configuration Management Workflow From bfa8b9b6ae6769aafa50bb198177323ae70703c6 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Mon, 16 Mar 2026 22:57:52 +0900 Subject: [PATCH 53/71] docs: correct Mistral Vibe terminology to CLI coding agent - Replace 'Mistral Vibe IDE with MCP support' with 'Mistral Vibe CLI coding agent' - Ensure accurate description across all documentation files - Maintain consistency in host identifier (mistral-vibe) --- README.md | 2 +- docs/articles/api/cli/mcp.md | 2 +- docs/articles/devs/architecture/mcp_backup_system.md | 2 +- docs/articles/users/MCPHostConfiguration.md | 2 +- .../04-mcp-host-configuration/01-host-platform-overview.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f547137..9890844 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Hatch supports deployment to the following MCP host platforms: - **Codex** — OpenAI Codex with MCP server configuration support - **LM Studio** — Local LLM inference platform with MCP server integration - **Google Gemini CLI** — Command-line interface for Google's Gemini model with MCP support -- **Mistral Vibe** — Mistral Vibe IDE with MCP support +- **Mistral Vibe** — Mistral Vibe CLI coding agent ## Quick Start diff --git a/docs/articles/api/cli/mcp.md b/docs/articles/api/cli/mcp.md index bd4650e..252c9dd 100644 --- a/docs/articles/api/cli/mcp.md +++ b/docs/articles/api/cli/mcp.md @@ -24,7 +24,7 @@ This module provides handlers for: - codex: OpenAI Codex - lm-studio: LM Studio - gemini: Google Gemini -- mistral-vibe: Mistral Vibe IDE +- mistral-vibe: Mistral Vibe CLI coding agent ## Handler Functions diff --git a/docs/articles/devs/architecture/mcp_backup_system.md b/docs/articles/devs/architecture/mcp_backup_system.md index 46f705d..34f167f 100644 --- a/docs/articles/devs/architecture/mcp_backup_system.md +++ b/docs/articles/devs/architecture/mcp_backup_system.md @@ -146,7 +146,7 @@ The system supports all MCP host platforms: | `cursor` | Cursor IDE MCP integration | | `lmstudio` | LM Studio MCP support | | `gemini` | Google Gemini MCP integration | -| `mistral-vibe` | Mistral Vibe IDE MCP integration | +| `mistral-vibe` | Mistral Vibe CLI coding agent | ## Performance Characteristics diff --git a/docs/articles/users/MCPHostConfiguration.md b/docs/articles/users/MCPHostConfiguration.md index 8f9f034..47adab5 100644 --- a/docs/articles/users/MCPHostConfiguration.md +++ b/docs/articles/users/MCPHostConfiguration.md @@ -23,7 +23,7 @@ Hatch currently supports configuration for these MCP host platforms: - **Codex** - OpenAI Codex with MCP server configuration support - **LM Studio** - Local language model interface - **Gemini** - Google's AI development environment -- **Mistral Vibe** - Mistral Vibe IDE with MCP support +- **Mistral Vibe** - Mistral Vibe CLI coding agent ## Hands-on Learning diff --git a/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md b/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md index 3cacb91..ec98fe2 100644 --- a/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md +++ b/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md @@ -57,7 +57,7 @@ Hatch currently supports configuration for these MCP host platforms: - [**Codex**](https://github.com/openai/codex) - OpenAI Codex with MCP server configuration support - [**LM Studio**](https://lmstudio.ai/) - Local language model interface - [**Gemini**](https://github.com/google-gemini/gemini-cli) - Google's AI Command Line Interface -- [**Mistral Vibe**](https://mistral.ai/vibe) - Mistral Vibe IDE with MCP support +- [**Mistral Vibe**](https://mistral.ai/vibe) - Mistral Vibe CLI coding agent ## Configuration Management Workflow From 62f99cf4955c23983efea25c4ea8fe15ad0dc0c4 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Sat, 14 Mar 2026 00:13:00 +0900 Subject: [PATCH 54/71] fix(mcp-hosts): add explicit HTTP transport type for Claude URL-based co Agent-Id: agent-4b2c06a4-05c6-47fd-84e7-4c704eb9fa05 Linked-Note-Id: 3d4ca15e-5b56-430d-9ea4-f5b77e4207aa --- hatch/mcp_host_config/adapters/claude.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hatch/mcp_host_config/adapters/claude.py b/hatch/mcp_host_config/adapters/claude.py index 9761080..1606330 100644 --- a/hatch/mcp_host_config/adapters/claude.py +++ b/hatch/mcp_host_config/adapters/claude.py @@ -161,5 +161,11 @@ def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: # Validate filtered fields self.validate_filtered(filtered) - # Return filtered (no transformations needed for Claude) + # Claude's URL-based remote configs should explicitly declare HTTP + # transport when callers omit the type field. + if "url" in filtered and "type" not in filtered: + filtered = filtered.copy() + filtered["type"] = "http" + + # Return filtered Claude config return filtered From b5c7191a18fca67dcccef73d6fc1d15074ea4886 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Sat, 14 Mar 2026 00:17:29 +0900 Subject: [PATCH 55/71] test(mcp): fix whitespace in Claude transport serialization test Agent-Id: agent-4b2c06a4-05c6-47fd-84e7-4c704eb9fa05 Linked-Note-Id: 3d4ca15e-5b56-430d-9ea4-f5b77e4207aa --- .../test_claude_transport_serialization.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/regression/mcp/test_claude_transport_serialization.py diff --git a/tests/regression/mcp/test_claude_transport_serialization.py b/tests/regression/mcp/test_claude_transport_serialization.py new file mode 100644 index 0000000..d37904a --- /dev/null +++ b/tests/regression/mcp/test_claude_transport_serialization.py @@ -0,0 +1,66 @@ +"""Regression tests for Claude-family transport serialization.""" + +import json +from pathlib import Path + +import pytest + +from hatch.mcp_host_config.adapters.claude import ClaudeAdapter +from hatch.mcp_host_config.models import MCPServerConfig + +try: + from wobble.decorators import regression_test +except ImportError: + + def regression_test(func): + return func + + +FIXTURES_PATH = ( + Path(__file__).resolve().parents[2] + / "test_data" + / "mcp_adapters" + / "claude_transport_regressions.json" +) + +with open(FIXTURES_PATH) as f: + FIXTURES = json.load(f) + + +def get_variant(host_name: str) -> str: + """Return Claude adapter variant from host name.""" + return host_name.removeprefix("claude-") + + +class TestClaudeTransportSerialization: + """Regression coverage for Claude Desktop/Code transport serialization.""" + + @pytest.mark.parametrize( + "test_case", + FIXTURES["remote_http"], + ids=lambda tc: tc["host"], + ) + @regression_test + def test_remote_url_defaults_to_http_type(self, test_case): + """URL-based Claude configs serialize with explicit HTTP transport.""" + adapter = ClaudeAdapter(variant=get_variant(test_case["host"])) + config = MCPServerConfig(**test_case["config"]) + + result = adapter.serialize(config) + + assert result == test_case["expected"] + + @pytest.mark.parametrize( + "test_case", + FIXTURES["stdio_without_type"], + ids=lambda tc: tc["host"], + ) + @regression_test + def test_stdio_config_does_not_require_type_input(self, test_case): + """Stdio Claude configs still serialize when type is omitted.""" + adapter = ClaudeAdapter(variant=get_variant(test_case["host"])) + config = MCPServerConfig(**test_case["config"]) + + result = adapter.serialize(config) + + assert result == test_case["expected"] From 904f22be42eb567e437f99513cdd25eb696b30cd Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Sat, 14 Mar 2026 00:18:09 +0900 Subject: [PATCH 56/71] fix(mcp-hosts): always set HTTP transport type for Claude URL-based conf Agent-Id: agent-3a5c1a45-c9cd-44cf-8caf-a2ba36ec4cd7 Linked-Note-Id: de87f08e-40a3-4c70-8f99-833526ed8cd5 --- hatch/mcp_host_config/adapters/claude.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hatch/mcp_host_config/adapters/claude.py b/hatch/mcp_host_config/adapters/claude.py index 1606330..a17daa5 100644 --- a/hatch/mcp_host_config/adapters/claude.py +++ b/hatch/mcp_host_config/adapters/claude.py @@ -162,8 +162,8 @@ def serialize(self, config: MCPServerConfig) -> Dict[str, Any]: self.validate_filtered(filtered) # Claude's URL-based remote configs should explicitly declare HTTP - # transport when callers omit the type field. - if "url" in filtered and "type" not in filtered: + # transport in serialized output. + if "url" in filtered: filtered = filtered.copy() filtered["type"] = "http" From d1cc2b0cb6b624e6305190e40ffa429d04806335 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Sat, 21 Mar 2026 23:54:39 +0900 Subject: [PATCH 57/71] test(mcp): add fixture for claude remote setup --- .../test_claude_transport_serialization.py | 4 +- .../claude_transport_regressions.json | 118 ++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 tests/test_data/mcp_adapters/claude_transport_regressions.json diff --git a/tests/regression/mcp/test_claude_transport_serialization.py b/tests/regression/mcp/test_claude_transport_serialization.py index d37904a..ffae1de 100644 --- a/tests/regression/mcp/test_claude_transport_serialization.py +++ b/tests/regression/mcp/test_claude_transport_serialization.py @@ -38,7 +38,7 @@ class TestClaudeTransportSerialization: @pytest.mark.parametrize( "test_case", FIXTURES["remote_http"], - ids=lambda tc: tc["host"], + ids=lambda tc: f'{tc["host"]}-{tc["case"]}', ) @regression_test def test_remote_url_defaults_to_http_type(self, test_case): @@ -53,7 +53,7 @@ def test_remote_url_defaults_to_http_type(self, test_case): @pytest.mark.parametrize( "test_case", FIXTURES["stdio_without_type"], - ids=lambda tc: tc["host"], + ids=lambda tc: f'{tc["host"]}-{tc["case"]}', ) @regression_test def test_stdio_config_does_not_require_type_input(self, test_case): diff --git a/tests/test_data/mcp_adapters/claude_transport_regressions.json b/tests/test_data/mcp_adapters/claude_transport_regressions.json new file mode 100644 index 0000000..f3a4589 --- /dev/null +++ b/tests/test_data/mcp_adapters/claude_transport_regressions.json @@ -0,0 +1,118 @@ +{ + "remote_http": [ + { + "case": "input_type_omitted", + "host": "claude-desktop", + "config": { + "name": "remote-server", + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + } + }, + "expected": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + }, + "type": "http" + } + }, + { + "case": "input_type_omitted", + "host": "claude-code", + "config": { + "name": "remote-server", + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + } + }, + "expected": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + }, + "type": "http" + } + }, + { + "case": "input_type_sse", + "host": "claude-desktop", + "config": { + "name": "remote-server", + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + }, + "type": "sse" + }, + "expected": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + }, + "type": "http" + } + }, + { + "case": "input_type_sse", + "host": "claude-code", + "config": { + "name": "remote-server", + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + }, + "type": "sse" + }, + "expected": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer token" + }, + "type": "http" + } + } + ], + "stdio_without_type": [ + { + "case": "input_type_omitted", + "host": "claude-desktop", + "config": { + "name": "stdio-server", + "command": "python", + "args": ["-m", "mcp_server"], + "env": { + "API_KEY": "secret" + } + }, + "expected": { + "command": "python", + "args": ["-m", "mcp_server"], + "env": { + "API_KEY": "secret" + } + } + }, + { + "case": "input_type_omitted", + "host": "claude-code", + "config": { + "name": "stdio-server", + "command": "python", + "args": ["-m", "mcp_server"], + "env": { + "API_KEY": "secret" + } + }, + "expected": { + "command": "python", + "args": ["-m", "mcp_server"], + "env": { + "API_KEY": "secret" + } + } + } + ] +} From d6a75a8518fc26e138573bec79ed7311aff47ac7 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Sun, 22 Mar 2026 01:03:27 +0900 Subject: [PATCH 58/71] fix(mcp-hosts): remove redundant Claude Desktop/Code URL validation Agent-Id: agent-ab465f6f-4347-4a55-a0d3-064e4d929c77 --- hatch/cli/cli_mcp.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/hatch/cli/cli_mcp.py b/hatch/cli/cli_mcp.py index 4563fae..a5c1fc3 100644 --- a/hatch/cli/cli_mcp.py +++ b/hatch/cli/cli_mcp.py @@ -1586,18 +1586,6 @@ def handle_mcp_configure(args: Namespace) -> int: ) return EXIT_ERROR - # Validate Claude Desktop/Code transport restrictions (Issue 2) - if host_type in (MCPHostType.CLAUDE_DESKTOP, MCPHostType.CLAUDE_CODE): - if url is not None: - format_validation_error( - ValidationError( - f"{host} does not support remote servers (--url)", - field="--url", - suggestion="Only local servers with --command are supported for this host", - ) - ) - return EXIT_ERROR - # Validate argument dependencies if command and header: format_validation_error( From 7aaa6eb825d61ca599235acfb5f395dfeca35656 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 23 Mar 2026 14:51:45 +0000 Subject: [PATCH 59/71] chore(release): 0.8.1-dev.3 [skip ci] ## 0.8.1-dev.3 (2026-03-23) * Merge pull request #49 from LittleCoinCoin/dev ([73666d9](https://github.com/CrackingShells/Hatch/commit/73666d9)), closes [#49](https://github.com/CrackingShells/Hatch/issues/49) * fix(mcp-hosts): add explicit HTTP transport type for Claude URL-based co ([62f99cf](https://github.com/CrackingShells/Hatch/commit/62f99cf)) * fix(mcp-hosts): always set HTTP transport type for Claude URL-based conf ([904f22b](https://github.com/CrackingShells/Hatch/commit/904f22b)) * fix(mcp-hosts): remove redundant Claude Desktop/Code URL validation ([d6a75a8](https://github.com/CrackingShells/Hatch/commit/d6a75a8)) * test(mcp-hosts): keep mistral vibe in shared adapter coverage ([1a81ae0](https://github.com/CrackingShells/Hatch/commit/1a81ae0)) * test(mcp): add fixture for claude remote setup ([d1cc2b0](https://github.com/CrackingShells/Hatch/commit/d1cc2b0)) * test(mcp): fix whitespace in Claude transport serialization test ([b5c7191](https://github.com/CrackingShells/Hatch/commit/b5c7191)) * docs: correct Mistral Vibe terminology to CLI coding agent ([bfa8b9b](https://github.com/CrackingShells/Hatch/commit/bfa8b9b)) * docs(mcp-hosts): add mistral vibe to supported platforms ([5130c84](https://github.com/CrackingShells/Hatch/commit/5130c84)) * docs(mcp-hosts): capture mistral vibe host analysis ([4ff2758](https://github.com/CrackingShells/Hatch/commit/4ff2758)) * feat(cli): let shared mcp configure target mistral vibe ([0e801d0](https://github.com/CrackingShells/Hatch/commit/0e801d0)) * feat(mcp-hosts): let hatch manage mistral vibe configs ([f213971](https://github.com/CrackingShells/Hatch/commit/f213971)) * ci: semantic-release streamlining ([db0fb91](https://github.com/CrackingShells/Hatch/commit/db0fb91)) --- CHANGELOG.md | 16 ++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cdaad5..1f327e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 0.8.1-dev.3 (2026-03-23) + +* Merge pull request #49 from LittleCoinCoin/dev ([73666d9](https://github.com/CrackingShells/Hatch/commit/73666d9)), closes [#49](https://github.com/CrackingShells/Hatch/issues/49) +* fix(mcp-hosts): add explicit HTTP transport type for Claude URL-based co ([62f99cf](https://github.com/CrackingShells/Hatch/commit/62f99cf)) +* fix(mcp-hosts): always set HTTP transport type for Claude URL-based conf ([904f22b](https://github.com/CrackingShells/Hatch/commit/904f22b)) +* fix(mcp-hosts): remove redundant Claude Desktop/Code URL validation ([d6a75a8](https://github.com/CrackingShells/Hatch/commit/d6a75a8)) +* test(mcp-hosts): keep mistral vibe in shared adapter coverage ([1a81ae0](https://github.com/CrackingShells/Hatch/commit/1a81ae0)) +* test(mcp): add fixture for claude remote setup ([d1cc2b0](https://github.com/CrackingShells/Hatch/commit/d1cc2b0)) +* test(mcp): fix whitespace in Claude transport serialization test ([b5c7191](https://github.com/CrackingShells/Hatch/commit/b5c7191)) +* docs: correct Mistral Vibe terminology to CLI coding agent ([bfa8b9b](https://github.com/CrackingShells/Hatch/commit/bfa8b9b)) +* docs(mcp-hosts): add mistral vibe to supported platforms ([5130c84](https://github.com/CrackingShells/Hatch/commit/5130c84)) +* docs(mcp-hosts): capture mistral vibe host analysis ([4ff2758](https://github.com/CrackingShells/Hatch/commit/4ff2758)) +* feat(cli): let shared mcp configure target mistral vibe ([0e801d0](https://github.com/CrackingShells/Hatch/commit/0e801d0)) +* feat(mcp-hosts): let hatch manage mistral vibe configs ([f213971](https://github.com/CrackingShells/Hatch/commit/f213971)) +* ci: semantic-release streamlining ([db0fb91](https://github.com/CrackingShells/Hatch/commit/db0fb91)) + ## 0.8.1-dev.2 (2026-03-04) * Merge branch 'feat/augment-mcp-host-support' into dev ([67bb767](https://github.com/CrackingShells/Hatch/commit/67bb767)) diff --git a/pyproject.toml b/pyproject.toml index 4dd5107..d7ff337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hatch-xclam" -version = "0.8.1-dev.2" +version = "0.8.1-dev.3" description = "Package manager for the Cracking Shells ecosystem" readme = "README.md" requires-python = ">=3.12" From 7d2634da12cb520bdc4b7e9e6a626ca0bbe9da70 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Mar 2026 00:11:19 +0900 Subject: [PATCH 60/71] fix(ci): rename caller workflow to match pypi trusted publisher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GITHUB_WORKFLOW_REF in a reusable workflow resolves to the calling workflow, not the called one. After the streamlining refactor, the top-level trigger was semantic-release.yml, so sigstore attestations embedded that path — PyPI rejected them because its trusted publisher was configured for publish.yml. Renamed: - semantic-release.yml → publish.yml (top-level trigger, now matches PyPI) - publish.yml → pypi-publish.yml (reusable sub-workflow) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish.yml | 186 ++++++++++++++++++------- .github/workflows/pypi-publish.yml | 91 ++++++++++++ .github/workflows/semantic-release.yml | 177 ----------------------- 3 files changed, 227 insertions(+), 227 deletions(-) create mode 100644 .github/workflows/pypi-publish.yml delete mode 100644 .github/workflows/semantic-release.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 14e31f4..e33803a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,36 +1,20 @@ -name: Publish to PyPI +name: Semantic Release on: - workflow_call: - # Automatic release path: semantic-release passes the exact tag to test and publish. - inputs: - tag: - description: 'Git tag to test and publish (for release orchestration)' - required: true - type: string - outputs: - tag: - description: 'Published git tag' - value: ${{ jobs.publish-pypi.outputs.tag }} - workflow_dispatch: - # Manual recovery path: choose the exact tag to test and publish. - inputs: - tag: - description: 'Git tag to test and publish (e.g., v1.0.0)' - required: true - type: string + push: + branches: + - main + - dev jobs: test: + if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - # Automatic path: semantic-release passes the released tag directly. - # Manual path: validate the same tag that will be published. - ref: ${{ inputs.tag }} - name: Setup Python uses: actions/setup-python@v5 @@ -46,46 +30,148 @@ jobs: run: | python -c "import hatch; print('Hatch package imports successfully')" - publish-pypi: - name: Publish to PyPI - runs-on: ubuntu-latest + release: + if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} needs: test + runs-on: ubuntu-latest outputs: - tag: ${{ steps.published_tag.outputs.tag }} - environment: - name: pypi - url: https://pypi.org/project/hatch-xclam/ - permissions: - id-token: write - + published: ${{ steps.semantic_release.outputs.published }} + tag: ${{ steps.semantic_release.outputs.tag }} steps: + - name: Generate GitHub App Token + id: generate_token + uses: tibdex/github-app-token@v2 + with: + app_id: ${{ secrets.SEMANTIC_RELEASE_APP_ID }} + private_key: ${{ secrets.SEMANTIC_RELEASE_PRIVATE_KEY }} + - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ inputs.tag }} + token: ${{ steps.generate_token.outputs.token }} - - name: Setup Python - uses: actions/setup-python@v5 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - python-version: "3.12" + node-version: "lts/*" - - name: Install Python dependencies + - name: Install Node dependencies + run: npm ci + + - name: Verify npm audit + run: npm audit signatures + + - name: Release + id: semantic_release + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + GH_TOKEN: ${{ steps.generate_token.outputs.token }} run: | - python -m pip install --upgrade pip - pip install build + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + node <<'EOF' + const fs = require('fs'); - - name: Build Python Package - run: python -m build + (async () => { + const semanticReleaseModule = await import('semantic-release'); + const semanticRelease = semanticReleaseModule.default || semanticReleaseModule; + const result = await semanticRelease(); - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + if (!process.env.GITHUB_OUTPUT) { + throw new Error('GITHUB_OUTPUT is not set'); + } + + if (!result) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'published=false\n'); + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'tag=\n'); + return; + } + + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'published=true\n'); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${result.nextRelease.gitTag}\n`); + })().catch((error) => { + console.error(error); + process.exit(1); + }); + EOF + + publish: + name: Publish released package + needs: release + if: ${{ needs.release.outputs.published == 'true' }} + uses: ./.github/workflows/pypi-publish.yml + with: + tag: ${{ needs.release.outputs.tag }} + secrets: inherit + + notify-discord: + name: Notify Discord + needs: + - release + - publish + if: ${{ needs.release.outputs.published == 'true' && needs.publish.result == 'success' }} + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Resolve GitHub release + id: release + uses: actions/github-script@v8 + env: + TAG_NAME: ${{ needs.publish.outputs.tag }} with: - print-hash: true - verbose: true - skip-existing: true + script: | + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: process.env.TAG_NAME, + }); - - name: Record published tag - id: published_tag - run: | - echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" + core.setOutput('tag_name', release.tag_name); + core.setOutput('html_url', release.html_url); + core.setOutput('is_prerelease', String(release.prerelease)); + + - name: Build Discord payload + id: discord + uses: actions/github-script@v8 + env: + TAG_NAME: ${{ steps.release.outputs.tag_name }} + HTML_URL: ${{ steps.release.outputs.html_url }} + IS_PRERELEASE: ${{ steps.release.outputs.is_prerelease }} + with: + script: | + const isPrerelease = process.env.IS_PRERELEASE === 'true'; + const tagName = process.env.TAG_NAME; + const htmlUrl = process.env.HTML_URL; + + core.setOutput('content', isPrerelease ? '' : '<@&1418053865818951721>'); + core.setOutput('title', isPrerelease + ? '🧪 Hatch Pre-release Available for Testing' + : '🎉 New *Hatch!* Release Available!'); + core.setOutput('description', isPrerelease + ? `**Version \`${tagName}\`** is now available for testing!\n\n⚠️ **This is a pre-release** - expect potential bugs and breaking changes\n🔬 Perfect for testing new features and providing feedback\n📋 Click [here](${htmlUrl}) to view what's new and download\n\n💻 Install with pip:\n\`\`\`bash\npip install hatch-xclam==${tagName}\n\`\`\`\n\nHelp us make *Hatch!* better by testing and reporting [issues](https://github.com/CrackingShells/Hatch/issues)! 🐛➡️✨` + : `**Version \`${tagName}\`** has been released!\n\n🚀 Get the latest features and improvements\n📚 Click [here](${htmlUrl}) to view the changelog and download\n\n💻 Install with pip:\n\`\`\`bash\npip install hatch-xclam\n\`\`\`\n\nHappy MCP coding with *Hatch!* 🐣`); + core.setOutput('color', isPrerelease ? '0xff9500' : '0x00ff88'); + core.setOutput('username', isPrerelease + ? 'Cracking Shells Pre-release Bot' + : 'Cracking Shells Release Bot'); + core.setOutput('image', isPrerelease + ? 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_dark_bg_transparent.png' + : 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_light_bg_transparent.png'); + core.setOutput('avatar_url', isPrerelease + ? 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_core_dark_bg.png' + : 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_icon_light_bg.png'); + + - name: Send Discord notification + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_HATCH_ANNOUNCEMENTS }} + nodetail: true + content: ${{ steps.discord.outputs.content }} + title: ${{ steps.discord.outputs.title }} + description: ${{ steps.discord.outputs.description }} + color: ${{ steps.discord.outputs.color }} + username: ${{ steps.discord.outputs.username }} + image: ${{ steps.discord.outputs.image }} + avatar_url: ${{ steps.discord.outputs.avatar_url }} diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 0000000..14e31f4 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,91 @@ +name: Publish to PyPI + +on: + workflow_call: + # Automatic release path: semantic-release passes the exact tag to test and publish. + inputs: + tag: + description: 'Git tag to test and publish (for release orchestration)' + required: true + type: string + outputs: + tag: + description: 'Published git tag' + value: ${{ jobs.publish-pypi.outputs.tag }} + workflow_dispatch: + # Manual recovery path: choose the exact tag to test and publish. + inputs: + tag: + description: 'Git tag to test and publish (e.g., v1.0.0)' + required: true + type: string + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + # Automatic path: semantic-release passes the released tag directly. + # Manual path: validate the same tag that will be published. + ref: ${{ inputs.tag }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Run import test + run: | + python -c "import hatch; print('Hatch package imports successfully')" + + publish-pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: test + outputs: + tag: ${{ steps.published_tag.outputs.tag }} + environment: + name: pypi + url: https://pypi.org/project/hatch-xclam/ + permissions: + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ inputs.tag }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build Python Package + run: python -m build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + print-hash: true + verbose: true + skip-existing: true + + - name: Record published tag + id: published_tag + run: | + echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml deleted file mode 100644 index 50fb9c4..0000000 --- a/.github/workflows/semantic-release.yml +++ /dev/null @@ -1,177 +0,0 @@ -name: Semantic Release - -on: - push: - branches: - - main - - dev - -jobs: - test: - if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install -e . - - - name: Run import test - run: | - python -c "import hatch; print('Hatch package imports successfully')" - - release: - if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} - needs: test - runs-on: ubuntu-latest - outputs: - published: ${{ steps.semantic_release.outputs.published }} - tag: ${{ steps.semantic_release.outputs.tag }} - steps: - - name: Generate GitHub App Token - id: generate_token - uses: tibdex/github-app-token@v2 - with: - app_id: ${{ secrets.SEMANTIC_RELEASE_APP_ID }} - private_key: ${{ secrets.SEMANTIC_RELEASE_PRIVATE_KEY }} - - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ steps.generate_token.outputs.token }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "lts/*" - - - name: Install Node dependencies - run: npm ci - - - name: Verify npm audit - run: npm audit signatures - - - name: Release - id: semantic_release - env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - GH_TOKEN: ${{ steps.generate_token.outputs.token }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - node <<'EOF' - const fs = require('fs'); - - (async () => { - const semanticReleaseModule = await import('semantic-release'); - const semanticRelease = semanticReleaseModule.default || semanticReleaseModule; - const result = await semanticRelease(); - - if (!process.env.GITHUB_OUTPUT) { - throw new Error('GITHUB_OUTPUT is not set'); - } - - if (!result) { - fs.appendFileSync(process.env.GITHUB_OUTPUT, 'published=false\n'); - fs.appendFileSync(process.env.GITHUB_OUTPUT, 'tag=\n'); - return; - } - - fs.appendFileSync(process.env.GITHUB_OUTPUT, 'published=true\n'); - fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${result.nextRelease.gitTag}\n`); - })().catch((error) => { - console.error(error); - process.exit(1); - }); - EOF - - publish: - name: Publish released package - needs: release - if: ${{ needs.release.outputs.published == 'true' }} - uses: ./.github/workflows/publish.yml - with: - tag: ${{ needs.release.outputs.tag }} - secrets: inherit - - notify-discord: - name: Notify Discord - needs: - - release - - publish - if: ${{ needs.release.outputs.published == 'true' && needs.publish.result == 'success' }} - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Resolve GitHub release - id: release - uses: actions/github-script@v8 - env: - TAG_NAME: ${{ needs.publish.outputs.tag }} - with: - script: | - const { data: release } = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: process.env.TAG_NAME, - }); - - core.setOutput('tag_name', release.tag_name); - core.setOutput('html_url', release.html_url); - core.setOutput('is_prerelease', String(release.prerelease)); - - - name: Build Discord payload - id: discord - uses: actions/github-script@v8 - env: - TAG_NAME: ${{ steps.release.outputs.tag_name }} - HTML_URL: ${{ steps.release.outputs.html_url }} - IS_PRERELEASE: ${{ steps.release.outputs.is_prerelease }} - with: - script: | - const isPrerelease = process.env.IS_PRERELEASE === 'true'; - const tagName = process.env.TAG_NAME; - const htmlUrl = process.env.HTML_URL; - - core.setOutput('content', isPrerelease ? '' : '<@&1418053865818951721>'); - core.setOutput('title', isPrerelease - ? '🧪 Hatch Pre-release Available for Testing' - : '🎉 New *Hatch!* Release Available!'); - core.setOutput('description', isPrerelease - ? `**Version \`${tagName}\`** is now available for testing!\n\n⚠️ **This is a pre-release** - expect potential bugs and breaking changes\n🔬 Perfect for testing new features and providing feedback\n📋 Click [here](${htmlUrl}) to view what's new and download\n\n💻 Install with pip:\n\`\`\`bash\npip install hatch-xclam==${tagName}\n\`\`\`\n\nHelp us make *Hatch!* better by testing and reporting [issues](https://github.com/CrackingShells/Hatch/issues)! 🐛➡️✨` - : `**Version \`${tagName}\`** has been released!\n\n🚀 Get the latest features and improvements\n📚 Click [here](${htmlUrl}) to view the changelog and download\n\n💻 Install with pip:\n\`\`\`bash\npip install hatch-xclam\n\`\`\`\n\nHappy MCP coding with *Hatch!* 🐣`); - core.setOutput('color', isPrerelease ? '0xff9500' : '0x00ff88'); - core.setOutput('username', isPrerelease - ? 'Cracking Shells Pre-release Bot' - : 'Cracking Shells Release Bot'); - core.setOutput('image', isPrerelease - ? 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_dark_bg_transparent.png' - : 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_light_bg_transparent.png'); - core.setOutput('avatar_url', isPrerelease - ? 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_core_dark_bg.png' - : 'https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/cs_icon_light_bg.png'); - - - name: Send Discord notification - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_HATCH_ANNOUNCEMENTS }} - nodetail: true - content: ${{ steps.discord.outputs.content }} - title: ${{ steps.discord.outputs.title }} - description: ${{ steps.discord.outputs.description }} - color: ${{ steps.discord.outputs.color }} - username: ${{ steps.discord.outputs.username }} - image: ${{ steps.discord.outputs.image }} - avatar_url: ${{ steps.discord.outputs.avatar_url }} From f7221c3f4ce93c3e2c5ac8872f88bb04f41eb9ff Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 23 Mar 2026 15:13:58 +0000 Subject: [PATCH 61/71] chore(release): 0.8.1-dev.4 [skip ci] ## 0.8.1-dev.4 (2026-03-23) * fix(ci): rename caller workflow to match pypi trusted publisher ([7d2634d](https://github.com/CrackingShells/Hatch/commit/7d2634d)) --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f327e1..1961443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.1-dev.4 (2026-03-23) + +* fix(ci): rename caller workflow to match pypi trusted publisher ([7d2634d](https://github.com/CrackingShells/Hatch/commit/7d2634d)) + ## 0.8.1-dev.3 (2026-03-23) * Merge pull request #49 from LittleCoinCoin/dev ([73666d9](https://github.com/CrackingShells/Hatch/commit/73666d9)), closes [#49](https://github.com/CrackingShells/Hatch/issues/49) diff --git a/pyproject.toml b/pyproject.toml index d7ff337..c0fa893 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hatch-xclam" -version = "0.8.1-dev.3" +version = "0.8.1-dev.4" description = "Package manager for the Cracking Shells ecosystem" readme = "README.md" requires-python = ">=3.12" From fc81e78df1d3cd8da6d5512f852c1f070347a52b Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Mar 2026 00:26:05 +0900 Subject: [PATCH 62/71] fix(ci): inline pypi publish jobs to satisfy trusted publishing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PyPI Trusted Publishing does not support reusable workflows — the publish step must run directly in the calling workflow so the OIDC token's job_workflow_ref matches the trusted publisher config. Removed the reusable pypi-publish.yml and inlined its two jobs (publish-test, publish-pypi) directly into publish.yml. Updated notify-discord to depend on publish-pypi instead of the former reusable publish job. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish.yml | 80 +++++++++++++++++++++++--- .github/workflows/pypi-publish.yml | 91 ------------------------------ 2 files changed, 71 insertions(+), 100 deletions(-) delete mode 100644 .github/workflows/pypi-publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e33803a..ef44050 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -96,21 +96,83 @@ jobs: }); EOF - publish: - name: Publish released package + publish-test: + name: Test released package needs: release if: ${{ needs.release.outputs.published == 'true' }} - uses: ./.github/workflows/pypi-publish.yml - with: - tag: ${{ needs.release.outputs.tag }} - secrets: inherit + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.release.outputs.tag }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Run import test + run: | + python -c "import hatch; print('Hatch package imports successfully')" + + publish-pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: [release, publish-test] + if: ${{ needs.release.outputs.published == 'true' }} + outputs: + tag: ${{ steps.published_tag.outputs.tag }} + environment: + name: pypi + url: https://pypi.org/project/hatch-xclam/ + permissions: + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.release.outputs.tag }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build Python Package + run: python -m build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + print-hash: true + verbose: true + skip-existing: true + + - name: Record published tag + id: published_tag + run: | + echo "tag=${{ needs.release.outputs.tag }}" >> "$GITHUB_OUTPUT" notify-discord: name: Notify Discord needs: - release - - publish - if: ${{ needs.release.outputs.published == 'true' && needs.publish.result == 'success' }} + - publish-pypi + if: ${{ needs.release.outputs.published == 'true' && needs.publish-pypi.result == 'success' }} runs-on: ubuntu-latest permissions: contents: read @@ -119,7 +181,7 @@ jobs: id: release uses: actions/github-script@v8 env: - TAG_NAME: ${{ needs.publish.outputs.tag }} + TAG_NAME: ${{ needs.publish-pypi.outputs.tag }} with: script: | const { data: release } = await github.rest.repos.getReleaseByTag({ diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml deleted file mode 100644 index 14e31f4..0000000 --- a/.github/workflows/pypi-publish.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Publish to PyPI - -on: - workflow_call: - # Automatic release path: semantic-release passes the exact tag to test and publish. - inputs: - tag: - description: 'Git tag to test and publish (for release orchestration)' - required: true - type: string - outputs: - tag: - description: 'Published git tag' - value: ${{ jobs.publish-pypi.outputs.tag }} - workflow_dispatch: - # Manual recovery path: choose the exact tag to test and publish. - inputs: - tag: - description: 'Git tag to test and publish (e.g., v1.0.0)' - required: true - type: string - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - # Automatic path: semantic-release passes the released tag directly. - # Manual path: validate the same tag that will be published. - ref: ${{ inputs.tag }} - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install -e . - - - name: Run import test - run: | - python -c "import hatch; print('Hatch package imports successfully')" - - publish-pypi: - name: Publish to PyPI - runs-on: ubuntu-latest - needs: test - outputs: - tag: ${{ steps.published_tag.outputs.tag }} - environment: - name: pypi - url: https://pypi.org/project/hatch-xclam/ - permissions: - id-token: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ inputs.tag }} - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install build - - - name: Build Python Package - run: python -m build - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - print-hash: true - verbose: true - skip-existing: true - - - name: Record published tag - id: published_tag - run: | - echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" From c605bc76b802f1165503289b99dd18169593654e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 23 Mar 2026 15:28:10 +0000 Subject: [PATCH 63/71] chore(release): 0.8.1-dev.5 [skip ci] ## 0.8.1-dev.5 (2026-03-23) * fix(ci): inline pypi publish jobs to satisfy trusted publishing ([fc81e78](https://github.com/CrackingShells/Hatch/commit/fc81e78)) --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1961443..08a9a87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.1-dev.5 (2026-03-23) + +* fix(ci): inline pypi publish jobs to satisfy trusted publishing ([fc81e78](https://github.com/CrackingShells/Hatch/commit/fc81e78)) + ## 0.8.1-dev.4 (2026-03-23) * fix(ci): rename caller workflow to match pypi trusted publisher ([7d2634d](https://github.com/CrackingShells/Hatch/commit/7d2634d)) diff --git a/pyproject.toml b/pyproject.toml index c0fa893..b4c6c71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hatch-xclam" -version = "0.8.1-dev.4" +version = "0.8.1-dev.5" description = "Package manager for the Cracking Shells ecosystem" readme = "README.md" requires-python = ">=3.12" From cff376a035f5c0a969947ef9edbbad40fb5fcc8e Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Mar 2026 14:05:43 +0900 Subject: [PATCH 64/71] chore(git): add .DS_Store to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7a12ef8..9b25ba9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ __temp__/ ## VS Code .vscode/ +## macOS +.DS_Store + # vvvvvvv Default Python Ignore vvvvvvvv # Byte-compiled / optimized / DLL files From c5cec5be85aeac252303e385726f9b3943da51d0 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Mar 2026 15:40:56 +0900 Subject: [PATCH 65/71] style(docs): add CrackingShells brand theme stylesheets for MkDocs Agent-Id: agent-194600e5-7319-4944-9b73-3a08259f3c10 Linked-Note-Id: 4472a8ad-d181-4bfc-bfa6-5bc08de20c29 --- docs/stylesheets/brand.css | 199 +++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 docs/stylesheets/brand.css diff --git a/docs/stylesheets/brand.css b/docs/stylesheets/brand.css new file mode 100644 index 0000000..96d724f --- /dev/null +++ b/docs/stylesheets/brand.css @@ -0,0 +1,199 @@ +/* ============================================================= + CrackingShells Brand Themes + Two custom MkDocs Material palette schemes: + - egg-shell (light) — custom scheme, fully standalone + - slate (dark) — Material built-in base + brand overrides + ============================================================= */ + +/* ── Light: Egg Shell ──────────────────────────────────────── */ +[data-md-color-scheme="egg-shell"] { + + /* Primary — egg yolk amber nav/header, dark green text on it */ + --md-primary-fg-color: #E8B84B; + --md-primary-fg-color--light: #F0D060; + --md-primary-fg-color--dark: #D4952A; + --md-primary-bg-color: #1D3328; + --md-primary-bg-color--light: rgba(29, 51, 40, 0.7); + + /* Accent — warm amber */ + --md-accent-fg-color: #D4952A; + --md-accent-fg-color--transparent: rgba(212, 149, 42, 0.15); + --md-accent-bg-color: #F0E8C8; + --md-accent-bg-color--light: #f5f0e0; + + /* Page & surface backgrounds — warm cream */ + --md-default-bg-color: #F7F3EA; + --md-default-bg-color--light: #EDE8DC; + --md-default-bg-color--lighter: #F0EBE0; + --md-default-bg-color--lightest: #faf7f2; + + /* Body text — deep dark green */ + --md-default-fg-color: #1D3328; + --md-default-fg-color--light: #3D5148; + --md-default-fg-color--lighter: #4A6B58; + --md-default-fg-color--lightest: rgba(29, 51, 40, 0.12); + + /* Links */ + --md-typeset-a-color: #D4952A; + + /* Code blocks */ + --md-code-fg-color: #1D3328; + --md-code-bg-color: #EDE5CE; + + /* Admonitions */ + --md-admonition-fg-color: #1D3328; + --md-admonition-bg-color: #f3edd8; + + /* Footer */ + --md-footer-fg-color: #F0E8C8; + --md-footer-fg-color--light: rgba(240, 232, 200, 0.7); + --md-footer-fg-color--lighter: rgba(240, 232, 200, 0.45); + --md-footer-bg-color: #1D3328; + --md-footer-bg-color--dark: #111D18; + + /* Keyboard key */ + --md-typeset-kbd-color: #EDE8DC; + --md-typeset-kbd-accent-color: #D4952A; + --md-typeset-kbd-border-color: #C8B898; + + /* Tables */ + --md-typeset-table-color: rgba(29, 51, 40, 0.12); + --md-typeset-table-color--light: rgba(29, 51, 40, 0.035); + + /* Sidebar scroll track */ + --md-scrollbar-thumb-bg-color: rgba(61, 81, 72, 0.35); +} + +/* Override logo for light scheme */ +[data-md-color-scheme="egg-shell"] .md-header__button.md-logo img { + content: url("../resources/images/cs_wide_light_bg.png"); +} + + +/* ── Dark: Hatch Night (scheme: slate) ────────────────────── */ +[data-md-color-scheme="slate"] { + + /* Primary — deep green nav/header */ + --md-primary-fg-color: #2A3D32; + --md-primary-fg-color--light: #3D5148; + --md-primary-fg-color--dark: #1D2B24; + --md-primary-bg-color: #F0E8C8; + --md-primary-bg-color--light: rgba(240, 232, 200, 0.7); + + /* Accent — golden amber */ + --md-accent-fg-color: #E8B84B; + --md-accent-fg-color--transparent: rgba(232, 184, 75, 0.15); + --md-accent-bg-color: #1D2B24; + --md-accent-bg-color--light: #243525; + + /* Page & surface backgrounds — near-black green */ + --md-default-bg-color: #111D18; + --md-default-bg-color--light: #1D2B24; + --md-default-bg-color--lighter: #162318; + --md-default-bg-color--lightest: rgba(255, 255, 255, 0.05); + + /* Body text — warm off-white */ + --md-default-fg-color: #E8DFC8; + --md-default-fg-color--light: rgba(232, 223, 200, 0.75); + --md-default-fg-color--lighter: rgba(232, 223, 200, 0.45); + --md-default-fg-color--lightest: rgba(232, 223, 200, 0.12); + + /* Links */ + --md-typeset-a-color: #E8B84B; + + /* Code blocks */ + --md-code-fg-color: #F0E8C8; + --md-code-bg-color: #1A2B22; + + /* Admonitions */ + --md-admonition-fg-color: #E8DFC8; + --md-admonition-bg-color: #1D2B24; + + /* Footer */ + --md-footer-fg-color: #F0E8C8; + --md-footer-fg-color--light: rgba(240, 232, 200, 0.7); + --md-footer-fg-color--lighter: rgba(240, 232, 200, 0.45); + --md-footer-bg-color: #0D1710; + --md-footer-bg-color--dark: #080F0A; + + /* Keyboard key */ + --md-typeset-kbd-color: #1D2B24; + --md-typeset-kbd-accent-color: #E8B84B; + --md-typeset-kbd-border-color: #3D5148; + + /* Tables */ + --md-typeset-table-color: rgba(232, 184, 75, 0.12); + --md-typeset-table-color--light: rgba(232, 184, 75, 0.035); + + /* Sidebar scroll track */ + --md-scrollbar-thumb-bg-color: rgba(232, 184, 75, 0.25); +} + +/* Override logo for dark scheme */ +[data-md-color-scheme="slate"] .md-header__button.md-logo img { + content: url("../resources/images/cs_wide_dark_bg.png"); +} + +/* Explicit link/nav/toc overrides — variables alone lose to Material's slate defaults */ + +/* Body content links */ +[data-md-color-scheme="slate"] .md-typeset a { + color: #E8B84B; +} +[data-md-color-scheme="slate"] .md-typeset a:hover, +[data-md-color-scheme="slate"] .md-typeset a:focus { + color: #F0D060; +} + +/* Left sidebar nav — active, hover, focus states */ +[data-md-color-scheme="slate"] .md-nav__link:focus, +[data-md-color-scheme="slate"] .md-nav__link:hover, +[data-md-color-scheme="slate"] .md-nav__link--active, +[data-md-color-scheme="slate"] .md-nav__item--active > .md-nav__link { + color: #E8B84B; +} + +/* Right TOC — active / focused section */ +[data-md-color-scheme="slate"] .md-nav--secondary .md-nav__link:focus, +[data-md-color-scheme="slate"] .md-nav--secondary .md-nav__link:hover, +[data-md-color-scheme="slate"] .md-nav--secondary .md-nav__link--active { + color: #E8B84B; +} + +/* Top navigation tabs — inactive, active, hover */ +[data-md-color-scheme="slate"] .md-tabs__link { + color: rgba(240, 232, 200, 0.65); + opacity: 1; +} +[data-md-color-scheme="slate"] .md-tabs__link--active, +[data-md-color-scheme="slate"] .md-tabs__link:hover { + color: #F0D060; + opacity: 1; +} + + +/* ── Shared tweaks ─────────────────────────────────────────── */ + +/* Slightly reduce logo height in the header so it sits comfortably */ +.md-header__button.md-logo img { + height: 1.8rem; + width: auto; +} + +/* Admonition title bars — use brand green for note, amber for warning/tip */ +[data-md-color-scheme="egg-shell"] .admonition.note > .admonition-title, +[data-md-color-scheme="egg-shell"] .admonition.info > .admonition-title { + background-color: rgba(61, 81, 72, 0.18); +} +[data-md-color-scheme="egg-shell"] .admonition.warning > .admonition-title, +[data-md-color-scheme="egg-shell"] .admonition.tip > .admonition-title { + background-color: rgba(212, 149, 42, 0.2); +} +[data-md-color-scheme="slate"] .admonition.note > .admonition-title, +[data-md-color-scheme="slate"] .admonition.info > .admonition-title { + background-color: rgba(61, 81, 72, 0.45); +} +[data-md-color-scheme="slate"] .admonition.warning > .admonition-title, +[data-md-color-scheme="slate"] .admonition.tip > .admonition-title { + background-color: rgba(232, 184, 75, 0.22); +} From 7749d487d7d2cd57c564da20daea050a560a9923 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Mar 2026 16:22:12 +0900 Subject: [PATCH 66/71] style(docs): apply CrackingShells org theme to MkDocs Agent-Id: agent-7ce7b78d-8589-4359-b7a6-af17b627c47a --- docs/stylesheets/brand.css | 4 ++-- mkdocs.yml | 27 +++++++++++++++++++++++++++ overrides/main.html | 31 +++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 overrides/main.html diff --git a/docs/stylesheets/brand.css b/docs/stylesheets/brand.css index 96d724f..e408c0e 100644 --- a/docs/stylesheets/brand.css +++ b/docs/stylesheets/brand.css @@ -66,7 +66,7 @@ /* Override logo for light scheme */ [data-md-color-scheme="egg-shell"] .md-header__button.md-logo img { - content: url("../resources/images/cs_wide_light_bg.png"); + content: url(https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_wide_light_bg_transparent.png); } @@ -131,7 +131,7 @@ /* Override logo for dark scheme */ [data-md-color-scheme="slate"] .md-header__button.md-logo img { - content: url("../resources/images/cs_wide_dark_bg.png"); + content: url(https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_wide_dark_bg_transparent.png); } /* Explicit link/nav/toc overrides — variables alone lose to Material's slate defaults */ diff --git a/mkdocs.yml b/mkdocs.yml index 1a044b2..b5752f4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,8 +7,28 @@ docs_dir: docs theme: name: material + custom_dir: overrides + logo: https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_wide_light_bg_transparent.png + favicon: https://raw.githubusercontent.com/CrackingShells/.github/main/resources/images/hatch_icon_light_bg_transparent.png + palette: + - media: "(prefers-color-scheme: light)" + scheme: egg-shell + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode features: - content.code.copy + - navigation.tabs + - navigation.top + - toc.follow + +extra_css: + - stylesheets/brand.css plugins: - search @@ -31,6 +51,13 @@ markdown_extensions: - admonition - tables - fenced_code + - attr_list + - pymdownx.tabbed: + alternate_style: true + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences + - pymdownx.inlinehilite - toc: permalink: true diff --git a/overrides/main.html b/overrides/main.html new file mode 100644 index 0000000..47cd516 --- /dev/null +++ b/overrides/main.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block scripts %} + {{ super() }} + +{% endblock %} From 2d75bcd5d4c4225f1334d02812edceadd421613e Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 24 Mar 2026 20:01:06 +0900 Subject: [PATCH 67/71] docs(cli): update to match current CLI state - Updated CLI reference documentation to match actual code state in hatch/cli/ package - Removed outdated/deprecated commands (hatch env list hosts, hatch env list servers) - Added missing commands (hatch env current) - Updated syntax, parameters, and example outputs for all commands - Reorganized Table of Contents to reflect current CLI structure - Verified against actual CLI handlers in cli_env.py, cli_package.py, cli_mcp.py, and cli_system.py" Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- docs/articles/users/CLIReference.md | 2089 +++++++++++++-------------- 1 file changed, 972 insertions(+), 1117 deletions(-) diff --git a/docs/articles/users/CLIReference.md b/docs/articles/users/CLIReference.md index 09ee038..527c61c 100644 --- a/docs/articles/users/CLIReference.md +++ b/docs/articles/users/CLIReference.md @@ -1,1117 +1,972 @@ -# CLI Reference - -This document is a compact reference of all Hatch CLI commands and options implemented in the `hatch/cli/` package, presented as tables for quick lookup. - -## Table of Contents - -``` -- [Global options](#global-options) -- [Commands](#commands) - - [hatch create](#hatch-create) - - [hatch validate](#hatch-validate) - - [hatch env](#hatch-env-environment-management) - - [hatch env create](#hatch-env-create) - - [hatch env remove](#hatch-env-remove) - - [hatch env list](#hatch-env-list) - - [hatch env use](#hatch-env-use) - - [hatch env current](#hatch-env-current) - - [hatch env python](#hatch-env-python-advanced-python-environment-subcommands) - - [hatch env python init](#hatch-env-python-init) - - [hatch env python info](#hatch-env-python-info) - - [hatch env python add-hatch-mcp](#hatch-env-python-add-hatch-mcp) - - [hatch env python remove](#hatch-env-python-remove) - - [hatch env python shell](#hatch-env-python-shell) - - [hatch package](#hatch-package-package-management) - - [hatch package add](#hatch-package-add) - - [hatch package remove](#hatch-package-remove) - - [hatch package list](#hatch-package-list) - - [hatch package sync](#hatch-package-sync) - - [hatch mcp](#hatch-mcp) - - [hatch mcp configure](#hatch-mcp-configure) - - [hatch mcp sync](#hatch-mcp-sync) - - [hatch mcp remove server](#hatch-mcp-remove-server) - - [hatch mcp remove host](#hatch-mcp-remove-host) - - [hatch mcp list hosts](#hatch-mcp-list-hosts) - - [hatch mcp list servers](#hatch-mcp-list-servers) - - [hatch mcp discover hosts](#hatch-mcp-discover-hosts) - - [hatch mcp discover servers](#hatch-mcp-discover-servers) - - [hatch mcp backup list](#hatch-mcp-backup-list) - - [hatch mcp backup restore](#hatch-mcp-backup-restore) - - [hatch mcp backup clean](#hatch-mcp-backup-clean) -``` - -## Global options - -These flags are accepted by the top-level parser and apply to all commands unless overridden. - -| Flag | Type | Description | Default | -|------|------|-------------|---------| -| `--version` | flag | Show program version and exit | n/a | -| `--envs-dir` | path | Directory to store environments | `~/.hatch/envs` | -| `--cache-ttl` | int | Cache time-to-live in seconds | `86400` (1 day) | -| `--cache-dir` | path | Directory to store cached packages | `~/.hatch/cache` | -| `--log-level` | choice | Log verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` | `WARNING` | - -Example: - -```bash -hatch --version -# Output: hatch 0.6.1 -``` - -## Commands - -Each top-level command has its own table. Use the Syntax line before the table to see how to call it. - -### `hatch create` - -Create a new package template. - -Syntax: - -`hatch create [--dir DIR] [--description DESC]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `name` | string (positional) | Package name (required) | n/a | -| `--dir`, `-d` | path | Target directory for the template | current directory | -| `--description`, `-D` | string | Package description | empty string | - -Examples: - -`hatch create my_package` - -`hatch create my_package --dir ./packages --description "My awesome package"` - ---- - -### `hatch validate` - -Validate a package structure and metadata. - -Syntax: - -`hatch validate ` - -| Argument | Type | Description | -|---:|---|---| -| `package_dir` | path (positional) | Path to package directory to validate (required) | - -Examples: - -`hatch validate ./my_package` - ---- - -### `hatch env` (environment management) - -Top-level syntax: `hatch env ...` - -#### `hatch env create` - -Create a new Hatch environment bootstrapping a Python/conda environment. - -Syntax: - -`hatch env create [--description DESC] [--python-version VER] [--no-python] [--no-hatch-mcp-server] [--hatch_mcp_server_tag TAG]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `name` | string (positional) | Environment name (required) | n/a | -| `--description`, `-D` | string | Human-readable environment description | empty string | -| `--python-version` | string | Python version to create (e.g., `3.11`) | none (manager default) | -| `--no-python` | flag | Do not create a Python environment (skip conda/mamba) | false | -| `--no-hatch-mcp-server` | flag | Do not install `hatch_mcp_server` wrapper | false | -| `--hatch-mcp-server-tag` | string | Git tag/branch for wrapper installation (e.g., `dev`, `v0.1.0`) | none | - -#### `hatch env remove` - -Syntax: - -`hatch env remove ` - -| Argument | Type | Description | -|---:|---|---| -| `name` | string (positional) | Environment name to remove (required) | - -#### `hatch env list` - -List all environments with package counts. - -Syntax: - -`hatch env list [--pattern PATTERN] [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--pattern` | string | Filter environments by name using regex pattern | none | -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch env list -Environments: - Name Python Packages - ─────────────────────────────────────── - * default 3.14.2 0 - test-env 3.11.5 3 -``` - -**Key Details**: -- Header: `"Environments:"` only -- Columns: Name (width 15), Python (width 10), Packages (width 10, right-aligned) -- Current environment marked with `"* "` prefix -- Packages column shows COUNT only -- Separator: `"─"` character (U+2500) - -#### `hatch env list hosts` - -List environment/host/server deployments from environment data. - -Syntax: - -`hatch env list hosts [--env PATTERN] [--server PATTERN] [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--env`, `-e` | string | Filter by environment name using regex pattern | none | -| `--server` | string | Filter by server name using regex pattern | none | -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch env list hosts -Environment Host Deployments: - Environment Host Server Version - ───────────────────────────────────────────────────────────────── - default claude-desktop weather-server 1.0.0 - default cursor weather-server 1.0.0 -``` - -**Description**: -Lists environment/host/server deployments from environment data. Shows only Hatch-managed packages and their host deployments. - -#### `hatch env list servers` - -List environment/server/host deployments from environment data. - -Syntax: - -`hatch env list servers [--env PATTERN] [--host PATTERN] [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--env`, `-e` | string | Filter by environment name using regex pattern | none | -| `--host` | string | Filter by host name using regex pattern (use '-' for undeployed) | none | -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch env list servers -Environment Servers: - Environment Server Host Version - ───────────────────────────────────────────────────────────────── - default weather-server claude-desktop 1.0.0 - default weather-server cursor 1.0.0 - test-env utility-pkg - 2.1.0 -``` - -**Description**: -Lists environment/server/host deployments from environment data. Shows only Hatch-managed packages. Undeployed packages show '-' in Host column. - -#### `hatch env show` - -Display detailed hierarchical view of a specific environment. - -Syntax: - -`hatch env show ` - -| Argument | Type | Description | -|---:|---|---| -| `name` | string (positional) | Environment name to show (required) | - -**Example Output**: - -```bash -$ hatch env show default -Environment: default (active) - Description: My development environment - Created: 2026-01-15 10:30:00 - - Python Environment: - Version: 3.14.2 - Executable: /path/to/python - Conda env: N/A - Status: Active - - Packages (2): - weather-server - Version: 1.0.0 - Source: registry (https://registry.example.com) - Deployed to: claude-desktop, cursor - - utility-pkg - Version: 2.1.0 - Source: local (/path/to/package) - Deployed to: (none) -``` - -**Key Details**: -- Header shows `"(active)"` suffix if current environment -- Hierarchical structure with 2-space indentation -- No separator lines between sections -- Packages section shows count in header -- Each package shows version, source, and deployed hosts - -#### `hatch env use` - -Syntax: - -`hatch env use ` - -| Argument | Type | Description | -|---:|---|---| -| `name` | string (positional) | Environment name to set as current (required) | - -#### `hatch env current` - -Syntax: - -`hatch env current` - -Description: Print the name of the current environment. - ---- - -### `hatch env python` (advanced Python environment subcommands) - -Top-level syntax: `hatch env python ...` - -#### `hatch env python init` - -Initialize or recreate a Python environment inside a Hatch environment. - -Syntax: - -`hatch env python init [--hatch_env NAME] [--python-version VER] [--force] [--no-hatch-mcp-server] [--hatch_mcp_server_tag TAG]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--hatch_env` | string | Hatch environment name (defaults to current env) | current environment | -| `--python-version` | string | Desired Python version (e.g., `3.12`) | none | -| `--force` | flag | Force recreation if it already exists | false | -| `--no-hatch-mcp-server` | flag | Skip installing `hatch_mcp_server` wrapper | false | -| `--hatch_mcp_server_tag` | string | Git tag/branch for wrapper installation | none | - -#### `hatch env python info` - -Show information about the Python environment for a Hatch environment. - -Syntax: - -`hatch env python info [--hatch_env NAME] [--detailed]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | -| `--detailed` | flag | Show additional diagnostics and package listing | false | - -When available this command prints: status, python executable, python version, conda env name, environment path, creation time, package count and package list. With `--detailed` it also prints diagnostics from the manager. - -#### `hatch env python add-hatch-mcp` - -Install the `hatch_mcp_server` wrapper into the Python environment of a Hatch env. - -Syntax: - -`hatch env python add-hatch-mcp [--hatch_env NAME] [--tag TAG]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | -| `--tag` | string | Git tag/branch for wrapper install | none | - -#### `hatch env python remove` - -Remove the Python environment associated with a Hatch environment. - -Syntax: - -`hatch env python remove [--hatch_env NAME] [--force]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | -| `--force` | flag | Skip confirmation prompt and force removal | false | - -#### `hatch env python shell` - -Launch a Python REPL or run a single command inside the Python environment. - -Syntax: - -`hatch env python shell [--hatch_env NAME] [--cmd CMD]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | -| `--cmd` | string | Command to execute inside the Python shell (optional) | none | - ---- - -### `hatch package` (package management) - -Top-level syntax: `hatch package ...` - -#### `hatch package add` - -Add a package (local path or registry name) into an environment. - -Syntax: - -`hatch package add [--env NAME] [--version VER] [--force-download] [--refresh-registry] [--auto-approve]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `package_path_or_name` | string (positional) | Path to package directory or registry package name (required) | n/a | -| `--env`, `-e` | string | Target Hatch environment name (defaults to current) | current environment | -| `--version`, `-v` | string | Version for registry packages | none | -| `--force-download`, `-f` | flag | Force fetching even if cached | false | -| `--refresh-registry`, `-r` | flag | Refresh registry metadata before resolving | false | -| `--auto-approve` | flag | Automatically approve dependency installation prompts | false | -| `--host` | string | Comma-separated list of MCP host platforms to configure (e.g., claude-desktop,cursor) | none | - -**Note:** Dependency installation prompts are also automatically approved in non-TTY environments (such as CI/CD pipelines) or when the `HATCH_AUTO_APPROVE` environment variable is set. See [Environment Variables](#environment-variables) for details. - -**MCP Host Integration:** When adding a package, if the `--host` flag is specified, Hatch will automatically configure the package's MCP servers on the specified hosts. This includes analyzing package dependencies and configuring all related MCP servers. - -**MCP Host Integration Examples:** - -```bash -# Add package and automatically configure MCP servers on specific hosts -hatch package add ./my_package --host claude-desktop,cursor - -# Add package for all available hosts -hatch package add ./my_package --host all - -# Skip host configuration (no MCP servers configured) -hatch package add ./my_package - -# Add with other flags and MCP configuration -hatch package add registry_package --version 1.0.0 --env dev-env --host gemini --auto-approve -``` - -Examples: - -`hatch package add ./my_package` - -`hatch package add registry_package --version 1.0.0 --env dev-env --auto-approve` - -#### `hatch package remove` - -Remove a package from a Hatch environment. - -Syntax: - -`hatch package remove [--env NAME]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `package_name` | string (positional) | Name of the package to remove (required) | n/a | -| `--env`, `-e` | string | Hatch environment name (defaults to current) | current environment | - -#### `hatch package list` - -**⚠️ DEPRECATED**: This command is deprecated. Use `hatch env list` to see packages inline with environment information, or `hatch env show ` for detailed package information. - -List packages installed in a Hatch environment. - -Syntax: - -`hatch package list [--env NAME]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--env`, `-e` | string | Hatch environment name (defaults to current) | current environment | - -**Example Output**: - -```bash -$ hatch package list -Warning: 'hatch package list' is deprecated. Use 'hatch env list' instead, which shows packages inline. -Packages in environment 'default': -weather-server (1.0.0) Hatch compliant: True source: https://registry.example.com location: /path/to/package -``` - -**Migration Guide**: -- For package counts: Use `hatch env list` (shows package count per environment) -- For detailed package info: Use `hatch env show ` (shows full package details) -- For deployment info: Use `hatch env list hosts` or `hatch env list servers` - -#### `hatch package sync` - -Synchronize package MCP servers to host platforms. - -Syntax: - -`hatch package sync --host [--env ENV] [--dry-run] [--auto-approve] [--no-backup]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `package_name` | string (positional) | Name of package whose MCP servers to sync | n/a | -| `--host` | string | Comma-separated list of host platforms or 'all' | n/a | -| `--env`, `-e` | string | Target Hatch environment name (defaults to current) | current environment | -| `--dry-run` | flag | Preview changes without execution | false | -| `--auto-approve` | flag | Skip confirmation prompts | false | -| `--no-backup` | flag | Disable default backup behavior of the MCP host's config file | false | - -Examples: - -`hatch package sync my-package --host claude-desktop` - -`hatch package sync weather-server --host claude-desktop,cursor --dry-run` - -# Multi-package synchronization examples -# Sync main package AND all its dependencies: -hatch package sync my-package --host all - -# Sync without creating backups -hatch package sync my-package --host claude-desktop --no-backup - ---- - -## Environment Variables - -Hatch recognizes the following environment variables to control behavior: - -| Variable | Description | Accepted Values | Default | -|----------|-------------|-----------------|---------| -| `HATCH_AUTO_APPROVE` | Automatically approve dependency installation prompts in non-interactive environments | `1`, `true`, `yes` (case-insensitive) | unset | - -### `HATCH_AUTO_APPROVE` - -When set to a truthy value (`1`, `true`, or `yes`, case-insensitive), this environment variable enables automatic approval of dependency installation prompts. This is particularly useful in CI/CD pipelines and other automated environments where user interaction is not possible. - -**Behavior:** - -- In TTY environments: User is still prompted for consent unless this variable is set -- In non-TTY environments: Installation is automatically approved regardless of this variable -- When set in any environment: Installation is automatically approved without prompting - -**Examples:** - -```bash -# Enable auto-approval for the current session -export HATCH_AUTO_APPROVE=1 -hatch package add my_package - -# Enable auto-approval for a single command -HATCH_AUTO_APPROVE=true hatch package add my_package - -# CI/CD pipeline usage -HATCH_AUTO_APPROVE=yes hatch package add production_package -``` - -**Note:** This environment variable works in conjunction with the `--auto-approve` CLI flag. Either method will enable automatic approval of installation prompts. - ---- - -## MCP Host Configuration Commands - -### `hatch mcp` - -Commands subset to manage non-hatch package MCP servers. -Top level syntax: ` ...` - -#### `hatch mcp configure` - -Configure an MCP server on a specific host platform. - -Syntax: - -`hatch mcp configure --host (--command CMD | --url URL) [--args ARGS] [--env-var ENV] [--header HEADER] [--dry-run] [--auto-approve] [--no-backup]` - -| Argument / Flag | Hosts | Type | Description | Default | -|---:|---|---|---|---| -| `server-name` | all | string (positional) | Name of the MCP server to configure | n/a | -| `--host` | all | string | Target host platform (claude-desktop, cursor, etc.) | n/a | -| `--command` | all | string | Command to execute for local servers (mutually exclusive with --url) | none | -| `--url` | all except Claude Desktop/Code | string | URL for remote MCP servers (mutually exclusive with --command) | none | -| `--http-url` | gemini | string | HTTP streaming endpoint URL | none | -| `--args` | all | string | Arguments for MCP server command (only with --command) | none | -| `--env-var` | all | string | Environment variables format: KEY=VALUE (can be used multiple times) | none | -| `--header` | all except Claude Desktop/Code | string | HTTP headers format: KEY=VALUE (only with --url) | none | -| `--timeout` | gemini | int | Request timeout in milliseconds | none | -| `--trust` | gemini | flag | Bypass tool call confirmations | false | -| `--cwd` | gemini, codex | string | Working directory for stdio transport | none | -| `--include-tools` | gemini, codex | multiple | Tool allowlist / enabled tools. Space-separated values. | none | -| `--exclude-tools` | gemini, codex | multiple | Tool blocklist / disabled tools. Space-separated values. | none | -| `--env-file` | cursor, vscode, lmstudio | string | Path to environment file | none | -| `--input` | vscode | multiple | Input variable definitions format: type,id,description[,password=true] | none | -| `--disabled` | kiro | flag | Disable the MCP server | false | -| `--auto-approve-tools` | kiro | multiple | Tool names to auto-approve. Can be used multiple times. | none | -| `--disable-tools` | kiro | multiple | Tool names to disable. Can be used multiple times. | none | -| `--env-vars` | codex | multiple | Environment variable names to whitelist/forward. Can be used multiple times. | none | -| `--startup-timeout` | codex | int | Server startup timeout in seconds (default: 10) | none | -| `--tool-timeout` | codex | int | Tool execution timeout in seconds (default: 60) | none | -| `--enabled` | codex | flag | Enable the MCP server | false | -| `--bearer-token-env-var` | codex | string | Name of env var containing bearer token for Authorization header | none | -| `--env-header` | codex | multiple | HTTP header from env var format: KEY=ENV_VAR_NAME. Can be used multiple times. | none | -| `--dry-run` | all | flag | Preview configuration without applying changes | false | -| `--auto-approve` | all | flag | Skip confirmation prompts | false | -| `--no-backup` | all | flag | Skip backup creation before configuration | false | - -**Behavior**: - -The command now displays a **conversion report** showing exactly what fields will be configured on the target host. This provides transparency about which fields are supported by the host and what values will be set. - -The conversion report shows: -- **UPDATED** fields: Fields being set with their new values (shown as `None --> value`) -- **UNSUPPORTED** fields: Fields not supported by the target host (automatically filtered out) -- **UNCHANGED** fields: Fields that already have the specified value (update operations only) - -Note: Internal metadata fields (like `name`) are not shown in the field operations list, as they are used for internal bookkeeping and are not written to host configuration files. The server name is displayed in the report header for context. - -**Example - Local Server Configuration**: - -```bash -$ hatch mcp configure my-server --host claude-desktop --command python --args server.py --env API_KEY=secret - -Server 'my-server' created for host 'claude-desktop': - command: UPDATED None --> 'python' - args: UPDATED None --> ['server.py'] - env: UPDATED None --> {'API_KEY': 'secret'} - -Configure MCP server 'my-server' on host 'claude-desktop'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'my-server' on host 'claude-desktop' -``` - -**Example - Remote Server Configuration**: - -```bash -$ hatch mcp configure api-server --host claude-desktop --url https://api.example.com --header Auth=token - -Server 'api-server' created for host 'claude-desktop': - name: UPDATED None --> 'api-server' - command: UPDATED None --> None - args: UPDATED None --> None - env: UPDATED None --> {} - url: UPDATED None --> 'https://api.example.com' - headers: UPDATED None --> {'Auth': 'token'} - -Configure MCP server 'api-server' on host 'claude-desktop'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'api-server' on host 'claude-desktop' -``` - -**Example - Advanced Gemini Configuration**: - -```bash -$ hatch mcp configure my-server --host gemini --command python --args server.py --timeout 30000 --trust --include-tools weather,calculator - -Server 'my-server' created for host 'gemini': - name: UPDATED None --> 'my-server' - command: UPDATED None --> 'python' - args: UPDATED None --> ['server.py'] - timeout: UPDATED None --> 30000 - trust: UPDATED None --> True - include_tools: UPDATED None --> ['weather', 'calculator'] - -Configure MCP server 'my-server' on host 'gemini'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'my-server' on host 'gemini' -``` - -**Example - Kiro Configuration**: - -```bash -$ hatch mcp configure my-server --host kiro --command python --args server.py --auto-approve-tools weather,calculator --disable-tools debug - -Server 'my-server' created for host 'kiro': - name: UPDATED None --> 'my-server' - command: UPDATED None --> 'python' - args: UPDATED None --> ['server.py'] - autoApprove: UPDATED None --> ['weather', 'calculator'] - disabledTools: UPDATED None --> ['debug'] - -Configure MCP server 'my-server' on host 'kiro'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'my-server' on host 'kiro' -``` - -**Example - Kiro with Disabled Server**: - -```bash -$ hatch mcp configure my-server --host kiro --command python --args server.py --disabled - -Server 'my-server' created for host 'kiro': - name: UPDATED None --> 'my-server' - command: UPDATED None --> 'python' - args: UPDATED None --> ['server.py'] - disabled: UPDATED None --> True - -Configure MCP server 'my-server' on host 'kiro'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'my-server' on host 'kiro' -``` - -**Example - Codex Configuration with Timeouts and Tool Filtering**: - -```bash -$ hatch mcp configure context7 --host codex --command npx --args "-y" "@upstash/context7-mcp" --env-vars PATH --env-vars HOME --startup-timeout 15 --tool-timeout 120 --enabled --include-tools read write --exclude-tools delete - -Server 'context7' created for host 'codex': - name: UPDATED None --> 'context7' - command: UPDATED None --> 'npx' - args: UPDATED None --> ['-y', '@upstash/context7-mcp'] - env_vars: UPDATED None --> ['PATH', 'HOME'] - startup_timeout_sec: UPDATED None --> 15 - tool_timeout_sec: UPDATED None --> 120 - enabled: UPDATED None --> True - enabled_tools: UPDATED None --> ['read', 'write'] - disabled_tools: UPDATED None --> ['delete'] - -Configure MCP server 'context7' on host 'codex'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'context7' on host 'codex' -``` - -**Example - Codex HTTP Server with Authentication**: - -```bash -$ hatch mcp configure figma --host codex --url https://mcp.figma.com/mcp --bearer-token-env-var FIGMA_OAUTH_TOKEN --env-header "X-Figma-Region=FIGMA_REGION" --header "X-Custom=static-value" - -Server 'figma' created for host 'codex': - name: UPDATED None --> 'figma' - url: UPDATED None --> 'https://mcp.figma.com/mcp' - bearer_token_env_var: UPDATED None --> 'FIGMA_OAUTH_TOKEN' - env_http_headers: UPDATED None --> {'X-Figma-Region': 'FIGMA_REGION'} - http_headers: UPDATED None --> {'X-Custom': 'static-value'} - -Configure MCP server 'figma' on host 'codex'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'figma' on host 'codex' -``` - -**Example - Remote Server Configuration**: - -```bash -$ hatch mcp configure api-server --host vscode --url https://api.example.com --header Auth=token - -Server 'api-server' created for host 'vscode': - name: UPDATED None --> 'api-server' - url: UPDATED None --> 'https://api.example.com' - headers: UPDATED None --> {'Auth': 'token'} - -Configure MCP server 'api-server' on host 'vscode'? [y/N]: y -[SUCCESS] Successfully configured MCP server 'api-server' on host 'vscode' -``` - -**Example - Dry Run Mode**: - -```bash -$ hatch mcp configure my-server --host gemini --command python --args server.py --dry-run - -[DRY RUN] Would configure MCP server 'my-server' on host 'gemini': -[DRY RUN] Command: python -[DRY RUN] Args: ['server.py'] -[DRY RUN] Backup: Enabled -[DRY RUN] Preview of changes for server 'my-server': - command: UPDATED None --> 'python' - args: UPDATED None --> ['server.py'] - -No changes were made. -``` - -**Host-Specific Field Support**: - -Different MCP hosts support different configuration fields. The conversion report automatically filters unsupported fields: - -- **Claude Desktop / Claude Code**: Supports universal fields only (command, args, env, url, headers, type) -- **Cursor / LM Studio**: Supports universal fields + envFile -- **VS Code**: Supports universal fields + envFile, inputs -- **Gemini CLI**: Supports universal fields + 14 additional fields (cwd, timeout, trust, OAuth settings, etc.) -- **Codex**: Supports universal fields + Codex-specific fields for URL-based servers (http_headers, env_http_headers, bearer_token_env_var, enabled, startup_timeout_sec, tool_timeout_sec, env_vars) - -When configuring a server with fields not supported by the target host, those fields are marked as UNSUPPORTED in the report and automatically excluded from the configuration. - -#### `hatch mcp sync` - -Synchronize MCP configurations across environments and hosts. - -The sync command displays a preview of servers to be synced before requesting confirmation, giving visibility into which servers will be affected. - -Syntax: - -`hatch mcp sync [--from-env ENV | --from-host HOST] --to-host HOSTS [--servers SERVERS | --pattern PATTERN] [--dry-run] [--auto-approve] [--no-backup]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--from-env` | string | Source Hatch environment (mutually exclusive with --from-host) | none | -| `--from-host` | string | Source host platform (mutually exclusive with --from-env) | none | -| `--to-host` | string | Target hosts (comma-separated or 'all') | n/a | -| `--servers` | string | Specific server names to sync (mutually exclusive with --pattern) | none | -| `--pattern` | string | Regex pattern for server selection (mutually exclusive with --servers) | none | -| `--dry-run` | flag | Preview synchronization without executing changes | false | -| `--auto-approve` | flag | Skip confirmation prompts | false | -| `--no-backup` | flag | Skip backup creation before synchronization | false | - -**Example Output (pre-prompt)**: - -``` -hatch mcp sync: - [INFO] Servers: weather-server, my-tool (2 total) - [SYNC] environment 'dev' → 'claude-desktop' - [SYNC] environment 'dev' → 'cursor' - Proceed? [y/N]: -``` - -When more than 3 servers match, the list is truncated: `Servers: srv1, srv2, srv3, ... (7 total)` - -**Error Output**: - -Sync failures use standardized error formatting with structured details: - -``` -[ERROR] Synchronization failed - claude-desktop: Config file not found -``` - -#### `hatch mcp remove server` - -Remove an MCP server from one or more hosts. - -Syntax: - -`hatch mcp remove server --host [--env ENV] [--dry-run] [--auto-approve] [--no-backup]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `server-name` | string (positional) | Name of the server to remove | n/a | -| `--host` | string | Target hosts (comma-separated or 'all') | n/a | -| `--env`, `-e` | string | Hatch environment name (reserved for future use) | none | -| `--dry-run` | flag | Preview removal without executing changes | false | -| `--auto-approve` | flag | Skip confirmation prompts | false | -| `--no-backup` | flag | Skip backup creation before removal | false | - -#### `hatch mcp remove host` - -Remove complete host configuration (all MCP servers from the specified host). - -Syntax: - -`hatch mcp remove host [--dry-run] [--auto-approve] [--no-backup]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `host-name` | string (positional) | Name of the host to remove | n/a | -| `--dry-run` | flag | Preview removal without executing changes | false | -| `--auto-approve` | flag | Skip confirmation prompts | false | -| `--no-backup` | flag | Skip backup creation before removal | false | - -#### `hatch mcp list hosts` - -List host/server pairs from host configuration files. - -**Purpose**: Shows ALL servers on hosts (both Hatch-managed and third-party) with Hatch management status. - -Syntax: - -`hatch mcp list hosts [--server PATTERN] [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--server` | string | Filter by server name using regex pattern | none | -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch mcp list hosts -MCP Hosts: - Host Server Hatch Environment - ───────────────────────────────────────────────────────────────── - claude-desktop weather-server ✅ default - claude-desktop third-party-tool ❌ - - cursor weather-server ✅ default -``` - -**Key Details**: -- Header: `"MCP Hosts:"` -- Columns: Host (width 18), Server (width 18), Hatch (width 8), Environment (width 15) -- Hatch column: `"✅"` for Hatch-managed, `"❌"` for third-party -- Shows ALL servers on hosts (both Hatch-managed and third-party) -- Environment column: environment name if Hatch-managed, `"-"` otherwise -- Sorted by: host (alphabetically), then server - -#### `hatch mcp list servers` - -List server/host pairs from host configuration files. - -**Purpose**: Shows ALL servers on hosts (both Hatch-managed and third-party) with Hatch management status. - -Syntax: - -`hatch mcp list servers [--host PATTERN] [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--host` | string | Filter by host name using regex pattern | none | -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch mcp list servers -MCP Servers: - Server Host Hatch Environment - ───────────────────────────────────────────────────────────────── - third-party-tool claude-desktop ❌ - - weather-server claude-desktop ✅ default - weather-server cursor ✅ default -``` - -**Key Details**: -- Header: `"MCP Servers:"` -- Columns: Server (width 18), Host (width 18), Hatch (width 8), Environment (width 15) -- Hatch column: `"✅"` for Hatch-managed, `"❌"` for third-party -- Shows ALL servers on hosts (both Hatch-managed and third-party) -- Environment column: environment name if Hatch-managed, `"-"` otherwise -- Sorted by: server (alphabetically), then host - -#### `hatch mcp show hosts` - -Show detailed hierarchical view of all MCP host configurations. - -**Purpose**: Displays comprehensive configuration details for all hosts with their servers. - -Syntax: - -`hatch mcp show hosts [--server PATTERN] [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--server` | string | Filter by server name using regex pattern | none | -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch mcp show hosts -═══════════════════════════════════════════════════════════════════════════════ -MCP Host: claude-desktop - Config Path: /Users/user/.config/claude/claude_desktop_config.json - Last Modified: 2026-02-01 15:30:00 - Backup Available: Yes (3 backups) - - Configured Servers (2): - weather-server (Hatch-managed: default) - Command: python - Args: ['-m', 'weather_server'] - Environment Variables: - API_KEY: ****** (hidden) - DEBUG: true - Last Synced: 2026-02-01 15:30:00 - Package Version: 1.0.0 - - third-party-tool (Not Hatch-managed) - Command: node - Args: ['server.js'] - -═══════════════════════════════════════════════════════════════════════════════ -MCP Host: cursor - Config Path: /Users/user/.cursor/mcp.json - Last Modified: 2026-02-01 14:20:00 - Backup Available: No - - Configured Servers (1): - weather-server (Hatch-managed: default) - Command: python - Args: ['-m', 'weather_server'] - Last Synced: 2026-02-01 14:20:00 - Package Version: 1.0.0 -``` - -**Key Details**: -- Separator: `"═" * 79` (U+2550) between hosts -- Host and server names highlighted (bold + amber when colors enabled) -- Hatch-managed servers show: `"(Hatch-managed: {environment})"` -- Third-party servers show: `"(Not Hatch-managed)"` -- Sensitive environment variables shown as `"****** (hidden)"` -- Hierarchical structure with 2-space indentation per level - -#### `hatch mcp show servers` - -Show detailed hierarchical view of all MCP server configurations across hosts. - -**Purpose**: Displays comprehensive configuration details for all servers across their host deployments. - -Syntax: - -`hatch mcp show servers [--host PATTERN] [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--host` | string | Filter by host name using regex pattern | none | -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch mcp show servers -═══════════════════════════════════════════════════════════════════════════════ -MCP Server: weather-server - Hatch Managed: Yes (default) - Package Version: 1.0.0 - - Host Configurations (2): - claude-desktop: - Command: python - Args: ['-m', 'weather_server'] - Environment Variables: - API_KEY: ****** (hidden) - DEBUG: true - Last Synced: 2026-02-01 15:30:00 - - cursor: - Command: python - Args: ['-m', 'weather_server'] - Last Synced: 2026-02-01 14:20:00 - -═══════════════════════════════════════════════════════════════════════════════ -MCP Server: third-party-tool - Hatch Managed: No - - Host Configurations (1): - claude-desktop: - Command: node - Args: ['server.js'] -``` - -**Key Details**: -- Separator: `"═" * 79` between servers -- Server and host names highlighted (bold + amber when colors enabled) -- Hatch-managed servers show: `"Hatch Managed: Yes ({environment})"` -- Third-party servers show: `"Hatch Managed: No"` -- Hierarchical structure with 2-space indentation per level - -#### `hatch mcp discover hosts` - -Discover available MCP host platforms on the system. - -**Purpose**: Shows ALL host platforms (both available and unavailable) with system detection status. - -Syntax: - -`hatch mcp discover hosts [--json]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--json` | flag | Output in JSON format | false | - -**Example Output**: - -```bash -$ hatch mcp discover hosts -Available MCP Host Platforms: - Host Status Config Path - ───────────────────────────────────────────────────────────────── - claude-desktop ✓ Available /Users/user/.config/claude/... - cursor ✓ Available /Users/user/.cursor/mcp.json - vscode ✗ Not Found - - mistral-vibe ✓ Available /Users/user/.config/mistral/mcp.toml -``` - -**Key Details**: -- Header: `"Available MCP Host Platforms:"` -- Columns: Host (width 18), Status (width 15), Config Path (width "auto") -- Status: `"✓ Available"` or `"✗ Not Found"` -- Shows ALL host types (MCPHostType enum), not just available ones - -Syntax: - -`hatch mcp discover hosts` - -**Example Output**: - -```text -Available MCP host platforms: - claude-desktop: ✓ Available - Config path: ~/.claude/config.json - cursor: ✓ Available - Config path: ~/.cursor/config.json - vscode: ✗ Not detected - Config path: ~/.vscode/config.json - mistral-vibe: ✓ Available - Config path: ~/.config/mistral/mcp.toml -``` - -#### `hatch mcp discover servers` - -Discover MCP servers in Hatch environments. - -Syntax: - -`hatch mcp discover servers [--env ENV]` - -| Flag | Type | Description | Default | -|---:|---|---|---| -| `--env` | string | Specific environment to discover servers in | current environment | - -#### `hatch mcp backup list` - -List available configuration backups for a specific host. - -Syntax: - -`hatch mcp backup list [--detailed]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `host` | string (positional) | Host platform to list backups for (e.g., claude-desktop, cursor) | n/a | -| `--detailed`, `-d` | flag | Show detailed backup information | false | - -#### `hatch mcp backup restore` - -Restore host configuration from a backup file. - -Syntax: - -`hatch mcp backup restore [--backup-file FILE] [--dry-run] [--auto-approve]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `host` | string (positional) | Host platform to restore (e.g., claude-desktop, cursor) | n/a | -| `--backup-file`, `-f` | string | Specific backup file to restore (defaults to latest) | latest backup | -| `--dry-run` | flag | Preview restore without executing changes | false | -| `--auto-approve` | flag | Skip confirmation prompts | false | - -#### `hatch mcp backup clean` - -Clean old backup files for a specific host based on retention criteria. - -Syntax: - -`hatch mcp backup clean [--older-than-days DAYS] [--keep-count COUNT] [--dry-run] [--auto-approve]` - -| Argument / Flag | Type | Description | Default | -|---:|---|---|---| -| `host` | string (positional) | Host platform to clean backups for (e.g., claude-desktop, cursor) | n/a | -| `--older-than-days` | integer | Remove backups older than specified days | none | -| `--keep-count` | integer | Keep only the most recent N backups | none | -| `--dry-run` | flag | Preview cleanup without executing changes | false | -| `--auto-approve` | flag | Skip confirmation prompts | false | - -**Note:** At least one of `--older-than-days` or `--keep-count` must be specified. - ---- - -## Exit codes - -| Code | Meaning | -|---:|---| -| `0` | Success | -| `1` | Error or failure | - -## Notes - -- The CLI is implemented in the `hatch/cli/` package with modular handler modules. Use `hatch --help` to inspect available commands and options. -- This reference mirrors the command names and option names implemented in the CLI handlers. If you change CLI arguments in code, update this file to keep documentation in sync. +# CLI Reference + +This document is a compact reference of all Hatch CLI commands and options implemented in the `hatch/cli/` package, presented as tables for quick lookup. + +## Table of Contents + +- [Global options](#global-options) +- [Commands](#commands) + - [hatch create](#hatch-create) + - [hatch validate](#hatch-validate) + - [hatch env](#hatch-env-environment-management) + - [hatch env create](#hatch-env-create) + - [hatch env remove](#hatch-env-remove) + - [hatch env list](#hatch-env-list) + - [hatch env use](#hatch-env-use) + - [hatch env current](#hatch-env-current) + - [hatch env show](#hatch-env-show) + - [hatch env python](#hatch-env-python-advanced-python-environment-subcommands) + - [hatch env python init](#hatch-env-python-init) + - [hatch env python info](#hatch-env-python-info) + - [hatch env python add-hatch-mcp](#hatch-env-python-add-hatch-mcp) + - [hatch env python remove](#hatch-env-python-remove) + - [hatch env python shell](#hatch-env-python-shell) + - [hatch package](#hatch-package-package-management) + - [hatch package add](#hatch-package-add) + - [hatch package remove](#hatch-package-remove) + - [hatch package list](#hatch-package-list) + - [hatch package sync](#hatch-package-sync) + - [hatch mcp](#hatch-mcp) + - [hatch mcp discover hosts](#hatch-mcp-discover-hosts) + - [hatch mcp discover servers](#hatch-mcp-discover-servers) + - [hatch mcp list hosts](#hatch-mcp-list-hosts) + - [hatch mcp list servers](#hatch-mcp-list-servers) + - [hatch mcp show hosts](#hatch-mcp-show-hosts) + - [hatch mcp show servers](#hatch-mcp-show-servers) + - [hatch mcp configure](#hatch-mcp-configure) + - [hatch mcp sync](#hatch-mcp-sync) + - [hatch mcp remove server](#hatch-mcp-remove-server) + - [hatch mcp remove host](#hatch-mcp-remove-host) + - [hatch mcp backup list](#hatch-mcp-backup-list) + - [hatch mcp backup restore](#hatch-mcp-backup-restore) + - [hatch mcp backup clean](#hatch-mcp-backup-clean) + +## Global options + +These flags are accepted by the top-level parser and apply to all commands unless overridden. + +| Flag | Type | Description | Default | +|------|------|-------------|---------| +| `--version` | flag | Show program version and exit | n/a | +| `--envs-dir` | path | Directory to store environments | `~/.hatch/envs` | +| `--cache-ttl` | int | Cache time-to-live in seconds | `86400` (1 day) | +| `--cache-dir` | path | Directory to store cached packages | `~/.hatch/cache` | +| `--log-level` | choice | Log verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` | `WARNING` | + +Example: + +```bash +hatch --version +# Output: hatch 0.6.1 +``` + +## Commands + +Each top-level command has its own table. Use the Syntax line before the table to see how to call it. + +### `hatch create` + + +Create a new package template. + +Syntax: + +`hatch create [--dir DIR] [--description DESC]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `name` | string (positional) | Package name (required) | n/a | +| `--dir`, `-d` | path | Target directory for the template | current directory | +| `--description`, `-D` | string | Package description | empty string | + +Examples: + +`hatch create my_package` + +`hatch create my_package --dir ./packages --description "My awesome package"` + + +--- + + +### `hatch validate` + + +Validate a package structure and metadata. + +Syntax: + +`hatch validate ` + + +| Argument | Type | Description | +|---:|---|---| +| `package_dir` | path (positional) | Path to package directory to validate (required) | + +Examples: + +`hatch validate ./my_package` + +--- + +### `hatch env` (environment management) + + +Top-level syntax: `hatch env ...` + + +#### `hatch env create` + + +Create a new Hatch environment bootstrapping a Python/conda environment. + +Syntax: + +`hatch env create [--description DESC] [--python-version VER] [--no-python] [--no-hatch-mcp-server] [--hatch-mcp-server-tag TAG]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `name` | string (positional) | Environment name (required) | n/a | +| `--description`, `-D` | string | Human-readable environment description | empty string | +| `--python-version` | string | Python version to create (e.g., `3.11`) | none (manager default) | +| `--no-python` | flag | Do not create a Python environment (skip conda/mamba) | false | +| `--no-hatch-mcp-server` | flag | Do not install `hatch_mcp_server` wrapper | false | +| `--hatch-mcp-server-tag` | string | Git tag/branch for wrapper installation | none | + +--- + +#### `hatch env remove` + + +Syntax: + +`hatch env remove ` + + +| Argument | Type | Description | +|---:|---|---| +| `name` | string (positional) | Environment name to remove (required) | + +--- + +#### `hatch env list` + + +List all environments with package counts. + +**Example Output**: + +```bash +$ hatch env list +Environments: + Name Python Packages + ─────────────────────────────────────── + * default 3.14.2 0 + test-env 3.11.5 3 +``` + +**Key Details**: +- Header: `"Environments:"` only +- Columns: Name (width 15), Python (width 10), Packages (width 10, right-aligned) +- Current environment marked with `"* "` prefix +- Packages column shows COUNT only +- Separator: `"─"` character (U+2500) + +Syntax: + +`hatch env list [--pattern PATTERN] [--json]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--pattern` | string | Filter environments by name using regex pattern | none | +| `--json` | flag | Output in JSON format | false | + +--- + +#### `hatch env use` + +Syntax: + +`hatch env use ` + + +| Argument | Type | Description | +|---:|---|---| +| `name` | string (positional) | Environment name to set as current (required) | + +--- + +#### `hatch env current` + +Syntax: + +`hatch env current` + + +Description: Print the name of the current environment. + +--- + +#### `hatch env show` + + +Display detailed hierarchical view of a specific environment. + +Syntax: + +`hatch env show ` + + +| Argument | Type | Description | +|---:|---|---| +| `name` | string (positional) | Environment name to show (required) | + +**Example Output**: + +```bash +$ hatch env show default +Environment: default (active) + Description: My development environment + Created: 2026-01-15 10:30:00 + + Python Environment: + Version: 3.14.2 + Executable: /path/to/python + Conda env: N/A + Status: Active + + Packages (2): + weather-server + Version: 1.0.0 + Source: registry (https://registry.example.com) + Deployed to: claude-desktop, cursor + + utility-pkg + Version: 2.1.0 + Source: local (/path/to/package) + Deployed to: (none) +``` + +**Key Details**: +- Header shows `"(active)"` suffix if current environment +- Hierarchical structure with 2-space indentation +- No separator lines between sections +- Packages section shows count in header +- Each package shows version, source, and deployed hosts + +--- + +### `hatch env python` (advanced Python environment subcommands) + +Top-level syntax: `hatch env python ...` + + +#### `hatch env python init` + +Initialize or recreate a Python environment inside a Hatch environment. + +Syntax: + +`hatch env python init [--hatch_env NAME] [--python-version VER] [--force] [--no-hatch-mcp-server] [--hatch_mcp_server_tag TAG]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--hatch_env` | string | Hatch environment name (defaults to current env) | current environment | +| `--python-version` | string | Desired Python version (e.g., `3.12`) | none | +| `--force` | flag | Force recreation if it already exists | false | +| `--no-hatch-mcp-server` | flag | Skip installing `hatch_mcp_server` wrapper | false | +| `--hatch_mcp_server_tag` | string | Git tag/branch for wrapper installation | none | + +--- + +#### `hatch env python info` + +Show information about the Python environment for a Hatch environment. + +Syntax: + +`hatch env python info [--hatch_env NAME] [--detailed]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | +| `--detailed` | flag | Show additional diagnostics and package listing | false | + +When available this command prints: status, python executable, python version, conda env name, environment path, creation time, package count and package list. With `--detailed` it also prints diagnostics from the manager. + + +--- + +#### `hatch env python add-hatch-mcp` + + +Install the `hatch_mcp_server` wrapper into the Python environment of a Hatch env. + + +Syntax: + +`hatch env python add-hatch-mcp [--hatch_env NAME] [--tag TAG]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | +| `--tag` | string | Git tag/branch for wrapper install | none | + +--- + +#### `hatch env python remove` + + +Remove the Python environment associated with a Hatch environment. + + +Syntax: + +`hatch env python remove [--hatch_env NAME] [--force]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | +| `--force` | flag | Skip confirmation prompt and force removal | false | + +--- + +#### `hatch env python shell` + +Launch a Python REPL or run a single command inside the Python environment. + + +Syntax: + +`hatch env python shell [--hatch_env NAME] [--cmd CMD]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--hatch_env` | string | Hatch environment name (defaults to current) | current environment | +| `--cmd` | string | Command to execute inside the Python shell (optional) | none | + +--- + +### `hatch package` (package management) + +Top-level syntax: `hatch package ...` + + +#### `hatch package add` + +Add a package (local path or registry name) into an environment. + +Syntax: + +`hatch package add [--env NAME] [--version VER] [--force-download] [--refresh-registry] [--auto-approve] [--host HOSTS]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `package_path_or_name` | string (positional) | Path to package directory or registry package name (required) | n/a | +| `--env`, `-e` | string | Target Hatch environment name (defaults to current) | current environment | +| `--version`, `-v` | string | Version for registry packages | none | +| `--force-download`, `-f` | flag | Force fetching even if cached | false | +| `--refresh-registry`, `-r` | flag | Refresh registry metadata before resolving | false | +| `--auto-approve` | flag | Automatically approve dependency installation prompts | false | +| `--host` | string | Comma-separated list of MCP host platforms to configure (e.g., claude-desktop,cursor) | none | + +**Note:** Dependency installation prompts are also automatically approved in non-TTY environments (such as CI/CD pipelines) or when the `HATCH_AUTO_APPROVE` environment variable is set. + +**MCP Host Integration:** When adding a package, if the `--host` flag is specified, Hatch will automatically configure the package's MCP servers on the specified hosts. + +Examples: + +`hatch package add ./my_package` + +`hatch package add registry_package --version 1.0.0 --env dev-env --host gemini --auto-approve` + +--- + +#### `hatch package remove` + +Remove a package from a Hatch environment. + +Syntax: + +`hatch package remove [--env NAME]` + + +| Argument / Flag | Type | Description | +|---:|---|---| +| `package_name` | string (positional) | Name of the package to remove (required) | n/a | +| `--env`, `-e` | string | Hatch environment name (defaults to current) | current environment | + +--- + +#### `hatch package list` + +**⚠️ DEPRECATED**: This command is deprecated. Use `hatch env list` to see packages inline with environment information, or `hatch env show ` for detailed package information. + +List packages installed in a Hatch environment. + +Syntax: + +`hatch package list [--env NAME]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--env`, `-e` | string | Target Hatch environment name (defaults to current) | current environment | + +**Example Output**: + +```bash +$ hatch package list +Warning: 'hatch package list' is deprecated. Use 'hatch env list' instead, which shows packages inline. +Packages in environment 'default': +weather-server (1.0.0) Hatch compliant: True source: https://registry.example.com location: /path/to/package +``` + +**Migration Guide**: +- For package counts: Use `hatch env list` (shows package count per environment) +- For detailed package info: Use `hatch env show ` (shows full package details) + +--- + +#### `hatch package sync` + +Synchronize package MCP servers to host platforms. + +Syntax: + +`hatch package sync --host [--env ENV] [--dry-run] [--auto-approve] [--no-backup]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `package_name` | string (positional) | Name of package whose MCP servers to sync | n/a | +| `--host` | string | Comma-separated list of host platforms or 'all' | n/a | +| `--env`, `-e` | string | Target Hatch environment name (defaults to current) | current environment | +| `--dry-run` | flag | Preview changes without execution | false | +| `--auto-approve` | flag | Skip confirmation prompts | false | +| `--no-backup` | flag | Disable default backup behavior of the MCP host's config file | false | + +Examples: + +`hatch package sync my-package --host claude-desktop` + +`hatch package sync weather-server --host claude-desktop,cursor --dry-run --no-backup` + + +--- + +## Environment Variables + +Hatch recognizes the following environment variables to control behavior: + +| Variable | Description | Accepted Values | Default | +|----------|-------------|-----------------|---------| +| `HATCH_AUTO_APPROVE` | Automatically approve dependency installation prompts in non-interactive environments | `1`, `true`, `yes` (case-insensitive) | unset | + +### `HATCH_AUTO_APPROVE` + +When set to a truthy value (`1`, `true`, or `yes`, case-insensitive), this environment variable enables automatic approval of dependency installation prompts. This is particularly useful in CI/CD pipelines and other automated environments where user interaction is not possible. + +**Behavior:** + +- In TTY environments: User is still prompted for consent unless this variable is set +- In non-TTY environments: Installation is automatically approved regardless of this variable +- When set in any environment: Installation is automatically approved without prompting + + +Examples: + +```bash +# Enable auto-approval for the current session +export HATCH_AUTO_APPROVE=1 +hatch package add my_package + +# Enable auto-approval for a single command +HATCH_AUTO_APPROVE=true hatch package add my_package + +# CI/CD pipeline usage +HATCH_AUTO_APPROVE=yes hatch package add production_package +``` + +--- + +## MCP Host Configuration Commands + +### `hatch mcp` + + +Commands subset to manage non-hatch package MCP servers. +Top level syntax: ` ...` + + +#### `hatch mcp discover hosts` + + +Discover available MCP host platforms on the system. + +**Purpose**: Shows ALL host platforms (both available and unavailable) with system detection status. + + +Syntax: + +`hatch mcp discover hosts [--json]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--json` | flag | Output in JSON format | false | + + +**Example Output**: + +```bash +$ hatch mcp discover hosts +Available MCP Host Platforms: + Host Status Config Path + ───────────────────────────────────────────────────────────────── + claude-desktop ✓ Available /Users/user/.config/claude/... + cursor ✓ Available /Users/user/.cursor/mcp.json + vscode ✗ Not Found - + mistral-vibe ✓ Available /Users/user/.config/mistral/mcp.toml +``` + +**Key Details**: +- Header: `"Available MCP Host Platforms:"` +- Columns: Host (width 18), Status (width 15), Config Path (width "auto") +- Status: `"✓ Available"` or `"✗ Not Found"` +- Shows ALL host types (MCPHostType enum), not just available ones + +--- + +#### `hatch mcp discover servers` + + +Discover MCP servers in Hatch environments. + + +Syntax: + +`hatch mcp discover servers [--env ENV]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--env` | string | Specific environment to discover servers in | current environment | + +--- + +#### `hatch mcp list hosts` + + +List host/server pairs from host configuration files. + + +**Purpose**: Shows ALL servers on hosts (both Hatch-managed and third-party) with Hatch management status. + + +Syntax: + +`hatch mcp list hosts [--server PATTERN] [--json]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--server` | string | Filter by server name using regex pattern | none | +| `--json` | flag | Output in JSON format | false | + + +**Example Output**: + +```bash +$ hatch mcp list hosts +MCP Hosts: + Host Server Hatch Environment + ───────────────────────────────────────────────────────────────── + claude-desktop weather-server ✅ default + claude-desktop third-party-tool ❌ - + cursor weather-server ✅ default +``` + +**Key Details**: +- Header: `"MCP Hosts:"` +- Columns: Host (width 18), Server (width 18), Hatch (width 8), Environment (width 15) +- Hatch column: `"✅"` for Hatch-managed, `"❌"` for third-party +- Shows ALL servers on hosts (both Hatch-managed and third-party) +- Environment column: environment name if Hatch-managed, `"-"` otherwise +- Sorted by: host (alphabetically), then server + +--- + +#### `hatch mcp list servers` + + +List server/host pairs from host configuration files. + +**Purpose**: Shows ALL servers on hosts (both Hatch-managed and third-party) with Hatch management status. + +Syntax: + +`hatch mcp list servers [--host PATTERN] [--json]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--host` | string | Filter by host name using regex pattern | none | +| `--json` | flag | Output in JSON format | false | + +**Example Output**: + +```bash +$ hatch mcp list servers +MCP Servers: + Server Host Hatch Environment + ───────────────────────────────────────────────────────────────── + third-party-tool claude-desktop ❌ - + weather-server claude-desktop ✅ default + weather-server cursor ✅ default +``` + +**Key Details**: +- Header: `"MCP Servers:"` +- Columns: Server (width 18), Host (width 18), Hatch (width 8), Environment (width 15) +- Hatch column: `"✅"` for Hatch-managed, `"❌"` for third-party +- Shows ALL servers on hosts (both Hatch-managed and third-party) +- Environment column: environment name if Hatch-managed, `"-"` otherwise +- Sorted by: server (alphabetically), then host + +--- + +#### `hatch mcp show hosts` + + +Show detailed hierarchical view of all MCP host configurations. + +**Purpose**: Displays comprehensive configuration details for all hosts with their servers. + +Syntax: + +`hatch mcp show hosts [--server PATTERN] [--json]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--server` | string | Filter by server name using regex pattern | none | +| `--json` | flag | Output in JSON format | false | + +**Example Output**: + +```bash +$ hatch mcp show hosts +═══════════════════════════════════════════════════════════════════════════════ +MCP Host: claude-desktop + Config Path: /Users/user/.config/claude/claude_desktop_config.json + Last Modified: 2026-02-01 15:30:00 + Backup Available: Yes (3 backups) + + Configured Servers (2): + weather-server (Hatch-managed: default) + Command: python + Args: ['-m', 'weather_server'] + Environment Variables: + API_KEY: ****** (hidden) + DEBUG: true + Last Synced: 2026-02-01 15:30:00 + Package Version: 1.0.0 + + third-party-tool (Not Hatch-managed) + Command: node + Args: ['server.js'] + +═══════════════════════════════════════════════════════════════════════════════ +MCP Host: cursor + Config Path: /Users/user/.config/cursor/mcp.json + Last Modified: 2026-02-01 14:20:00 + Backup Available: No + + Configured Servers (1): + weather-server (Hatch-managed: default) + Command: python + Args: ['-m', 'weather_server'] + Last Synced: 2026-02-01 14:20:00 + Package Version: 1.0.0 +``` + +**Key Details**: +- Separator: `"═" * 79` (U+2550) between hosts +- Host and server names highlighted (bold + amber when colors enabled) +- Hatch-managed servers show: `"(Hatch-managed: {environment})"` +- Third-party servers show: `"(Not Hatch-managed)"` +- Sensitive environment variables shown as `"****** (hidden)"` +- Hierarchical structure with 2-space indentation per level + +--- + +#### `hatch mcp show servers` + + +Show detailed hierarchical view of all MCP server configurations across hosts. + +**Purpose**: Displays comprehensive configuration details for all servers across their host deployments. + +Syntax: + +`hatch mcp show servers [--host PATTERN] [--json]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--host` | string | Filter by host name using regex pattern | none | +| `--json` | flag | Output in JSON format | false | + +**Example Output**: + +```bash +$ hatch mcp show servers +═══════════════════════════════════════════════════════════════════════════════ +MCP Server: weather-server + Hatch Managed: Yes (default) + Package Version: 1.0.0 + + Host Configurations (2): + claude-desktop: + Command: python + Args: ['-m', 'weather_server'] + Environment Variables: + API_KEY: ****** (hidden) + DEBUG: true + Last Synced: 2026-02-01 15:30:00 + + cursor: + Command: python + Args: ['-m', 'weather_server'] + Last Synced: 2026-02-01 14:20:00 + +═══════════════════════════════════════════════════════════════════════════════ +MCP Server: third-party-tool + Hatch Managed: No + + Host Configurations (1): + claude-desktop: + Command: node + Args: ['server.js'] +``` + +**Key Details**: +- Separator: `"═" * 79` between servers +- Server and host names highlighted (bold + amber when colors enabled) +- Hatch-managed servers show: `"Hatch Managed: Yes ({environment})"` +- Third-party servers show: `"Hatch Managed: No"` +- Hierarchical structure with 2-space indentation per level + +--- + +#### `hatch mcp configure` + + +Configure an MCP server on a specific host platform. + + +Syntax: + +`hatch mcp configure --host (--command CMD | --url URL) [--args ARGS] [--env-var ENV] [--header HEADER] [--http-url URL] [--timeout MS] [--trust] [--cwd DIR] [--include-tools TOOLS] [--exclude-tools TOOLS] [--env-file FILE] [--input INPUTS] [--disabled] [--auto-approve-tools TOOLS] [--disable-tools TOOLS] [--env-vars VARS] [--startup-timeout SEC] [--tool-timeout SEC] [--enabled] [--bearer-token-env-var VAR] [--env-header HEADERS] [--dry-run] [--auto-approve] [--no-backup]` + + +| Argument / Flag | Hosts | Type | Description | Default | +|---:|---:|---|---|---| +| `server-name` | all | string (positional) | Name of the MCP server to configure | n/a | +| `--host` | all | string | Target host platform (claude-desktop, cursor, etc.) | n/a | +| `--command` | all | string | Command to execute for local servers (mutually exclusive with --url) | none | +| `--url` | all except Claude Desktop/Code | string | URL for remote MCP servers (mutually exclusive with --command) | none | +| `--http-url` | gemini | string | HTTP streaming endpoint URL | none | +| `--args` | all | string list | Arguments for MCP server command (only with --command) | none | +| `--env-var` | all | string | Environment variables format: KEY=VALUE (can be used multiple times) | none | +| `--header` | all except Claude Desktop/Code | string | HTTP headers format: KEY=VALUE (only with --url) | none | +| `--timeout` | gemini | int | Request timeout in milliseconds | none | +| `--trust` | gemini | flag | Bypass tool call confirmations | false | +| `--cwd` | gemini, codex | string | Working directory for stdio transport | none | +| `--include-tools` | gemini, codex | multiple | Tool allowlist / enabled tools. Space-separated values. | none | +| `--exclude-tools` | gemini, codex | multiple | Tool blocklist / disabled tools. Space-separated values. | none | +| `--env-file` | cursor, vscode, lmstudio | string | Path to environment file | none | +| `--input` | vscode | multiple | Input variable definitions format: type,id,description[,password=true] | none | +| `--disabled` | kiro | flag | Disable the MCP server | false | +| `--auto-approve-tools` | kiro | multiple | Tool names to auto-approve. Can be used multiple times. | none | +| `--disable-tools` | kiro | multiple | Tool names to disable. Can be used multiple times. | none | +| `--env-vars` | codex | multiple | Environment variable names to whitelist/forward. Can be used multiple times. | none | +| `--startup-timeout` | codex | int | Server startup timeout in seconds (default: 10) | 10 | +| `--tool-timeout` | codex | int | Tool execution timeout in seconds (default: 60) | 60 | +| `--enabled` | codex | flag | Enable the MCP server | false | +| `--bearer-token-env-var` | codex | string | Name of env var containing bearer token for Authorization header | none | +| `--env-header` | codex | multiple | HTTP header from env var format: KEY=ENV_VAR_NAME. Can be used multiple times. | none | +| `--dry-run` | all | flag | Preview configuration without applying changes | false | +| `--auto-approve` | all | flag | Skip confirmation prompts | false | +| `--no-backup` | all | flag | Skip backup creation before configuration | false | + +**Behavior**: + +The command displays a **conversion report** showing exactly what fields will be configured on the target host. This provides transparency about which fields are supported by the host and what values will be set. + +The conversion report shows: +- **UPDATED** fields: Fields being set with their new values (shown as `None --> value`) +- **UNSUPPORTED** fields: Fields not supported by the target host (automatically filtered out) + + +Note: Internal metadata fields (like `name`) are not shown in the field operations list. + +--- + +#### `hatch mcp sync` + + +Synchronize MCP configurations across environments and hosts. + +The sync command displays a preview of servers to be synced before requesting confirmation, giving visibility into which servers will be affected. + +Syntax: + +`hatch mcp sync [--from-env ENV | --from-host HOST] --to-host HOSTS [--servers SERVERS | --pattern PATTERN] [--dry-run] [--auto-approve] [--no-backup]` + + +| Flag | Type | Description | Default | +|---:|---|---|---| +| `--from-env` | string | Source Hatch environment (mutually exclusive with --from-host) | none | +| `--from-host` | string | Source host platform (mutually exclusive with --from-env) | none | +| `--to-host` | string | Target hosts (comma-separated or 'all') | n/a | +| `--servers` | string | Specific server names to sync (mutually exclusive with --pattern) | none | +| `--pattern` | string | Regex pattern for server selection (mutually exclusive with --servers) | none | +| `--dry-run` | flag | Preview synchronization without executing changes | false | +| `--auto-approve` | flag | Skip confirmation prompts | false | +| `--no-backup` | flag | Skip backup creation before synchronization | false | + +**Example Output (pre-prompt)**: + +``` +hatch mcp sync: + [INFO] Servers: weather-server, my-tool (2 total) + [SYNC] environment 'dev' → 'claude-desktop' + [SYNC] environment 'dev' → 'cursor' + Proceed? [y/N]: +``` + +When more than 3 servers match, the list is truncated: `Servers: srv1, srv2, srv3, ... (7 total)` + + +--- + +#### `hatch mcp remove server` + + +Remove an MCP server from one or more hosts. + +Syntax: + +`hatch mcp remove server --host [--env ENV] [--dry-run] [--auto-approve] [--no-backup]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `server-name` | string (positional) | Name of the server to remove | n/a | +| `--host` | string | Target hosts (comma-separated or 'all') | n/a | +| `--env`, `-e` | string | Hatch environment name (reserved for future use) | none | +| `--dry-run` | flag | Preview removal without executing changes | false | +| `--auto-approve` | flag | Skip confirmation prompts | false | +| `--no-backup` | flag | Skip backup creation before removal | false | + +--- + +#### `hatch mcp remove host` + + +Remove complete host configuration (all MCP servers from the specified host). + +Syntax: + +`hatch mcp remove host [--dry-run] [--auto-approve] [--no-backup]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `host-name` | string (positional) | Name of the host to remove | n/a | +| `--dry-run` | flag | Preview removal without executing changes | false | +| `--auto-approve` | flag | Skip confirmation prompts | false | +| `--no-backup` | flag | Skip backup creation before removal | false | + +--- + +#### `hatch mcp backup list` + + +List available configuration backups for a specific host. + +Syntax: + +`hatch mcp backup list [--detailed]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `host` | string (positional) | Host platform to list backups for (e.g., claude-desktop, cursor) | n/a | +| `--detailed`, `-d` | flag | Show detailed backup information | false | + +--- + +#### `hatch mcp backup restore` + + +Restore host configuration from a backup file. + +Syntax: + +`hatch mcp backup restore [--backup-file FILE] [--dry-run] [--auto-approve]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `host` | string (positional) | Host platform to restore (e.g., claude-desktop, cursor) | n/a | +| `--backup-file`, `-f` | string | Specific backup file to restore (defaults to latest) | latest backup | +| `--dry-run` | flag | Preview restore without executing changes | false | +| `--auto-approve` | flag | Skip confirmation prompts | false | + +--- + +#### `hatch mcp backup clean` + + +Clean old backup files for a specific host based on retention criteria. + +Syntax: + +`hatch mcp backup clean [--older-than-days DAYS] [--keep-count COUNT] [--dry-run] [--auto-approve]` + + +| Argument / Flag | Type | Description | Default | +|---:|---|---|---| +| `host` | string (positional) | Host platform to clean backups for (e.g., claude-desktop, cursor) | n/a | +| `--older-than-days` | integer | Remove backups older than specified days | none | +| `--keep-count` | integer | Keep only the most recent N backups | none | +| `--dry-run` | flag | Preview cleanup without executing changes | false | +| `--auto-approve` | flag | Skip confirmation prompts | false | + +**Note:** At least one of `--older-than-days` or `--keep-count` must be specified. + +--- + +## Exit codes + +| Code | Meaning | +|---:|---| +| `0` | Success | +| `1` | Error or failure | + +## Notes + +- The CLI is implemented in the `hatch/cli/` package with modular handler modules. Use `hatch --help` to inspect available commands and options. +- This reference mirrors the command names and option names implemented in the CLI handlers. If you change CLI arguments in code, update this file to keep documentation in sync. From ed98ea4c6e9911afa06d643c763cc82147150a0e Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 25 Mar 2026 22:28:17 +0900 Subject: [PATCH 68/71] docs(known-issues): sync appendix with v0.8.1 codebase state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove two issues resolved since v0.4.2: non-interactive TTY handling (now detects isatty() and HATCH_AUTO_APPROVE) and external dependency coupling (hatch-validator is now a standard PyPI package). Update stale line references throughout — all cited locations shifted due to refactoring. Fix wrong cache clear path in workaround (~/.hatch/cache/* → ~/.hatch/packages/*). Correct two misframed descriptions: registry one-day fallback is intentional timezone coverage, not a robustness gap; simulation mode is a deferred planned feature with infrastructure ready, not broken. Co-Authored-By: Claude Sonnet 4.6 --- .../appendices/LimitsAndKnownIssues.md | 118 +++++++----------- 1 file changed, 45 insertions(+), 73 deletions(-) diff --git a/docs/articles/appendices/LimitsAndKnownIssues.md b/docs/articles/appendices/LimitsAndKnownIssues.md index 516ab05..0fdbbf5 100644 --- a/docs/articles/appendices/LimitsAndKnownIssues.md +++ b/docs/articles/appendices/LimitsAndKnownIssues.md @@ -1,34 +1,14 @@ # Limits and Known Issues -This appendix documents current limitations and known issues in Hatch v0.4.2, organized by impact severity and architectural domain. +This appendix documents current limitations and known issues in Hatch v0.8.1, organized by impact severity and architectural domain. ## Critical Limitations (High Impact) -### Non-Interactive Environment Handling - -**Issue**: The dependency installation orchestrator can block indefinitely in non-TTY environments. - -**Code Location**: `hatch/installers/dependency_installation_orchestrator.py:501` (`_request_user_consent`) - -**Symptoms**: - -- Hangs in CI/CD pipelines when TTY is unavailable -- Docker container execution may hang indefinitely -- Programmatic integration requires foreknowledge of `--auto-approve` parameter - -**Workaround**: Use `--auto-approve` flag for automated scenarios - -```bash -hatch package add my-package --auto-approve -``` - -**Root Cause**: Blocking `input()` call without TTY detection or environment variable fallback mechanisms. - ### System Package Version Constraint Simplification **Issue**: Complex version constraints for system packages are reduced to "install latest" with only warning messages. -**Code Location**: `hatch/installers/system_installer.py:332-365` (`_build_apt_command`) +**Code Location**: `hatch/installers/system_installer.py:366-403` (`_build_apt_command`) **Symptoms**: @@ -46,8 +26,9 @@ hatch package add my-package --auto-approve **Code Locations**: -- `hatch/environment_manager.py:85-90` -- `hatch/package_loader.py:80-85` +- `hatch/environment_manager.py:172-179` (`_save_environments`) +- `hatch/environment_manager.py:220` (`set_current_environment`, `current_env` file write) +- `hatch/package_loader.py:139-145` (cache write in `download_package`) **Symptoms**: @@ -63,25 +44,25 @@ hatch package add my-package --auto-approve ### Registry Fetch Fragility -**Issue**: Registry fetching uses date-based URL construction with limited fallback robustness. +**Issue**: Registry fetching has no retry logic for transient network failures. -**Code Location**: `hatch/registry_retriever.py:45-65` +**Code Location**: `hatch/registry_retriever.py:200-231` (`_fetch_remote_registry`) **Symptoms**: -- Package discovery breaks when registry publishing is delayed +- A single transient network error causes the fetch to fail immediately with no retry - Poor error messages during network connectivity issues -- Development workflow disruption during registry maintenance +- Development workflow disruption during registry unavailability **Workaround**: Use local packages (`hatch package add ./local-package`) when registry is unavailable -**Root Cause**: Registry URL construction assumes daily publishing schedule without robust fallback strategies. +**Root Cause**: Network requests are single-attempt with no retry strategy or back-off logic. ### Package Integrity Verification Gap **Issue**: Downloaded packages are not cryptographically verified for integrity. -**Code Location**: `hatch/package_loader.py:75-125` (`download_package`) +**Code Location**: `hatch/package_loader.py:56-157` (`download_package`) **Symptoms**: @@ -95,25 +76,25 @@ hatch package add my-package --auto-approve ### Cross-Platform Python Environment Detection -**Issue**: Hard-coded path assumptions limit Python environment detection across different platforms and installations. +**Issue**: Hard-coded path assumptions limit Python environment detection for non-standard conda/mamba installations. -**Code Location**: `hatch/python_environment_manager.py:85-120` (`_detect_conda_mamba`) +**Code Location**: `hatch/python_environment_manager.py:65-125` (`_detect_manager`) **Symptoms**: -- Inconsistent behavior across different conda installations -- Silent feature degradation when Python environments unavailable -- User confusion about Python integration capabilities +- Inconsistent behavior with custom conda/mamba installation locations +- Silent feature degradation when conda/mamba is not in a standard path +- User confusion about Python integration capabilities on non-standard setups -**Workaround**: Ensure conda/mamba are in system PATH or use explicit paths +**Workaround**: Set the `CONDA_EXE` or `MAMBA_EXE` environment variable to point to your conda/mamba executable, or ensure it is in your system PATH -**Root Cause**: Platform-specific path assumptions and limited environment variable checking. +**Root Cause**: Hard-coded fallback paths cover only common installation locations (`~/miniconda3`, `~/anaconda3`, `/opt/conda`). `PATH` and `CONDA_EXE`/`MAMBA_EXE` environment variables are checked first but may not cover all installation scenarios. ### Error Recovery and Rollback Gaps **Issue**: Limited transactional semantics during multi-dependency installation. -**Code Location**: `hatch/installers/dependency_installation_orchestrator.py:550-580` (`_execute_install_plan`) +**Code Location**: `hatch/installers/dependency_installation_orchestrator.py:605-681` (`_execute_install_plan`) **Symptoms**: @@ -147,7 +128,7 @@ hatch package add my-package --auto-approve **Issue**: Templates assume specific MCP server structure and dependencies. -**Code Location**: `hatch/template_generator.py:130-140` +**Code Location**: `hatch/template_generator.py` (particularly `generate_mcp_server_py:33`, `generate_hatch_mcp_server_entry_py:63`, `generate_metadata_json:87`) **Symptoms**: @@ -163,17 +144,17 @@ hatch package add my-package --auto-approve **Issue**: Limited handling of circular dependencies and complex version constraints. -**Code Location**: `hatch/installers/dependency_installation_orchestrator.py:290-320` +**Code Location**: `hatch/installers/dependency_installation_orchestrator.py:337-358` (`_get_install_ready_hatch_dependencies`) **Symptoms**: -- Potential infinite loops during dependency resolution +- Potential failures during dependency resolution for circular or deeply nested graphs - Unclear error messages for complex dependency conflicts - Unexpected behavior with deeply nested dependency trees **Workaround**: Simplify dependency structures and avoid circular dependencies -**Root Cause**: Dependency graph builder lacks edge case handling for complex scenarios. +**Root Cause**: Dependency graph resolution is delegated to `hatch_validator.utils.hatch_dependency_graph.HatchDependencyGraphBuilder`; edge case robustness depends on that external library. ## Minor Limitations (Quality of Life) @@ -181,7 +162,7 @@ hatch package add my-package --auto-approve **Issue**: System package installation assumes `sudo` availability without proper validation. -**Code Location**: `hatch/installers/system_installer.py:365-380` +**Code Location**: `hatch/installers/system_installer.py:382-403` (`_build_apt_command`, `install`) **Symptoms**: @@ -192,53 +173,45 @@ hatch package add my-package --auto-approve ### Simulation and Dry-Run Gaps -**Issue**: Inconsistent simulation mode implementation across installers. +**Issue**: Simulation mode infrastructure exists but is not yet wired through the orchestrator. -**Code Locations**: Various installer modules +**Code Locations**: + +- `hatch/installers/dependency_installation_orchestrator.py:635` (`simulation_mode=False`, marked "Future enhancement") +- `hatch/installers/system_installer.py:152` (simulation mode fully implemented at installer level) **Symptoms**: -- No unified dry-run capability across all dependency types +- No dry-run capability reachable through normal `hatch package add` flow +- `SystemInstaller` has full `apt-get --dry-run` support ready but not yet exposed - Limited preview capabilities for complex installation plans **Workaround**: Test installations in isolated environments first +**Root Cause**: Planned feature not yet implemented. `InstallationContext` supports `simulation_mode` and individual installers handle it, but the orchestrator does not yet accept or pass through a simulation flag. + ### Cache Management Strategy **Issue**: Basic TTL-based caching without intelligent invalidation or size limits. **Code Locations**: -- `hatch/package_loader.py:40-50` -- `hatch/registry_retriever.py:35-45` +- `hatch/registry_retriever.py:37` (24-hour TTL constant) +- `hatch/package_loader.py` (presence-only caching, no TTL or size limits) **Symptoms**: -- Fixed 24-hour TTL regardless of registry update frequency +- Fixed 24-hour TTL for registry data regardless of registry update frequency +- Package cache never expires — only invalidated by `force_download=True` - No automatic cache cleanup for disk space management - Force refresh only available at operation level **Workaround**: Manually clear cache directory when needed: ```bash -rm -rf ~/.hatch/cache/* +rm -rf ~/.hatch/packages/* ``` -### External Dependency Coupling - -**Issue**: Validator dependency fetched via git URL with network requirements. - -**Code Location**: `pyproject.toml:24` - -**Details**: `hatch_validator @ git+https://github.com/CrackingShells/Hatch-Validator.git@v0.6.3` - -**Symptoms**: - -- Build-time network access required -- Dependency on repository and tag availability - -**Workaround**: Ensure network access during installation or consider local installation methods - ### Documentation and Schema Evolution **Issue**: Limited handling of package schema version transitions. @@ -257,20 +230,19 @@ rm -rf ~/.hatch/cache/* | Severity | Automation | Reliability | Development | |----------|------------|-------------|-------------| -| **Critical** | Non-interactive handling | Concurrent access, System constraints | - | +| **Critical** | - | Concurrent access, System constraints | - | | **Significant** | Registry fragility, Error recovery | Package integrity, Python detection | - | | **Moderate** | - | - | Observability, Templates, Dependency resolution | -| **Minor** | Simulation gaps | Security context, Cache strategy | External coupling, Schema evolution | +| **Minor** | Simulation gaps | Security context, Cache strategy | Schema evolution | ## Recommended Mitigation Strategies ### For Production Use -1. **Always use `--auto-approve`** for automated deployments -2. **Avoid concurrent operations** until race conditions are resolved -3. **Use exact version constraints** for system packages when possible -4. **Implement external monitoring** for installation operations -5. **Regularly backup environment configurations** +1. **Avoid concurrent operations** until race conditions are resolved +2. **Use exact version constraints** for system packages when possible +3. **Implement external monitoring** for installation operations +4. **Regularly backup environment configurations** ### For Development @@ -290,7 +262,7 @@ rm -rf ~/.hatch/cache/* The Hatch team is aware of these limitations and they are prioritized for future releases: -**Phase 1 (Stability)**: Address concurrent access, non-interactive handling, and error recovery +**Phase 1 (Stability)**: Address concurrent access and error recovery **Phase 2 (Security)**: Implement package integrity verification and security context validation **Phase 3 (Robustness)**: Improve cross-platform consistency and system package handling **Phase 4 (Quality)**: Enhance observability, caching, and template flexibility From dbdba354350c80591bf227740a9888d300520d84 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 31 Mar 2026 17:57:31 +0900 Subject: [PATCH 69/71] docs(mcp-host-config): surface opencode and augment in all host lists Both hosts were fully implemented in code but absent from every doc page that enumerates supported platforms, making them undiscoverable to users. Also corrects the lm-studio identifier typo in the API CLI reference (canonical name is lmstudio, matching MCPHostType.LMSTUDIO). Files updated: - MCPHostConfiguration.md (user article) - tutorials/04.../01-host-platform-overview.md - api/cli/mcp.md (+ lmstudio fix) - devs/architecture/mcp_host_configuration.md (overview paragraph) --- docs/articles/api/cli/mcp.md | 4 +++- docs/articles/devs/architecture/mcp_host_configuration.md | 2 +- docs/articles/users/MCPHostConfiguration.md | 2 ++ .../04-mcp-host-configuration/01-host-platform-overview.md | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/articles/api/cli/mcp.md b/docs/articles/api/cli/mcp.md index 252c9dd..1162c74 100644 --- a/docs/articles/api/cli/mcp.md +++ b/docs/articles/api/cli/mcp.md @@ -22,9 +22,11 @@ This module provides handlers for: - vscode: Visual Studio Code with Copilot - kiro: Kiro IDE - codex: OpenAI Codex -- lm-studio: LM Studio +- lmstudio: LM Studio - gemini: Google Gemini - mistral-vibe: Mistral Vibe CLI coding agent +- opencode: OpenCode AI coding assistant +- augment: Augment Code AI assistant ## Handler Functions diff --git a/docs/articles/devs/architecture/mcp_host_configuration.md b/docs/articles/devs/architecture/mcp_host_configuration.md index 42de522..2784e51 100644 --- a/docs/articles/devs/architecture/mcp_host_configuration.md +++ b/docs/articles/devs/architecture/mcp_host_configuration.md @@ -10,7 +10,7 @@ This article covers: ## Overview -The MCP host configuration system manages Model Context Protocol server configurations across multiple host platforms (Claude Desktop, VS Code, Cursor, Gemini, Kiro, Codex, LM Studio). It uses the **Unified Adapter Architecture**: a single data model with host-specific adapters for validation and serialization. +The MCP host configuration system manages Model Context Protocol server configurations across multiple host platforms (Claude Desktop, Claude Code, VS Code, Cursor, LM Studio, Gemini, Kiro, Codex, Mistral Vibe, OpenCode, Augment). It uses the **Unified Adapter Architecture**: a single data model with host-specific adapters for validation and serialization. > **Adding a new host?** See the [Implementation Guide](../implementation_guides/mcp_host_configuration_extension.md) for step-by-step instructions. diff --git a/docs/articles/users/MCPHostConfiguration.md b/docs/articles/users/MCPHostConfiguration.md index 47adab5..9af7204 100644 --- a/docs/articles/users/MCPHostConfiguration.md +++ b/docs/articles/users/MCPHostConfiguration.md @@ -24,6 +24,8 @@ Hatch currently supports configuration for these MCP host platforms: - **LM Studio** - Local language model interface - **Gemini** - Google's AI development environment - **Mistral Vibe** - Mistral Vibe CLI coding agent +- **OpenCode** - OpenCode AI coding assistant +- **Augment** - Augment Code AI assistant ## Hands-on Learning diff --git a/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md b/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md index ec98fe2..479a521 100644 --- a/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md +++ b/docs/articles/users/tutorials/04-mcp-host-configuration/01-host-platform-overview.md @@ -58,6 +58,8 @@ Hatch currently supports configuration for these MCP host platforms: - [**LM Studio**](https://lmstudio.ai/) - Local language model interface - [**Gemini**](https://github.com/google-gemini/gemini-cli) - Google's AI Command Line Interface - [**Mistral Vibe**](https://mistral.ai/vibe) - Mistral Vibe CLI coding agent +- [**OpenCode**](https://opencode.ai) - OpenCode AI coding assistant +- [**Augment**](https://www.augmentcode.com/) - Augment Code AI assistant ## Configuration Management Workflow From d4d0bb94fe696d42184035166de8eb9cf96ca3c5 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Tue, 31 Mar 2026 23:24:08 +0900 Subject: [PATCH 70/71] docs(entry-docs): refresh content for primary use case identification The README and docs landing page were positioning Hatch as a package manager, burying the multi-host configuration feature that is most useful to users today. The package system is functional but still being reworked for MCP registry integration, so it should not lead. Also adds OpenCode and Augment Code (Auggie CLI and Intent) to the supported host lists, which were missing despite being implemented. --- README.md | 131 ++++++++++++++++++++++---------------------------- docs/index.md | 21 +++++--- 2 files changed, 70 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 9890844..18fa1b3 100644 --- a/README.md +++ b/README.md @@ -2,49 +2,36 @@ ![Hatch Logo](https://raw.githubusercontent.com/CrackingShells/Hatch/refs/heads/main/docs/resources/images/Logo/hatch_wide_dark_bg_transparent.png) -## Introduction +Hatch is a CLI tool for configuring MCP servers across AI host platforms. Adding a server to Claude Desktop, Cursor, VS Code, and others normally means editing separate JSON config files in different locations. Hatch does it from one command. -Hatch is the package manager for managing Model Context Protocol (MCP) servers with environment isolation, multi-type dependency resolution, and multi-host deployment. Deploy MCP servers to Claude Desktop, VS Code, Cursor, Kiro, Codex, and other platforms with automatic dependency management. +It also has a package system for managing MCP servers with dependency isolation, though that part is still being developed — see [Getting Started](./docs/articles/users/GettingStarted.md) for the current state. -The canonical documentation is at `docs/index.md` and published at . +**Current status:** suitable for development and trusted environments. Not hardened for production or multi-tenant use yet — see [Security and Trust](./docs/articles/users/SecurityAndTrust.md). -## Key Features +## What it does -- **Environment Isolation** — Create separate, isolated workspaces for different projects without conflicts -- **Multi-Type Dependency Resolution** — Automatically resolve and install system packages, Python packages, Docker containers, and Hatch packages -- **Multi-Host Deployment** — Configure MCP servers on multiple host platforms -- **Package Validation** — Ensure packages meet schema requirements before distribution -- **Development-Focused** — Optimized for rapid development and testing of MCP server ecosystems +- Configure MCP servers on one or more AI host platforms at once +- Discover which host platforms are installed on your machine +- List and inspect server registrations across all your tools +- Manage MCP server packages with dependency isolation (system, Python, Docker) ## Supported MCP Hosts -Hatch supports deployment to the following MCP host platforms: +Claude Desktop, Claude Code, VS Code, Cursor, Kiro, Codex, LM Studio, Google Gemini CLI, Mistral Vibe, OpenCode, Augment Code (Auggie CLI and Intent) -- **Claude Desktop** — Anthropic's desktop application for Claude with native MCP support -- **Claude Code** — Claude integration for VS Code with MCP capabilities -- **VS Code** — Visual Studio Code with the MCP extension for tool integration -- **Cursor** — AI-first code editor with built-in MCP server support -- **Kiro** — Kiro IDE with MCP support -- **Codex** — OpenAI Codex with MCP server configuration support -- **LM Studio** — Local LLM inference platform with MCP server integration -- **Google Gemini CLI** — Command-line interface for Google's Gemini model with MCP support -- **Mistral Vibe** — Mistral Vibe CLI coding agent - -## Quick Start - -### Install from PyPI +## Install ```bash pip install hatch-xclam ``` -Verify installation: +Verify: ```bash hatch --version ``` -### Install from source +Or install from source: ```bash git clone https://github.com/CrackingShells/Hatch.git @@ -52,76 +39,72 @@ cd Hatch pip install -e . ``` -### Create your first environment and *Hatch!* MCP server package +## Usage -```bash -# Create an isolated environment -hatch env create my_project +### Configure MCP servers on your hosts -# Switch to it -hatch env use my_project +```bash +# Local server via npx — register it on VS Code +hatch mcp configure context7 --host vscode \ + --command npx --args "-y @upstash/context7-mcp" -# Create a package template -hatch create my_mcp_server --description "My MCP server" +# Remote server with an auth header — register it on Gemini CLI +export GIT_PAT_TOKEN=your_github_personal_access_token +hatch mcp configure github-mcp --host gemini \ + --httpUrl https://api.github.com/mcp \ + --header Authorization="Bearer $GIT_PAT_TOKEN" -# Validate the package -hatch validate ./my_mcp_server +# Register the same server on multiple hosts at once +hatch mcp configure my-server --host claude-desktop,cursor,vscode \ + --command python --args "-m my_server" ``` -### Deploy MCP servers to your tools - -**Package-First Deployment (Recommended)** — Add a Hatch package and automatically configure it on Claude Desktop and Cursor: +### Inspect what is configured ```bash -hatch package add ./my_mcp_server --host claude-desktop,cursor +# See all servers across all hosts +hatch mcp list servers + +# See all hosts a specific server is registered on +hatch mcp show servers --server "context7" + +# Detect which MCP host platforms are installed +hatch mcp discover hosts ``` -**Direct Configuration (Advanced)** — Configure arbitrary MCP servers on your hosts: +### Package management (in development) -```bash -# Remote server example: GitHub MCP Server with authentication -export GIT_PAT_TOKEN=your_github_personal_access_token -hatch mcp configure github-mcp --host gemini \ - --httpUrl https://api.github.com/mcp \ - --header Authorization="Bearer $GIT_PAT_TOKEN" +The package system lets you install MCP servers with automatic dependency resolution and environment isolation. It is functional but being reworked for better integration with MCP registries. -# Local server example: Context7 via npx -hatch mcp configure context7 --host vscode \ - --command npx --args "-y @upstash/context7-mcp" +```bash +hatch env create my_project +hatch env use my_project +hatch package add ./my_mcp_server ``` ## Documentation -- **[Full Documentation](https://hatch.readthedocs.io/en/latest/)** — Complete reference and guides -- **[Getting Started](./docs/articles/users/GettingStarted.md)** — Quick start for users -- **[CLI Reference](./docs/articles/users/CLIReference.md)** — All commands and options -- **[Tutorials](./docs/articles/users/tutorials/)** — Step-by-step guides from installation to package authoring -- **[MCP Host Configuration](./docs/articles/users/MCPHostConfiguration.md)** — Deploy to multiple platforms -- **[Developer Docs](./docs/articles/devs/)** — Architecture, implementation guides, and contribution guidelines -- **[Troubleshooting](./docs/articles/users/Troubleshooting/ReportIssues.md)** — Common issues and solutions +- **[Full Documentation](https://hatch.readthedocs.io/en/latest/)** +- **[Getting Started](./docs/articles/users/GettingStarted.md)** +- **[CLI Reference](./docs/articles/users/CLIReference.md)** +- **[MCP Host Configuration](./docs/articles/users/MCPHostConfiguration.md)** +- **[Tutorials](./docs/articles/users/tutorials/)** +- **[Troubleshooting](./docs/articles/users/Troubleshooting/ReportIssues.md)** ## Contributing -We welcome contributions! See the [How to Contribute](./docs/articles/devs/contribution_guides/how_to_contribute.md) guide for details. - -### Quick start for developers - -1. **Fork and clone** the repository -2. **Install dependencies**: `pip install -e .` and `npm install` -3. **Create a feature branch**: `git checkout -b feat/your-feature` -4. **Make changes** and add tests -5. **Use conventional commits**: `npm run commit` for guided commits -6. **Run tests**: `wobble` -7. **Create a pull request** - -We use [Conventional Commits](https://www.conventionalcommits.org/) for automated versioning. Use `npm run commit` for guided commit messages. +We welcome contributions. See [How to Contribute](./docs/articles/devs/contribution_guides/how_to_contribute.md) for details. -## Getting Help +Quick setup: -- Search existing [GitHub Issues](https://github.com/CrackingShells/Hatch/issues) -- Read [Troubleshooting](./docs/articles/users/Troubleshooting/ReportIssues.md) for common problems -- Check [Developer Onboarding](./docs/articles/devs/development_processes/developer_onboarding.md) for setup help +1. Fork and clone the repository +2. Install dependencies: `pip install -e .` and `npm install` +3. Create a feature branch: `git checkout -b feat/your-feature` +4. Make changes and add tests +5. Use conventional commits: `npm run commit` +6. Run tests: `wobble` +7. Open a pull request ## License -This project is licensed under the GNU Affero General Public License v3 — see `LICENSE` for details. +GNU Affero General Public License v3 — see `LICENSE` for details. diff --git a/docs/index.md b/docs/index.md index 78eb741..379db89 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,21 +1,26 @@ # Hatch Documentation -Welcome to the documentation for Hatch, the official package manager for the Hatch! ecosystem. - ## Overview -Hatch provides powerful tools for managing MCP server packages, environments, and interacting with the Hatch registry. It serves as the package management foundation for [Hatchling](https://github.com/CrackingShells/Hatchling) and other projects in the ecosystem. +Hatch is a CLI tool for configuring MCP servers across AI host platforms. Instead of editing JSON config files for each tool separately, you register servers from the command line — once, on as many hosts as you need. + +```bash +hatch mcp configure context7 --host claude-desktop,cursor,vscode \ + --command npx --args "-y @upstash/context7-mcp" +``` + +Hatch also has a package system for installing MCP servers with dependency isolation (Python, system packages, Docker). That part is still being developed and will eventually integrate with MCP registries. -Hatch also supports MCP host configuration across popular platforms including Claude Desktop/Code, VS Code, Cursor, Kiro, Codex, LM Studio, and Gemini. +Supported hosts: Claude Desktop, Claude Code, VS Code, Cursor, Kiro, Codex, LM Studio, Google Gemini CLI, Mistral Vibe, OpenCode, Augment Code (Auggie CLI and Intent). ## Documentation Sections ### For Users -- **[Getting Started](./articles/users/GettingStarted.md)** - Quick start guide for using Hatch -- **[Command Reference](./articles/users/CLIReference.md)** - Complete CLI command documentation -- **[MCP Host Configuration](./articles/users/MCPHostConfiguration.md)** - Configure MCP servers across different host platforms -- **[Tutorials Start](./articles/users/tutorials/01-getting-started/01-installation.md)** - Step-by-step guides for your journey from installation to authoring Hatch packages for MCP server easy sharing. +- **[Getting Started](./articles/users/GettingStarted.md)** - Installation and first steps +- **[MCP Host Configuration](./articles/users/MCPHostConfiguration.md)** - Configure MCP servers across host platforms +- **[Command Reference](./articles/users/CLIReference.md)** - Complete CLI reference +- **[Tutorials](./articles/users/tutorials/01-getting-started/01-installation.md)** - Step-by-step guides, including package authoring ### For Developers From 10a2e4878808b05fd9e5ed0d1d110f56eac2c2c1 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 1 Apr 2026 04:36:45 +0000 Subject: [PATCH 71/71] chore(release): 0.8.1-dev.6 [skip ci] ## 0.8.1-dev.6 (2026-04-01) * Merge pull request #50 from LittleCoinCoin/dev ([8f6c5f8](https://github.com/CrackingShells/Hatch/commit/8f6c5f8)), closes [#50](https://github.com/CrackingShells/Hatch/issues/50) * docs(cli): update to match current CLI state ([2d75bcd](https://github.com/CrackingShells/Hatch/commit/2d75bcd)) * docs(entry-docs): refresh content for primary use case identification ([d4d0bb9](https://github.com/CrackingShells/Hatch/commit/d4d0bb9)) * docs(known-issues): sync appendix with v0.8.1 codebase state ([ed98ea4](https://github.com/CrackingShells/Hatch/commit/ed98ea4)) * docs(mcp-host-config): surface opencode and augment in all host lists ([dbdba35](https://github.com/CrackingShells/Hatch/commit/dbdba35)) * style(docs): add CrackingShells brand theme stylesheets for MkDocs ([c5cec5b](https://github.com/CrackingShells/Hatch/commit/c5cec5b)) * style(docs): apply CrackingShells org theme to MkDocs ([7749d48](https://github.com/CrackingShells/Hatch/commit/7749d48)) * chore(git): add .DS_Store to .gitignore ([cff376a](https://github.com/CrackingShells/Hatch/commit/cff376a)) --- CHANGELOG.md | 11 +++++++++++ pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08a9a87..c22c538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.8.1-dev.6 (2026-04-01) + +* Merge pull request #50 from LittleCoinCoin/dev ([8f6c5f8](https://github.com/CrackingShells/Hatch/commit/8f6c5f8)), closes [#50](https://github.com/CrackingShells/Hatch/issues/50) +* docs(cli): update to match current CLI state ([2d75bcd](https://github.com/CrackingShells/Hatch/commit/2d75bcd)) +* docs(entry-docs): refresh content for primary use case identification ([d4d0bb9](https://github.com/CrackingShells/Hatch/commit/d4d0bb9)) +* docs(known-issues): sync appendix with v0.8.1 codebase state ([ed98ea4](https://github.com/CrackingShells/Hatch/commit/ed98ea4)) +* docs(mcp-host-config): surface opencode and augment in all host lists ([dbdba35](https://github.com/CrackingShells/Hatch/commit/dbdba35)) +* style(docs): add CrackingShells brand theme stylesheets for MkDocs ([c5cec5b](https://github.com/CrackingShells/Hatch/commit/c5cec5b)) +* style(docs): apply CrackingShells org theme to MkDocs ([7749d48](https://github.com/CrackingShells/Hatch/commit/7749d48)) +* chore(git): add .DS_Store to .gitignore ([cff376a](https://github.com/CrackingShells/Hatch/commit/cff376a)) + ## 0.8.1-dev.5 (2026-03-23) * fix(ci): inline pypi publish jobs to satisfy trusted publishing ([fc81e78](https://github.com/CrackingShells/Hatch/commit/fc81e78)) diff --git a/pyproject.toml b/pyproject.toml index b4c6c71..ca04048 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hatch-xclam" -version = "0.8.1-dev.5" +version = "0.8.1-dev.6" description = "Package manager for the Cracking Shells ecosystem" readme = "README.md" requires-python = ">=3.12"