From 3a589080761cf3808f04955c5ff2c66e8413d6c9 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Thu, 26 Feb 2026 19:33:15 +0900 Subject: [PATCH 1/6] 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 fb2ee4cb8a35f2df1421f1dda8c1ed38e54d3038 Mon Sep 17 00:00:00 2001 From: Eliott Jacopin Date: Wed, 4 Mar 2026 16:41:15 +0900 Subject: [PATCH 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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: