fix: Replace sys.exit() with exception in config validation

Fixes MCP server hanging when bd executable is not found at default path.

Problem:
- Config validation used sys.exit(1) on failure
- sys.exit() hangs in async MCP server context (doesn't properly terminate)
- Default bd path was hardcoded to ~/.local/bin/bd

Solution:
1. Use shutil.which() to find bd in PATH before falling back to default
2. Raise ConfigError instead of calling sys.exit()
3. MCP server now properly fails with error message instead of hanging

This fixes the multi-minute hang when trying to use MCP tools if bd is
installed in a non-default location (e.g., /usr/local/bin/bd).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-10-14 16:10:52 -07:00
parent 747697c6c0
commit 3b5b4842ff

View File

@@ -1,6 +1,7 @@
"""Configuration for beads MCP server.""" """Configuration for beads MCP server."""
import os import os
import shutil
import sys import sys
from pathlib import Path from pathlib import Path
@@ -11,9 +12,17 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
def _default_beads_path() -> str: def _default_beads_path() -> str:
"""Get default bd executable path. """Get default bd executable path.
First tries to find bd in PATH, falls back to ~/.local/bin/bd.
Returns: Returns:
Default path to bd executable (~/.local/bin/bd) Default path to bd executable
""" """
# Try to find bd in PATH first
bd_in_path = shutil.which("bd")
if bd_in_path:
return bd_in_path
# Fall back to common install location
return str(Path.home() / ".local" / "bin" / "bd") return str(Path.home() / ".local" / "bin" / "bd")
@@ -84,6 +93,12 @@ class Config(BaseSettings):
return v return v
class ConfigError(Exception):
"""Configuration error with helpful message."""
pass
def load_config() -> Config: def load_config() -> Config:
"""Load and validate configuration from environment variables. """Load and validate configuration from environment variables.
@@ -91,13 +106,13 @@ def load_config() -> Config:
Validated configuration Validated configuration
Raises: Raises:
SystemExit: If configuration is invalid ConfigError: If configuration is invalid
""" """
try: try:
return Config() return Config()
except Exception as e: except Exception as e:
default_path = _default_beads_path() default_path = _default_beads_path()
print( error_msg = (
f"Configuration Error: {e}\n\n" f"Configuration Error: {e}\n\n"
+ "Environment variables:\n" + "Environment variables:\n"
+ f" BEADS_PATH - Path to bd executable (default: {default_path})\n" + f" BEADS_PATH - Path to bd executable (default: {default_path})\n"
@@ -105,7 +120,7 @@ def load_config() -> Config:
+ " 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)\n\n"
+ "Make sure bd is installed and the path is correct.", + "Make sure bd is installed and the path is correct."
file=sys.stderr,
) )
sys.exit(1) print(error_msg, file=sys.stderr)
raise ConfigError(error_msg) from e