# Bug: `mvs setup` fails with `NotImplementedError` in `list_servers`

**Date**: 2026-04-30
**Severity**: High — blocks `mvs setup` on any system with `claude` CLI in PATH
**Category**: Actionable / Bug Fix

---

## Summary

`mvs setup` (and `mvs install mcp`) crashes with:

```
NotImplementedError: Native CLI list not supported, use JSON strategy
  File ".../py_mcp_installer/installer.py", line 411, in list_servers
    return self._strategy.list_servers(scope)
  File ".../py_mcp_installer/installation_strategy.py", line 276, in list_servers
    raise NotImplementedError("Native CLI list not supported, use JSON strategy")
```

---

## Root Cause — Full Call Chain

### 1. Call site: `cleanup_stale_mcp_servers`

**File**: `/Users/masa/Projects/mcp-vector-search/src/mcp_vector_search/cli/commands/install.py`
**Lines**: 102–156

```python
def cleanup_stale_mcp_servers(platform_info: PlatformInfo) -> list[str]:
    installer = MCPInstaller(platform=platform_info.platform)  # line 118
    all_servers = installer.list_servers()                      # line 121 — CRASHES
```

This function is called from `_install_to_platform` at line 637:

```python
removed = cleanup_stale_mcp_servers(platform_info)
```

Which is called during `setup.py`'s Phase 5 MCP Integration loop (line 1099) and from the `install mcp` command.

### 2. Why `list_servers` raises `NotImplementedError`

`MCPInstaller.__init__` calls `_select_strategy()` (installer.py line 163).

For `Platform.CLAUDE_CODE`, `_select_strategy` delegates to `ClaudeCodeStrategy.get_strategy()`:

```python
# platforms/claude_code.py, lines 98–104
def get_strategy(self, scope: Scope) -> InstallationStrategy:
    if resolve_command_path("claude"):          # claude CLI is in PATH
        return NativeCLIStrategy(self.platform, "claude")   # <-- this is returned
    config_path = self.get_config_path(scope)
    return JSONManipulationStrategy(self.platform, config_path)  # never reached
```

When the `claude` CLI is present, the selected strategy is `NativeCLIStrategy`.

`NativeCLIStrategy.list_servers()` is intentionally unimplemented:

```python
# installation_strategy.py, lines 263–276
def list_servers(self, scope: Scope) -> list[MCPServerConfig]:
    raise NotImplementedError("Native CLI list not supported, use JSON strategy")
```

The `claude` CLI does not expose a `list` subcommand. The library authors left this as an explicit `NotImplementedError` with a note to use JSON strategy instead.

### 3. Why the exception propagates despite the try/except

`installer.list_servers()` (installer.py lines 394–414) wraps the call with:

```python
try:
    return self._strategy.list_servers(scope)
except Exception as e:
    logger.error(f"Failed to list servers: {e}", exc_info=True)
    return []
```

However, `NotImplementedError` is a subclass of `Exception`, so it **is** caught here and `[]` is returned — meaning the `installer.list_servers()` call itself does NOT propagate. The traceback in the bug report shows the error originates at line 411 inside `installer.py`, which means it is the `list_servers` method itself from the installer, not a re-raise.

Wait — re-reading the traceback more carefully:
```
File ".../installer.py", line 411, in list_servers
    return self._strategy.list_servers(scope)
```
Line 411 is inside the `try` block. `NotImplementedError` IS caught by `except Exception`. So `installer.list_servers()` **returns `[]`** silently.

The error therefore propagates from `installer.get_server()` which calls `list_servers()`:

```python
def get_server(self, name, scope=Scope.PROJECT):
    servers = self.list_servers(scope)   # returns []
    ...
```

This means `get_server` works fine (returns `None`).

**Re-examining**: The actual crash comes from `_install_to_platform` calling `installer.install_server()` at line 576. Inside `install_server`, it calls `self.get_server(name, scope)` (line 318), which calls `self.list_servers()`. `list_servers` catches `NotImplementedError` and returns `[]`. So `get_server` returns `None`.

Then `install_server` proceeds to call `self._strategy.install(server, scope)` — i.e., `NativeCLIStrategy.install()` — which runs `claude mcp add ...`. That might succeed.

But then `cleanup_stale_mcp_servers` (line 637 of install.py) directly calls `installer.list_servers()` via:

```python
installer = MCPInstaller(platform=platform_info.platform)
all_servers = installer.list_servers()
```

Here `installer.list_servers()` **does** suppress the error and return `[]`. So the loop body never executes.

