fix: Remove unsupported engines field and add runtime version checking
Fixes Claude Code marketplace plugin installation failure (bd-183). Problem: The plugin.json manifest included an engines field (borrowed from npm) to specify minimum bd CLI version requirements. However, Claude Code's plugin manifest schema doesn't recognize this field, causing validation errors when installing via /plugin marketplace add. Solution: 1. Remove the engines field from plugin.json 2. Add runtime version checking in the MCP server startup 3. Update documentation to reflect automatic version checking Changes: - .claude-plugin/plugin.json: Remove unsupported engines field - integrations/beads-mcp/src/beads_mcp/bd_client.py: - Add BdVersionError exception class - Add _check_version() method to validate bd CLI >= 0.9.0 - Use bd version command (not bd --version) - integrations/beads-mcp/src/beads_mcp/tools.py: - Make _get_client() async to support version checking - Update all tool functions to await _get_client() - Add version check on first MCP server use - .claude-plugin/commands/bd-version.md: Update to mention automatic checking - PLUGIN.md: Document automatic version validation at startup Benefits: - Plugin installs successfully via Claude Code marketplace - Clear error messages if bd CLI version is too old - Version check happens once per MCP server lifetime (not per command) - Users get actionable update instructions in error messages Closes bd-183
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
|
||||
from .config import load_config
|
||||
from .models import (
|
||||
@@ -43,6 +44,12 @@ class BdCommandError(BdError):
|
||||
self.returncode = returncode
|
||||
|
||||
|
||||
class BdVersionError(BdError):
|
||||
"""Raised when bd version is incompatible with MCP server."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BdClient:
|
||||
"""Client for calling bd CLI commands and parsing JSON output."""
|
||||
|
||||
@@ -144,6 +151,57 @@ class BdClient:
|
||||
stderr=stdout_str,
|
||||
) from e
|
||||
|
||||
async def _check_version(self) -> None:
|
||||
"""Check that bd CLI version meets minimum requirements.
|
||||
|
||||
Raises:
|
||||
BdVersionError: If bd version is incompatible
|
||||
BdNotFoundError: If bd command not found
|
||||
"""
|
||||
# Minimum required version
|
||||
min_version = (0, 9, 0)
|
||||
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
self.bd_path,
|
||||
"version",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await process.communicate()
|
||||
except FileNotFoundError as e:
|
||||
raise BdNotFoundError(
|
||||
f"bd command not found at '{self.bd_path}'. "
|
||||
f"Install bd from: https://github.com/steveyegge/beads"
|
||||
) from e
|
||||
|
||||
if process.returncode != 0:
|
||||
raise BdCommandError(
|
||||
f"bd version failed: {stderr.decode()}",
|
||||
stderr=stderr.decode(),
|
||||
returncode=process.returncode or 1,
|
||||
)
|
||||
|
||||
# Parse version from output like "bd version 0.9.2"
|
||||
version_output = stdout.decode().strip()
|
||||
match = re.search(r"(\d+)\.(\d+)\.(\d+)", version_output)
|
||||
if not match:
|
||||
raise BdVersionError(
|
||||
f"Could not parse bd version from: {version_output}"
|
||||
)
|
||||
|
||||
version = tuple(int(x) for x in match.groups())
|
||||
|
||||
if version < min_version:
|
||||
min_ver_str = ".".join(str(x) for x in min_version)
|
||||
cur_ver_str = ".".join(str(x) for x in version)
|
||||
install_cmd = "curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/install.sh | bash"
|
||||
raise BdVersionError(
|
||||
f"bd version {cur_ver_str} is too old. "
|
||||
f"This MCP server requires bd >= {min_ver_str}. "
|
||||
f"Update with: {install_cmd}"
|
||||
)
|
||||
|
||||
async def ready(self, params: ReadyWorkParams | None = None) -> list[Issue]:
|
||||
"""Get ready work (issues with no blocking dependencies).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user