feat(mcp): improve error messages for missing bd CLI
Add helpful installation instructions when bd CLI is not found, making it clear that the CLI must be installed separately. Changes: - Add BdNotFoundError.installation_message() with clear install steps - Update all BdNotFoundError raises to use new formatted message - Improve config error message with installation instructions first - Update tests to match new error message format Error message now shows: - Clear explanation that bd CLI is required - Installation command with curl one-liner - Link to GitHub installation docs - Reminder to restart Claude Code after installation Test results: 90/91 tests passing (1 unrelated path assertion) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,24 @@ class BdError(Exception):
|
|||||||
class BdNotFoundError(BdError):
|
class BdNotFoundError(BdError):
|
||||||
"""Raised when bd command is not found."""
|
"""Raised when bd command is not found."""
|
||||||
|
|
||||||
pass
|
@staticmethod
|
||||||
|
def installation_message(attempted_path: str) -> str:
|
||||||
|
"""Get helpful installation message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
attempted_path: Path where we tried to find bd
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted error message with installation instructions
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
f"bd CLI not found at: {attempted_path}\n\n"
|
||||||
|
"The beads Claude Code plugin requires the bd CLI to be installed separately.\n\n"
|
||||||
|
"Install bd CLI:\n"
|
||||||
|
" curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/install.sh | bash\n\n"
|
||||||
|
"Or visit: https://github.com/steveyegge/beads#installation\n\n"
|
||||||
|
"After installation, restart Claude Code to reload the MCP server."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BdCommandError(BdError):
|
class BdCommandError(BdError):
|
||||||
@@ -130,7 +147,7 @@ class BdClient:
|
|||||||
stdout, stderr = await process.communicate()
|
stdout, stderr = await process.communicate()
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
raise BdNotFoundError(
|
raise BdNotFoundError(
|
||||||
f"bd command not found at '{self.bd_path}'. Make sure bd is installed and in PATH."
|
BdNotFoundError.installation_message(self.bd_path)
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
@@ -174,8 +191,7 @@ class BdClient:
|
|||||||
stdout, stderr = await process.communicate()
|
stdout, stderr = await process.communicate()
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
raise BdNotFoundError(
|
raise BdNotFoundError(
|
||||||
f"bd command not found at '{self.bd_path}'. "
|
BdNotFoundError.installation_message(self.bd_path)
|
||||||
f"Install bd from: https://github.com/steveyegge/beads"
|
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
@@ -388,7 +404,7 @@ class BdClient:
|
|||||||
_stdout, stderr = await process.communicate()
|
_stdout, stderr = await process.communicate()
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
raise BdNotFoundError(
|
raise BdNotFoundError(
|
||||||
f"bd command not found at '{self.bd_path}'. Make sure bd is installed and in PATH."
|
BdNotFoundError.installation_message(self.bd_path)
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
@@ -416,7 +432,7 @@ class BdClient:
|
|||||||
stdout, stderr = await process.communicate()
|
stdout, stderr = await process.communicate()
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
raise BdNotFoundError(
|
raise BdNotFoundError(
|
||||||
f"bd command not found at '{self.bd_path}'. Make sure bd is installed and in PATH."
|
BdNotFoundError.installation_message(self.bd_path)
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
@@ -483,7 +499,7 @@ class BdClient:
|
|||||||
stdout, stderr = await process.communicate()
|
stdout, stderr = await process.communicate()
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
raise BdNotFoundError(
|
raise BdNotFoundError(
|
||||||
f"bd command not found at '{self.bd_path}'. Make sure bd is installed and in PATH."
|
BdNotFoundError.installation_message(self.bd_path)
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
|
|||||||
@@ -61,8 +61,12 @@ class Config(BaseSettings):
|
|||||||
path = Path(v)
|
path = Path(v)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"bd executable not found at: {v}\n"
|
f"bd executable not found at: {v}\n\n"
|
||||||
+ "Please verify BEADS_PATH points to a valid bd executable."
|
+ "The beads Claude Code plugin requires the bd CLI to be installed.\n\n"
|
||||||
|
+ "Install bd CLI:\n"
|
||||||
|
+ " curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/install.sh | bash\n\n"
|
||||||
|
+ "Or visit: https://github.com/steveyegge/beads#installation\n\n"
|
||||||
|
+ "After installation, restart Claude Code to reload the MCP server."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.access(v, os.X_OK):
|
if not os.access(v, os.X_OK):
|
||||||
@@ -119,14 +123,18 @@ def load_config() -> Config:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
default_path = _default_beads_path()
|
default_path = _default_beads_path()
|
||||||
error_msg = (
|
error_msg = (
|
||||||
f"Configuration Error: {e}\n\n"
|
f"Beads MCP Server Configuration Error\n\n"
|
||||||
+ "Environment variables:\n"
|
+ f"{e}\n\n"
|
||||||
|
+ "Common fix: Install the bd CLI first:\n"
|
||||||
|
+ " curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/install.sh | bash\n\n"
|
||||||
|
+ "Or visit: https://github.com/steveyegge/beads#installation\n\n"
|
||||||
|
+ "After installation, restart Claude Code.\n\n"
|
||||||
|
+ "Advanced configuration (optional):\n"
|
||||||
+ f" BEADS_PATH - Path to bd executable (default: {default_path})\n"
|
+ f" BEADS_PATH - Path to bd executable (default: {default_path})\n"
|
||||||
+ " BEADS_DB - Optional path to beads database file\n"
|
+ " BEADS_DB - Path to beads database file (default: auto-discover)\n"
|
||||||
+ " BEADS_ACTOR - Actor name for audit trail (default: $USER)\n"
|
+ " BEADS_ACTOR - Actor name for audit trail (default: $USER)\n"
|
||||||
+ " BEADS_NO_AUTO_FLUSH - Disable automatic JSONL sync (default: false)\n"
|
+ " BEADS_NO_AUTO_FLUSH - Disable automatic JSONL sync (default: false)\n"
|
||||||
+ " BEADS_NO_AUTO_IMPORT - Disable automatic JSONL import (default: false)\n\n"
|
+ " BEADS_NO_AUTO_IMPORT - Disable automatic JSONL import (default: false)"
|
||||||
+ "Make sure bd is installed and the path is correct."
|
|
||||||
)
|
)
|
||||||
print(error_msg, file=sys.stderr)
|
print(error_msg, file=sys.stderr)
|
||||||
raise ConfigError(error_msg) from e
|
raise ConfigError(error_msg) from e
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ async def test_run_command_not_found(bd_client):
|
|||||||
"""Test command execution when bd executable not found."""
|
"""Test command execution when bd executable not found."""
|
||||||
with (
|
with (
|
||||||
patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError()),
|
patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError()),
|
||||||
pytest.raises(BdNotFoundError, match="bd command not found"),
|
pytest.raises(BdNotFoundError, match="bd CLI not found"),
|
||||||
):
|
):
|
||||||
await bd_client._run_command("show", "bd-1")
|
await bd_client._run_command("show", "bd-1")
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ async def test_add_dependency_not_found(bd_client):
|
|||||||
"""Test add_dependency when bd executable not found."""
|
"""Test add_dependency when bd executable not found."""
|
||||||
with (
|
with (
|
||||||
patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError()),
|
patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError()),
|
||||||
pytest.raises(BdNotFoundError, match="bd command not found"),
|
pytest.raises(BdNotFoundError, match="bd CLI not found"),
|
||||||
):
|
):
|
||||||
params = AddDependencyParams(from_id="bd-2", to_id="bd-1", dep_type="blocks")
|
params = AddDependencyParams(from_id="bd-2", to_id="bd-1", dep_type="blocks")
|
||||||
await bd_client.add_dependency(params)
|
await bd_client.add_dependency(params)
|
||||||
@@ -507,7 +507,7 @@ async def test_quickstart_not_found(bd_client):
|
|||||||
"""Test quickstart when bd executable not found."""
|
"""Test quickstart when bd executable not found."""
|
||||||
with (
|
with (
|
||||||
patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError()),
|
patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError()),
|
||||||
pytest.raises(BdNotFoundError, match="bd command not found"),
|
pytest.raises(BdNotFoundError, match="bd CLI not found"),
|
||||||
):
|
):
|
||||||
await bd_client.quickstart()
|
await bd_client.quickstart()
|
||||||
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -48,7 +48,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "beads-mcp"
|
name = "beads-mcp"
|
||||||
version = "0.9.3"
|
version = "0.9.4"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "fastmcp" },
|
{ name = "fastmcp" },
|
||||||
|
|||||||
Reference in New Issue
Block a user