**HOWEVER** — the bug in the original report shows the `NotImplementedError` is **not** being caught. This means the installed version of `py_mcp_installer` may be different from the one examined here. Let me check: the installed `installer.py` shows the try/except wrapping. But the error traceback says line 411 raises. If the installed version does NOT have the try/except, the error propagates raw.

The key point: regardless of whether it's caught at the `installer.list_servers()` level, the fundamental problem is that `NativeCLIStrategy` is being selected when `claude` is in PATH, but `list_servers` does not work with `NativeCLI`.

---

## The Correct Fix

The fix should be applied in our code (`install.py`), not in the third-party library.

### Option A (Recommended): Use `JSONManipulationStrategy` directly in `cleanup_stale_mcp_servers`

The `ClaudeCodeStrategy` exposes `get_strategy_with_fallback()` which always returns a `JSONManipulationStrategy` as fallback. For listing, we always want JSON (reads the config file directly).

**File**: `/Users/masa/Projects/mcp-vector-search/src/mcp_vector_search/cli/commands/install.py`

Change `cleanup_stale_mcp_servers` to use `get_server` via a JSON-backed installer:

```python
from py_mcp_installer.installation_strategy import JSONManipulationStrategy
from py_mcp_installer.platforms import ClaudeCodeStrategy
from py_mcp_installer.types import Scope

def cleanup_stale_mcp_servers(platform_info: PlatformInfo) -> list[str]:
    removed_servers = []
    try:
        # Always use JSON strategy for listing — NativeCLI does not support list_servers
        from py_mcp_installer.platforms import ClaudeCodeStrategy, CursorStrategy, CodexStrategy
        from py_mcp_installer.installation_strategy import JSONManipulationStrategy
        from py_mcp_installer.types import Scope

        if platform_info.platform in (Platform.CLAUDE_CODE, Platform.CLAUDE_DESKTOP):
            cc = ClaudeCodeStrategy()
            scope = Scope.PROJECT if platform_info.platform == Platform.CLAUDE_CODE else Scope.GLOBAL
            json_strategy = JSONManipulationStrategy(
                platform=platform_info.platform,
                config_path=cc.get_config_path(scope),
            )
        elif platform_info.config_path:
            json_strategy = JSONManipulationStrategy(
                platform=platform_info.platform,
                config_path=platform_info.config_path,
            )
        else:
            return []

        all_servers = json_strategy.list_servers(Scope.PROJECT)
        # ... rest of cleanup loop unchanged
```

### Option B (Minimal): Catch `NotImplementedError` at the call site

Since the library may or may not catch this internally, add an explicit guard:

```python
def cleanup_stale_mcp_servers(platform_info: PlatformInfo) -> list[str]:
    removed_servers = []
    try:
        installer = MCPInstaller(platform=platform_info.platform)
        try:
            all_servers = installer.list_servers()
        except NotImplementedError:
            # NativeCLI strategy (used when claude CLI is in PATH) does not
            # support list_servers. Skip cleanup for this platform.
            logger.debug(
                f"list_servers not supported for {platform_info.platform.value} "
                "with NativeCLI strategy — skipping stale server cleanup"
            )
            return []
        # ... rest unchanged
```

### Option C (Deeper): Fix strategy selection at `MCPInstaller` level for `list_servers`

This would require patching the third-party library or subclassing `MCPInstaller`. Not recommended.

---

## Recommendation

**Use Option B** as the immediate minimal fix — it is a one-line guard at the call site that accurately describes the skip reason in a debug log. It is safe, non-breaking, and resilient to future strategy changes.

**Option A** is cleaner for correctness but requires importing private implementation details from `py_mcp_installer.platforms`.

---

## Files Involved

| File | Lines | Role |
|------|-------|------|
| `src/mcp_vector_search/cli/commands/install.py` | 102–156 | `cleanup_stale_mcp_servers` — call site of `list_servers` |
| `src/mcp_vector_search/cli/commands/install.py` | 636–638 | `_install_to_platform` — calls `cleanup_stale_mcp_servers` |
| `src/mcp_vector_search/cli/commands/setup.py` | 1097–1109 | Phase 5 loop — calls `_install_to_platform` |
| `py_mcp_installer/platforms/claude_code.py` | 98–104 | `get_strategy` — returns `NativeCLI` when `claude` is in PATH |
| `py_mcp_installer/installation_strategy.py` | 263–276 | `NativeCLIStrategy.list_servers` — raises `NotImplementedError` |
| `py_mcp_installer/installer.py` | 394–414 | `MCPInstaller.list_servers` — wraps with try/except |
