Skip to content
28 changes: 27 additions & 1 deletion .claude/skills/adding-mcp-hosts/references/testing-fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
1 change: 1 addition & 0 deletions docs/articles/users/CLIReference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
9 changes: 8 additions & 1 deletion hatch/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)

Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion hatch/environment_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion hatch/installers/dependency_installation_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion hatch/installers/docker_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion hatch/installers/python_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 0 additions & 1 deletion hatch/installers/system_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 0 additions & 1 deletion hatch/package_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 0 additions & 1 deletion hatch/python_environment_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
25 changes: 18 additions & 7 deletions hatch/registry_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -37,7 +48,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
Expand All @@ -59,7 +69,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:
Expand All @@ -69,7 +79,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}"
)

Expand Down Expand Up @@ -180,7 +190,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:
Expand All @@ -193,7 +203,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
Expand All @@ -211,7 +221,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()
Expand Down Expand Up @@ -298,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,
Expand Down
Loading